NexusCS

Django

Python
Quick reference guide for Django 5.1 - Python web framework for building web applications with clean, pragmatic design.
web
framework
python
orm

Getting started

Installation

# Install Django
python -m pip install Django

# Check version
python -m django --version

Create project

# Create new project
django-admin startproject mysite

# Navigate to project
cd mysite

# Run development server
python manage.py runserver

Create app

# Create new app
python manage.py startapp appname

# Add to INSTALLED_APPS in settings.py
INSTALLED_APPS = [
    'appname',
    # ...
]

Project structure

mysite/
├── manage.py
├── mysite/
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   ├── asgi.py
│   └── wsgi.py
└── appname/
    ├── migrations/
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── models.py
    ├── tests.py
    └── views.py

Models

Basic model

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    pub_date = models.DateTimeField()
    is_published = models.BooleanField(default=False)

    def __str__(self):
        return self.title

Field types

Field Description
CharField(max_length) Short text
TextField() Long text
IntegerField() Integer number
FloatField() Floating point
BooleanField() True/False
DateField() Date
DateTimeField() Date and time
EmailField() Email address
URLField() URL
FileField() File upload
ImageField() Image upload
JSONField() JSON data

Relationships

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(
        Author,
        on_delete=models.CASCADE
    )

class Tag(models.Model):
    name = models.CharField(max_length=50)
    articles = models.ManyToManyField(Article)

class Profile(models.Model):
    user = models.OneToOneField(
        User,
        on_delete=models.CASCADE
    )

on_delete options

Option Description
CASCADE Delete related objects
PROTECT Prevent deletion
SET_NULL Set to NULL
SET_DEFAULT Set to default
DO_NOTHING Do nothing

Meta options

class Article(models.Model):
    title = models.CharField(max_length=200)
    pub_date = models.DateTimeField()

    class Meta:
        ordering = ['-pub_date']
        verbose_name = 'article'
        verbose_name_plural = 'articles'
        db_table = 'blog_articles'
        unique_together = ['title', 'pub_date']

QuerySet / ORM

Creating objects

# Method 1: Create and save
article = Article(title='Hello', content='World')
article.save()

# Method 2: Create in one step
article = Article.objects.create(
    title='Hello',
    content='World'
)

# Bulk create
Article.objects.bulk_create([
    Article(title='One'),
    Article(title='Two')
])

Retrieving objects

# Get all objects
articles = Article.objects.all()

# Get single object
article = Article.objects.get(id=1)

# Filter
articles = Article.objects.filter(is_published=True)

# Exclude
articles = Article.objects.exclude(is_published=False)

# Get first/last
first = Article.objects.first()
last = Article.objects.last()

# Check if exists
exists = Article.objects.filter(title='Hello').exists()

Field lookups

# Exact match
Article.objects.filter(title__exact='Hello')

# Case-insensitive exact
Article.objects.filter(title__iexact='hello')

# Contains
Article.objects.filter(title__contains='ell')

# Case-insensitive contains
Article.objects.filter(title__icontains='ELL')

# Starts with
Article.objects.filter(title__startswith='Hel')

# Ends with
Article.objects.filter(title__endswith='lo')

# Greater than
Article.objects.filter(id__gt=5)

# Less than or equal
Article.objects.filter(id__lte=10)

# In list
Article.objects.filter(id__in=[1, 3, 5])

# Is null
Article.objects.filter(pub_date__isnull=True)

# Range
Article.objects.filter(id__range=(1, 10))

# Date
Article.objects.filter(pub_date__year=2024)
Article.objects.filter(pub_date__month=12)

Ordering and slicing

# Order by
Article.objects.order_by('title')
Article.objects.order_by('-pub_date')  # Descending

# Multiple ordering
Article.objects.order_by('author', '-pub_date')

# Slicing (LIMIT)
Article.objects.all()[:5]

# Offset and limit
Article.objects.all()[5:10]

# Reverse order
Article.objects.all().reverse()

