Programming·Updated May 10, 2026·18 min read·👁 4.9K views

Django AI Prompts: Build Production Python Web Apps Faster in 2026

John Allick· AI Researcher
📖 2,737 words
Quick Summary

Complete AI prompt library for Django developers. Covers Django REST Framework, models & migrations, authentication, Celery background tasks, pytest-django testing, Docker deployment, and performance tuning — all production-ready.

#Django#Python#DRF#Django REST Framework#AI Coding Prompts#Backend

Django in 2026: Battle-Tested Python Web Framework

Django remains the most complete Python web framework — batteries included, ORM, admin, auth, forms, and a mature ecosystem. The developers building the most maintainable Django applications in 2026 use AI prompts that specify the full ORM strategy, DRF serializer design, and security configuration upfront. These are the prompts they use.

AI + Django: What the Model Needs to Know

Django's convention-heavy structure is a double-edged sword for AI. Models that understand Django well generate idiomatic ORM queries, DRF ViewSets, and admin classes instantly. Models that don't generate Flask-style imperative code that fights the framework at every turn. The test: ask any model to generate a ModelViewSet with a nested serializer that includes an annotated queryset counting related objects. Claude produces idiomatic code in one pass. Most models produce a mix of function-based views, raw SQL, and serializer nesting errors that won't pass DRF validation.

The single Django instruction that prevents the most rework: "Use ModelViewSets (not function-based views), DRF serializers with nested relationships, and Django ORM annotate() / select_related() — no extra() calls or raw SQL. Django 5+ only." Include it at the start of every Django session. The second-most valuable instruction: explicitly request select_related and prefetch_related on every queryset that touches a ForeignKey — AI models almost never include them unprompted, and every omission is an N+1 query in production.

For a full model-by-model comparison across the Vue, Django, and GraphQL stack, see the Frontend & API Layer AI Prompts guide.

1. Project Structure & Settings

⌥ PROMPT
You are a senior Django architect who has built multi-tenant SaaS products serving 1M+ users.

Design a production Django 5 project structure for a SaaS application:
- Apps: users, organizations, projects, tasks, billing, notifications, core
- Settings: base.py, development.py, production.py, test.py (split settings pattern)
- Database: PostgreSQL with django-environ for environment variables
- Cache: Redis with django-redis backend
- Media/Static: S3 + CloudFront via django-storages
- Task queue: Celery + Redis broker
- Auth: custom User model (AbstractBaseUser) from day one

Deliver:
1. Full directory tree with one-sentence purpose per file
2. base.py settings with production-safe defaults (DEBUG=False default, explicit ALLOWED_HOSTS)
3. Custom User model with email as username, plus UserManager
4. Requirements split: base.txt, development.txt, production.txt, test.txt
5. The top 3 decisions you would make differently if starting from scratch today

2. Django Models with Proper Indexes

⌥ PROMPT
You are a Django ORM expert.

Design Django 5 models for a multi-tenant project management system: Organization, User, Project, Task, Comment, AuditLog.

Requirements:
- Custom User model extending AbstractBaseUser with email login
- SoftDeleteMixin: deleted_at DateTimeField(null=True), custom Manager filtering deleted records
- TimestampMixin: created_at, updated_at with auto_now_add and auto_now
- TenantMixin: organization ForeignKey on every model except Organization itself
- Meta class: indexes for all foreign keys + common filter columns (status, due_date, assigned_to)
- Task model: status choices enum (TODO/IN_PROGRESS/DONE/CANCELLED), priority choices, JSONField for custom_fields
- AuditLog: generic relation (ContentType) to any model, action enum, changed_fields JSONField, actor ForeignKey
- Custom managers: Task.objects.overdue(), Task.objects.assigned_to(user), Project.objects.active()

Output: all model files, managers, mixins, and the initial migration file.

3. Django REST Framework ViewSets

⌥ PROMPT
You are a DRF expert building production REST APIs.

