@@ -104,45 +104,53 @@ Summary
104104 - What it is
105105 - Python type
106106 - Naming convention
107+ - Storage
107108 * - Integer Primary Key
108109 - Auto-incremented integer row identifier
109110 - ``int ``
110111 - * ``id ``, ``*_id `` on the model.
111112 * ``pk ``, ``*_pk `` everywhere else.
113+ - ``BigAutoField ``
112114 * - Code
113115 - Locally-scoped slug-like string
114116 - ``str ``
115117 - * ``*_code ``
118+ - ``code_field ``
116119 * - OpaqueKey
117120 - Codes composed together into a semi-readable instance-wide identifier
118121 - subclass of ``OpaqueKey ``
119122 - * ``*_key `` for parsed OpaqueKey objects
120- * ``*_key `` or ``*_key_string `` for serialized OpaqueKey strings
123+ * ``*_key `` or ``*_key_str `` for serialized OpaqueKey strings
124+ - ``OpaqueKeyField ``
121125 * - UUID
122126 - Globally unique identifier scoped across all Open edX instances
123127 - ``uuid.UUID ``
124128 - * ``*_uuid `` for parsed UUID objects
125- * ``*_uuid `` or ``*_uuid_string `` for serialized UUID strings
129+ * ``*_uuid `` or ``*_uuid_str `` for serialized UUID strings
130+ - ``UUIDField ``
126131
127132Integer Primary Keys
128133====================
129134
130- Every Open edX Django model should use ``django.db.models.BigAutoField `` as its primary key.
131- This is an auto-incremented integer assigned by the database, meaningful only within that
132- database.
135+ Every Open edX Django model should declare an auto-incrementing integer primary key.
133136
134137**When to use: ** Primary keys are the default way to reference a database row within a single
135- IDA on a single instance. Always use them for Django model foreign key relationships—as
138+ deployed service on a single instance. Always use them for Django model foreign key relationships—as
136139integers, they can be indexed with almost no overhead, making lookups, joins, and constraint
137140enforcement as fast as possible.
138141The trade-off is that primary keys are meaningless outside the database that assigned them.
139142
140143**How to name: ** On Django models, the primary key is ``id `` and foreign keys automatically
141144get the ``_id `` suffix (e.g. ``collection_id ``). Outside of model definitions—in variable
142145names, REST APIs, event schemas, and so on—use the suffix ``_pk `` instead (e.g.
143- ``collection_pk ``). "id" is an overloaded term that means many things; ``_pk `` leaves no
146+ ``collection_pk ``). When accessing the primary key on a django model, prefer ``.pk ``, e.g.
147+ ``collection.pk ``. "id" is an overloaded term that means many things; ``_pk `` leaves no
144148doubt that the value is a Django model integer primary key.
145149
150+ **How to store: ** By default, use ``django.db.models.BigAutoField `` for all primary keys.
151+ In rare cases—when a model has very few rows (like an enumeration) or receives a massive
152+ number of foreign key references—use the smaller ``django.db.models.AutoField `` instead.
153+
146154.. code-block :: python
147155
148156 @dataclass
@@ -187,6 +195,10 @@ Historically, codes have been called "slugs" or "shortnames", and existing code
187195suffixes like ``_slug ``, ``_id ``, or no suffix at all (e.g. ``org ``, ``run ``,
188196``block_type ``). The suffix ``_code `` is preferred for new code.
189197
198+ **How to store: ** By default, store codes in a case-sensitive ``CharField `` of length 255
199+ with a regex validator. A factory function is available at ``openedx_django_lib.fields.code_field ``.
200+
201+
190202OpaqueKeys
191203==========
192204
@@ -237,7 +249,7 @@ an instance, so when do you choose one over the other?
237249 strings → ``_key `` suffix.
238250* REST APIs, event data fields, and other external representations → ``_key `` suffix.
239251* When parsed objects and raw strings co-exist in the same context (e.g. a parsing
240- function) → use ``_key_string `` for the raw string to disambiguate.
252+ function) → use ``_key_str `` for the raw string to disambiguate.
241253* Frontend variables → ``*Key `` suffix. ``*KeyString `` is not needed because parsed
242254 OpaqueKey objects don't exist on the frontend.
243255
@@ -254,6 +266,10 @@ Prefer passing parsed ``OpaqueKey`` objects over raw strings whenever possible
254266type-safe and keep all parsing logic in one place. Use the specific ``OpaqueKey `` subclass
255267as a type annotation wherever it's known.
256268
269+ **How to store: ** The best way to store an OpaqueKey is an ``OpaqueKeyField `` subclass such
270+ as ``UsageKeyField ``, providing automatic marshalling between OpaqueKey objects and their
271+ string representations in SQL VARCHARs.
272+
257273💡 **Historical note: ** Concrete OpaqueKey subclasses use the suffix ``Locator `` instead of
258274``Key `` for historical reasons. This distinction can be ignored by consumers—they're all
259275``_keys ``. We plan to rename all ``*Locator `` classes to ``*Key `` in the future.
@@ -273,26 +289,33 @@ Open edX instances, not just one. Unlike primary keys and OpaqueKeys, UUIDs have
273289dependency on any particular database or deployment.
274290
275291**When to use: ** Use UUIDs when an object needs an identity that is stable and globally
276- unique across every Open edX instance—for example, when content must be tracked or
277- synchronized across instances without any shared database.
292+ unique across every Open edX instance, allowing the objects to be shared outside and
293+ across instances without risk of collision. For example:
294+ * Learner certificates awarded on different Open edX instances should have distinct
295+ UUIDs, even if their instance-local identifiers (course run key, user primary
296+ key) are identical.
297+ * Changelog entries should have distinct UUIDs, even the changes are identical.
278298
279299**How to name: **
280-
281300* Python variables and attributes holding a parsed ``uuid.UUID `` object → ``_uuid `` suffix.
282301* Django Model Fields and Serializer Fields that convert between UUID objects and strings →
283302 ``_uuid `` suffix.
284303* REST APIs, event data fields, and other external representations → ``_uuid `` suffix.
285- * When parsed objects and raw strings co-exist in the same context → use ``_uuid_string ``
304+ * When parsed objects and raw strings co-exist in the same context → use ``_uuid_str ``
286305 for the raw string to disambiguate.
287306* Frontend variables → ``*Uuid `` suffix. ``*UuidString `` is not needed because parsed UUID
288307 objects are not used in Open edX frontend code.
289308
309+ **How to store: ** The best way to store a UUID is a ``UUIDField ``.
310+
311+ Not every object needs a universal identity. Consider the need before defining a ``UUDIField ``.
312+
290313Other Identifiers
291314=================
292315
293316Not every identifier fits neatly into one of the four categories above. When that happens,
294- choose a name that avoids the reserved suffixes ``_pk ``, ``_key ``, ``_key_string ``,
295- ``_code ``, ``_uuid ``, and ``_uuid_string ``. Using a different name signals clearly to
317+ choose a name that avoids the reserved suffixes ``_pk ``, ``_key ``, ``_key_str ``,
318+ ``_code ``, ``_uuid ``, and ``_uuid_str ``. Using a different name signals clearly to
296319readers that this identifier has its own semantics and shouldn't be treated as one of the
297320standard types.
298321
@@ -320,7 +343,6 @@ Start: New conventions
320343======================
321344
322345* ``_pk `` for integer primary key variables.
323- * ``_key_string `` and ``_uuid_string `` for stringified OpaqueKeys and UUIDs.
324346* ``_code `` for codes (and, the term "code" in general)
325347* ``BlockRef `` and ``block_ref `` for 2-tuples of ``(type_code, block_code) ``
326348
@@ -340,6 +362,7 @@ Continue: Already widely adopted
340362* ``id `` field on models for integer primary keys.
341363* ``_key `` for OpaqueKey objects.
342364* ``_uuid `` for UUID objects.
365+ * ``_key_str `` and ``_uuid_str `` for stringified OpaqueKeys and UUIDs.
343366
344367Migration plan
345368==============
0 commit comments