Key Principles
Readability First
Prioritize code readability and maintainability. Follow PEP 8 compliance and Django conventions
Modular Structure
Organize code using Django apps within SuperApp for clear separation and reuse
Django Features
Leverage built-in Django features. Avoid raw SQL, prefer Django ORM
MVT Separation
Follow strict Model-View-Template separation. Keep business logic in models/forms
App Organization
Directory Structure
Maintain a consistent directory structure across all apps:superapp/apps/my_app/
├── __init__.py
├── settings.py # App settings integration
├── urls.py # URL patterns
├── requirements.txt # App-specific dependencies
├── models.py # Database models
├── views.py # View logic (or views/ for large apps)
├── forms.py # Form definitions
├── serializers.py # DRF serializers (if using API)
├── signals.py # Signal handlers
├── tasks.py # Celery tasks
├── admin/ # Admin configurations
│ ├── __init__.py
│ ├── user.py
│ └── product.py
├── management/ # Management commands
│ └── commands/
│ └── my_command.py
├── migrations/ # Database migrations
├── templates/ # Templates
│ └── my_app/
│ └── index.html
├── static/ # Static files
│ └── my_app/
│ ├── css/
│ ├── js/
│ └── images/
└── tests/ # Tests
├── __init__.py
├── test_models.py
├── test_views.py
└── test_api.py
For large apps, split
views.py into a views/ package with separate modules for different concerns (e.g., views/api.py, views/public.py, views/admin.py).Naming Conventions
# Models: Singular, PascalCase
class Product(models.Model):
pass
# Views: Descriptive, snake_case for FBVs
def product_list(request):
pass
# Views: PascalCase for CBVs with descriptive suffix
class ProductListView(ListView):
pass
# URLs: Lowercase with hyphens
path('product-list/', views.product_list, name='product_list')
# App names: Lowercase with underscores
superapp.apps.product_catalog
Model Design
Keep Business Logic in Models
# Good: Business logic in model methods
class Order(models.Model):
total = models.DecimalField(max_digits=10, decimal_places=2)
discount = models.DecimalField(max_digits=10, decimal_places=2)
def calculate_final_total(self):
"""Calculate total after discount."""
return self.total - self.discount
def can_be_cancelled(self):
"""Check if order can be cancelled."""
return self.status in ['pending', 'processing']
def cancel(self):
"""Cancel the order."""
if not self.can_be_cancelled():
raise ValueError("Order cannot be cancelled")
self.status = 'cancelled'
self.save()
# Bad: Business logic in views
def cancel_order(request, order_id):
order = Order.objects.get(id=order_id)
if order.status in ['pending', 'processing']:
order.status = 'cancelled'
order.save()
Use Django ORM Features
# Good: Use select_related for foreign keys
orders = Order.objects.select_related('customer', 'shipping_address').all()
# Good: Use prefetch_related for reverse relations
customers = Customer.objects.prefetch_related('orders').all()
# Good: Use annotations
from django.db.models import Count, Sum
products = Product.objects.annotate(
order_count=Count('orderitem'),
total_sold=Sum('orderitem__quantity')
)
# Avoid: N+1 queries
orders = Order.objects.all()
for order in orders:
print(order.customer.name) # Triggers query for each order
Model Validation
from django.core.exceptions import ValidationError
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
stock = models.IntegerField()
def clean(self):
"""Validate model data."""
if self.price < 0:
raise ValidationError({'price': 'Price cannot be negative'})
if self.stock < 0:
raise ValidationError({'stock': 'Stock cannot be negative'})
def save(self, *args, **kwargs):
"""Call clean before saving."""
self.full_clean()
super().save(*args, **kwargs)
View Design
Use Class-Based Views for Complex Logic
from django.views.generic import ListView, DetailView, CreateView
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
# Good: CBV for complex views
class ProductListView(LoginRequiredMixin, ListView):
model = Product
template_name = 'products/list.html'
context_object_name = 'products'
paginate_by = 20
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.select_related('category')
# Apply filters
if search := self.request.GET.get('search'):
queryset = queryset.filter(name__icontains=search)
return queryset.filter(is_active=True)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
return context
# Good: FBV for simple views
from django.shortcuts import render
def hello_world(request):
"""Simple greeting view."""
return render(request, 'hello.html', {'message': 'Hello, World!'})
Keep Views Thin
# Good: Views handle requests/responses only
class OrderCreateView(LoginRequiredMixin, CreateView):
model = Order
form_class = OrderForm
def form_valid(self, form):
form.instance.customer = self.request.user
response = super().form_valid(form)
# Business logic in model method
self.object.process_payment()
return response
# Bad: Business logic in views
def create_order(request):
if request.method == 'POST':
# Too much business logic here
order = Order(customer=request.user, ...)
if order.total > 0:
# Calculate shipping
# Apply discounts
# Process payment
# Send emails
# Update inventory
order.save()
Dependency Management
App-Specific Requirements
Each app should declare its own dependencies:superapp/apps/my_app/requirements.txt
# API and data processing
requests>=2.28.0,<3.0.0
pandas>=1.5.0,<2.0.0
# Background tasks
celery>=5.2.0,<6.0.0
redis>=4.3.0,<5.0.0
# Additional utilities
python-dateutil>=2.8.0
pillow>=9.0.0
Pin dependencies to specific versions or version ranges to ensure reproducibility and prevent unexpected breaking changes.
Version Pinning Strategies
# Production: Exact versions
Django==4.2.7
djangorestframework==3.14.0
# Development: Compatible versions
pytest>=7.4.0,<8.0.0
black~=23.11.0
# Avoid: Unpinned versions (can break builds)
requests # Bad: No version specified
pandas>=1.0.0 # Bad: Too broad range
Managing Dependencies
# Install app-specific requirements during setup
pip install -r superapp/apps/my_app/requirements.txt
# Install all app requirements
find superapp/apps -name "requirements.txt" -exec pip install -r {} \;
# Generate locked requirements
pip freeze > requirements-lock.txt
Settings Integration
Extend Settings Cleanly
superapp/apps/my_app/settings.py
def extend_superapp_settings(main_settings):
# Register app
main_settings['INSTALLED_APPS'] += ['superapp.apps.my_app']
# Add middleware if needed
if 'my_app.middleware.CustomMiddleware' not in main_settings.get('MIDDLEWARE', []):
main_settings['MIDDLEWARE'] += ['my_app.middleware.CustomMiddleware']
# App-specific configuration
main_settings.setdefault('MY_APP_CONFIG', {})
main_settings['MY_APP_CONFIG'].update({
'feature_enabled': True,
'api_timeout': 30,
'cache_duration': 3600,
})
# Add templates directory
main_settings['TEMPLATES'][0]['DIRS'] += [
'superapp/apps/my_app/templates'
]
Environment-Specific Settings
import os
def extend_superapp_settings(main_settings):
main_settings['INSTALLED_APPS'] += ['superapp.apps.my_app']
# Use environment variables for sensitive data
main_settings['MY_APP_API_KEY'] = os.getenv('MY_APP_API_KEY')
main_settings['MY_APP_API_SECRET'] = os.getenv('MY_APP_API_SECRET')
# Environment-specific configuration
if main_settings.get('DEBUG'):
main_settings['MY_APP_CACHE_ENABLED'] = False
else:
main_settings['MY_APP_CACHE_ENABLED'] = True
Security Best Practices
Input Validation
from django import forms
from django.core.validators import MinValueValidator, MaxValueValidator
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ['name', 'price', 'stock']
def clean_price(self):
price = self.cleaned_data['price']
if price < 0:
raise forms.ValidationError("Price cannot be negative")
if price > 1000000:
raise forms.ValidationError("Price is unreasonably high")
return price
def clean(self):
cleaned_data = super().clean()
# Cross-field validation
return cleaned_data
Protect Sensitive Data
# Good: Use environment variables
import os
API_KEY = os.getenv('API_KEY')
DATABASE_PASSWORD = os.getenv('DATABASE_PASSWORD')
# Bad: Hardcoded secrets
API_KEY = 'sk_live_12345...' # Never do this!
CSRF Protection
# Good: Use Django's CSRF protection
from django.views.decorators.csrf import csrf_protect
@csrf_protect
def submit_form(request):
if request.method == 'POST':
# Process form
pass
return render(request, 'form.html')
# In templates
# {% csrf_token %}
SQL Injection Prevention
# Good: Use ORM
products = Product.objects.filter(category=user_input)
# Good: Use parameterized queries when raw SQL is necessary
from django.db import connection
cursor = connection.cursor()
cursor.execute("SELECT * FROM products WHERE category = %s", [user_input])
# Bad: String interpolation (vulnerable to SQL injection)
cursor.execute(f"SELECT * FROM products WHERE category = '{user_input}'")
Performance Optimization
Query Optimization
# Use select_related for foreign keys (single JOIN)
orders = Order.objects.select_related('customer', 'shipping_address')
# Use prefetch_related for reverse relations (separate queries)
customers = Customer.objects.prefetch_related('orders', 'addresses')
# Use only() to fetch specific fields
products = Product.objects.only('id', 'name', 'price')
# Use defer() to exclude fields
products = Product.objects.defer('description', 'full_text')
# Use iterator() for large querysets
for product in Product.objects.iterator(chunk_size=1000):
process_product(product)
Caching
from django.core.cache import cache
from django.views.decorators.cache import cache_page
# Cache function results
def get_popular_products():
cache_key = 'popular_products'
products = cache.get(cache_key)
if products is None:
products = Product.objects.filter(
is_active=True
).order_by('-views')[:10]
cache.set(cache_key, products, 3600) # Cache for 1 hour
return products
# Cache views
@cache_page(60 * 15) # Cache for 15 minutes
def product_list(request):
products = Product.objects.all()
return render(request, 'products/list.html', {'products': products})
Database Indexes
class Product(models.Model):
name = models.CharField(max_length=200, db_index=True)
sku = models.CharField(max_length=50, unique=True, db_index=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
class Meta:
indexes = [
models.Index(fields=['name', 'category']),
models.Index(fields=['-created_at']),
]
Background Tasks
# Use Celery for time-consuming operations
from celery import shared_task
@shared_task
def process_order(order_id):
"""Process order in background."""
order = Order.objects.get(id=order_id)
order.process_payment()
order.send_confirmation_email()
order.update_inventory()
# Call from views
def create_order(request):
order = Order.objects.create(...)
# Process in background instead of blocking request
process_order.delay(order.id)
return redirect('order_success')
Testing
Write Comprehensive Tests
from django.test import TestCase
from decimal import Decimal
class ProductModelTest(TestCase):
def setUp(self):
self.product = Product.objects.create(
name='Test Product',
price=Decimal('99.99'),
stock=10
)
def test_calculate_discount(self):
"""Test discount calculation."""
discounted_price = self.product.calculate_discount(20)
self.assertEqual(discounted_price, Decimal('79.99'))
def test_negative_stock_validation(self):
"""Test that negative stock raises error."""
with self.assertRaises(ValidationError):
self.product.stock = -1
self.product.full_clean()
Test Views
from django.test import TestCase, Client
from django.urls import reverse
class ProductViewTest(TestCase):
def setUp(self):
self.client = Client()
self.product = Product.objects.create(name='Test', price=10)
def test_product_list_view(self):
"""Test product list displays correctly."""
response = self.client.get(reverse('product_list'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Test')
def test_product_list_requires_login(self):
"""Test login requirement."""
response = self.client.get(reverse('product_list'))
self.assertRedirects(response, '/login/?next=/products/')
Error Handling
Use Django’s Error Handling
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.http import Http404
def get_product_or_404(product_id):
"""Get product or raise 404."""
try:
return Product.objects.get(id=product_id)
except ObjectDoesNotExist:
raise Http404("Product not found")
# Use get_object_or_404 shortcut
from django.shortcuts import get_object_or_404
def product_detail(request, product_id):
product = get_object_or_404(Product, id=product_id)
return render(request, 'product.html', {'product': product})
Custom Error Pages
# Create custom error handlers
def handler404(request, exception):
return render(request, '404.html', status=404)
def handler500(request):
return render(request, '500.html', status=500)
Signals for Decoupling
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=Order)
def order_created(sender, instance, created, **kwargs):
"""Send notification when order is created."""
if created:
# Send email
send_order_confirmation_email.delay(instance.id)
# Log event
logger.info(f"Order {instance.id} created for {instance.customer}")
# Update analytics
track_order_event(instance)
Documentation
Docstrings
def calculate_shipping_cost(weight, distance, shipping_method='standard'):
"""
Calculate shipping cost based on weight and distance.
Args:
weight (float): Package weight in kg
distance (float): Shipping distance in km
shipping_method (str): One of 'standard', 'express', 'overnight'
Returns:
Decimal: Calculated shipping cost
Raises:
ValueError: If weight or distance is negative
Examples:
>>> calculate_shipping_cost(2.5, 100)
Decimal('15.00')
"""
if weight < 0 or distance < 0:
raise ValueError("Weight and distance must be positive")
# Implementation
Next Steps
Creating Apps
Apply these practices when creating new apps
Admin Integration
Follow best practices for admin development
Templates
Use templates that follow best practices
API Reference
View the complete API reference