CodeOath
← All posts
Python70 min total · 18 parts

Django Fundamentals: MVT Architecture, the ORM, and Middleware

Contents — Part 4 of 18: Models In Depth
Part 4 of 18 · ~1 min

Models In Depth

class Order(models.Model):
    STATUS_CHOICES = [
        ("pending", "Pending"),
        ("shipped", "Shipped"),
        ("delivered", "Delivered"),
    ]

    customer_name = models.CharField(max_length=100)
    total = models.DecimalField(max_digits=10, decimal_places=2)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="pending")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at"]      # default ordering for every queryset on this model
        indexes = [models.Index(fields=["status"])]

    def __str__(self):
        return f"Order #{self.pk} — {self.customer_name}"

    @property
    def is_pending(self):
        return self.status == "pending"

This single class definition gives you a database table (via a migration — Django generates the CREATE TABLE SQL for you), plus a Python API to query it without writing raw SQL. A few field details worth knowing precisely:

  • auto_now_add vs. auto_nowauto_now_add sets the field once, at creation, and never touches it again; auto_now overwrites the field to the current time on every .save() call. Mixing these up (using auto_now for a "created at" field) silently corrupts the creation timestamp on every future update.
  • choices restricts the field at the form/admin/validation level, not at the database level by default — the database column is still a plain VARCHAR, so a raw SQL insert or a .save() that skips full_clean() can still write a value outside the choices list.
  • Meta.ordering sets the default order for Model.objects.all(), but any explicit .order_by() on a queryset overrides it — and relying on default ordering without an explicit .order_by() for anything requiring a stable/deterministic order (like pagination) is a common source of "rows show up twice across pages" bugs.
  • __str__ isn't optional in practice — it's what the admin site, shell, and debug output display for an instance; without it, every object shows up as the unhelpful Order object (1).