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_now — auto_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).