# Random order
Article.objects.order_by('?')

Updating objects

# Update single object
article = Article.objects.get(id=1)
article.title = 'New Title'
article.save()

# Update multiple objects
Article.objects.filter(is_published=False).update(
    is_published=True
)

# Update with F expressions
from django.db.models import F
Article.objects.update(views=F('views') + 1)

Deleting objects

# Delete single object
article = Article.objects.get(id=1)
article.delete()

# Delete multiple objects
Article.objects.filter(is_published=False).delete()

# Delete all
Article.objects.all().delete()

Aggregation

from django.db.models import Count, Avg, Sum, Max, Min

# Count
Article.objects.count()

# Aggregate
Article.objects.aggregate(Avg('views'))
Article.objects.aggregate(total=Sum('views'))

# Annotate
Article.objects.annotate(num_comments=Count('comment'))

Q objects (complex queries)

from django.db.models import Q

# OR condition
Article.objects.filter(
    Q(title__contains='Django') | Q(title__contains='Python')
)

# AND condition
Article.objects.filter(
    Q(title__contains='Django') & Q(is_published=True)
)

# NOT condition
Article.objects.filter(~Q(title__contains='Test'))

Relationship spanning

# Forward relationship (ForeignKey)
Book.objects.filter(author__name='John')

# Reverse relationship
Author.objects.filter(book__title='Django')

# Multiple levels
Book.objects.filter(
    author__profile__country='USA'
)

Views

Function-based views

from django.shortcuts import render, redirect
from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world!")

def article_detail(request, id):
    article = Article.objects.get(id=id)
    return render(request, 'article.html', {
        'article': article
    })

def article_create(request):
    if request.method == 'POST':
        # Process form
        return redirect('article_list')
    return render(request, 'form.html')

Class-based views

from django.views.generic import (
    ListView, DetailView, CreateView,
    UpdateView, DeleteView
)

class ArticleListView(ListView):
    model = Article
    template_name = 'article_list.html'
    context_object_name = 'articles'
    paginate_by = 10

class ArticleDetailView(DetailView):
    model = Article
    template_name = 'article_detail.html'

class ArticleCreateView(CreateView):
    model = Article
    fields = ['title', 'content']
    success_url = '/articles/'

Shortcuts

from django.shortcuts import (
    render, redirect, get_object_or_404
)

# Render template
render(request, 'template.html', context)

# Redirect
redirect('view_name')
redirect('/url/path/')

# Get object or 404
article = get_object_or_404(Article, id=1)

# Get list or 404
from django.shortcuts import get_list_or_404
articles = get_list_or_404(Article, is_published=True)

Request object

def my_view(request):
    # Method
    request.method  # 'GET', 'POST', etc.

    # GET parameters
    request.GET['key']
    request.GET.get('key', 'default')

    # POST data
    request.POST['key']

    # Files
    request.FILES['file']

    # Session
    request.session['key'] = 'value'

    # User
    request.user
    request.user.is_authenticated

URL routing

Basic patterns

# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('articles/', views.article_list, name='article_list'),
    path('articles/<int:id>/', views.article_detail, name='article_detail'),
]

Path converters

Converter Description Example
str Any non-empty string <str:slug>
int Positive integer <int:id>
slug Slug string <slug:slug>
uuid UUID <uuid:uuid>
path Any string with slashes <path:path>

Include other URLconfs

# project/urls.py
from django.urls import path, include

urlpatterns = [
    path('blog/', include('blog.urls')),
    path('admin/', admin.site.urls),
]

URL namespaces

# app/urls.py
app_name = 'blog'

urlpatterns = [
    path('', views.index, name='index'),
    path('<int:id>/', views.detail, name='detail'),
]

# In templates or views
{% url 'blog:index' %}
reverse('blog:detail', args=[1])

Reverse URLs

from django.urls import reverse

# In views
url = reverse('article_detail', args=[1])
url = reverse('article_detail', kwargs={'id': 1})

