Primary keys and field-level options
coreintermediateDjango adds an auto-incrementing id (AutoField) primary key unless a field is explicitly marked primary_key=True — a UUIDField with default=uuid.uuid4 is a common non-sequential alternative. db_index=True and unique=True add a database-level index/constraint; db_column renames just the underlying column, independent of the Python attribute name.
Think of it as
The default id is a ticket-counter primary key: sequential, predictable, and revealing (row 42 was probably created before row 100). A UUIDField primary key trades that predictability away deliberately — for public-facing IDs where guessing '/orders/43/' should not work, or for merging data from multiple sources where sequential IDs would collide. db_index/unique/db_column are all about the COLUMN, not the Python attribute — a field can be named one thing in Python and a completely different thing in the actual table via db_column.
What we're doing: Use a UUID primary key for a model whose IDs are exposed in public URLs, so sequential guessing isn't possible.
- 5
- default=uuid.uuid4 passes the FUNCTION itself, not a call to it — Django calls it once per new instance, generating a fresh UUID each time; editable=False keeps it out of any ModelForm/admin edit view.
Why this works: A sequential id would let anyone guess "/orders/44/" exists right after seeing "/orders/43/" — a UUID primary key removes that predictability entirely, at the cost of a less compact, non-sortable-by-creation-order identifier compared to the default AutoField.
Calling uuid.uuid4() instead of passing the function as default
Wrong
Better
What you see: Every Order created gets the exact same UUID as its primary key, causing an IntegrityError on the second insert — a unique constraint violation that seems to make no sense at first glance.
Why: uuid.uuid4() calls the function immediately, once, when the class body runs at import time — that single generated value becomes the default for every future instance. default=uuid.uuid4 (no parentheses) instead passes the callable itself, which Django calls fresh for each new instance, generating a distinct UUID every time.
- AutoField (default id)
- Sequential, predictable
- Row 42 was likely created before row 100
- Compact, sortable by creation order
- UUIDField (default=uuid.uuid4)
- Non-sequential — not guessable
- Safe for public-facing URLs
- Needs the callable, not a call: uuid.uuid4
Field-level lookup and naming options
Together
Remember: default=uuid.uuid4 (the callable, not a call) for a UUID primary key; db_index/unique are enforced by the database itself; db_column renames only the underlying column, not the Python attribute.
See also: models · meta ordering · constraints and indexes

