Django ORM Setup for Python Web Application
Django ORM is built into the framework and requires no separate installation, but proper configuration significantly affects performance and scalability. We consider Django 4.2+ with PostgreSQL.
Database Connection
In settings.py define multiple databases if needed:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': env('DB_NAME'),
'USER': env('DB_USER'),
'PASSWORD': env('DB_PASSWORD'),
'HOST': env('DB_HOST', default='127.0.0.1'),
'PORT': env('DB_PORT', default='5432'),
'CONN_MAX_AGE': 60,
'OPTIONS': {
'connect_timeout': 10,
'options': '-c search_path=public',
},
},
}
CONN_MAX_AGE enables persistent connections — Django won't close database connections after each HTTP request, reducing latency. Standard practice for gunicorn with multiple workers.
Models
# catalog/models.py
from django.db import models
class Product(models.Model):
title = models.CharField(max_length=500)
slug = models.SlugField(unique=True, max_length=520)
category = models.ForeignKey(
'Category',
on_delete=models.PROTECT,
related_name='products',
)
price = models.DecimalField(max_digits=12, decimal_places=2)
status = models.CharField(
max_length=10,
choices=[('draft', 'Draft'), ('published', 'Published')],
default='draft',
)
tags = models.ManyToManyField('Tag', blank=True, related_name='products')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
indexes = [
models.Index(fields=['status', '-created_at']),
models.Index(fields=['category', 'status']),
]
Custom Managers and QuerySet
class PublishedProductQuerySet(models.QuerySet):
def published(self):
return self.filter(status='published')
def with_category(self):
return self.select_related('category')
def with_tags(self):
return self.prefetch_related('tags')
class ProductManager(models.Manager):
def get_queryset(self):
return PublishedProductQuerySet(self.model, using=self._db)
def published(self):
return self.get_queryset().published()
# In model:
# objects = ProductManager()
Annotations and Aggregations
from django.db.models import Count, Avg, F, Q
stats = (
Category.objects
.annotate(
product_count=Count('products', filter=Q(products__status='published')),
avg_price=Avg('products__price', filter=Q(products__status='published')),
)
.filter(product_count__gt=0)
.order_by('-product_count')
)
Migrations in Production
Several rules to prevent downtime:
- Adding nullable column doesn't block table in PostgreSQL 11+
- Adding index — use
CONCURRENTLY: AddIndex uses it automatically for PostgreSQL - Renaming column — always in two stages: add new → copy data → remove old
-
--fakemigration needed only to sync state without re-applying SQL
python manage.py migrate --plan
python manage.py migrate
python manage.py showmigrations
Timeline
Initial setup of new Django project with models, migrations, replica router, basic managers: 1 day. Refactoring existing project to eliminate N+1 queries, adding indexes: 1–2 days.







