Django Expert Overview This skill provides expert guidance for Django backend development with comprehensive coverage of models, views, Django REST Framework, forms, authentication, testing, and performance optimization. It follows official Django best practices and modern Python conventions to help you build robust, maintainable applications. Key Capabilities: - Model design with optimal ORM patterns - View implementation (FBV, CBV, DRF viewsets) - Django REST Framework API development - Query optimization and performance tuning - Authentication and permissions - Testing strategies and patte…

),\n re.compile(r'^/phpmyadmin/'),\n]\n```\n\n**Limitations of email notifications:**\n- Don't scale well with traffic\n- Can overwhelm email\n- No aggregation or analytics\n- Delayed notification\n\n### Sentry Integration\n\n```python\n# ✅ BETTER: Sentry for error tracking\nimport sentry_sdk\nfrom sentry_sdk.integrations.django import DjangoIntegration\n\nsentry_sdk.init(\n dsn=os.environ.get('SENTRY_DSN'),\n integrations=[DjangoIntegration()],\n traces_sample_rate=0.1, # Performance monitoring\n send_default_pii=False, # Don't send personal data\n environment=os.environ.get('ENVIRONMENT', 'production'),\n)\n```\n\n**Benefits of Sentry:**\n- Real-time error tracking\n- Error aggregation and deduplication\n- Stack trace analysis\n- Performance monitoring\n- Release tracking\n\n**Rule**: Use Sentry or similar service for production error monitoring.\n\n---\n\n## Security Headers\n\n```python\n# ✅ PRODUCTION: Security headers\nSECURE_BROWSER_XSS_FILTER = True\nSECURE_CONTENT_TYPE_NOSNIFF = True\nX_FRAME_OPTIONS = 'DENY'\n\n# ✅ GOOD: Content Security Policy\nCSP_DEFAULT_SRC = (\"'self'\",)\nCSP_SCRIPT_SRC = (\"'self'\", \"'unsafe-inline'\") # Avoid unsafe-inline in production\nCSP_STYLE_SRC = (\"'self'\", \"'unsafe-inline'\")\nCSP_IMG_SRC = (\"'self'\", \"data:\", \"https:\")\n```\n\n**Using django-csp:**\n\n```python\nMIDDLEWARE = [\n # ...\n 'csp.middleware.CSPMiddleware',\n]\n\nCSP_DEFAULT_SRC = (\"'none'\",)\nCSP_SCRIPT_SRC = (\"'self'\", \"https://cdn.example.com\")\nCSP_STYLE_SRC = (\"'self'\",)\nCSP_IMG_SRC = (\"'self'\", \"https://cdn.example.com\")\nCSP_FONT_SRC = (\"'self'\", \"https://fonts.gstatic.com\")\n```\n\n**Rule**: Enable all security headers in production.\n\n---\n\n## Performance Optimization\n\n### Database Query Optimization\n\n```python\n# ✅ PRODUCTION: Monitor slow queries\nLOGGING['loggers']['django.db.backends'] = {\n 'level': 'DEBUG' if DEBUG else 'WARNING',\n 'handlers': ['console'],\n}\n\n# ✅ GOOD: Query timeout (PostgreSQL)\nDATABASES['default']['OPTIONS'] = {\n 'options': '-c statement_timeout=5000' # 5 seconds\n}\n```\n\n### Middleware Optimization\n\n```python\n# ✅ Order matters for performance\nMIDDLEWARE = [\n 'django.middleware.security.SecurityMiddleware',\n 'whitenoise.middleware.WhiteNoiseMiddleware', # Serve static files early\n 'django.contrib.sessions.middleware.SessionMiddleware',\n 'django.middleware.cache.UpdateCacheMiddleware', # Cache first\n 'django.middleware.common.CommonMiddleware',\n 'django.middleware.cache.FetchFromCacheMiddleware', # Then fetch\n 'django.middleware.csrf.CsrfViewMiddleware',\n 'django.contrib.auth.middleware.AuthenticationMiddleware',\n 'django.contrib.messages.middleware.MessageMiddleware',\n 'django.middleware.clickjacking.XFrameOptionsMiddleware',\n]\n```\n\n### Compression\n\n```python\n# ✅ Enable GZip compression\nMIDDLEWARE = [\n 'django.middleware.gzip.GZipMiddleware', # Add near top\n # ... other middleware\n]\n```\n\n**Rule**: Always enable compression in production.\n\n---\n\n## Environment-Based Settings\n\n### Settings Structure\n\n```\nmyproject/\n├── settings/\n│ ├── __init__.py\n│ ├── base.py # Shared settings\n│ ├── development.py # Local development\n│ ├── staging.py # Staging environment\n│ └── production.py # Production environment\n```\n\n**base.py:**\n\n```python\n# Common settings for all environments\nINSTALLED_APPS = [...]\nMIDDLEWARE = [...]\n```\n\n**production.py:**\n\n```python\nfrom .base import *\n\nDEBUG = False\nALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')\nSECURE_SSL_REDIRECT = True\n# ... production-specific settings\n```\n\n**Set environment:**\n\n```bash\nexport DJANGO_SETTINGS_MODULE=myproject.settings.production\n```\n\n### Using Environment Variables\n\n```python\n# ✅ RECOMMENDED: python-decouple\nfrom decouple import config, Csv\n\nDEBUG = config('DEBUG', default=False, cast=bool)\nSECRET_KEY = config('SECRET_KEY')\nALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())\nDATABASE_URL = config('DATABASE_URL')\n```\n\n**.env file (never commit this):**\n\n```env\nDEBUG=False\nSECRET_KEY=your-secret-key-here\nALLOWED_HOSTS=example.com,www.example.com\nDATABASE_URL=postgresql://user:pass@localhost/dbname\nREDIS_URL=redis://localhost:6379/1\n```\n\n**Rule**: Store all environment-specific config in environment variables.\n\n---\n\n## Deployment Process\n\n### Pre-Deployment Steps\n\n```bash\n# 1. Run tests\npython manage.py test\n\n# 2. Check for issues\npython manage.py check --deploy\n\n# 3. Check migrations\npython manage.py makemigrations --check --dry-run\n\n# 4. Collect static files\npython manage.py collectstatic --no-input\n\n# 5. Check for security issues\nbandit -r myproject/\nsafety check\n```\n\n### Zero-Downtime Deployment\n\n```bash\n#!/bin/bash\n# deploy.sh\n\n# Pull latest code\ngit pull origin main\n\n# Install dependencies\npip install -r requirements.txt\n\n# Run migrations\npython manage.py migrate --no-input\n\n# Collect static files\npython manage.py collectstatic --no-input\n\n# Graceful restart (sends SIGHUP to workers)\nsystemctl reload gunicorn\n\n# Or for manual process management\nkill -HUP $(cat /tmp/gunicorn.pid)\n```\n\n### Health Checks\n\n```python\n# myapp/views.py\nfrom django.http import JsonResponse\nfrom django.db import connection\n\ndef health_check(request):\n \"\"\"Simple health check endpoint for load balancers.\"\"\"\n try:\n # Check database\n with connection.cursor() as cursor:\n cursor.execute(\"SELECT 1\")\n\n # Check cache\n from django.core.cache import cache\n cache.set('health_check', 'ok', 10)\n cache.get('health_check')\n\n return JsonResponse({'status': 'healthy'})\n except Exception as e:\n return JsonResponse({'status': 'unhealthy', 'error': str(e)}, status=500)\n```\n\n**Rule**: Implement health checks for load balancers and monitoring.\n\n---\n\n## Monitoring and Observability\n\n### Application Performance Monitoring\n\n```python\n# ✅ New Relic\nimport newrelic.agent\nnewrelic.agent.initialize('/etc/newrelic.ini')\n\n# ✅ DataDog\nfrom ddtrace import patch_all\npatch_all()\n\n# ✅ Application Insights (Azure)\nfrom applicationinsights.django import ApplicationInsightsMiddleware\nMIDDLEWARE = [\n 'applicationinsights.django.ApplicationInsightsMiddleware',\n # ...\n]\n```\n\n### Key Metrics to Monitor\n\n- **Response time**: Average, 95th, 99th percentile\n- **Error rate**: 4xx and 5xx responses\n- **Database query time**: Slow query detection\n- **Cache hit rate**: Redis/Memcached efficiency\n- **Memory usage**: Application and database\n- **CPU usage**: Under load\n- **Disk space**: Logs and media files\n\n**Rule**: Monitor application performance from day one.\n\n---\n\n## Backup Strategy\n\n### Database Backups\n\n```bash\n#!/bin/bash\n# backup-db.sh\n\nBACKUP_DIR=\"/backups/postgres\"\nDATE=$(date +%Y%m%d_%H%M%S)\nDB_NAME=\"mydb\"\n\n# Create backup\npg_dump -h localhost -U postgres -Fc $DB_NAME > $BACKUP_DIR/db_$DATE.dump\n\n# Keep only last 7 days\nfind $BACKUP_DIR -type f -mtime +7 -delete\n\n# Upload to S3\naws s3 cp $BACKUP_DIR/db_$DATE.dump s3://my-backups/postgres/\n```\n\n### Media Files Backup\n\n```bash\n#!/bin/bash\n# backup-media.sh\n\nMEDIA_DIR=\"/var/www/myproject/media\"\nBACKUP_DIR=\"/backups/media\"\nDATE=$(date +%Y%m%d)\n\n# Incremental backup\nrsync -avz --delete $MEDIA_DIR $BACKUP_DIR/latest/\n\n# Daily snapshot\ncp -al $BACKUP_DIR/latest $BACKUP_DIR/$DATE\n```\n\n**Rule**: Test backup restoration regularly.\n\n---\n\n## Checklist Summary\n\nBefore going live, verify:\n\n- [ ] `DEBUG = False`\n- [ ] `SECRET_KEY` loaded from environment\n- [ ] `ALLOWED_HOSTS` configured\n- [ ] Database backups scheduled\n- [ ] Static files collected and served by CDN/nginx\n- [ ] HTTPS enabled with proper certificates\n- [ ] `SESSION_COOKIE_SECURE = True`\n- [ ] `CSRF_COOKIE_SECURE = True`\n- [ ] Error monitoring configured (Sentry)\n- [ ] Logging configured\n- [ ] Cache backend configured (Redis)\n- [ ] Email backend configured\n- [ ] Security headers enabled\n- [ ] `python manage.py check --deploy` passes\n- [ ] Health check endpoint implemented\n- [ ] Monitoring and alerts configured\n- [ ] Deployment process documented\n- [ ] Rollback plan prepared\n\n**Final command:**\n\n```bash\npython manage.py check --deploy --settings=myproject.settings.production\n```\n\nIf this passes with no warnings, you're ready for production.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":19657,"content_sha256":"d01137f561d0c9d43dec3f3f9ed6c63e0417bb4f102ab1a654badcbdb1bf9e8d"},{"filename":"references/README.md","content":"# Django Expert - Reference Documentation\n\nThis directory contains comprehensive Django best practices documentation that\nwill be loaded into the active AI coding agent's context when needed.\n\n## Purpose\n\nReference files provide detailed guidelines, patterns, and examples that would\nmake SKILL.md too long (>5k words). The agent will read these files as needed\nbased on the specific Django task at hand.\n\n## Recommended Reference Files\n\nCreate the following markdown files in this directory to provide comprehensive Django guidance:\n\n### 1. `models-and-orm.md` - Model Design & ORM Best Practices\n\n**Topics to cover:**\n- Model field types and when to use each\n- Database relationships (ForeignKey, ManyToMany, OneToOne)\n- Model Meta options (ordering, indexes, constraints)\n- Custom model methods and properties\n- Model managers and custom querysets\n- Database query optimization (select_related, prefetch_related, only, defer)\n- Common ORM patterns and anti-patterns\n- Migration best practices\n- Database indexing strategies\n\n**Example structure:**\n```markdown\n# Django Models and ORM Best Practices\n\n## Model Design Patterns\n\n### Field Choices\n- Use TextChoices or IntegerChoices for Django 3.0+\n- Keep choices close to the model definition\n[detailed examples...]\n\n## Query Optimization\n\n### N+1 Query Problem\n[explanation and solutions...]\n\n### Using select_related vs prefetch_related\n[detailed guidance with examples...]\n```\n\n### 2. `views-and-urls.md` - Views, URLs, and Request Handling\n\n**Topics to cover:**\n- Function-based views vs class-based views\n- When to use generic CBVs (ListView, DetailView, CreateView, etc.)\n- Custom mixins and view composition\n- URL patterns and routing best practices\n- Request/response handling\n- Middleware usage\n- Context processors\n- Form handling in views\n- Error handling and custom error pages\n\n### 3. `drf-guidelines.md` - Django REST Framework Best Practices\n\n**Topics to cover:**\n- Serializer patterns (ModelSerializer, nested serializers, write-only fields)\n- ViewSets vs APIView vs function-based views\n- Permissions and authentication\n- Filtering, searching, and pagination\n- Versioning strategies\n- Custom actions and decorators\n- Response formatting\n- Error handling in APIs\n- Testing DRF endpoints\n\n### 4. `testing-strategies.md` - Testing Django Applications\n\n**Topics to cover:**\n- Test organization and structure\n- TestCase vs TransactionTestCase\n- Factory patterns and fixtures\n- Mocking external services\n- Testing models, views, and APIs\n- Testing permissions and authentication\n- Database testing best practices\n- Coverage and test performance\n- Integration vs unit testing\n\n### 5. `security-checklist.md` - Django Security Best Practices\n\n**Topics to cover:**\n- CSRF protection\n- XSS prevention\n- SQL injection prevention\n- Authentication and authorization\n- Password handling and storage\n- Secure settings for production\n- HTTPS and SSL/TLS configuration\n- Content Security Policy\n- Rate limiting and throttling\n- Security headers\n- Common vulnerabilities and mitigations\n\n### 6. `performance-optimization.md` - Performance and Scaling\n\n**Topics to cover:**\n- Database query optimization\n- Caching strategies (per-view cache, template fragment cache, low-level cache)\n- Redis/Memcached integration\n- Database connection pooling\n- Async views and background tasks (Celery)\n- Static file optimization\n- Database indexing and query profiling\n- Monitoring and profiling tools\n- Pagination strategies for large datasets\n\n## How to Populate These Files\n\n1. **Research**: Gather Django best practices from official docs, style guides, and community resources\n2. **Structure**: Use clear headings, code examples, and explanations\n3. **Examples**: Include both good and bad examples with explanations\n4. **Keep Updated**: Maintain version-specific guidance (note Django version compatibility)\n5. **Be Specific**: Provide concrete examples rather than general statements\n\n## Example Reference File\n\nHere's an example of what a well-structured reference file looks like:\n\n```markdown\n# Django REST Framework Guidelines\n\n## Serializer Best Practices\n\n### 1. Use ModelSerializer for Standard CRUD\n\nFor basic model serialization, always start with ModelSerializer:\n\n```python\n# Good\nfrom rest_framework import serializers\n\nclass ProductSerializer(serializers.ModelSerializer):\n class Meta:\n model = Product\n fields = ['id', 'name', 'price', 'category']\n read_only_fields = ['id', 'created_at']\n```\n\n### 2. Nested Serializers for Relationships\n\nFor related objects, use nested serializers or use depth:\n\n```python\n# Option 1: Nested serializer (more control)\nclass OrderSerializer(serializers.ModelSerializer):\n items = OrderItemSerializer(many=True, read_only=True)\n\n class Meta:\n model = Order\n fields = ['id', 'items', 'total']\n\n# Option 2: Auto-nesting with depth (simpler but less control)\nclass OrderSerializer(serializers.ModelSerializer):\n class Meta:\n model = Order\n fields = '__all__'\n depth = 1 # Be careful with performance\n```\n\n# [Continue with more patterns...]\n```\n\n## Usage\n\nCompatible agents automatically read these files when working on Django tasks.\nYou don't need to manually reference them—the active agent will determine which\nreferences are needed based on the task.\n\n## Notes\n\n- Keep files focused on specific topics (don't create one giant file)\n- Use code examples liberally\n- Include both do's and don'ts\n- Reference Django and DRF version numbers when relevant\n- Link to official documentation for deep dives\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5559,"content_sha256":"1ee7b21323cd2f09a0f38e6b21fdadbbadf392ee739caeaa9625290e53548a32"},{"filename":"references/security-checklist.md","content":"# Django Security Checklist\n\nDjango is secure by default, but you must configure it correctly and avoid common pitfalls.\n\n## OWASP Top 10 & Django\n\n### 1. SQL Injection\n\n**Django protects you automatically** when using the ORM.\n\n```python\n# ✅ SAFE: ORM parameterizes queries\nUser.objects.filter(email=user_input)\n\n# ✅ SAFE: Using parameters\nUser.objects.raw('SELECT * FROM users WHERE email = %s', [user_input])\n\n# ❌ DANGEROUS: String formatting\nUser.objects.raw(f'SELECT * FROM users WHERE email = \"{user_input}\"')\n\n# ❌ DANGEROUS: String concatenation\ncursor.execute('SELECT * FROM users WHERE id = ' + user_id)\n```\n\n**Rule**: Never use string formatting or concatenation for SQL queries.\n\n### 2. Cross-Site Scripting (XSS)\n\n**Django templates auto-escape by default.**\n\n```django\n{# ✅ SAFE: Auto-escaped #}\n\u003cp>Hello, {{ user.name }}\u003c/p>\n\n{# ❌ DANGEROUS: Marks as safe, disables escaping #}\n\u003cp>{{ user_bio|safe }}\u003c/p>\n\n{# ✅ SAFE: Use linebreaks filter instead #}\n\u003cp>{{ user_bio|linebreaks }}\u003c/p>\n```\n\n**In views returning JSON for HTMX:**\n\n```python\nfrom django.utils.html import escape\n\n# ✅ GOOD: Escape user input\nreturn JsonResponse({\n 'message': escape(user_message)\n})\n```\n\n**Rule**: Never use `|safe` or `mark_safe()` on user-generated content.\n\n### 3. Cross-Site Request Forgery (CSRF)\n\n**Django's CSRF protection is enabled by default.**\n\n```django\n{# ✅ REQUIRED: Include CSRF token in forms #}\n\u003cform method=\"post\">\n {% csrf_token %}\n ...\n\u003c/form>\n\n{# For HTMX #}\n\u003cbutton hx-post=\"/api/delete/\" hx-headers='{\"X-CSRFToken\": \"{{ csrf_token }}\"}'>\n Delete\n\u003c/button>\n```\n\n**In views:**\n\n```python\nfrom django.views.decorators.csrf import csrf_exempt, csrf_protect\n\n# ✅ DEFAULT: CSRF protection enabled\n@csrf_protect\ndef my_view(request):\n ...\n\n# ❌ DANGEROUS: Only exempt for APIs with other auth (tokens)\n@csrf_exempt\ndef api_view(request):\n # Must have other protection (API key, JWT, etc.)\n ...\n```\n\n**For AJAX/HTMX, include CSRF token in headers:**\n\n```javascript\n// HTMX auto-includes if you set this\ndocument.body.addEventListener('htmx:configRequest', (event) => {\n event.detail.headers['X-CSRFToken'] = getCookie('csrftoken');\n});\n```\n\n**Rule**: Never disable CSRF protection unless you have alternative authentication.\n\n### 4. Broken Authentication\n\n```python\nfrom django.contrib.auth.decorators import login_required\nfrom rest_framework.permissions import IsAuthenticated\n\n# ✅ GOOD: Require authentication\n@login_required\ndef user_profile(request):\n ...\n\nclass UserProfileView(APIView):\n permission_classes = [IsAuthenticated]\n ...\n\n# ❌ BAD: Checking authentication manually\ndef user_profile(request):\n if 'user_id' in request.session: # Fragile!\n ...\n```\n\n**Password Security:**\n\n```python\n# settings.py\n\n# ✅ GOOD: Strong password validation\nAUTH_PASSWORD_VALIDATORS = [\n {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},\n {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',\n 'OPTIONS': {'min_length': 12}},\n {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},\n {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},\n]\n\n# ✅ GOOD: Use Argon2 for password hashing\nPASSWORD_HASHERS = [\n 'django.contrib.auth.hashers.Argon2PasswordHasher',\n 'django.contrib.auth.hashers.PBKDF2PasswordHasher',\n]\n```\n\n**Rule**: Always use Django's authentication system, never roll your own.\n\n### 5. Broken Access Control\n\n```python\n# ❌ BAD: No authorization check\n@login_required\ndef delete_post(request, post_id):\n post = Post.objects.get(id=post_id)\n post.delete() # Any logged-in user can delete any post!\n return HttpResponse('Deleted')\n\n# ✅ GOOD: Check authorization\n@login_required\ndef delete_post(request, post_id):\n post = get_object_or_404(Post, id=post_id)\n\n # Authorization check\n if post.author != request.user and not request.user.is_staff:\n return HttpResponseForbidden('You cannot delete this post')\n\n post.delete()\n return HttpResponse('Deleted')\n\n# ✅ BETTER: Use permissions\nfrom rest_framework import permissions\n\nclass IsOwnerOrReadOnly(permissions.BasePermission):\n def has_object_permission(self, request, view, obj):\n if request.method in permissions.SAFE_METHODS:\n return True\n return obj.author == request.user\n\nclass PostDetailView(APIView):\n permission_classes = [IsOwnerOrReadOnly]\n ...\n```\n\n**Rule**: Authentication ≠ Authorization. Always check permissions.\n\n### 6. Security Misconfiguration\n\n```python\n# settings.py\n\n# ❌ DANGEROUS in production\nDEBUG = True\nSECRET_KEY = 'hardcoded-secret-key'\nALLOWED_HOSTS = ['*']\n\n# ✅ GOOD: Production settings\nDEBUG = False\nSECRET_KEY = os.environ['DJANGO_SECRET_KEY']\nALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']\n\n# ✅ GOOD: Security headers\nSECURE_SSL_REDIRECT = True\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\nSECURE_BROWSER_XSS_FILTER = True\nSECURE_CONTENT_TYPE_NOSNIFF = True\nX_FRAME_OPTIONS = 'DENY'\nSECURE_HSTS_SECONDS = 31536000 # 1 year\nSECURE_HSTS_INCLUDE_SUBDOMAINS = True\nSECURE_HSTS_PRELOAD = True\n\n# ✅ GOOD: Use environment variables\nimport os\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent.parent\n\nSECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')\nDEBUG = os.environ.get('DEBUG', 'False') == 'True'\nALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')\n\n# Database from environment\nDATABASES = {\n 'default': {\n 'ENGINE': 'django.db.backends.postgresql',\n 'NAME': os.environ.get('DB_NAME'),\n 'USER': os.environ.get('DB_USER'),\n 'PASSWORD': os.environ.get('DB_PASSWORD'),\n 'HOST': os.environ.get('DB_HOST'),\n 'PORT': os.environ.get('DB_PORT', '5432'),\n }\n}\n```\n\n**Rule**: Never commit secrets. Always use environment variables.\n\n### 7. Sensitive Data Exposure\n\n```python\n# ✅ GOOD: Don't log sensitive data\nimport logging\nlogger = logging.getLogger(__name__)\n\ndef process_payment(payment_data):\n # ❌ BAD: Logs credit card number\n logger.info(f\"Processing payment: {payment_data}\")\n\n # ✅ GOOD: Log only safe data\n logger.info(f\"Processing payment for order {payment_data['order_id']}\")\n\n# ✅ GOOD: Don't expose sensitive fields in API\nclass UserSerializer(serializers.ModelSerializer):\n class Meta:\n model = User\n fields = ['id', 'email', 'first_name', 'last_name']\n # NOT password, social_security_number, etc.\n\n# ✅ GOOD: Redact in admin interface\nfrom django.contrib import admin\n\nclass UserAdmin(admin.ModelAdmin):\n readonly_fields = ['password'] # Don't allow editing\n exclude = ['social_security_number'] # Don't show at all\n```\n\n**For database encryption:**\n\n```bash\n# Use django-encrypted-model-fields\npip install django-encrypted-model-fields\n```\n\n```python\nfrom encrypted_model_fields.fields import EncryptedCharField\n\nclass PaymentInfo(models.Model):\n card_number = EncryptedCharField(max_length=16)\n```\n\n**Rule**: Assume logs, databases, and backups may be compromised. Encrypt sensitive data.\n\n### 8. Insecure Deserialization\n\n```python\nimport pickle\nimport json\n\n# ❌ DANGEROUS: pickle can execute arbitrary code\ndata = pickle.loads(request.POST['data'])\n\n# ✅ SAFE: Use JSON\ndata = json.loads(request.POST['data'])\n\n# ✅ SAFE: Use Django's serialization\nfrom django.core import serializers\ndata = serializers.deserialize('json', request.POST['data'])\n```\n\n**Rule**: Never use `pickle` or `eval()` on user input.\n\n### 9. Using Components with Known Vulnerabilities\n\n```bash\n# Check for vulnerabilities\npip install safety\nsafety check\n\n# Keep dependencies updated\npip list --outdated\n```\n\n**In requirements.txt, pin versions:**\n\n```\nDjango==4.2.7 # Not Django>=4.0 (prevents auto-updates)\npsycopg2-binary==2.9.9\nredis==5.0.1\n```\n\n**Rule**: Keep dependencies updated and monitor security advisories.\n\n### 10. Insufficient Logging & Monitoring\n\n```python\nimport logging\nlogger = logging.getLogger(__name__)\n\n# ✅ GOOD: Log security events\n@login_required\ndef delete_account(request):\n user = request.user\n\n # Log security-relevant action\n logger.warning(\n f\"Account deletion requested\",\n extra={\n 'user_id': user.id,\n 'user_email': user.email,\n 'ip_address': request.META.get('REMOTE_ADDR'),\n 'user_agent': request.META.get('HTTP_USER_AGENT'),\n }\n )\n\n user.delete()\n return HttpResponse('Account deleted')\n\n# ✅ GOOD: Log failed authentication\nfrom django.contrib.auth.signals import user_login_failed\n\ndef log_failed_login(sender, credentials, request, **kwargs):\n logger.warning(\n f\"Failed login attempt\",\n extra={\n 'username': credentials.get('username'),\n 'ip_address': request.META.get('REMOTE_ADDR'),\n }\n )\n\nuser_login_failed.connect(log_failed_login)\n```\n\n**Rule**: Log security events, failed auth attempts, privilege escalations.\n\n## Additional Django Security\n\n### Clickjacking Protection\n\n```python\n# settings.py\nX_FRAME_OPTIONS = 'DENY' # Prevent embedding in iframes\n\n# Or allow specific domains\nX_FRAME_OPTIONS = 'SAMEORIGIN' # Only same domain\n```\n\n### Host Header Validation\n\n```python\n# settings.py\nALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']\n\n# Never use:\nALLOWED_HOSTS = ['*'] # Allows host header injection attacks\n```\n\n### File Upload Security\n\n```python\n# settings.py\nFILE_UPLOAD_MAX_MEMORY_SIZE = 5242880 # 5MB\nDATA_UPLOAD_MAX_MEMORY_SIZE = 5242880\n\n# Validate file types\nfrom django.core.exceptions import ValidationError\n\ndef validate_file_extension(value):\n import os\n ext = os.path.splitext(value.name)[1]\n valid_extensions = ['.pdf', '.jpg', '.png', '.jpeg']\n if ext.lower() not in valid_extensions:\n raise ValidationError('Unsupported file extension.')\n\nclass Document(models.Model):\n file = models.FileField(\n upload_to='documents/',\n validators=[validate_file_extension]\n )\n\n# ✅ GOOD: Store uploads outside web root\nMEDIA_ROOT = '/var/www/media/' # Not in static files directory!\nMEDIA_URL = '/media/'\n```\n\n### Rate Limiting\n\n```bash\npip install django-ratelimit\n```\n\n```python\nfrom django_ratelimit.decorators import ratelimit\n\n# Limit login attempts\n@ratelimit(key='ip', rate='5/m', block=True)\ndef login_view(request):\n ...\n\n# Limit API calls\n@ratelimit(key='user', rate='100/h', block=True)\ndef api_endpoint(request):\n ...\n```\n\n## Security Checklist\n\nBefore deploying to production:\n\n✅ `DEBUG = False`\n✅ `SECRET_KEY` from environment variable\n✅ `ALLOWED_HOSTS` configured properly\n✅ All security headers enabled (HSTS, CSP, etc.)\n✅ HTTPS enforced (`SECURE_SSL_REDIRECT = True`)\n✅ Cookies secure (`SESSION_COOKIE_SECURE = True`)\n✅ CSRF protection enabled (default)\n✅ Strong password validators configured\n✅ Database credentials in environment variables\n✅ File upload size limits set\n✅ Rate limiting on authentication endpoints\n✅ Logging configured for security events\n✅ Dependencies scanned for vulnerabilities\n✅ Admin interface protected (strong password, 2FA if possible)\n✅ Run `python manage.py check --deploy`\n\n## Useful Django Management Commands\n\n```bash\n# Check deployment security\npython manage.py check --deploy\n\n# This checks for:\n# - DEBUG = False\n# - SECRET_KEY not hardcoded\n# - ALLOWED_HOSTS configured\n# - Security middleware enabled\n# - And more...\n```\n\n---\n\n**Remember**: Security is not a feature, it's a requirement. Defense in depth - use multiple layers of security.","content_type":"text/markdown; charset=utf-8","language":"markdown","size":11570,"content_sha256":"eb3d05a93335c50b4f89cbf8d560b474327a07eb7ff4d990f99a3e1a8d60b56d"},{"filename":"references/testing-strategies.md","content":"# Django Testing Strategies\n\nThis guide covers testing Django applications using both Django's built-in `TestCase` and `pytest-django`. Both approaches are valid and widely used in production.\n\n**Quick comparison:**\n- **Django TestCase**: Built-in, class-based, familiar to Django developers\n- **pytest-django**: Modern, fixture-based, less boilerplate, extensive plugin ecosystem\n\nChoose based on your team's preference and project needs. Both can coexist in the same project.\n\n## Test Organization\n\n### Directory Structure\n\n```\nmyapp/\n├── models.py\n├── views.py\n├── serializers.py\n└── tests/\n ├── __init__.py\n ├── test_models.py\n ├── test_views.py\n ├── test_serializers.py\n ├── test_api.py\n └── factories.py # Factory Boy factories\n```\n\n### Test Discovery\n\nDjango automatically discovers tests in:\n- Files starting with `test_`\n- Files in `tests/` directories\n\n```bash\n# Run all tests\npython manage.py test\n\n# Run specific app\npython manage.py test myapp\n\n# Run specific test file\npython manage.py test myapp.tests.test_models\n\n# Run specific test class\npython manage.py test myapp.tests.test_models.PostModelTest\n\n# Run specific test method\npython manage.py test myapp.tests.test_models.PostModelTest.test_create_post\n```\n\n## TestCase vs TransactionTestCase\n\n### TestCase (Recommended)\n\n```python\nfrom django.test import TestCase\n\n# ✅ GOOD: Faster, uses transactions\nclass PostModelTest(TestCase):\n def setUp(self):\n self.user = User.objects.create_user(username='test', password='pass')\n self.post = Post.objects.create(title='Test', author=self.user)\n\n def test_post_creation(self):\n self.assertEqual(self.post.title, 'Test')\n self.assertEqual(Post.objects.count(), 1)\n```\n\n**Features:**\n- Wraps tests in transactions (rolled back after each test)\n- Much faster than TransactionTestCase\n- Database is reset between tests automatically\n\n### TransactionTestCase\n\n```python\nfrom django.test import TransactionTestCase\n\n# ✅ GOOD: When you need to test transactions\nclass PaymentProcessingTest(TransactionTestCase):\n def test_atomic_payment(self):\n with transaction.atomic():\n # Test transaction behavior\n ...\n```\n\n**Use TransactionTestCase when:**\n- Testing transaction behavior (commit/rollback)\n- Testing database-level constraints\n- Testing raw SQL that doesn't work in transactions\n\n**Rule**: Use TestCase unless you specifically need TransactionTestCase.\n\n---\n\n## pytest-django Setup\n\n### Installation\n\n```bash\npip install pytest-django\n```\n\n### Configuration\n\nCreate `pytest.ini` in your project root:\n\n```ini\n[pytest]\nDJANGO_SETTINGS_MODULE = myproject.settings\npython_files = tests.py test_*.py *_tests.py\npython_classes = Test*\npython_functions = test_*\n```\n\nOr use `pyproject.toml`:\n\n```toml\n[tool.pytest.ini_options]\nDJANGO_SETTINGS_MODULE = \"myproject.settings\"\npython_files = [\"test_*.py\", \"*_test.py\"]\n```\n\n### Running pytest\n\n```bash\n# Run all tests\npytest\n\n# Run specific file\npytest tests/test_models.py\n\n# Run specific test\npytest tests/test_models.py::test_create_post\n\n# Verbose output\npytest -v\n\n# Parallel execution (requires pytest-xdist)\npip install pytest-xdist\npytest -n auto\n\n# Reuse database between runs (faster)\npytest --reuse-db\n\n# Skip migrations (much faster for large projects)\npytest --reuse-db --no-migrations\n```\n\n### Key pytest-django Fixtures\n\n```python\n# Database access fixtures\ndb # Standard database access\ntransactional_db # Transaction testing\ndjango_db_reset_sequences # Reset DB sequences\n\n# Client fixtures\nclient # Django test client\nadmin_client # Pre-authenticated admin client\nasync_client # AsyncClient for async views\n\n# Request factory fixtures\nrf # RequestFactory\nasync_rf # Async RequestFactory\n\n# User fixtures\nadmin_user # Superuser instance\ndjango_user_model # User model class\n\n# Utility fixtures\nsettings # Modify settings temporarily\nlive_server # Run development server\nmailoutbox # Captured emails\ndjango_assert_num_queries # Query count assertions\n```\n\n### pytest-django Markers\n\n```python\nimport pytest\n\n# Grant database access\[email protected]_db\ndef test_model_save():\n pass\n\n# Transaction testing\[email protected]_db(transaction=True)\ndef test_transaction():\n pass\n\n# Override URLs\[email protected]('myapp.test_urls')\ndef test_with_custom_urls():\n pass\n```\n\n---\n\n## Model Testing\n\n```python\nfrom django.test import TestCase\nfrom django.core.exceptions import ValidationError\n\n# ✅ GOOD: Test model methods and validation\nclass PostModelTest(TestCase):\n def setUp(self):\n self.user = User.objects.create_user(\n username='testuser',\n password='testpass123'\n )\n\n def test_create_post(self):\n \"\"\"Test post creation.\"\"\"\n post = Post.objects.create(\n title='Test Post',\n content='Test content',\n author=self.user\n )\n self.assertEqual(post.title, 'Test Post')\n self.assertEqual(post.author, self.user)\n self.assertIsNotNone(post.created_at)\n\n def test_str_method(self):\n \"\"\"Test string representation.\"\"\"\n post = Post.objects.create(title='Test', author=self.user)\n self.assertEqual(str(post), 'Test')\n\n def test_slug_generation(self):\n \"\"\"Test automatic slug generation.\"\"\"\n post = Post.objects.create(\n title='Test Post Title',\n author=self.user\n )\n self.assertEqual(post.slug, 'test-post-title')\n\n def test_title_max_length(self):\n \"\"\"Test title max length validation.\"\"\"\n long_title = 'x' * 201\n post = Post(title=long_title, author=self.user)\n\n with self.assertRaises(ValidationError):\n post.full_clean()\n\n def test_get_absolute_url(self):\n \"\"\"Test get_absolute_url method.\"\"\"\n post = Post.objects.create(title='Test', author=self.user)\n expected_url = f'/posts/{post.slug}/'\n self.assertEqual(post.get_absolute_url(), expected_url)\n\n def test_published_posts_manager(self):\n \"\"\"Test custom manager.\"\"\"\n Post.objects.create(title='Published', author=self.user, is_published=True)\n Post.objects.create(title='Draft', author=self.user, is_published=False)\n\n published = Post.published.all()\n self.assertEqual(published.count(), 1)\n self.assertEqual(published.first().title, 'Published')\n```\n\n### Model Testing with pytest-django\n\n```python\nimport pytest\nfrom django.core.exceptions import ValidationError\n\n# ✅ GOOD: Function-based tests with pytest\[email protected]_db\ndef test_create_post(django_user_model):\n \"\"\"Test post creation.\"\"\"\n user = django_user_model.objects.create_user(\n username='testuser',\n password='testpass123'\n )\n post = Post.objects.create(\n title='Test Post',\n content='Test content',\n author=user\n )\n assert post.title == 'Test Post'\n assert post.author == user\n assert post.created_at is not None\n\n\ndef test_str_method(db):\n \"\"\"Test string representation.\"\"\"\n user = User.objects.create_user(username='test', password='pass')\n post = Post.objects.create(title='Test', author=user)\n assert str(post) == 'Test'\n\n\ndef test_slug_generation(db):\n \"\"\"Test automatic slug generation.\"\"\"\n user = User.objects.create_user(username='test', password='pass')\n post = Post.objects.create(\n title='Test Post Title',\n author=user\n )\n assert post.slug == 'test-post-title'\n\n\ndef test_title_max_length(db):\n \"\"\"Test title max length validation.\"\"\"\n user = User.objects.create_user(username='test', password='pass')\n long_title = 'x' * 201\n post = Post(title=long_title, author=user)\n\n with pytest.raises(ValidationError):\n post.full_clean()\n\n\ndef test_get_absolute_url(db):\n \"\"\"Test get_absolute_url method.\"\"\"\n user = User.objects.create_user(username='test', password='pass')\n post = Post.objects.create(title='Test', author=user)\n expected_url = f'/posts/{post.slug}/'\n assert post.get_absolute_url() == expected_url\n\n\ndef test_published_posts_manager(db):\n \"\"\"Test custom manager.\"\"\"\n user = User.objects.create_user(username='test', password='pass')\n Post.objects.create(title='Published', author=user, is_published=True)\n Post.objects.create(title='Draft', author=user, is_published=False)\n\n published = Post.published.all()\n assert published.count() == 1\n assert published.first().title == 'Published'\n\n\n# ✅ GOOD: Using fixtures for reusable test data\[email protected]\ndef test_user(db):\n \"\"\"Create a test user.\"\"\"\n return User.objects.create_user(\n username='testuser',\n password='testpass123'\n )\n\n\[email protected]\ndef test_post(test_user):\n \"\"\"Create a test post.\"\"\"\n return Post.objects.create(\n title='Test Post',\n content='Test content',\n author=test_user\n )\n\n\ndef test_post_with_fixtures(test_post):\n \"\"\"Test using fixtures.\"\"\"\n assert test_post.title == 'Test Post'\n assert test_post.author.username == 'testuser'\n\n\n# ✅ GOOD: Parametrized testing\[email protected]_db\[email protected]('is_published,expected_count', [\n (True, 1),\n (False, 0),\n])\ndef test_published_filter(is_published, expected_count):\n \"\"\"Test published posts filter.\"\"\"\n user = User.objects.create_user(username='test', password='pass')\n Post.objects.create(\n title='Test',\n author=user,\n is_published=is_published\n )\n assert Post.published.count() == expected_count\n```\n\n---\n\n## View Testing\n\n```python\nfrom django.test import TestCase, Client\nfrom django.urls import reverse\n\n# ✅ GOOD: Test views thoroughly\nclass PostViewTest(TestCase):\n def setUp(self):\n self.client = Client()\n self.user = User.objects.create_user(\n username='testuser',\n password='testpass123'\n )\n self.post = Post.objects.create(\n title='Test Post',\n content='Test content',\n author=self.user\n )\n\n def test_post_list_view(self):\n \"\"\"Test post list view.\"\"\"\n response = self.client.get(reverse('post_list'))\n\n self.assertEqual(response.status_code, 200)\n self.assertTemplateUsed(response, 'blog/post_list.html')\n self.assertContains(response, 'Test Post')\n self.assertIn('posts', response.context)\n\n def test_post_detail_view(self):\n \"\"\"Test post detail view.\"\"\"\n url = reverse('post_detail', kwargs={'pk': self.post.pk})\n response = self.client.get(url)\n\n self.assertEqual(response.status_code, 200)\n self.assertEqual(response.context['post'], self.post)\n\n def test_post_detail_404(self):\n \"\"\"Test 404 for non-existent post.\"\"\"\n url = reverse('post_detail', kwargs={'pk': 9999})\n response = self.client.get(url)\n self.assertEqual(response.status_code, 404)\n\n def test_create_post_authenticated(self):\n \"\"\"Test creating post when logged in.\"\"\"\n self.client.login(username='testuser', password='testpass123')\n\n data = {\n 'title': 'New Post',\n 'content': 'New content'\n }\n response = self.client.post(reverse('post_create'), data)\n\n self.assertEqual(response.status_code, 302) # Redirect after success\n self.assertEqual(Post.objects.count(), 2)\n new_post = Post.objects.latest('created_at')\n self.assertEqual(new_post.title, 'New Post')\n self.assertEqual(new_post.author, self.user)\n\n def test_create_post_unauthenticated(self):\n \"\"\"Test creating post when not logged in.\"\"\"\n data = {'title': 'New Post', 'content': 'New content'}\n response = self.client.post(reverse('post_create'), data)\n\n # Should redirect to login\n self.assertEqual(response.status_code, 302)\n self.assertIn('/login/', response.url)\n\n def test_update_own_post(self):\n \"\"\"Test updating own post.\"\"\"\n self.client.login(username='testuser', password='testpass123')\n\n url = reverse('post_update', kwargs={'pk': self.post.pk})\n data = {'title': 'Updated Title', 'content': 'Updated content'}\n response = self.client.post(url, data)\n\n self.assertEqual(response.status_code, 302)\n self.post.refresh_from_db()\n self.assertEqual(self.post.title, 'Updated Title')\n\n def test_cannot_update_others_post(self):\n \"\"\"Test cannot update another user's post.\"\"\"\n other_user = User.objects.create_user(username='other', password='pass')\n self.client.login(username='other', password='pass')\n\n url = reverse('post_update', kwargs={'pk': self.post.pk})\n data = {'title': 'Hacked', 'content': 'Hacked'}\n response = self.client.post(url, data)\n\n # Should get 403 or 404\n self.assertIn(response.status_code, [403, 404])\n self.post.refresh_from_db()\n self.assertNotEqual(self.post.title, 'Hacked')\n\n def test_delete_post(self):\n \"\"\"Test deleting post.\"\"\"\n self.client.login(username='testuser', password='testpass123')\n\n url = reverse('post_delete', kwargs={'pk': self.post.pk})\n response = self.client.post(url)\n\n self.assertEqual(response.status_code, 302)\n self.assertEqual(Post.objects.count(), 0)\n```\n\n### View Testing with pytest-django\n\n```python\nimport pytest\nfrom django.urls import reverse\n\n# ✅ GOOD: Using client fixture\ndef test_post_list_view(client, test_post):\n \"\"\"Test post list view.\"\"\"\n response = client.get(reverse('post_list'))\n\n assert response.status_code == 200\n assert b'Test Post' in response.content\n\n\ndef test_post_detail_view(client, test_post):\n \"\"\"Test post detail view.\"\"\"\n url = reverse('post_detail', kwargs={'pk': test_post.pk})\n response = client.get(url)\n\n assert response.status_code == 200\n assert response.context['post'] == test_post\n\n\ndef test_post_detail_404(client, db):\n \"\"\"Test 404 for non-existent post.\"\"\"\n url = reverse('post_detail', kwargs={'pk': 9999})\n response = client.get(url)\n assert response.status_code == 404\n\n\ndef test_create_post_authenticated(client, test_user):\n \"\"\"Test creating post when logged in.\"\"\"\n client.force_login(test_user)\n\n data = {\n 'title': 'New Post',\n 'content': 'New content'\n }\n response = client.post(reverse('post_create'), data)\n\n assert response.status_code == 302 # Redirect after success\n assert Post.objects.count() == 1\n new_post = Post.objects.latest('created_at')\n assert new_post.title == 'New Post'\n assert new_post.author == test_user\n\n\ndef test_create_post_unauthenticated(client, db):\n \"\"\"Test creating post when not logged in.\"\"\"\n data = {'title': 'New Post', 'content': 'New content'}\n response = client.post(reverse('post_create'), data)\n\n # Should redirect to login\n assert response.status_code == 302\n assert '/login/' in response.url\n\n\ndef test_update_own_post(client, test_post):\n \"\"\"Test updating own post.\"\"\"\n client.force_login(test_post.author)\n\n url = reverse('post_update', kwargs={'pk': test_post.pk})\n data = {'title': 'Updated Title', 'content': 'Updated content'}\n response = client.post(url, data)\n\n assert response.status_code == 302\n test_post.refresh_from_db()\n assert test_post.title == 'Updated Title'\n\n\ndef test_cannot_update_others_post(client, test_post, db):\n \"\"\"Test cannot update another user's post.\"\"\"\n other_user = User.objects.create_user(username='other', password='pass')\n client.force_login(other_user)\n\n url = reverse('post_update', kwargs={'pk': test_post.pk})\n data = {'title': 'Hacked', 'content': 'Hacked'}\n response = client.post(url, data)\n\n # Should get 403 or 404\n assert response.status_code in [403, 404]\n test_post.refresh_from_db()\n assert test_post.title != 'Hacked'\n\n\ndef test_delete_post(client, test_post):\n \"\"\"Test deleting post.\"\"\"\n client.force_login(test_post.author)\n\n url = reverse('post_delete', kwargs={'pk': test_post.pk})\n response = client.post(url)\n\n assert response.status_code == 302\n assert Post.objects.count() == 0\n\n\n# ✅ GOOD: Testing with RequestFactory\ndef test_view_with_request_factory(rf, test_user):\n \"\"\"Test view logic directly without full HTTP cycle.\"\"\"\n from myapp.views import post_list\n\n request = rf.get('/posts/')\n request.user = test_user\n\n response = post_list(request)\n assert response.status_code == 200\n```\n\n---\n\n## API Testing (DRF)\n\n```python\nfrom rest_framework.test import APITestCase, APIClient\nfrom rest_framework import status\n\n# ✅ GOOD: Test API endpoints\nclass PostAPITest(APITestCase):\n def setUp(self):\n self.client = APIClient()\n self.user = User.objects.create_user(\n username='testuser',\n password='testpass123'\n )\n self.post = Post.objects.create(\n title='Test Post',\n content='Test content',\n author=self.user\n )\n\n def test_list_posts(self):\n \"\"\"Test GET /api/posts/\"\"\"\n response = self.client.get('/api/posts/')\n\n self.assertEqual(response.status_code, status.HTTP_200_OK)\n self.assertEqual(len(response.data['results']), 1)\n\n def test_retrieve_post(self):\n \"\"\"Test GET /api/posts/{id}/\"\"\"\n response = self.client.get(f'/api/posts/{self.post.id}/')\n\n self.assertEqual(response.status_code, status.HTTP_200_OK)\n self.assertEqual(response.data['title'], 'Test Post')\n\n def test_create_post_authenticated(self):\n \"\"\"Test POST /api/posts/ when authenticated.\"\"\"\n self.client.force_authenticate(user=self.user)\n\n data = {'title': 'New Post', 'content': 'New content'}\n response = self.client.post('/api/posts/', data, format='json')\n\n self.assertEqual(response.status_code, status.HTTP_201_CREATED)\n self.assertEqual(Post.objects.count(), 2)\n self.assertEqual(response.data['author'], self.user.id)\n\n def test_create_post_unauthenticated(self):\n \"\"\"Test POST /api/posts/ when not authenticated.\"\"\"\n data = {'title': 'New Post', 'content': 'New content'}\n response = self.client.post('/api/posts/', data, format='json')\n\n self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)\n\n def test_update_post(self):\n \"\"\"Test PUT /api/posts/{id}/\"\"\"\n self.client.force_authenticate(user=self.user)\n\n data = {'title': 'Updated', 'content': 'Updated content'}\n response = self.client.put(\n f'/api/posts/{self.post.id}/',\n data,\n format='json'\n )\n\n self.assertEqual(response.status_code, status.HTTP_200_OK)\n self.post.refresh_from_db()\n self.assertEqual(self.post.title, 'Updated')\n\n def test_partial_update_post(self):\n \"\"\"Test PATCH /api/posts/{id}/\"\"\"\n self.client.force_authenticate(user=self.user)\n\n data = {'title': 'Patched Title'}\n response = self.client.patch(\n f'/api/posts/{self.post.id}/',\n data,\n format='json'\n )\n\n self.assertEqual(response.status_code, status.HTTP_200_OK)\n self.post.refresh_from_db()\n self.assertEqual(self.post.title, 'Patched Title')\n self.assertEqual(self.post.content, 'Test content') # Unchanged\n\n def test_delete_post(self):\n \"\"\"Test DELETE /api/posts/{id}/\"\"\"\n self.client.force_authenticate(user=self.user)\n\n response = self.client.delete(f'/api/posts/{self.post.id}/')\n\n self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)\n self.assertEqual(Post.objects.count(), 0)\n\n def test_filter_by_author(self):\n \"\"\"Test filtering posts by author.\"\"\"\n other_user = User.objects.create_user(username='other', password='pass')\n Post.objects.create(title='Other Post', author=other_user)\n\n response = self.client.get(f'/api/posts/?author={self.user.id}')\n\n self.assertEqual(response.status_code, status.HTTP_200_OK)\n self.assertEqual(len(response.data['results']), 1)\n self.assertEqual(response.data['results'][0]['author'], self.user.id)\n\n def test_search_posts(self):\n \"\"\"Test searching posts.\"\"\"\n Post.objects.create(title='Django Tutorial', content='Learn Django', author=self.user)\n\n response = self.client.get('/api/posts/?search=Django')\n\n self.assertEqual(response.status_code, status.HTTP_200_OK)\n self.assertEqual(len(response.data['results']), 1)\n```\n\n### API Testing with pytest-django\n\n```python\nimport pytest\nfrom rest_framework import status\n\n# ✅ GOOD: API tests with pytest\[email protected]_db\ndef test_list_posts(client, test_post):\n \"\"\"Test GET /api/posts/\"\"\"\n response = client.get('/api/posts/')\n\n assert response.status_code == status.HTTP_200_OK\n assert len(response.json()['results']) == 1\n\n\[email protected]_db\ndef test_retrieve_post(client, test_post):\n \"\"\"Test GET /api/posts/{id}/\"\"\"\n response = client.get(f'/api/posts/{test_post.id}/')\n\n assert response.status_code == status.HTTP_200_OK\n assert response.json()['title'] == 'Test Post'\n\n\ndef test_create_post_authenticated(client, test_user):\n \"\"\"Test POST /api/posts/ when authenticated.\"\"\"\n client.force_login(test_user)\n\n data = {'title': 'New Post', 'content': 'New content'}\n response = client.post('/api/posts/', data, content_type='application/json')\n\n assert response.status_code == status.HTTP_201_CREATED\n assert Post.objects.count() == 1\n assert response.json()['author'] == test_user.id\n\n\ndef test_create_post_unauthenticated(client, db):\n \"\"\"Test POST /api/posts/ when not authenticated.\"\"\"\n data = {'title': 'New Post', 'content': 'New content'}\n response = client.post('/api/posts/', data, content_type='application/json')\n\n assert response.status_code == status.HTTP_401_UNAUTHORIZED\n\n\ndef test_update_post(client, test_post):\n \"\"\"Test PUT /api/posts/{id}/\"\"\"\n client.force_login(test_post.author)\n\n data = {'title': 'Updated', 'content': 'Updated content'}\n response = client.put(\n f'/api/posts/{test_post.id}/',\n data,\n content_type='application/json'\n )\n\n assert response.status_code == status.HTTP_200_OK\n test_post.refresh_from_db()\n assert test_post.title == 'Updated'\n\n\ndef test_partial_update_post(client, test_post):\n \"\"\"Test PATCH /api/posts/{id}/\"\"\"\n client.force_login(test_post.author)\n\n data = {'title': 'Patched Title'}\n response = client.patch(\n f'/api/posts/{test_post.id}/',\n data,\n content_type='application/json'\n )\n\n assert response.status_code == status.HTTP_200_OK\n test_post.refresh_from_db()\n assert test_post.title == 'Patched Title'\n assert test_post.content == 'Test content' # Unchanged\n\n\ndef test_delete_post(client, test_post):\n \"\"\"Test DELETE /api/posts/{id}/\"\"\"\n client.force_login(test_post.author)\n\n response = client.delete(f'/api/posts/{test_post.id}/')\n\n assert response.status_code == status.HTTP_204_NO_CONTENT\n assert Post.objects.count() == 0\n\n\[email protected]_db\ndef test_filter_by_author(client, test_user):\n \"\"\"Test filtering posts by author.\"\"\"\n other_user = User.objects.create_user(username='other', password='pass')\n Post.objects.create(title='User Post', author=test_user)\n Post.objects.create(title='Other Post', author=other_user)\n\n response = client.get(f'/api/posts/?author={test_user.id}')\n\n assert response.status_code == status.HTTP_200_OK\n results = response.json()['results']\n assert len(results) == 1\n assert results[0]['author'] == test_user.id\n\n\[email protected]_db\ndef test_search_posts(client, test_user):\n \"\"\"Test searching posts.\"\"\"\n Post.objects.create(title='Django Tutorial', content='Learn Django', author=test_user)\n Post.objects.create(title='Python Guide', content='Learn Python', author=test_user)\n\n response = client.get('/api/posts/?search=Django')\n\n assert response.status_code == status.HTTP_200_OK\n results = response.json()['results']\n assert len(results) == 1\n assert 'Django' in results[0]['title']\n\n\n# ✅ GOOD: Parametrized API testing\[email protected]_db\[email protected]('endpoint,method,expected_status', [\n ('/api/posts/', 'get', status.HTTP_200_OK),\n ('/api/posts/', 'post', status.HTTP_401_UNAUTHORIZED),\n])\ndef test_api_endpoints(client, endpoint, method, expected_status):\n \"\"\"Test various API endpoints.\"\"\"\n response = getattr(client, method)(endpoint)\n assert response.status_code == expected_status\n```\n\n---\n\n## Fixtures and Factories\n\n### Fixtures (JSON)\n\n```python\n# fixtures/test_data.json\n[\n {\n \"model\": \"auth.user\",\n \"pk\": 1,\n \"fields\": {\n \"username\": \"testuser\",\n \"email\": \"[email protected]\"\n }\n },\n {\n \"model\": \"blog.post\",\n \"pk\": 1,\n \"fields\": {\n \"title\": \"Test Post\",\n \"author\": 1\n }\n }\n]\n\n# Using fixtures\nclass PostTest(TestCase):\n fixtures = ['test_data.json']\n\n def test_with_fixture(self):\n post = Post.objects.get(pk=1)\n self.assertEqual(post.title, 'Test Post')\n```\n\n### Factory Boy (Recommended)\n\n```bash\npip install factory-boy\n```\n\n```python\n# tests/factories.py\nimport factory\nfrom factory.django import DjangoModelFactory\n\nclass UserFactory(DjangoModelFactory):\n class Meta:\n model = User\n\n username = factory.Sequence(lambda n: f'user{n}')\n email = factory.LazyAttribute(lambda obj: f'{obj.username}@example.com')\n first_name = factory.Faker('first_name')\n last_name = factory.Faker('last_name')\n\nclass PostFactory(DjangoModelFactory):\n class Meta:\n model = Post\n\n title = factory.Faker('sentence', nb_words=5)\n content = factory.Faker('paragraph', nb_sentences=10)\n author = factory.SubFactory(UserFactory)\n is_published = True\n\nclass CommentFactory(DjangoModelFactory):\n class Meta:\n model = Comment\n\n post = factory.SubFactory(PostFactory)\n author = factory.SubFactory(UserFactory)\n content = factory.Faker('paragraph')\n\n# Usage in tests\nclass PostTest(TestCase):\n def test_create_post_with_factory(self):\n post = PostFactory()\n self.assertIsNotNone(post.id)\n self.assertIsNotNone(post.author)\n\n def test_create_multiple_posts(self):\n posts = PostFactory.create_batch(10)\n self.assertEqual(Post.objects.count(), 10)\n\n def test_custom_values(self):\n user = UserFactory(username='specific_user')\n post = PostFactory(author=user, title='Specific Title')\n self.assertEqual(post.author.username, 'specific_user')\n```\n\n### Factory Boy with pytest-django\n\n```python\n# conftest.py - pytest fixtures available to all tests\nimport pytest\nfrom tests.factories import UserFactory, PostFactory\n\[email protected]\ndef user():\n \"\"\"Create a test user using factory.\"\"\"\n return UserFactory()\n\[email protected]\ndef post(user):\n \"\"\"Create a test post using factory.\"\"\"\n return PostFactory(author=user)\n\[email protected]\ndef published_posts(user):\n \"\"\"Create multiple published posts.\"\"\"\n return PostFactory.create_batch(5, author=user, is_published=True)\n\n\n# test_models.py - using the fixtures\ndef test_create_post_with_factory(post):\n \"\"\"Test post creation with factory.\"\"\"\n assert post.id is not None\n assert post.author is not None\n\n\ndef test_create_multiple_posts(published_posts):\n \"\"\"Test multiple posts creation.\"\"\"\n assert Post.objects.count() == 5\n assert all(p.is_published for p in published_posts)\n\n\ndef test_custom_values():\n \"\"\"Test factory with custom values.\"\"\"\n user = UserFactory(username='specific_user')\n post = PostFactory(author=user, title='Specific Title')\n assert post.author.username == 'specific_user'\n assert post.title == 'Specific Title'\n```\n\n**Rule**: Use Factory Boy over JSON fixtures for maintainability and flexibility.\n\n---\n\n## Mocking External Services\n\n```python\nfrom unittest.mock import patch, Mock\n\n# ✅ GOOD: Mock external API calls\nclass PaymentTest(TestCase):\n @patch('myapp.payment.stripe.Charge.create')\n def test_process_payment(self, mock_charge):\n \"\"\"Test payment processing.\"\"\"\n # Configure mock\n mock_charge.return_value = Mock(\n id='ch_123',\n status='succeeded'\n )\n\n # Call function that uses Stripe\n result = process_payment(amount=1000, token='tok_123')\n\n # Verify mock was called\n mock_charge.assert_called_once_with(\n amount=1000,\n currency='usd',\n source='tok_123'\n )\n\n # Verify result\n self.assertTrue(result['success'])\n self.assertEqual(result['charge_id'], 'ch_123')\n\n @patch('myapp.tasks.send_email.delay')\n def test_user_registration_sends_email(self, mock_send_email):\n \"\"\"Test registration sends welcome email.\"\"\"\n user = User.objects.create_user(\n username='test',\n email='[email protected]',\n password='pass'\n )\n\n # Verify email task was called\n mock_send_email.assert_called_once_with(\n user_id=user.id,\n template='welcome'\n )\n```\n\n### Mocking with pytest-django\n\n```python\nimport pytest\nfrom unittest.mock import patch, Mock\n\n# ✅ GOOD: Using pytest with mocks\[email protected]_db\n@patch('myapp.payment.stripe.Charge.create')\ndef test_process_payment(mock_charge):\n \"\"\"Test payment processing.\"\"\"\n # Configure mock\n mock_charge.return_value = Mock(\n id='ch_123',\n status='succeeded'\n )\n\n # Call function that uses Stripe\n result = process_payment(amount=1000, token='tok_123')\n\n # Verify mock was called\n mock_charge.assert_called_once_with(\n amount=1000,\n currency='usd',\n source='tok_123'\n )\n\n # Verify result\n assert result['success'] is True\n assert result['charge_id'] == 'ch_123'\n\n\[email protected]_db\n@patch('myapp.tasks.send_email.delay')\ndef test_user_registration_sends_email(mock_send_email):\n \"\"\"Test registration sends welcome email.\"\"\"\n user = User.objects.create_user(\n username='test',\n email='[email protected]',\n password='pass'\n )\n\n # Verify email task was called\n mock_send_email.assert_called_once_with(\n user_id=user.id,\n template='welcome'\n )\n\n\n# ✅ GOOD: Using pytest-mock plugin\ndef test_with_mocker(mocker, db):\n \"\"\"Test using pytest-mock plugin.\"\"\"\n # pip install pytest-mock\n mock_send = mocker.patch('myapp.notifications.send_sms')\n\n send_notification(phone='+1234567890', message='Test')\n\n mock_send.assert_called_once_with(\n phone='+1234567890',\n message='Test'\n )\n```\n\n---\n\n## Testing Forms\n\n```python\nfrom django.test import TestCase\n\nclass PostFormTest(TestCase):\n def test_valid_form(self):\n \"\"\"Test form with valid data.\"\"\"\n data = {\n 'title': 'Test Post',\n 'content': 'Test content'\n }\n form = PostForm(data=data)\n self.assertTrue(form.is_valid())\n\n def test_invalid_form_missing_title(self):\n \"\"\"Test form with missing required field.\"\"\"\n data = {'content': 'Test content'}\n form = PostForm(data=data)\n self.assertFalse(form.is_valid())\n self.assertIn('title', form.errors)\n\n def test_invalid_form_title_too_long(self):\n \"\"\"Test form with title exceeding max length.\"\"\"\n data = {\n 'title': 'x' * 201,\n 'content': 'Test content'\n }\n form = PostForm(data=data)\n self.assertFalse(form.is_valid())\n self.assertIn('title', form.errors)\n```\n\n### Form Testing with pytest-django\n\n```python\nimport pytest\n\ndef test_valid_form():\n \"\"\"Test form with valid data.\"\"\"\n data = {\n 'title': 'Test Post',\n 'content': 'Test content'\n }\n form = PostForm(data=data)\n assert form.is_valid()\n\n\ndef test_invalid_form_missing_title():\n \"\"\"Test form with missing required field.\"\"\"\n data = {'content': 'Test content'}\n form = PostForm(data=data)\n assert not form.is_valid()\n assert 'title' in form.errors\n\n\ndef test_invalid_form_title_too_long():\n \"\"\"Test form with title exceeding max length.\"\"\"\n data = {\n 'title': 'x' * 201,\n 'content': 'Test content'\n }\n form = PostForm(data=data)\n assert not form.is_valid()\n assert 'title' in form.errors\n\n\n# ✅ GOOD: Parametrized form testing\[email protected]('title,content,is_valid', [\n ('Valid', 'Valid content', True),\n ('', 'Valid content', False), # Missing title\n ('Valid', '', False), # Missing content\n ('x' * 201, 'Valid', False), # Title too long\n])\ndef test_post_form_validation(title, content, is_valid):\n \"\"\"Test various form validation scenarios.\"\"\"\n form = PostForm(data={'title': title, 'content': content})\n assert form.is_valid() == is_valid\n```\n\n---\n\n## Coverage\n\n```bash\npip install coverage\n```\n\n```bash\n# Run tests with coverage\ncoverage run --source='.' manage.py test\n\n# View coverage report\ncoverage report\n\n# Generate HTML report\ncoverage html\n# Open htmlcov/index.html in browser\n```\n\n```ini\n# .coveragerc\n[run]\nomit =\n */migrations/*\n */tests/*\n */venv/*\n manage.py\n\n[report]\nexclude_lines =\n pragma: no cover\n def __repr__\n raise AssertionError\n raise NotImplementedError\n if __name__ == .__main__.:\n```\n\n### Coverage with pytest\n\n```bash\n# Install pytest-cov\npip install pytest-cov\n\n# Run tests with coverage\npytest --cov=myapp\n\n# Generate HTML report\npytest --cov=myapp --cov-report=html\n\n# Coverage with minimum threshold\npytest --cov=myapp --cov-fail-under=80\n\n# Show missing lines\npytest --cov=myapp --cov-report=term-missing\n```\n\n## Performance Testing\n\n```python\nfrom django.test import TestCase\nfrom django.test.utils import override_settings\nimport time\n\nclass PerformanceTest(TestCase):\n def test_query_count(self):\n \"\"\"Test number of queries.\"\"\"\n PostFactory.create_batch(10)\n\n with self.assertNumQueries(2): # Should be 2 queries max\n posts = Post.objects.select_related('author').all()\n list(posts) # Force evaluation\n\n def test_response_time(self):\n \"\"\"Test view response time.\"\"\"\n start = time.time()\n response = self.client.get('/posts/')\n duration = time.time() - start\n\n self.assertEqual(response.status_code, 200)\n self.assertLess(duration, 0.5) # Should respond in \u003c 500ms\n```\n\n### Performance Testing with pytest\n\n```python\nimport pytest\nimport time\n\[email protected]_db\ndef test_query_count(django_assert_num_queries):\n \"\"\"Test number of queries.\"\"\"\n PostFactory.create_batch(10)\n\n # Should be 2 queries max\n with django_assert_num_queries(2):\n posts = Post.objects.select_related('author').all()\n list(posts) # Force evaluation\n\n\ndef test_response_time(client, test_post):\n \"\"\"Test view response time.\"\"\"\n start = time.time()\n response = client.get('/posts/')\n duration = time.time() - start\n\n assert response.status_code == 200\n assert duration \u003c 0.5 # Should respond in \u003c 500ms\n\n\[email protected]_db\ndef test_no_n_plus_one(django_assert_num_queries):\n \"\"\"Test avoiding N+1 queries.\"\"\"\n PostFactory.create_batch(10)\n\n # Should execute constant queries regardless of post count\n with django_assert_num_queries(3):\n posts = Post.objects.select_related('author').prefetch_related('comments')\n for post in posts:\n # Access related objects\n _ = post.author.username\n _ = list(post.comments.all())\n```\n\n---\n\n## Advanced pytest Patterns\n\n### Shared Fixtures with conftest.py\n\n```python\n# tests/conftest.py - Available to all test files\nimport pytest\nfrom tests.factories import UserFactory, PostFactory\n\[email protected]\ndef api_client():\n \"\"\"DRF API client.\"\"\"\n from rest_framework.test import APIClient\n return APIClient()\n\[email protected]\ndef authenticated_client(api_client, user):\n \"\"\"Authenticated API client.\"\"\"\n api_client.force_authenticate(user=user)\n return api_client\n\[email protected]\ndef user(db):\n \"\"\"Create test user.\"\"\"\n return UserFactory()\n\[email protected]\ndef admin_user(db):\n \"\"\"Create admin user.\"\"\"\n return UserFactory(is_staff=True, is_superuser=True)\n```\n\n### Custom Markers\n\n```python\n# pytest.ini\n[pytest]\nmarkers =\n slow: marks tests as slow (deselect with '-m \"not slow\"')\n integration: marks tests as integration tests\n unit: marks tests as unit tests\n smoke: marks tests as smoke tests\n\n# Usage in tests\[email protected]\ndef test_expensive_operation(db):\n \"\"\"This test takes a long time.\"\"\"\n pass\n\[email protected]\ndef test_full_workflow(client):\n \"\"\"Integration test.\"\"\"\n pass\n\n# Run only fast tests\n# pytest -m \"not slow\"\n\n# Run only smoke tests\n# pytest -m smoke\n```\n\n### Testing Settings Overrides\n\n```python\ndef test_with_custom_setting(settings):\n \"\"\"Test with modified settings.\"\"\"\n settings.DEBUG = False\n settings.CACHES = {\n 'default': {\n 'BACKEND': 'django.core.cache.backends.dummy.DummyCache'\n }\n }\n # Test behavior with these settings\n\n\[email protected]_db\ndef test_email_sending(mailoutbox):\n \"\"\"Test email is sent.\"\"\"\n send_welcome_email('[email protected]')\n\n assert len(mailoutbox) == 1\n assert mailoutbox[0].to == ['[email protected]']\n assert 'Welcome' in mailoutbox[0].subject\n```\n\n### Testing Management Commands\n\n```python\nimport pytest\nfrom io import StringIO\nfrom django.core.management import call_command\n\[email protected]_db\ndef test_management_command():\n \"\"\"Test custom management command.\"\"\"\n out = StringIO()\n call_command('import_posts', '--source=test.csv', stdout=out)\n\n assert 'Successfully imported' in out.getvalue()\n assert Post.objects.count() > 0\n\n\[email protected]_db\ndef test_command_with_error():\n \"\"\"Test command error handling.\"\"\"\n with pytest.raises(CommandError):\n call_command('import_posts', '--source=nonexistent.csv')\n```\n\n### Async View Testing\n\n```python\nimport pytest\n\[email protected]_db\[email protected]\nasync def test_async_view(async_client):\n \"\"\"Test async view.\"\"\"\n response = await async_client.get('/async-endpoint/')\n assert response.status_code == 200\n\n\[email protected]_db(transaction=True)\[email protected]\nasync def test_async_with_transactions(async_client):\n \"\"\"Test async view with transaction support.\"\"\"\n user = await User.objects.acreate(username='async_user')\n response = await async_client.get(f'/users/{user.id}/')\n assert response.status_code == 200\n```\n\n---\n\n## Choosing Between Django TestCase and pytest-django\n\n### Use Django TestCase when:\n- Team is already familiar with unittest-style tests\n- Working on legacy Django project with existing TestCase tests\n- Prefer explicit class-based organization\n- Need Django-specific test client features\n\n### Use pytest-django when:\n- Starting a new project\n- Want less boilerplate code\n- Need powerful fixtures and dependency injection\n- Want parametrized testing\n- Need to run tests in parallel easily\n- Want to use pytest plugins ecosystem\n\n### Can use both:\npytest can run Django TestCase tests, so you can gradually migrate or use both styles in the same project.\n\n```bash\n# pytest runs both styles\npytest # Runs both TestCase classes and pytest functions\n```\n\n---\n\n## Testing Best Practices Checklist\n\n✅ Test one thing per test method/function\n✅ Use descriptive test names (test_what_when_expected)\n✅ Use Factory Boy instead of JSON fixtures\n✅ Test both success and failure cases\n✅ Test edge cases and boundary conditions\n✅ Test permissions and authorization\n✅ Mock external services (APIs, email, payment gateways)\n✅ Use fixtures (pytest) or setUp() (TestCase) for reusable data\n✅ Test database queries (assertNumQueries / django_assert_num_queries)\n✅ Aim for >80% code coverage\n✅ Run tests before committing code\n✅ Use CI/CD to run tests automatically\n✅ Keep tests fast (mock slow operations, use --reuse-db)\n✅ Test what could break, not Django's built-in functionality\n✅ Use parametrized tests to reduce duplication\n✅ Organize tests in dedicated test directories\n✅ Run tests in parallel (pytest -n auto)\n✅ Use meaningful assertion messages\n\n---\n\n## Quick Reference\n\n### Django TestCase\n```python\nfrom django.test import TestCase\n\nclass MyTest(TestCase):\n def setUp(self):\n self.user = User.objects.create_user(username='test')\n\n def test_something(self):\n self.assertEqual(self.user.username, 'test')\n```\n\n### pytest-django\n```python\nimport pytest\n\[email protected]\ndef user(db):\n return User.objects.create_user(username='test')\n\ndef test_something(user):\n assert user.username == 'test'\n```\n\n### Running Tests\n```bash\n# Django TestCase\npython manage.py test\n\n# pytest-django\npytest\npytest --reuse-db --no-migrations # Faster\npytest -n auto # Parallel\npytest -v # Verbose\npytest -k \"test_user\" # Run matching tests\npytest tests/test_models.py::test_create_user # Specific test\n```\n\n---\n\n**Remember**: Good tests give you confidence to refactor and catch bugs early. Write tests first (TDD) or immediately after implementation.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":41722,"content_sha256":"c7eaf96a23d7a5448960b0d23dfb102f95bd003286d3b9bc030b0ed92650e398"},{"filename":"references/views-and-urls.md","content":"# Django Views & URLs Best Practices\n\n## Function-Based Views (FBV) vs Class-Based Views (CBV)\n\n### When to Use Each\n\n**Use Function-Based Views when:**\n- Simple, straightforward logic\n- Unique behavior that doesn't fit standard CRUD patterns\n- You prefer explicit code over implicit behavior\n- Handling a single HTTP method\n\n**Use Class-Based Views when:**\n- Standard CRUD operations (list, detail, create, update, delete)\n- Sharing behavior across multiple views (mixins)\n- Handling multiple HTTP methods on the same endpoint\n- You want DRY code with Django's built-in generic views\n\n## Function-Based Views\n\n### Basic FBV Pattern\n\n```python\nfrom django.shortcuts import render, redirect, get_object_or_404\nfrom django.http import HttpResponse, JsonResponse\nfrom django.contrib.auth.decorators import login_required\n\n# ✅ GOOD: Simple, explicit view\ndef blog_list(request):\n posts = Post.objects.filter(is_published=True).order_by('-created_at')\n return render(request, 'blog/list.html', {'posts': posts})\n\n# ✅ GOOD: Handle form submission\ndef create_post(request):\n if request.method == 'POST':\n form = PostForm(request.POST)\n if form.is_valid():\n post = form.save(commit=False)\n post.author = request.user\n post.save()\n return redirect('post_detail', pk=post.pk)\n else:\n form = PostForm()\n\n return render(request, 'blog/create.html', {'form': form})\n\n# ✅ GOOD: Protected view\n@login_required\ndef user_dashboard(request):\n user_posts = Post.objects.filter(author=request.user)\n return render(request, 'dashboard.html', {'posts': user_posts})\n```\n\n### Handling Different HTTP Methods\n\n```python\nfrom django.views.decorators.http import require_http_methods, require_POST\n\n# ✅ GOOD: Restrict to specific methods\n@require_http_methods([\"GET\", \"POST\"])\ndef contact_form(request):\n if request.method == 'POST':\n # Handle form submission\n ...\n return render(request, 'contact.html')\n\n# ✅ GOOD: POST-only view\n@require_POST\ndef delete_post(request, pk):\n post = get_object_or_404(Post, pk=pk, author=request.user)\n post.delete()\n return redirect('blog_list')\n\n# ❌ BAD: No method restriction\ndef delete_post(request, pk):\n # Can be triggered by GET request! (CSRF vulnerability)\n post = get_object_or_404(Post, pk=pk)\n post.delete()\n return redirect('blog_list')\n```\n\n## Class-Based Views\n\n### Generic Class-Based Views\n\n```python\nfrom django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView\nfrom django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.urls import reverse_lazy\n\n# ✅ GOOD: Simple list view\nclass PostListView(ListView):\n model = Post\n template_name = 'blog/list.html'\n context_object_name = 'posts'\n paginate_by = 20\n\n def get_queryset(self):\n return Post.objects.filter(is_published=True).select_related('author')\n\n# ✅ GOOD: Detail view with related objects\nclass PostDetailView(DetailView):\n model = Post\n template_name = 'blog/detail.html'\n context_object_name = 'post'\n\n def get_queryset(self):\n return Post.objects.select_related('author').prefetch_related('comments')\n\n# ✅ GOOD: Create view with form validation\nclass PostCreateView(LoginRequiredMixin, CreateView):\n model = Post\n form_class = PostForm\n template_name = 'blog/create.html'\n success_url = reverse_lazy('blog_list')\n\n def form_valid(self, form):\n form.instance.author = self.request.user\n return super().form_valid(form)\n\n# ✅ GOOD: Update view with permission check\nclass PostUpdateView(LoginRequiredMixin, UpdateView):\n model = Post\n form_class = PostForm\n template_name = 'blog/edit.html'\n\n def get_queryset(self):\n # Only allow editing own posts\n return Post.objects.filter(author=self.request.user)\n\n# ✅ GOOD: Delete view\nclass PostDeleteView(LoginRequiredMixin, DeleteView):\n model = Post\n success_url = reverse_lazy('blog_list')\n\n def get_queryset(self):\n return Post.objects.filter(author=self.request.user)\n```\n\n### Custom Class-Based Views\n\n```python\nfrom django.views import View\n\n# ✅ GOOD: Handle multiple methods\nclass PostTogglePublishView(LoginRequiredMixin, View):\n def post(self, request, pk):\n post = get_object_or_404(Post, pk=pk, author=request.user)\n post.is_published = not post.is_published\n post.save()\n return JsonResponse({\n 'success': True,\n 'is_published': post.is_published\n })\n\n# ✅ GOOD: API-like view\nclass PostAPIView(View):\n def get(self, request, pk):\n post = get_object_or_404(Post, pk=pk)\n return JsonResponse({\n 'id': post.id,\n 'title': post.title,\n 'content': post.content,\n })\n\n def post(self, request, pk):\n post = get_object_or_404(Post, pk=pk, author=request.user)\n data = json.loads(request.body)\n post.title = data.get('title', post.title)\n post.save()\n return JsonResponse({'success': True})\n```\n\n## Mixins\n\nMixins add reusable functionality to CBVs.\n\n### Built-in Mixins\n\n```python\nfrom django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin, UserPassesTestMixin\n\n# ✅ GOOD: Require login\nclass UserDashboardView(LoginRequiredMixin, ListView):\n model = Post\n login_url = '/login/' # Where to redirect if not logged in\n\n def get_queryset(self):\n return Post.objects.filter(author=self.request.user)\n\n# ✅ GOOD: Require specific permission\nclass PostCreateView(PermissionRequiredMixin, CreateView):\n model = Post\n permission_required = 'blog.add_post'\n # Redirects to login if permission denied\n\n# ✅ GOOD: Custom permission check\nclass PostUpdateView(UserPassesTestMixin, UpdateView):\n model = Post\n\n def test_func(self):\n post = self.get_object()\n return post.author == self.request.user or self.request.user.is_staff\n```\n\n### Custom Mixins\n\n```python\n# ✅ GOOD: Reusable mixin for adding context\nclass PageTitleMixin:\n page_title = ''\n\n def get_context_data(self, **kwargs):\n context = super().get_context_data(**kwargs)\n context['page_title'] = self.page_title\n return context\n\n# ✅ GOOD: Mixin for filtering by author\nclass FilterByAuthorMixin:\n def get_queryset(self):\n queryset = super().get_queryset()\n return queryset.filter(author=self.request.user)\n\n# Usage\nclass MyPostsView(FilterByAuthorMixin, PageTitleMixin, ListView):\n model = Post\n page_title = 'My Posts'\n template_name = 'blog/my_posts.html'\n```\n\n**Rule**: Mixins should go before the view class in inheritance order.\n\n## URL Configuration\n\n### URL Patterns\n\n```python\nfrom django.urls import path, include\nfrom . import views\n\napp_name = 'blog' # ✅ GOOD: Namespace your URLs\n\nurlpatterns = [\n # ✅ GOOD: Named URL patterns\n path('', views.PostListView.as_view(), name='post_list'),\n path('post/\u003cint:pk>/', views.PostDetailView.as_view(), name='post_detail'),\n path('post/create/', views.PostCreateView.as_view(), name='post_create'),\n path('post/\u003cint:pk>/edit/', views.PostUpdateView.as_view(), name='post_edit'),\n path('post/\u003cint:pk>/delete/', views.PostDeleteView.as_view(), name='post_delete'),\n\n # ✅ GOOD: Use path converters\n path('post/\u003cslug:slug>/', views.post_detail_by_slug, name='post_by_slug'),\n path('category/\u003cstr:category>/', views.posts_by_category, name='posts_by_category'),\n path('archive/\u003cint:year>/\u003cint:month>/', views.archive, name='archive'),\n]\n```\n\n### URL Converters\n\nBuilt-in converters:\n- `\u003cint:name>` - Matches integers\n- `\u003cstr:name>` - Matches non-empty strings (excluding '/')\n- `\u003cslug:name>` - Matches slugs (letters, numbers, hyphens, underscores)\n- `\u003cuuid:name>` - Matches UUIDs\n- `\u003cpath:name>` - Matches any string (including '/')\n\n```python\n# Custom converter\nclass YearConverter:\n regex = '[0-9]{4}'\n\n def to_python(self, value):\n return int(value)\n\n def to_url(self, value):\n return f'{value:04d}'\n\n# Register it\nfrom django.urls import register_converter\nregister_converter(YearConverter, 'year')\n\n# Use it\npath('archive/\u003cyear:year>/', views.archive, name='archive')\n```\n\n### Including Other URLconfs\n\n```python\n# project/urls.py\nfrom django.contrib import admin\nfrom django.urls import path, include\n\nurlpatterns = [\n path('admin/', admin.site.urls),\n path('blog/', include('blog.urls')), # ✅ GOOD: Prefix app URLs\n path('api/', include('api.urls')),\n path('accounts/', include('django.contrib.auth.urls')), # Login/logout\n]\n```\n\n### Reverse URL Resolution\n\n```python\nfrom django.urls import reverse\nfrom django.shortcuts import redirect\n\n# ✅ GOOD: Use reverse() in views\ndef my_view(request):\n return redirect(reverse('blog:post_list'))\n\n# ✅ GOOD: With arguments\ndef after_create(request, post):\n return redirect(reverse('blog:post_detail', kwargs={'pk': post.pk}))\n\n# ✅ GOOD: In templates\n# \u003ca href=\"{% url 'blog:post_detail' pk=post.pk %}\">View Post\u003c/a>\n```\n\n**Rule**: Never hardcode URLs. Always use `reverse()` in Python and `{% url %}` in templates.\n\n## Context and Template Rendering\n\n### Adding Context to Views\n\n```python\n# Function-based view\ndef blog_list(request):\n posts = Post.objects.filter(is_published=True)\n categories = Category.objects.all()\n\n context = {\n 'posts': posts,\n 'categories': categories,\n 'page_title': 'Blog Posts',\n }\n return render(request, 'blog/list.html', context)\n\n# Class-based view\nclass PostListView(ListView):\n model = Post\n\n def get_context_data(self, **kwargs):\n context = super().get_context_data(**kwargs)\n context['categories'] = Category.objects.all()\n context['page_title'] = 'Blog Posts'\n return context\n```\n\n### Context Processors\n\nFor data needed across ALL templates:\n\n```python\n# blog/context_processors.py\ndef blog_stats(request):\n return {\n 'total_posts': Post.objects.count(),\n 'total_published': Post.objects.filter(is_published=True).count(),\n }\n\n# settings.py\nTEMPLATES = [\n {\n 'OPTIONS': {\n 'context_processors': [\n ...\n 'blog.context_processors.blog_stats',\n ],\n },\n },\n]\n```\n\n**Rule**: Only use context processors for truly global data. Don't overuse them.\n\n## HTTP Responses\n\n### Response Types\n\n```python\nfrom django.http import HttpResponse, JsonResponse, HttpResponseRedirect, Http404\nfrom django.shortcuts import render, redirect\n\n# ✅ GOOD: HTML response\ndef blog_list(request):\n posts = Post.objects.all()\n return render(request, 'blog/list.html', {'posts': posts})\n\n# ✅ GOOD: JSON response\ndef post_api(request, pk):\n post = get_object_or_404(Post, pk=pk)\n return JsonResponse({\n 'id': post.id,\n 'title': post.title,\n 'content': post.content,\n })\n\n# ✅ GOOD: Redirect\ndef old_url(request):\n return redirect('new_url_name')\n\n# ✅ GOOD: 404 error\ndef post_detail(request, pk):\n try:\n post = Post.objects.get(pk=pk)\n except Post.DoesNotExist:\n raise Http404(\"Post not found\")\n\n return render(request, 'blog/detail.html', {'post': post})\n\n# ✅ BETTER: Use get_object_or_404\ndef post_detail(request, pk):\n post = get_object_or_404(Post, pk=pk)\n return render(request, 'blog/detail.html', {'post': post})\n```\n\n### Custom Response Status Codes\n\n```python\nfrom django.http import HttpResponse\n\n# 201 Created\ndef create_post(request):\n post = Post.objects.create(...)\n return HttpResponse('Created', status=201)\n\n# 204 No Content\ndef delete_post(request, pk):\n post = get_object_or_404(Post, pk=pk)\n post.delete()\n return HttpResponse(status=204)\n\n# 400 Bad Request\ndef api_view(request):\n if not request.POST.get('required_field'):\n return JsonResponse({'error': 'Missing field'}, status=400)\n ...\n\n# 403 Forbidden\nfrom django.http import HttpResponseForbidden\n\ndef delete_post(request, pk):\n post = get_object_or_404(Post, pk=pk)\n if post.author != request.user:\n return HttpResponseForbidden('You cannot delete this post')\n ...\n```\n\n## Error Handling\n\n### Custom Error Pages\n\n```python\n# views.py\ndef custom_404(request, exception):\n return render(request, '404.html', status=404)\n\ndef custom_500(request):\n return render(request, '500.html', status=500)\n\n# urls.py\nhandler404 = 'blog.views.custom_404'\nhandler500 = 'blog.views.custom_500'\n```\n\n### Exception Handling in Views\n\n```python\nfrom django.core.exceptions import ValidationError, PermissionDenied\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n# ✅ GOOD: Handle specific exceptions\ndef process_payment(request):\n try:\n result = payment_service.charge(request.POST)\n return JsonResponse({'success': True})\n except ValidationError as e:\n return JsonResponse({'error': str(e)}, status=400)\n except PermissionDenied:\n return JsonResponse({'error': 'Forbidden'}, status=403)\n except Exception as e:\n logger.exception(\"Payment processing failed\")\n return JsonResponse({'error': 'Server error'}, status=500)\n```\n\n## Middleware\n\nMiddleware processes requests/responses globally.\n\n### Custom Middleware\n\n```python\n# middleware.py\nclass RequestLoggingMiddleware:\n def __init__(self, get_response):\n self.get_response = get_response\n\n def __call__(self, request):\n # Process request before view\n logger.info(f\"{request.method} {request.path}\")\n\n # Call the view\n response = self.get_response(request)\n\n # Process response after view\n logger.info(f\"Response: {response.status_code}\")\n\n return response\n\n# settings.py\nMIDDLEWARE = [\n ...\n 'blog.middleware.RequestLoggingMiddleware',\n]\n```\n\n### Common Middleware Patterns\n\n```python\n# Add custom header to all responses\nclass CustomHeaderMiddleware:\n def __init__(self, get_response):\n self.get_response = get_response\n\n def __call__(self, request):\n response = self.get_response(request)\n response['X-Custom-Header'] = 'value'\n return response\n\n# Block requests from specific IPs\nclass IPBlockMiddleware:\n def __init__(self, get_response):\n self.get_response = get_response\n self.blocked_ips = ['192.168.1.1']\n\n def __call__(self, request):\n ip = request.META.get('REMOTE_ADDR')\n if ip in self.blocked_ips:\n return HttpResponseForbidden('Access denied')\n return self.get_response(request)\n```\n\n## Decorators\n\n### Common View Decorators\n\n```python\nfrom django.views.decorators.http import require_http_methods, require_POST\nfrom django.views.decorators.cache import cache_page\nfrom django.contrib.auth.decorators import login_required, permission_required\n\n# ✅ GOOD: Combine decorators\n@login_required\n@require_POST\ndef delete_post(request, pk):\n ...\n\n# ✅ GOOD: Cache view for 5 minutes\n@cache_page(60 * 5)\ndef blog_list(request):\n posts = Post.objects.all()\n return render(request, 'blog/list.html', {'posts': posts})\n\n# ✅ GOOD: Require permission\n@permission_required('blog.add_post')\ndef create_post(request):\n ...\n```\n\n### Custom Decorators\n\n```python\nfrom functools import wraps\n\n# ✅ GOOD: Custom decorator for AJAX-only views\ndef ajax_required(view_func):\n @wraps(view_func)\n def wrapper(request, *args, **kwargs):\n if not request.headers.get('X-Requested-With') == 'XMLHttpRequest':\n return HttpResponseBadRequest('AJAX required')\n return view_func(request, *args, **kwargs)\n return wrapper\n\n# Usage\n@ajax_required\ndef get_comments(request, post_id):\n comments = Comment.objects.filter(post_id=post_id)\n return JsonResponse({'comments': list(comments.values())})\n```\n\n## View Best Practices Checklist\n\n✅ Use `get_object_or_404()` instead of try/except\n✅ Always validate user permissions (authentication ≠ authorization)\n✅ Use `select_related()` and `prefetch_related()` to avoid N+1 queries\n✅ Name all URL patterns for easier maintenance\n✅ Use namespaces (`app_name`) in URL configurations\n✅ Validate form data before saving\n✅ Return appropriate HTTP status codes\n✅ Log errors and security-relevant events\n✅ Use CSRF protection on all POST/PUT/DELETE views\n✅ Add caching to expensive views when appropriate\n✅ Keep views focused (single responsibility)\n✅ Extract complex logic into model methods or services\n\n---\n\n**Remember**: Views should be thin - they orchestrate, they don't contain business logic. Put complex logic in models, managers, or service modules.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":16623,"content_sha256":"e7ac4a465a4103018d8dee1dd51e8af9dfe612bc36bffc83da99d20f76c5a573"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Django Expert","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill provides expert guidance for Django backend development with comprehensive coverage of models, views, Django REST Framework, forms, authentication, testing, and performance optimization. It follows official Django best practices and modern Python conventions to help you build robust, maintainable applications.","type":"text"}]},{"type":"paragraph","content":[{"text":"Key Capabilities:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Model design with optimal ORM patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"View implementation (FBV, CBV, DRF viewsets)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Django REST Framework API development","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Query optimization and performance tuning","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Authentication and permissions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testing strategies and patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Security best practices","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use","type":"text"}]},{"type":"paragraph","content":[{"text":"Invoke this skill when you encounter these triggers:","type":"text"}]},{"type":"paragraph","content":[{"text":"Model & Database Work:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Create a Django model for...\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Optimize this queryset/database query\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Generate migrations for...\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Design database schema for...\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Fix N+1 query problem\"","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"View & API Development:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Create an API endpoint for...\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Build a Django view that...\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Implement DRF serializer/viewset\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Add filtering/pagination to API\"","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Authentication & Security:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Implement authentication/permissions\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Create custom user model\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Secure this endpoint/view\"","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Testing & Quality:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Write tests for this Django app\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Debug this Django error/issue\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Review Django code for issues\"","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Performance & Optimization:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"This Django view is slow\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Optimize database queries\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Add caching to...\"","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Production Deployment:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Deploy Django to production\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Configure Django for production\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Set up HTTPS/SSL for Django\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Production settings checklist\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Configure production database/cache\"","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Instructions","type":"text"}]},{"type":"paragraph","content":[{"text":"Follow this workflow when handling Django development requests:","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. Analyze the Request and Gather Context","type":"text"}]},{"type":"paragraph","content":[{"text":"Identify the task type:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Model design (database schema, relationships, migrations)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"View/API development (FBV, CBV, DRF viewsets, serializers)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Query optimization (N+1 problems, database performance)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Authentication/permissions (user models, access control)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testing (unit tests, integration tests, fixtures)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Security review (CSRF, XSS, SQL injection, permissions)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Production deployment (settings, HTTPS, database, caching, monitoring)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Template rendering (Django templates, context processors)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Leverage available context:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If ","type":"text"},{"text":"django-ai-boost","type":"text","marks":[{"type":"code_inline"}]},{"text":" MCP server is available, use it to understand project structure and existing patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read relevant existing code to understand conventions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check Django version for compatibility considerations","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Load Relevant Reference Documentation","type":"text"}]},{"type":"paragraph","content":[{"text":"Based on the task type, reference the appropriate bundled documentation:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Models/ORM work","type":"text","marks":[{"type":"strong"}]},{"text":" -> ","type":"text"},{"text":"references/models-and-orm.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Model design patterns and field choices","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Relationship configurations (ForeignKey, ManyToMany)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Custom managers and QuerySet methods","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Migration strategies","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"View/API development","type":"text","marks":[{"type":"strong"}]},{"text":" -> ","type":"text"},{"text":"references/views-and-urls.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"references/drf-guidelines.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"FBV vs CBV decision criteria","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"DRF serializers, viewsets, and routers","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"URL configuration patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Middleware and request/response handling","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Performance issues","type":"text","marks":[{"type":"strong"}]},{"text":" -> ","type":"text"},{"text":"references/performance-optimization.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Query optimization techniques (select_related, prefetch_related)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Caching strategies (Redis, Memcached, database caching)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Database indexing and query profiling","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Connection pooling and async patterns","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Production deployment","type":"text","marks":[{"type":"strong"}]},{"text":" -> ","type":"text"},{"text":"references/production-deployment.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Critical settings (DEBUG, SECRET_KEY, ALLOWED_HOSTS)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"HTTPS and SSL/TLS configuration","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Database and cache configuration","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Static/media file serving","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Error monitoring and logging","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Deployment process and health checks","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Security concerns","type":"text","marks":[{"type":"strong"}]},{"text":" -> ","type":"text"},{"text":"references/security-checklist.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CSRF/XSS/SQL injection prevention","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Authentication and authorization patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Secure configuration practices","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Input validation and sanitization","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testing tasks","type":"text","marks":[{"type":"strong"}]},{"text":" -> ","type":"text"},{"text":"references/testing-strategies.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test structure and organization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Fixtures and factories","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mocking external dependencies","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Coverage and CI/CD integration","type":"text"}]}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. Implement Following Django Best Practices","type":"text"}]},{"type":"paragraph","content":[{"text":"Code quality standards:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Follow PEP 8 and Django coding style","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use Django built-ins over third-party packages when possible","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Keep views thin, use services/managers for business logic","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Write descriptive variable names and add docstrings for complex logic","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Handle errors gracefully with appropriate exceptions","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Django-specific patterns:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"select_related()","type":"text","marks":[{"type":"code_inline"}]},{"text":" for FK/OneToOne, ","type":"text"},{"text":"prefetch_related()","type":"text","marks":[{"type":"code_inline"}]},{"text":" for reverse FK/M2M","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Leverage class-based views and mixins for code reuse","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use Django forms/serializers for validation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Follow Django's migration workflow (never edit applied migrations)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use Django's built-in security features (CSRF tokens, auth decorators)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"API development (DRF):","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ModelSerializer for standard CRUD operations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implement proper pagination and filtering","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use appropriate permission classes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Follow RESTful conventions for endpoints","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Version APIs when making breaking changes","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. Validate and Test","type":"text"}]},{"type":"paragraph","content":[{"text":"Before presenting the solution:","type":"text"}]},{"type":"paragraph","content":[{"text":"Code review:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check for N+1 query problems (use Django Debug Toolbar mentally)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify proper error handling and edge cases","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ensure security best practices are followed","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Confirm migrations are clean and reversible","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Testing considerations:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Suggest or write appropriate tests for new functionality","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify test coverage for critical paths","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check that fixtures/factories are maintainable","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Performance check:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Review database queries for efficiency","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Consider caching opportunities","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify proper use of database indexes","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Bundled Resources","type":"text"}]},{"type":"paragraph","content":[{"text":"references/","type":"text","marks":[{"type":"strong"}]},{"text":" - Comprehensive Django documentation loaded into context as needed","type":"text"}]},{"type":"paragraph","content":[{"text":"These reference files provide detailed guidance beyond this SKILL.md overview:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/models-and-orm.md","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" (~11k words)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Model field types and best practices","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Relationship configurations (ForeignKey, OneToOne, ManyToMany)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Custom managers and QuerySet methods","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Migration patterns and common pitfalls","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Database-level constraints and indexes","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/views-and-urls.md","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" (~17k words)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Function-based vs class-based view trade-offs","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CBV mixins and inheritance patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"URL routing and reverse resolution","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Middleware implementation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Request/response lifecycle","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/drf-guidelines.md","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" (~18k words)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Serializer patterns (ModelSerializer, nested serializers)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ViewSet and router configurations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pagination, filtering, and search","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Authentication and permission classes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"API versioning strategies","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Performance optimization for APIs","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/testing-strategies.md","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" (~18k words)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test organization and structure","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Factory patterns vs fixtures","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testing views, models, and serializers","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mocking external services","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test database optimization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CI/CD integration","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/security-checklist.md","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" (~12k words)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CSRF protection implementation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"XSS prevention techniques","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SQL injection defense","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Authentication best practices","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Permission and authorization patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Secure settings configuration","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/performance-optimization.md","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" (~14k words)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Query optimization (select_related, prefetch_related, only, defer)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Database indexing strategies","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Caching layers (Redis, Memcached, database cache)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Database connection pooling","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Profiling and monitoring tools","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Async views and background tasks","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/production-deployment.md","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" (~20k words)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Critical settings (DEBUG, SECRET_KEY, ALLOWED_HOSTS)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Database configuration and connection pooling","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"HTTPS/SSL configuration and security headers","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Static and media file serving","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Caching with Redis/Memcached","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Email configuration for production","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Error monitoring with Sentry","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Logging and health checks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Zero-downtime deployment strategies","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/examples.md","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" - Practical implementation examples","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Model design with custom managers","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"N+1 query optimization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"DRF API endpoint implementation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Writing Django tests","type":"text"}]}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Additional Notes","type":"text"}]},{"type":"paragraph","content":[{"text":"Django Version Compatibility:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Consider LTS releases (4.2, 5.2) for production","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check deprecation warnings when upgrading","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"django-upgrade","type":"text","marks":[{"type":"code_inline"}]},{"text":" tool for automated migration","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Common Pitfalls to Avoid:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Circular imports (use lazy references)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Missing ","type":"text"},{"text":"related_name","type":"text","marks":[{"type":"code_inline"}]},{"text":" on relationships","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Forgetting database indexes on frequently queried fields","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Using ","type":"text"},{"text":"get()","type":"text","marks":[{"type":"code_inline"}]},{"text":" without exception handling","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"N+1 queries in templates and serializers","type":"text"}]}]}]}]},"metadata":{"date":"2026-11-02","name":"django-expert","author":"@skillopedia","source":{"stars":168,"repo_name":"django-ai-plugins","origin_url":"https://github.com/vintasoftware/django-ai-plugins/blob/HEAD/skills/django-expert/SKILL.md","repo_owner":"vintasoftware","body_sha256":"a4919acb26157e182b2d5da524f85efa8c837eaeff3596cbeadf8ec6b61702bb","cluster_key":"05e1da9208e6c86b2c0d8369569521eafc202e3cea8fe7168ab8cd1077de8ec5","clean_bundle":{"format":"clean-skill-bundle-v1","source":"vintasoftware/django-ai-plugins/skills/django-expert/SKILL.md","text_bytes":148442,"source_commit":"b8af2688d994747666e6a793d441365807d6772f","skills_sh_rank":3915,"attachment_count":9,"text_attachments":9,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"skills/django-expert/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"web-development","category_label":"Web"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"web-development","import_tag":"skills-sh-top20k-2026-11","description":"Expert Django backend development guidance. Use when creating Django models, views, serializers, or APIs; debugging ORM queries or migrations; optimizing database performance; implementing authentication; writing tests; or working with Django REST Framework. Follows Django best practices and modern patterns."}},"renderedAt":1793671718408}

Django Expert Overview This skill provides expert guidance for Django backend development with comprehensive coverage of models, views, Django REST Framework, forms, authentication, testing, and performance optimization. It follows official Django best practices and modern Python conventions to help you build robust, maintainable applications. Key Capabilities: - Model design with optimal ORM patterns - View implementation (FBV, CBV, DRF viewsets) - Django REST Framework API development - Query optimization and performance tuning - Authentication and permissions - Testing strategies and patte…