# Django Backend Dev

> Django REST Framework backend development skill. Use when building, extending, or fixing Django APIs — models, serializers, views, filters, admin, migrations, tests. Triggers on: "create a Django app", "add an API endpoint", "build the backend for", "write a DRF serializer", "create a model for", "add filtering to", "write pytest tests for", "implement the API for", "fix this Django view", "add soft delete to". Always use this skill for any backend-only Django/DRF task. See django-react-dev for full-stack.

- Skill: `ayithamsetty-vamsi-krishna/django-backend-dev` (Agent Skill, multi-file: 21 files)
- Install (CLI): `npx skillmds@latest add ayithamsetty-vamsi-krishna/django-backend-dev`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ayithamsetty-vamsi-krishna/django-backend-dev/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: Ayithamsetty-Vamsi-krishna (https://skillmd.com/u/ayithamsetty-vamsi-krishna)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/ayithamsetty-vamsi-krishna/django-backend-dev

---


# Django Backend Dev Skill — v1.5.2

You are a senior Django REST Framework engineer. Follow this skill precisely.

---

## PHASE 0 — INPUT GATHERING

### Step 1: Identify input type FIRST
Before anything else — understand what the user has given you:
- **Direct instruction** → read it carefully, extract requirement
- **PDF PRD** → extract text first, THEN continue:
  - Claude.ai: PDF already in context — read directly
  - Claude Code: `pdftotext path/to/prd.pdf -`
- **Existing codebase reference** → note which apps are involved

### Step 2: Check for CLAUDE.md
Now check if `CLAUDE.md` exists at the project root:
- **If it exists:** read it immediately. Use it as primary source of project context.
  Skip or shorten codebase analysis for anything already documented.
- **If it does not exist — new project:** generate it from
  `assets/templates/CLAUDE.md.template` at the end of the first task.
- **If it does not exist — existing project:** do full codebase analysis (Step 3),
  then generate `CLAUDE.md` at the end so future sessions skip this step.

### Step 3: Analyse existing codebase (if CLAUDE.md absent or incomplete)
**Small (< 20 files):** Map inline — apps, models, serializers, views, FilterSets, patterns.
**Large (20+ files):** Spawn analysis agent:
```
Analyse this Django codebase. Concise report (max 400 words, bullets only):
- All apps and their purpose
- Models with fields and relationships
- Serializer patterns (FK handling, nested data)
- View patterns (generics, mixins, permissions)
- URL structure and existing endpoints
- FilterSet classes
- Base classes in core/
- Error handling pattern (custom exception handler?)
- Settings structure (env vars, decouple?)
```

### Step 4: Intelligent Clarifying Questions

**Always use `ask_user_input_v0` regardless of environment (Claude Code or Claude.ai).**

Do NOT use a static question list. Instead:

1. **Analyse the requirement** — identify what is already clear vs what is genuinely ambiguous
2. **Skip obvious questions** — if the requirement says "extend the orders app", don't ask "new app or existing?"
3. **Suggest best practice defaults** for anything not specified — present as choices, not open questions
4. **Ask only what is ambiguous** — maximum clarity, minimum friction

**Decision framework before asking each question:**

| Question | Ask if... | Skip if... |
|---|---|---|
| New app or extend existing? | App not mentioned in requirement | Requirement names an existing app |
| User roles / permissions? | Access control not mentioned | Requirement says "all users" or "admin only" |
| New models or extend existing? | Data structure unclear | Requirement clearly names existing models |
| Business rules / validation? | **Always ask** — rarely fully specified in PRDs | Never skip |
| External integrations? | Requirement mentions email, files, payments etc. | No third-party systems mentioned |
| FilterSet update needed? | Task adds/modifies a model field | No new fields, or field clearly non-filterable |

**Best practice suggestions — present these as choices when not specified in the requirement:**

```
Pagination: I recommend 20 records/page (our default). Change?
  → [Keep 20] [Change to 10] [Change to 50] [Custom]

Permissions: Who can access this endpoint?
  → [All authenticated users] [Specific Django permission] [Admin only]

Soft delete: Should records be soft-deletable?
  → [Yes — standard soft delete] [No — hard delete acceptable here]

Filter fields: Which fields should be filterable?
  → [Suggest based on model fields] [None needed] [I'll specify]

New field added: Should it be added to the FilterSet?
  → [Yes — add to <App>Filter] [No — not needed for filtering]
```

**Round limit:** There is no fixed limit — ask as many rounds as needed until everything is clear.
But group related questions in one `ask_user_input_v0` call. Never ask one question per call.

**Only proceed to Phase 1 once ALL ambiguities are resolved.**

---

## PHASE 1 — ANALYSIS & TEST CASES

### Requirement Summary
Restate clearly: models affected, endpoints needed, business rules, validation constraints.

### Test Cases (generate BEFORE any code)
- ✅ Happy path per endpoint (GET list, GET detail, POST, PATCH, DELETE)
- ❌ Negative: invalid payload, missing fields, wrong types
- ❌ Business rule violations → correct error message + shape returned
- 🔒 Auth: unauthenticated, wrong role
- 🔁 Edge: empty list, nulls, boundary values, duplicate submissions
- 🗑️ Soft delete: deleted record absent from list, 404 on detail
- 🔍 Filters: each FilterSet field, combined filters, invalid values
- 📄 Pagination: first/last page, out of range
- 📐 Error response shape: all errors match `{ success, message, errors }` contract

---

## PHASE 2 — PLAN (show, wait for approval, no code until approved)

### Task size detection
Before writing the plan, assess complexity:
- **Single field / single filter / single component change** → use QUICK CHANGE PLAN below
- **Everything else** → use FULL PLAN below

```
─────────────────────────────────
QUICK CHANGE PLAN  (single field/filter change only)
─────────────────────────────────
CHANGE: [exact change in one line]
FILES AFFECTED: [list]
MIGRATION NEEDED: [yes — run makemigrations + migrate / no]
STEPS:
  1. [step]
  2. [step]
  ...
FILTERSET UPDATE: [yes — add <field> to <App>Filter / no]
TEST CASES: [list only directly relevant ones]
─────────────────────────────────
```

```
═══════════════════════════════════
BACKEND IMPLEMENTATION PLAN  (all other tasks)
═══════════════════════════════════
SUMMARY: [1-2 sentences max]

TASKS
─────
B1: [Task name]
  B1.1 [sub-task]
  B1.2 [sub-task]
B2: [Task name]
  ...
T1: Tests
  T1.1 [test file/class]

API CONTRACT
────────────
[METHOD /api/v1/path/ — description, one line each]
[All errors return: { success: false, message, errors }]

MODELS AFFECTED: [list]
BUSINESS RULES: [list any validate_<field> / validate() needed]
COMPLEXITY: Medium / High  (use Quick Change Plan for Low)
═══════════════════════════════════
```
**Ask: "Plan looks good? Any changes before I start?"**

---

## PHASE 3 — IMPLEMENTATION (one task at a time, confirm between each)

### Critical rule for all serializer create()/update() tasks
⚠️ NEVER use `bulk_create()` or `bulk_update()` inside serializer `create()` or `update()`. These bypass Django `save()` signals and break any model-level code generation (e.g. sequential codes). Always use individual `Model.objects.create()` calls. See `references/orm-settings.md` for full explanation.

### Cross-app logic rule
⚠️ Logic touching models from MORE THAN ONE app → create a service class in `services.py`. Same-app logic stays in serializer. See `references/services.md`.

### Reference Loading (load ONLY what the current task needs)
- Models / BaseModel / mixins → `references/models.md`
- Serializers / views / filters / URLs / permissions → `references/serializers-views.md`
- Admin registration → `references/admin.md`
- Testing setup (pytest.ini, factories, conftest) → `references/testing-setup.md`
- Serializer + API view tests → `references/testing.md`
- Advanced tests (services, signals, concurrency) → `references/testing-advanced.md`
- ORM / settings → `references/orm-settings.md`
- Error handling / env vars / CORS → `references/error-settings.md`
- API versioning / breaking changes → `references/api-versioning.md`
- Cross-app service layer → `references/services.md`
- Sequential code generation (ORD-0001) → `references/code-generation.md`
- Django signals (model events → tasks/cache/notifications) → `references/services.md` (signals section)
- Redis caching → route to `django-integrations-dev` → `references/caching.md`
- Audit log (who did what, when, from where) → `references/audit-log.md`
- Multi-tenancy (shared-schema with tenant_id) → `references/multi-tenancy.md` (only if multi-tenant chosen at setup)
- Feature flags (custom implementation) → `references/feature-flags.md`
- Full-text search — PostgreSQL → `references/search-postgres.md` (default for <1M records)
- Full-text search — Elasticsearch → `references/search-elasticsearch.md` (for scale or fuzzy/faceted search)
- Field-level encryption (MultiFernet rotating keys) → `references/field-encryption.md` (for PII, secrets, tokens)
- GDPR compliance (cookie consent + data export) → `references/gdpr-compliance.md` (if EU/EEA users)
- New app scaffold → `assets/templates/django-app-scaffold.py`
- New project (no CLAUDE.md yet) → generate from `assets/templates/CLAUDE.md.template`

### After each task:
1. Show the completed code
2. **If the task created or modified a model:** run migrations before moving on:
   ```bash
   python manage.py makemigrations <app_name>
   python manage.py migrate
   ```
3. Suggest a git commit: `git add . && git commit -m "feat: [task description]"`
4. Ask: **"Task [X] done ✓ — ready to move to [next task name]?"**

---

## PHASE 4 — REVIEW CHECKLIST

> **Adaptive checklist:** Skip any item that was explicitly opted out of during Phase 0 clarifying questions (e.g. user chose hard delete → skip SoftDeleteMixin item; user chose no OAuth → skip OAuth items). The checklist reflects defaults — document any deliberate deviations in CLAUDE.md.

- [ ] All models inherit `BaseModel` — no manual id/timestamps
- [ ] All models have meaningful `__str__` method
- [ ] All views use `AuditMixin` — `created_by`/`updated_by` auto-filled
- [ ] All destroy views use `SoftDeleteMixin` — no `.delete()` calls
- [ ] All querysets filter `is_deleted=False`
- [ ] Zero N+1 — `select_related`/`prefetch_related` on every queryset incl. `created_by`, `updated_by`
- [ ] DRF Generics only — no APIView
- [ ] FilterSet classes only — no raw query params
- [ ] All views have explicit `permission_classes` — `IsAuthenticated` or `GetPermission(...)`
- [ ] Dual FK serializer: `<field>_id` write + `<field>` nested read
- [ ] Child serializers have `list_serializer_class = FilteredListSerializer`
- [ ] Child serializers have `id = UUIDField(required=False)` (or IntegerField for int PKs)
- [ ] Child serializers have `dodelete = BooleanField(write_only=True, required=False)`
- [ ] Parent `create()` and `update()` wrapped with `@transaction.atomic`
- [ ] `update()` soft-deletes children via `is_deleted=True, is_active=False` — no hard delete
- [ ] New children only created when `dodelete=False`
- [ ] FK querysets filter `is_deleted=False` (e.g. `Product.objects.filter(is_deleted=False)`)
- [ ] `SerializerMethodField` for all computed/display fields
- [ ] No DB queries inside `SerializerMethodField` (use prefetched data)
- [ ] `validate_<field>()` / `validate()` for all business rules
- [ ] All errors return `{ success, message, errors }` via custom exception handler
- [ ] `core/exceptions.py` registered in `REST_FRAMEWORK` settings
- [ ] `core/serializers.py` has `FilteredListSerializer` (with queryset/list safety check)
- [ ] `core/permissions.py` has `GetPermission` factory
- [ ] Settings use `python-decouple` | `.env.example` committed | `.env` gitignored
- [ ] `throttle_classes` on all public-facing and mutation endpoints (rate limiting)
- [ ] `DEFAULT_THROTTLE_RATES` configured in settings (anon + user rates)
- [ ] Integration test: full request → serializer → DB → response cycle verified
- [ ] Migrations created: `python manage.py makemigrations <app_name>` and applied: `python manage.py migrate`
- [ ] Full `admin.py` registration with soft-delete override
- [ ] Silk/debug-toolbar checked — zero N+1 confirmed
- [ ] All test cases from Phase 1 implemented
- [ ] Business rule violation tests with correct error shape
- [ ] Soft-delete test: deleted record absent from list, 404 on detail
- [ ] dodelete test: child soft-deleted, not hard-deleted
- [ ] `created_by`/`updated_by` verified in create/update tests
- [ ] CLAUDE.md created or updated with new app/feature info

**If audit logging is enabled (enterprise):**
- [ ] `AuditLog` model registered with GenericForeignKey and DB-level DELETE trigger
- [ ] `AuditContextMiddleware` in MIDDLEWARE (after AuthenticationMiddleware)
- [ ] Tracked models use `@track_audit` decorator — auto-log on save/delete
- [ ] Manual log points added: login, logout, sensitive access, data export
- [ ] Audit log API endpoint created (read-only, staff-only permission)
- [ ] Test: audit entry created on model save, update captures diff, delete blocked

**If multi-tenancy is enabled:**
- [ ] `Tenant` model created with subdomain/slug strategy chosen
- [ ] Business models inherit `TenantAwareBaseModel` (NOT plain BaseModel)
- [ ] `TenantMiddleware` registered AFTER auth, BEFORE audit middleware
- [ ] JWT claims include `tenant_id` (set in token serializer)
- [ ] Composite indexes: `(tenant, ...)` on every business model
- [ ] Unique constraints scoped to tenant (e.g. `unique_together=['tenant', 'code']`)
- [ ] Admin uses `.all_tenants()` to bypass default filtering
- [ ] Celery tasks wrapped in `TenantContext(tenant=...)` context manager
- [ ] Test: queryset returns 0 rows when accessed from wrong tenant context

**If feature flags are used:**
- [ ] `FeatureFlag` model created with OFF/ON/ROLLOUT/TARGETED states
- [ ] `FeatureFlagsMiddleware` registered AFTER TenantMiddleware
- [ ] Flag cache invalidated on `post_save` and `m2m_changed` signals
- [ ] Flag keys follow `<area>.<feature-name>` dotted-kebab convention
- [ ] Views use `feature_flag_required` decorator or `FeatureFlagRequired` mixin (returns 404 not 403)
- [ ] `last_toggled_by` + `last_toggled_at` populated in admin `save_model`
- [ ] Frontend-visible flags exposed via `/api/flags` endpoint with allow-list
- [ ] Celery tasks use `is_enabled_for_user()` helper (no request in worker)
- [ ] Test: rollout is sticky (same user always same bucket), kill switch wins

**If full-text search is enabled (Postgres):**
- [ ] `SearchableMixin` added to searchable models with declared `search_fields` weights
- [ ] GIN index on `search_vector`; composite `(tenant, search_vector)` for multi-tenant
- [ ] Signal OR DB trigger rebuilds `search_vector` on every save
- [ ] Every search query filters by `tenant` FIRST (missing = data leak)
- [ ] `ts_headline` used for highlighted snippets in API response
- [ ] `reindex_search` management command available for bulk rebuild
- [ ] Test: cross-tenant isolation — search in tenant B cannot find tenant A rows

**If full-text search is enabled (Elasticsearch):**
- [ ] `@registry.register_document` on each searchable Document class
- [ ] `tenant_id` indexed as KeywordField on every document
- [ ] Index name prefixed with `{APP_NAME}_{ENV}_` — no cross-env pollution
- [ ] Analyzer chain: `lowercase_analyzer` + stop/stem; `autocomplete_analyzer` if used
- [ ] EVERY query calls `.filter('term', tenant_id=str(tenant.pk))` FIRST
- [ ] Health check endpoint added to monitoring
- [ ] `search_index --populate` command verified in CI/deploy pipeline
- [ ] Test: cross-tenant isolation — manual assertion since no ORM auto-filter

**If field-level encryption is enabled:**
- [ ] `cryptography` library in requirements.txt
- [ ] `FERNET_KEYS` set in .env (comma-separated; position 0 is current)
- [ ] `EncryptedCharField` / `EncryptedTextField` used for all PII/secret fields
- [ ] Sensitive fields with lookup need: separate `<field>_hash` SHA-256 column
- [ ] Admin `list_display` does NOT include encrypted fields (N decryptions/page)
- [ ] `search_fields` in admin excludes encrypted fields
- [ ] `rotate_encryption` management command available for key rotation
- [ ] Test: DB raw query on encrypted field shows ciphertext, not plaintext
- [ ] Test: hash-based lookup finds the row via normalized+hashed value
- [ ] ADR in CLAUDE.md §7 documenting: what's encrypted, why, key rotation policy

---

## CLAUDE.md v2 Update Rules (saas-dev 4.0.0+)

At the end of Phase 3, update CLAUDE.md following the v2 protocol. Full rules:
`saas-dev/references/router/claude-md-update-protocol.md`. Quick reference for this skill:

**Always update:**
- §2 `last_updated` — today's date
- §3 `version_last_used` — current saas-dev version
- §9 Recent Changes — prepend one entry: `| YYYY-MM-DD | [SKILL_NAME] | [VERSION] | [change] |`

**Update as relevant to work done:**
- §4 Dependency Registry — new packages added (version + one-line purpose)
- §5 Environment Variables — new env vars (under correct subsection)
- §6 Third-Party Integrations — new row if integration added
- §7 Architecture Decisions — new ADR for non-obvious design choices
- §8 Known Issues — append if discovered during work

**Emit update checkpoint to chat:**
```
✓ CLAUDE.md updated:
  §4: +N dependencies
  §5: +N env vars
  §7: +ADR-NNN (title)
  §9: +1 change entry
```

Full format spec: `saas-dev/references/router/claude-md-v2.md`

