-
bars/models.py — model definition:
from django.db import models
from common.models import OrganizationModel
from common.fields import OrganizationForeignKey
from bars.managers import FooManager
class Foo(OrganizationModel):
name = models.CharField(max_length=200)
parent = OrganizationForeignKey(
"bars.Parent",
on_delete=models.CASCADE,
related_name="foos",
)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = FooManager()
class Meta:
db_table = "bars_foo"
constraints = [
models.UniqueConstraint(
fields=["organization", "name"],
name="bars_foo_unique_org_name",
),
]
indexes = [
models.Index(fields=["organization", "is_active"]),
]
def __str__(self) -> str:
return f"{self.name} ({self.organization_id})"
- Always include
organization in unique / index keys. A unique=True on name alone collides across tenants and ruins the migration on the first cross-tenant duplicate.
- Always lead composite indexes with
organization_id — every tenant-scoped query starts with the org filter.
-
bars/querysets.py — queryset class (chainable methods):
from django.db.models import QuerySet
class FooQuerySet(QuerySet["Foo"]):
def active(self) -> "FooQuerySet":
return self.filter(is_active=True)
def for_parent(self, parent_id: int) -> "FooQuerySet":
return self.filter(parent_id=parent_id)
-
bars/managers.py — manager (use from_queryset so chainable methods are also manager methods):
from common.managers import OrganizationManager
from bars.querysets import FooQuerySet
FooManager = OrganizationManager.from_queryset(FooQuerySet)
OrganizationManager is the project base that raises on missing org filter. Subclass / from_queryset it — never plain models.Manager.
-
bars/admin.py — admin registration:
from django.contrib import admin
from bars.models import Foo
@admin.register(Foo)
class FooAdmin(admin.ModelAdmin):
list_display = ("name", "organization", "parent", "is_active", "created_at")
list_filter = ("organization", "is_active")
search_fields = ("name",)
autocomplete_fields = ("organization", "parent")
-
bars/factories.py — model-bakery / factory_boy factory:
from model_bakery import baker
from bars.models import Foo
def foo_factory(organization, **overrides) -> Foo:
return baker.make(
Foo,
organization=organization,
**overrides,
)
Always require organization as a positional / keyword arg; never default it. Tests that forget to pass org should fail loudly.
-
bars/migrations/ — generate the migration:
docker compose run --rm api uv run python manage.py makemigrations bars
Inspect the generated file. For tenant-scoped FKs, both the <name>_fk concrete column and the <name> ForeignObject should be present.
-
bars/virtual_models.py — only if the model surfaces through DRF and the default queryset would N+1. Add the virtual model wired to the serializer.
-
bars/tests/test_foo_models.py — at minimum:
- Creating a
Foo without an organization raises.
- The manager's
.active() / .for_parent(...) methods filter correctly.
- The unique constraint allows the same
name in two different orgs.
__str__ doesn't crash.