# In templates
{% url 'article_detail' 1 %}
{% url 'article_detail' id=1 %}

Regular expressions

from django.urls import re_path

urlpatterns = [
    re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
    re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
]

Templates

Variables

{# Simple variable #}
{{ variable }}

{# Object attribute #}
{{ article.title }}

{# Dictionary key #}
{{ dict.key }}

{# List index #}
{{ list.0 }}

{# Method call (no parentheses) #}
{{ article.get_title }}

Filters

{# Lower case #}
{{ text|lower }}

{# Upper case #}
{{ text|upper }}

{# Title case #}
{{ text|title }}

{# Length #}
{{ list|length }}

{# Default value #}
{{ value|default:"N/A" }}

{# Date formatting #}
{{ date|date:"Y-m-d" }}

{# Truncate #}
{{ text|truncatewords:30 }}

{# Safe (disable escaping) #}
{{ html|safe }}

{# Join list #}
{{ list|join:", " }}

{# Chain filters #}
{{ text|lower|truncatewords:10 }}

Tags

{# If statement #}
{% if user.is_authenticated %}
    <p>Welcome, {{ user.username }}!</p>
{% elif user.is_guest %}
    <p>Welcome, guest!</p>
{% else %}
    <p>Please log in.</p>
{% endif %}

{# For loop #}
{% for article in articles %}
    <h2>{{ article.title }}</h2>
{% empty %}
    <p>No articles found.</p>
{% endfor %}

{# With #}
{% with total=articles.count %}
    <p>Total: {{ total }}</p>
{% endwith %}

{# URL #}
<a href="{% url 'article_detail' article.id %}">Read</a>

{# Static files #}
{% load static %}
<img src="{% static 'img/logo.png' %}">

{# CSRF token #}
<form method="post">
    {% csrf_token %}
    ...
</form>

Template inheritance

{# base.html #}
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}My Site{% endblock %}</title>
</head>
<body>
    {% block content %}{% endblock %}
</body>
</html>

{# article.html #}
{% extends "base.html" %}

{% block title %}{{ article.title }}{% endblock %}

{% block content %}
    <h1>{{ article.title }}</h1>
    <p>{{ article.content }}</p>
{% endblock %}

Includes

{# Include template #}
{% include "header.html" %}

{# Include with variables #}
{% include "article_card.html" with article=article %}

{# Include only if exists #}
{% include "optional.html" only %}

Comments

{# Single line comment #}

{% comment %}
Multi-line comment
Can span multiple lines
{% endcomment %}

For loop variables

{% for item in items %}
    {{ forloop.counter }}      {# 1-indexed #}
    {{ forloop.counter0 }}     {# 0-indexed #}
    {{ forloop.revcounter }}   {# Reverse counter #}
    {{ forloop.first }}        {# True on first iteration #}
    {{ forloop.last }}         {# True on last iteration #}
    {{ forloop.parentloop }}   {# Parent loop #}
{% endfor %}

Forms

Form definition

from django import forms

class ContactForm(forms.Form):
    name = forms.CharField(max_length=100)
    email = forms.EmailField()
    message = forms.CharField(widget=forms.Textarea)

    def clean_email(self):
        email = self.cleaned_data['email']
        if not email.endswith('@example.com'):
            raise forms.ValidationError('Must be @example.com')
        return email

ModelForm

from django.forms import ModelForm

class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'content', 'is_published']
        # Or exclude fields
        exclude = ['created_at']

        widgets = {
            'content': forms.Textarea(attrs={'rows': 5}),
        }

        labels = {
            'title': 'Article Title',
        }

Using forms in views

def contact(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            name = form.cleaned_data['name']
            email = form.cleaned_data['email']
            # Process data
            return redirect('success')
    else:
        form = ContactForm()

    return render(request, 'contact.html', {'form': form})

Rendering forms

{# Auto-render entire form #}
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Submit</button>
</form>

{# Manual field rendering #}
<form method="post">
    {% csrf_token %}
    {{ form.name.label_tag }}
    {{ form.name }}
    {{ form.name.errors }}

    <button type="submit">Submit</button>
</form>

{# Loop through fields #}
<form method="post">
    {% csrf_token %}
    {% for field in form %}
        {{ field.label_tag }}
        {{ field }}
        {{ field.errors }}
    {% endfor %}
    <button type="submit">Submit</button>
</form>

Form rendering options

{{ form.as_p }}      {# Wrapped in <p> tags #}
{{ form.as_table }}  {# As table rows #}
{{ form.as_ul }}     {# As <li> items #}

Field types

Field Description
CharField Text input
EmailField Email input
URLField URL input
IntegerField Integer input
BooleanField Checkbox
ChoiceField Select dropdown
DateField Date picker
FileField File upload
ImageField Image upload

Admin interface

Basic registration

# admin.py
from django.contrib import admin
from .models import Article

admin.site.register(Article)

ModelAdmin customization

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_display = ['title', 'author', 'pub_date', 'is_published']
    list_filter = ['is_published', 'pub_date']
    search_fields = ['title', 'content']
    date_hierarchy = 'pub_date'
    ordering = ['-pub_date']

    fieldsets = [
        ('Basic Info', {
            'fields': ['title', 'content']
        }),
        ('Meta', {
            'fields': ['author', 'pub_date', 'is_published'],
            'classes': ['collapse']
        }),
    ]

Inline editing

class CommentInline(admin.TabularInline):
    model = Comment
    extra = 1

class ArticleAdmin(admin.ModelAdmin):
    inlines = [CommentInline]

Custom actions

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    actions = ['make_published']

    def make_published(self, request, queryset):
        queryset.update(is_published=True)
    make_published.short_description = "Mark as published"

List display options

Option Description
list_display Columns to display
list_filter Sidebar filters
search_fields Searchable fields
ordering Default ordering
date_hierarchy Date drill-down
list_per_page Items per page
list_editable Editable fields

Management commands

Database migrations

# Create migrations
python manage.py makemigrations

# Apply migrations
python manage.py migrate

# Show migrations
python manage.py showmigrations

# SQL for migration
python manage.py sqlmigrate app_name 0001

# Fake migration
python manage.py migrate --fake

User management

# Create superuser
python manage.py createsuperuser

# Change password
python manage.py changepassword username

Development server

# Run server
python manage.py runserver

# Custom port
python manage.py runserver 8080

# Custom host and port
python manage.py runserver 0.0.0.0:8000

Shell

# Django shell
python manage.py shell

# Shell with imports
python manage.py shell_plus

Other commands

# Check for problems
python manage.py check

# Collect static files
python manage.py collectstatic

# Create cache table
python manage.py createcachetable

# Clear cache
python manage.py clear_cache

# Load data from fixture
python manage.py loaddata fixture.json

# Dump data to fixture
python manage.py dumpdata app.Model > fixture.json

# Test
python manage.py test

# Show URLs
python manage.py show_urls

Settings

Common settings

# settings.py

# Debug mode
DEBUG = True

# Allowed hosts
ALLOWED_HOSTS = ['localhost', '127.0.0.1']

# Database
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

# Static files
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [BASE_DIR / 'static']

# Media files
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

# Templates
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

PostgreSQL database

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydatabase',
        'USER': 'mydatabaseuser',
        'PASSWORD': 'mypassword',
        'HOST': '127.0.0.1',
        'PORT': '5432',
    }
}

Internationalization

LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True

Authentication

Login/Logout views

# urls.py
from django.contrib.auth import views as auth_views

urlpatterns = [
    path('login/', auth_views.LoginView.as_view(), name='login'),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
]

Login required decorator

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    return render(request, 'template.html')

# With custom redirect
@login_required(login_url='/accounts/login/')
def my_view(request):
    pass

Permission checks

from django.contrib.auth.decorators import permission_required

@permission_required('app.add_article')
def create_article(request):
    pass

# In templates
{% if perms.app.add_article %}
    <a href="{% url 'create_article' %}">Create</a>
{% endif %}

User model

from django.contrib.auth.models import User

# Create user
user = User.objects.create_user(
    username='john',
    email='john@example.com',
    password='password'
)

# Check authentication
if user.is_authenticated:
    print(user.username)

# Check permissions
if user.has_perm('app.add_article'):
    pass

Middleware

Custom middleware

# middleware.py
class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # Code before view
        response = self.get_response(request)
        # Code after view
        return response

Add to settings

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'myapp.middleware.SimpleMiddleware',  # Custom
    # ...
]

Signals

Built-in signals

from django.db.models.signals import (
    pre_save, post_save,
    pre_delete, post_delete
)
from django.dispatch import receiver

@receiver(post_save, sender=Article)
def article_saved(sender, instance, created, **kwargs):
    if created:
        print(f'New article: {instance.title}')

Custom signals

# signals.py
from django.dispatch import Signal

article_published = Signal()

# Send signal
article_published.send(sender=Article, instance=article)

# Receive signal
@receiver(article_published)
def handle_published(sender, instance, **kwargs):
    pass

Testing

Test case

from django.test import TestCase
from .models import Article

class ArticleTestCase(TestCase):
    def setUp(self):
        Article.objects.create(title='Test', content='Content')

    def test_article_title(self):
        article = Article.objects.get(title='Test')
        self.assertEqual(article.title, 'Test')

Test client

from django.test import Client

class ViewTestCase(TestCase):
    def test_index(self):
        client = Client()
        response = client.get('/')
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Welcome')

Run tests

# Run all tests
python manage.py test

# Specific app
python manage.py test myapp

# Specific test
python manage.py test myapp.tests.ArticleTestCase.test_article_title

# Keep database
python manage.py test --keepdb

# Parallel
python manage.py test --parallel

Pagination

In views

from django.core.paginator import Paginator

def article_list(request):
    articles = Article.objects.all()
    paginator = Paginator(articles, 10)  # 10 per page

    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)

    return render(request, 'list.html', {'page_obj': page_obj})

In templates

{% for article in page_obj %}
    <h2>{{ article.title }}</h2>
{% endfor %}

<div class="pagination">
    {% if page_obj.has_previous %}
        <a href="?page=1">First</a>
        <a href="?page={{ page_obj.previous_page_number }}">Previous</a>
    {% endif %}

    <span>Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>

    {% if page_obj.has_next %}
        <a href="?page={{ page_obj.next_page_number }}">Next</a>
        <a href="?page={{ page_obj.paginator.num_pages }}">Last</a>
    {% endif %}
</div>

Caching

Cache configuration

# settings.py
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379',
    }
}

Using cache

from django.core.cache import cache

# Set cache
cache.set('key', 'value', 300)  # 300 seconds

# Get cache
value = cache.get('key')

# Delete cache
cache.delete('key')

# Clear all
cache.clear()

View caching

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)  # 15 minutes
def my_view(request):
    return render(request, 'template.html')

Template fragment caching

{% load cache %}
{% cache 500 sidebar %}
    .. expensive sidebar content ..
{% endcache %}

Gotchas

Always await params in Next.js 16

Django views receive resolved params, but remember Django uses synchronous Python functions by default.

Migrations must be committed

Always commit migration files to version control. They define your database schema evolution.

Use select_related and prefetch_related

Avoid N+1 queries by eager loading relationships:

# Bad: N+1 queries
articles = Article.objects.all()
for article in articles:
    print(article.author.name)  # Query per article

# Good: 2 queries total
articles = Article.objects.select_related('author')
for article in articles:
    print(article.author.name)

CSRF protection required

Always include {% csrf_token %} in POST forms or use @csrf_exempt decorator (not recommended).

Static files in production

Run python manage.py collectstatic before deploying. Configure web server to serve static files.

Also see