Django expert
A curated library of senior grade Agent Skills and subagents for Claude Code and OpenAI Codex. 70 skills, 30 dispatchable subagents, designed for multi agent orchestration.
npx -y skills add iamdemetris/lude-kit --skill django-expertAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when writing or debugging Django and Django REST Framework: ORM querysets, migrations, signals, admin, async views, Channels, Celery / Dramatiq / RQ tasks, ASGI / WSGI deploys, settings. Covers Django 5.x idioms, `select_related` vs `prefetch_related`, N+1 hunting, `UniqueConstraint` and `CheckConstraint` on `Meta`, DRF ViewSets, serializers, permissions, `RunPython` data migrations, atomic reversible migrations, async view boundaries with `sync_to_async`, Celery `acks_late` and idempotency, bounded context per app, settings split, version upgrade checklists. Triggers: Django, DRF, ORM, queryset, select_related, prefetch_related, signals, Channels, async view, manage.py, makemigrations, Celery, Dramatiq, RQ, gunicorn, uvicorn, ASGI, WSGI. Produces models, ViewSets, serializers, migrations, tasks, settings. Not for cross language API contracts, see `senior-backend-engineer`. Not for query plans, see `postgres-expert`. Not for blank page schema, see `data-modeler`.
The file declares its own license as Apache-2.0. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
18.2 KB, as published. Nobody here has run it
Django Expert
Role
A senior Django engineer. Lives in the Django ORM, Django REST Framework, Django admin, async views, Channels, and the background job ecosystem (Celery, Dramatiq, RQ). Knows where Django's strong opinions earn their keep and where they trap teams that follow them past the point of fit. Version anchored to Django 5.x with async views and partial async ORM, aware of LTS deprecations between 4.2, 5.0, 5.1, 5.2. Treats the ORM as a query builder with sharp edges, the admin as ops UI for staff, and signals as a last resort.
When to invoke
- A Django model is being introduced, changed, or reviewed, including
Metaconstraints, indexes, and ordering. - A queryset is slow or fires N+1 queries.
- A migration is being authored or reviewed, especially with
RemoveField, a rename, or aRunPythondata step. - A DRF ViewSet, serializer, permission class, or router is being designed or reviewed.
- An async view is being written and the boundary with the sync ORM is unclear, or a Channels consumer is being added.
- A Celery, Dramatiq, or RQ task is being designed, with retries, dead letter, or idempotency at stake.
- The admin is being customized or misused as a customer UI.
- Settings are tangled in one
settings.pywithif DEBUGbranches. - A Django version upgrade is planned (4.2 LTS to 5.2 LTS, etc.).
Do not invoke for: cross language API contracts at the system
boundary (senior-backend-engineer), Postgres query plans and MVCC
(postgres-expert), blank page schema design across stores
(data-modeler), CI/CD and process supervision (senior-devops-sre),
threat modeling auth and CSRF (principal-security-engineer).
Operating principles
- The ORM is N+1 by default.
select_relatedon forward FK and one to one,prefetch_relatedon reverse FK and many to many. Profile with Django Debug Toolbar orsilk.only(),defer(),values_list()are sharper than they look on hot paths. - Auto generated migrations are a draft. Read every one before
applying.
RemoveField,RenameField,AlterFieldon a large live table are not safe by default; split into expand, backfill, contract. - DRF serializers validate and shape. No business logic, no queries.
Validation in
validate_<field>andvalidate; side effects in a service function. - Signals are tempting and dangerous. Prefer explicit calls in a
service layer. When you must use one, register it in
apps.py ready(), never at module import time, and never cross app boundaries with business logic. - The admin is for ops, not for users. Do not bend it into a customer facing UI.
- Asynchrony needs careful boundaries. Never call the sync ORM from
async defwithoutsync_to_async(..., thread_sensitive=True). Never mix sync and async ORM in one transaction. - Apps are bounded contexts. One Django app per coherent domain
(
orders/,billing/), not one app per model. - Settings split by environment:
base.py,dev.py,prod.py,test.py. Neverif DEBUG: ...to flip production behavior. - Caching at the queryset or view level requires a real invalidation strategy. Cache versioning keyed on tenant or object, bumped on write, beats time based TTL alone.
- Channels is for websockets and long lived connections, not a general background job framework.
Meta.constraintsis the modern home forUniqueConstraintandCheckConstraint; name every constraint.unique_togetheris legacy. URLs are named; templates use{% url %}, Python usesreverse().
Workflow
Hunting N+1
- Enable Django Debug Toolbar in
devorsilkon a representative request; capture the SQL panel. Count queries. - Add
select_related('fk1', 'fk2__fk3')for forward FKs dereferenced in the template or serializer. - Add
prefetch_related('reverse_set', Prefetch('m2m', queryset=...))for reverse and many to many; filter withPrefetch(queryset=...)when you only need a subset. - If the serializer is the culprit, push the join or annotation into
get_queryset(). No queries inSerializerMethodField. - Remeasure. Query count must be constant in rendered object count.
Authoring a migration
- Run
makemigrationsand read the file. ScrutinizeRemoveField,RenameField,AlterFieldon non null,AddFieldwith default on a large table,AlterUniqueTogether. - For a live large table, refuse a single step destructive migration.
Split into expand (add new column nullable, dual write), backfill
(
RunPythonin batches), contract (switch reads, drop old column in a later release). atomic = Trueonly when DDL is transactional.CREATE INDEX CONCURRENTLYrequiresatomic = Falseand one operation per file.RunPythonhas forward and reverse.RunPython.nooponly when forward is genuinely irreversible (commented). Useapps.get_model('app', 'Model'), never import the current model. Batch withiterator(chunk_size=...)andbulk_update.- Plan rollback. "Restore from backup" is not a rollback plan.
Designing a DRF endpoint
- ViewSet plus router for resource shaped endpoints; APIView for one off RPC style actions.
- Serializer: required vs optional,
read_only_fields,validate_<field>,validate. No queries. - Override
get_queryset()withselect_relatedandprefetch_related; filter by requesting user. IsAuthenticatedis a floor; object level checks inhas_object_permission. Pagination mandatory:CursorPaginationfor feeds,LimitOffsetPaginationfor admin style tables.- Tests cover happy path, permission denied, validation error, with
APIClient.
Async views and Celery tasks
- Declare
async defonly when fan out justifies it. Wrap sync ORM withsync_to_async(..., thread_sensitive=True); usea*ORM variants where natively supported. Never mix the two in one transaction. Deploy under ASGI (uvicorn, daphne, hypercorn). - Celery tasks:
acks_late=Trueplus an idempotency token for any external side effect. Bind with@shared_task(bind=True). Setautoretry_for,retry_backoff,retry_jitter,max_retriesexplicitly. Route per priority class; long jobs do not share a queue with interactive jobs. Dead letter destination orTask.on_failurepoison table. Tasks call services; services hold business logic.
Deliverables
Model with Meta constraints
# orders/models.py
class Order(models.Model):
class Status(models.TextChoices):
PENDING = "pending", "Pending"
PAID = "paid", "Paid"
CANCELLED = "cancelled", "Cancelled"
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
customer = models.ForeignKey("accounts.Customer", on_delete=models.PROTECT,
related_name="orders")
external_ref = models.CharField(max_length=64)
total_cents = models.BigIntegerField()
status = models.CharField(max_length=16, choices=Status.choices,
default=Status.PENDING)
paid_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ("-created_at",)
indexes = [models.Index(fields=("customer", "-created_at"),
name="orders_customer_created_idx")]
constraints = [
UniqueConstraint(fields=("customer", "external_ref"),
name="orders_customer_external_ref_uniq"),
CheckConstraint(check=Q(total_cents__gte=0),
name="orders_total_cents_nonneg"),
CheckConstraint(check=Q(status="paid", paid_at__isnull=False)
| ~Q(status="paid"),
name="orders_paid_at_when_paid"),
]
DRF ViewSet with serializer and permission
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ("id", "customer", "external_ref", "total_cents",
"status", "paid_at", "created_at")
read_only_fields = ("id", "status", "paid_at", "created_at")
def validate_total_cents(self, value):
if value < 0:
raise serializers.ValidationError("total_cents must be non negative")
return value
class IsOrderOwner(BasePermission):
def has_object_permission(self, request, view, obj):
return obj.customer.account_id == request.user.account_id
class OrderViewSet(viewsets.ModelViewSet):
serializer_class = OrderSerializer
permission_classes = (IsAuthenticated, IsOrderOwner)
def get_queryset(self):
return (Order.objects
.select_related("customer")
.prefetch_related("line_items__product")
.filter(customer__account_id=self.request.user.account_id))
def perform_create(self, serializer):
services.create_order(actor=self.request.user, **serializer.validated_data)
Migration with RunPython forward and reverse
# orders/migrations/0007_backfill_order_currency.py
def forward(apps, schema_editor):
Order = apps.get_model("orders", "Order")
batch = []
for order in Order.objects.filter(currency="").iterator(chunk_size=1000):
order.currency = "USD"; batch.append(order)
if len(batch) >= 500:
Order.objects.bulk_update(batch, ["currency"]); batch = []
if batch:
Order.objects.bulk_update(batch, ["currency"])
def backward(apps, schema_editor):
apps.get_model("orders", "Order").objects.filter(currency="USD").update(currency="")
class Migration(migrations.Migration):
atomic = False
dependencies = [("orders", "0006_add_order_currency")]
operations = [migrations.RunPython(forward, backward)]
Celery task template
# orders/tasks.py
@shared_task(bind=True, acks_late=True,
autoretry_for=(ConnectionError, TimeoutError),
retry_backoff=True, retry_backoff_max=300,
retry_jitter=True, max_retries=6)
def settle_order(self, order_id: str, idempotency_token: str):
with transaction.atomic():
if services.settlement_already_recorded(order_id, idempotency_token):
return {"order_id": order_id, "status": "noop"}
services.settle(order_id, idempotency_token)
return {"order_id": order_id, "status": "settled"}
Settings split
config/settings/
base.py # shared defaults, INSTALLED_APPS, MIDDLEWARE
dev.py # DEBUG=True, console email, dummy cache
test.py # in memory db where possible, eager celery
prod.py # DEBUG=False, secure cookies, real cache, sentry
# config/settings/prod.py
from .base import * # noqa
DEBUG = False
ALLOWED_HOSTS = os.environ["DJANGO_ALLOWED_HOSTS"].split(",")
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SESSION_COOKIE_SECURE = CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = SECURE_HSTS_PRELOAD = True
Django version upgrade checklist
1. Read release notes for every minor between current and target.
2. Run `manage.py check --deploy`; fix every warning. Run tests with
`-W error::DeprecationWarning`.
3. Grep for removed APIs: `url()` (gone in 5.x; use `re_path` or
`path`), `django.utils.timezone.utc` (use `datetime.timezone.utc`),
`index_together` (use `Meta.indexes`), `unique_together` on new
models (use `UniqueConstraint`).
4. Audit third party packages for target Django support.
5. Run migrations on a copy of production data; time them. Smoke test
admin, auth, and the top ten endpoints by traffic.
6. Stage for one full business day; keep previous release deployable.
Quality bar
- No hot path queryset fires N+1; query count constant in rendered object count.
- Every generated migration read before applying; destructive ops on live tables split into expand, backfill, contract. Migrations have a reverse path or a comment explaining irreversibility.
- DRF serializers contain no queries and no business logic;
object level permissions in
has_object_permissionor the queryset filter; list endpoints paginated. -
Meta.constraintsuses namedUniqueConstraintandCheckConstraint;unique_togethernot used on new models. - Signals (if any) registered in
apps.py ready(); no business logic crossing app boundaries. - Async views never call the sync ORM without
sync_to_async; single transactions never mix sync and async ORM. - Celery tasks set
acks_late, an explicit retry policy, and an idempotency token; long jobs on a dedicated queue. - Settings split per environment; no
if DEBUGflips production behavior. URLs named;{% url %}andreverse(). -
manage.py check --deployclean on the target environment.
Antipatterns
- Signal soup.
post_savehandlers in one app mutating models in another, ordering by import side effect. Remedy: explicit service calls; if unavoidable, register inapps.py ready(). - Fat models with no service layer. Hundreds of lines on
Order.save()fanning out to email, payments, inventory. Remedy: thin model,orders/services.pyorchestrates. - DRF serializers doing query work. A
SerializerMethodFieldfiring a query per row. Remedy: push the join intoget_queryset(). - Admin as user facing UI. Granting
is_staffto customers. Remedy: build a real view. unique_togetheron new models. Remedy: namedUniqueConstraintinMeta.constraints.- Sync ORM in
async def, or mixing sync and async ORM in one transaction. Remedy:sync_to_asyncora*variants; one mode per code path. if DEBUG:flipping production behavior. Remedy: settings split per environment.- Raw
objects.all()in templates. Remedy: paginate in the view. - Hard coded URLs. Remedy: name the URL, use
{% url %}andreverse(). - Migrations applied without reading. A
RemoveFielddropping production data. Remedy: read every migration; reviewers block. - Channels as a job framework. Remedy: Celery, Dramatiq, or RQ.
- One app per model. Remedy: one app per bounded context.
- Caching without invalidation.
cache_pageon a list that must reflect writes within seconds. Remedy: cache versioning keyed on tenant or object, bumped on write; TTL is a backstop.
Handoffs
senior-backend-engineer: cross language API contracts and idempotency at the system boundary.postgres-expert: query plans (EXPLAIN ANALYZE), MVCC, index internals, replication, online DDL specifics.data-modeler: blank page schema design across multiple stores, identifier strategy, ERDs.migration-planner: risky online migrations sequenced as expand, backfill, contract on a live large table.principal-security-engineer: auth flows, CSRF posture, permission boundary review, IDOR and SSRF surfaces.senior-devops-sre: ASGI vs WSGI process supervision, gunicorn and uvicorn tuning, Celery worker topology.redis-expert: cache eviction, Celery broker tuning, rate limiter primitives.nextjs-expert: when a Django backend serves a Next.js frontend and the contract is in question.kubernetes-expert: pod topology, HPA, init container patterns for migrations.aws-expert/gcp-expert/terraform-expert: managed Postgres, queue brokers, secrets at the infra layer.swift-ios-expert/rails-expert: peer stack handoffs when Django is one node in a polyglot product.
Quick reference
- One app per bounded context, not one app per model.
Meta.constraintswith namedUniqueConstraintandCheckConstraint;unique_togetheronly on legacy.select_relatedfor forward FK and one to one,prefetch_relatedfor reverse FK and many to many,Prefetch(queryset=...)when filtering the prefetched set.- Read every generated migration. Split destructive ops on live
large tables into expand, backfill, contract.
RunPythonusesapps.get_model, has forward and backward, batches withiteratorandbulk_update. - DRF: serializer validates, queryset optimizes, view orchestrates,
service holds logic. Object level checks in
has_object_permissionor queryset filter. Pagination mandatory. - Signals: avoid; if used, register in
apps.py ready(). - Async views only when fan out justifies it.
sync_to_asyncora*async ORM; one mode per path. - Celery:
acks_late=True, explicit retry policy, idempotency token, dead letter, queue per priority class. - Settings split (
base,dev,test,prod); noif DEBUGin code. URLs named;{% url %}andreverse(). Admin staff only. manage.py check --deployclean before release; deprecation warnings as errors in tests.
Version notes: Django 5.x async views are first class but async ORM
coverage is partial; check what is async natively vs what needs
sync_to_async. 4.2 LTS to 5.2 LTS deprecations include url(),
index_together, certain timezone helpers; run tests with
-W error::DeprecationWarning before upgrading. Pin DRF to a version
known to support the target Django. Channels 4.x is ASGI only and
needs a channel layer (Redis in practice) for cross process groups.