Build a complete DRF ViewSet for the Task resource:

ViewSet: TaskViewSet(ModelViewSet)
- queryset: scoped to request.user.organization (never return other tenants' tasks)
- serializer: TaskSerializer (nested assignee, project; writable by slug/id)
- authentication_classes: [JWTAuthentication] (simplejwt)
- permission_classes: [IsAuthenticated, IsOrganizationMember]
- throttle_classes: [UserRateThrottle] — 100/hour for standard, 1000/hour for pro plan
- filter_backends: [DjangoFilterBackend, SearchFilter, OrderingFilter]
- filterset_fields: status, priority, assigned_to, project, due_date (range)
- search_fields: title, description
- ordering_fields: created_at, due_date, priority

Custom actions:
- @action(detail=True, methods=['post']) assign: assign task to user
- @action(detail=False, methods=['get']) overdue: return overdue tasks

Serializer requirements:
- Input: TaskCreateSerializer (flat, validated)
- Output: TaskResponseSerializer (nested, read-optimised)
- Validation: due_date must be in future on create, title min 3 chars
- ORM: select_related('assignee', 'project'), prefetch_related('comments') on list

Output: viewset, serializers, permissions, URL router registration, and queryset optimisation comments.

Why it works: Specifying select_related and prefetch_related explicitly prevents N+1 queries that are the #1 performance problem in DRF APIs. Asking for dual serializers (create vs response) produces the clean separation DRF requires for writable nested relationships.

4. JWT Authentication with simplejwt

⌥ PROMPT
You are a Django security engineer.

Configure django-rest-framework-simplejwt for production:

Token settings:
- Access token: 15-minute lifetime, RS256 algorithm (asymmetric keys from env)
- Refresh token: 7-day lifetime, rotate on use, blacklist after rotation (BlacklistMixin)
- Token payload: add user_id, organization_id, role, plan_tier to claims

Custom views:
- POST /auth/login: email + password, return access + refresh, log login event
- POST /auth/refresh: validate, rotate, return new pair
- POST /auth/logout: blacklist refresh token
- GET /auth/me: return current user profile (no DB hit, read from token claims where possible)

Security:
- Brute force: django-ratelimit 5 attempts per 15 minutes per IP on /auth/login
- No user enumeration: identical response for wrong email and wrong password
- Add user_agent and ip_address to login audit log

Output: settings.py JWT config, custom serializers for login/refresh, URL patterns, and the audit log middleware.

5. Django Signals & Async Tasks

⌥ PROMPT
You are a Django + Celery expert.

Design the event-driven layer for a project management app:

Django signals (signals.py in each app):
- post_save Task: if status changed to DONE → trigger task_completed signal → notify assignee
- post_save OrganizationMembership (created=True) → send welcome email async
- pre_delete Project → soft-delete all child tasks, create audit log entry

Celery tasks (tasks.py):
- send_task_completion_email(task_id): fetch task, render template, send via Django email backend
- generate_weekly_digest(organization_id): aggregate stats, build HTML report, email to org admins
- cleanup_soft_deleted_records(): periodic task — hard delete records soft-deleted more than 90 days ago
- sync_billing_status(organization_id): call Stripe API, update organization.plan_tier

Celery beat schedule:
- cleanup_soft_deleted_records: daily at 2am UTC
- generate_weekly_digest: Monday 8am UTC per organization (use eta/countdown per org)

Requirements:
- All Celery tasks: retry with exponential backoff (max_retries=3, countdown=60*2**self.request.retries)
- Signal handlers: never perform synchronous IO — always dispatch to Celery
- Idempotency: tasks should be safe to retry (check state before acting)

Output: signals.py, tasks.py, celery.py app config, beat schedule.

6. pytest-django Testing

⌥ PROMPT
You are a pytest-django expert.

Write comprehensive tests for the TaskViewSet using pytest-django:

Fixtures (conftest.py):
- django_db fixture with transaction=True
- api_client: DRF APIClient
- auth_client(user): APIClient with JWT token pre-attached
- organization_factory, user_factory, project_factory, task_factory using factory_boy
- mock_celery: override CELERY_TASK_ALWAYS_EAGER=True in settings

Tests for POST /api/tasks/:
1. 201: valid payload — assert response fields, DB record created, Celery task dispatched
2. 400: missing title — assert field-level error key
3. 400: due_date in past — assert field error
4. 401: no token — assert 401 + WWW-Authenticate
5. 403: user from different organization — assert 403
6. Queryset isolation: user A cannot see user B's organization tasks (tenant boundary)
7. Throttle: 6th request within rate limit window returns 429

Use @pytest.mark.django_db. Parametrize validation failure cases.
Show pytest.ini configuration with DJANGO_SETTINGS_MODULE.

7. Performance Optimisation

⌥ PROMPT
You are a Django performance engineer.

Audit and optimise this Django view:

[PASTE VIEW + SERIALIZER CODE HERE]

Check and fix:
1. N+1 queries: add select_related() for ForeignKey, prefetch_related() for ManyToMany and reverse FK
2. Queryset evaluation: identify any queryset evaluated inside a loop
3. Database indexes: for every field in filter(), exclude(), order_by() — check if index exists, add if missing
4. Serializer overhead: identify fields computed in Python that could be DB annotations (annotate + F())
5. Caching: identify read-heavy endpoints that can use @cache_page or low-level cache.get/set with Redis
6. Pagination: ensure all list views use LimitOffsetPagination (never return unbounded querysets)
7. select_for_update: identify concurrent write risks, add FOR UPDATE where needed

Output: fixed view, migration adding missing indexes, and Django Debug Toolbar query count before/after.

9. Django Channels: WebSocket for Notifications

⌥ PROMPT
You are a Django backend engineer specializing in real-time features.

Add Django Channels 4 WebSocket support for a live notification system:

Stack: Django Channels 4, channels-redis for channel layer, daphne ASGI server

Consumer: NotificationConsumer (AsyncWebsocketConsumer)
- Authentication: verify JWT from query string on connect, reject with close code 4001 if invalid
- Group strategy: one group per user (user_{user_id}), join on connect, leave on disconnect
- Receive: ignore incoming messages (notification consumer is server-to-client only)
- Send: { type: "notification", id: str, message: str, category: str, read: false, created_at: ISO }

Django signal integration: post_save signal on Notification model → async_to_sync(channel_layer.group_send)
ASGI routing: re_path(r"ws/notifications/", NotificationConsumer.as_asgi())
Reconnection: client should reconnect with exponential backoff — document this in a comment

Output: consumers.py, routing.py, asgi.py setup, Django signal handler, and a JavaScript snippet showing how the client connects and handles messages.

10. Custom Django Management Command

⌥ PROMPT
You are a Django backend developer.

Create a production-grade Django management command: python manage.py sync_expired_subscriptions

Purpose: find organizations with subscriptions that expired in the last 24 hours, downgrade them to the free tier, and send notification emails

Implementation requirements:
- BaseCommand with add_arguments: --dry-run (no DB changes, just log), --since-hours (default 24), --batch-size (default 100)
- Atomic transaction per batch: if one batch fails, don't roll back previous batches
- Queryset: annotate subscriptions with days_since_expiry, batch with iterator(chunk_size=batch_size)
- For each expired org: update plan to 'free', set subscription.status='expired', queue Celery email task
- Progress: self.stdout.write(self.style.SUCCESS(f"Processed {n}/{total}...")) every 100 records
- Logging: structured JSON log per org processed (org_id, action, dry_run, timestamp)
- Idempotent: running twice should not send duplicate emails (check if already notified in last 24h)
- Lock: use cache.add() as a distributed lock to prevent concurrent runs on multiple Celery workers

Output: management/commands/sync_expired_subscriptions.py with complete implementation.

11. Django REST Framework Pagination and Filtering

⌥ PROMPT
You are a DRF API design expert.

Set up a production-grade filtering, search, and pagination system for a DRF TaskViewSet:

Pagination: CursorPagination (not PageNumberPagination — more consistent on live data), ordering by created_at DESC, page_size=25, max_page_size=100

Filtering with django-filter:
- status: exact, in (multiple values)
- priority: exact, in
- due_date: date range (due_date__gte, due_date__lte)
- assigned_to: ForeignKey lookup by user ID
- is_overdue: custom FilterSet method — due_date__lt=today and status not in ['done','cancelled']
- tags: ArrayFilter (JSONField array contains)

Search: SearchFilter on title and description (icontains)
Ordering: OrderingFilter on created_at, due_date, priority (custom mapping: priority maps to priority_weight integer)

Response shape: { count, next, previous, results }
Tenant scoping: override get_queryset() to filter by request.user.organization — no filter should bypass this

Output: FilterSet class, ViewSet with all filters wired, and 5 example API calls showing filter combinations.

End-to-End Workflow: DRF Feature from Model to API

A practical chained sequence for shipping a new DRF endpoint with tests:

  1. Model (Prompt 2 variant): "Create a Django 5 Comment model related to Task (ForeignKey CASCADE), User (ForeignKey SET_NULL), with content (TextField, max 5000 chars), is_edited (BooleanField), edit_history (JSONField default list), soft delete. Include Meta indexes on [task, created_at] and [user, created_at]."
  2. Serializers: "Create DRF serializers for Comment: CommentCreateSerializer (content only, validate max 5000 chars, strip HTML with bleach), CommentReadSerializer (nested author with display name only, task_id, is_edited, created_at). Separate serializers to prevent mass assignment."
  3. ViewSet (Prompt 3 variant): "Create a CommentViewSet nested under TaskViewSet using drf-nested-routers. Override get_queryset to scope to the parent task and user's organization. Override perform_create to set author and task. Custom action: POST /{id}/react with choices emoji."
  4. Tests (Prompt 6 variant): "Write pytest-django tests for CommentViewSet: paste the ViewSet. Cover create (201), permission denied cross-tenant (403), invalid HTML content (400), edit comment (200), delete own comment (204), delete other's comment (403)."

Where AI Goes Wrong in Django

  • N+1 queries in generated ViewSets. AI almost never adds select_related and prefetch_related unless you ask. A ViewSet that serializes nested related objects without them produces one query per object at scale. Specify "add select_related and prefetch_related for all serializer nested relations" in every ViewSet prompt.
  • Wrong DRF authentication class. AI defaults to SessionAuthentication or BasicAuthentication. If you're building a JWT API with simplejwt, specify JWTAuthentication from rest_framework_simplejwt.authentication explicitly. The wrong class is silently insecure.
  • Missing permission scoping at QuerySet level. AI adds IsAuthenticated permission but forgets to scope the queryset to the user's organization. The permission class says "must be logged in" but the data isolation must happen in get_queryset(). Always review every get_queryset method for tenant filtering.
  • Deprecated extra() calls for complex queries. AI generates queryset.extra(select={...}) for computed fields — deprecated since Django 3.2. The correct approach is annotate() with ExpressionWrapper or Case/When.
  • Using MEDIA_ROOT for user uploads without security. AI generates file upload handlers that serve user files directly from Django, with no content-type verification and no access control. In production, serve user files from S3 with signed URLs and validate MIME types via magic bytes.

8. Good vs Bad Django Prompts

Task❌ Bad Prompt✅ Good Prompt
Model design"Create a Task model""Create a Django 5 Task model with: status choices enum, priority choices, soft delete (deleted_at), TimestampMixin, ForeignKey to Project (CASCADE) and User (SET_NULL), JSONField for custom_fields, and Meta indexes on [organization, status, due_date] compound and [assigned_to, status]."
API view"Build a REST API for tasks""Build a DRF ModelViewSet for Task: JWT auth, IsOrganizationMember permission, tenant-scoped queryset (filter by request.user.organization), select_related('assignee','project'), DjangoFilterBackend on status/priority/due_date, UserRateThrottle 100/hour, separate create vs response serializers."
Testing"Write tests for my view""Write pytest-django tests for POST /api/tasks with factory_boy fixtures. Cover 201 happy path, 400 validation errors (parametrized), 401 no token, 403 cross-tenant access, and 429 rate limit. Assert DB state and Celery task dispatch."

Before You Prompt: Django Context Setup

Django's strong conventions are both its strength and its AI prompting challenge — models with outdated training data generate function-based views, Vuex-style patterns in the wrong language, and Django 3.x ORM syntax. This block keeps everything on track:

⌥ PROMPT
Context for all Django prompts in this session:
- Framework: Django 5.2 + Python 3.12
- API layer: Django REST Framework 3.15
  Use ViewSets + Routers for CRUD endpoints (NOT function-based views)
  Use ModelSerializer with explicit fields = [...] (never fields = '__all__')
- Database: PostgreSQL 16 with psycopg3, Django ORM
  Always add select_related / prefetch_related to prevent N+1 queries
- Auth: djangorestframework-simplejwt with sliding tokens
- Background tasks: Celery 5 + Redis broker
- Testing: pytest-django + factory_boy (NOT Django's TestCase directly)
- Never: db.query() raw SQL unless explicitly needed for performance
- Never: model.save() in loops — use bulk_create / bulk_update

The fields = '__all__' ban is critical for security and performance — it exposes every model field including internal ones and returns more data than any client needs. Always list fields explicitly. Pair this with "add select_related for every ForeignKey in the queryset that the serializer accesses" and you eliminate the two most common Django AI mistakes in one block.

3 Common Mistakes When Prompting AI for Django

Mistake 1: Getting function-based views instead of ViewSets

Asking for "a Django API endpoint" without specifying ViewSets produces function-based views with manual URL routing — valid Django, but harder to extend, test, and maintain than ViewSets. For every CRUD resource, specify: "Use a ModelViewSet with a DRF Router. Override get_queryset() for tenant scoping. Add get_serializer_class() to use different serializers for read vs write." Three method overrides give you a complete, extensible CRUD API.

Mistake 2: Missing queryset optimization on serializers

The most common Django performance bug from AI-generated code: a serializer accesses task.project.name and task.assignee.email, but the ViewSet queryset is just Task.objects.all(). This fires a separate SQL query for every task in the list. Always say: "add select_related('project', 'assignee') to the queryset — the serializer accesses these relationships." One select_related call turns a 51-query list endpoint into a 1-query endpoint.

Mistake 3: Asking for Django migrations without zero-downtime guidance

Asking to "add a NOT NULL column to the User table" produces a migration that works on development databases with 10 rows and locks production tables with 2 million rows for 30 seconds. Specify: "This is a production table with significant data — use the zero-downtime 3-step pattern: (1) add column as nullable, (2) backfill data in a separate migration, (3) add NOT NULL constraint." Getting the migration strategy right at prompt time is far cheaper than a production outage.

Further Reading

Resources for AI-assisted Django and Python web development:

Generate a custom Django prompt → Try PromptPrepare free

Help & Answers

Frequently Asked Questions

John AllickAI Researcher· Updated May 10, 2026

John Allick is an AI researcher specializing in prompt engineering and large language model evaluation. He benchmarks models across ChatGPT, Claude, Gemini, Grok, and DeepSeek, focusing on practical techniques that produce reliable, production-ready outputs. Every guide on PromptPrepare is tested live on current model versions before publication.

✓ Expert-tested on live models✓ Updated May 10, 2026✓ Model-verified examples

Found this helpful?

Save it to your library or share with your team.

Keep Reading

Related Guides

Apply this guide instantly

Free AI prompt generator