All topics
Backend · Learning hub

Django notes for developers

Master Django with a curated set of 3 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Backend notes
Django

Models, Views & URLs

Models, Views & URLs Project Setup pip install django djangorestframework django-admin startproject myproject . python manage.py startapp users # Run dev server

Models, Views & URLs

Project Setup

pip install django djangorestframework
django-admin startproject myproject .
python manage.py startapp users

# Run dev server
python manage.py runserver

# Migrations
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser

# Shell
python manage.py shell

Models

# users/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    bio = models.TextField(blank=True)
    avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

class Category(models.Model):
    name = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(unique=True)

    class Meta:
        verbose_name_plural = 'categories'
        ordering = ['name']

    def __str__(self):
        return self.name

class Post(models.Model):
    STATUS_DRAFT = 'draft'
    STATUS_PUBLISHED = 'published'
    STATUS_CHOICES = [
        (STATUS_DRAFT, 'Draft'),
        (STATUS_PUBLISHED, 'Published'),
    ]

    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
    tags = models.ManyToManyField('Tag', blank=True)
    body = models.TextField()
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_DRAFT)
    view_count = models.PositiveIntegerField(default=0)
    published_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']
        indexes = [models.Index(fields=['status', '-created_at'])]

    def __str__(self):
        return self.title

# Field types reference
# CharField, TextField, IntegerField, FloatField, DecimalField
# BooleanField, DateField, DateTimeField, EmailField, URLField
# ForeignKey, OneToOneField, ManyToManyField
# ImageField, FileField, JSONField
# on_delete: CASCADE, SET_NULL, SET_DEFAULT, PROTECT, DO_NOTHING

ORM Queries

from users.models import Post, User

# Create
post = Post.objects.create(title='Hello', author=user, slug='hello')
post = Post(title='Hello', author=user)
post.save()

# Read
Post.objects.all()
Post.objects.filter(status='published')
Post.objects.filter(author__username='alice')     # traverse FK
Post.objects.filter(title__icontains='hello')     # field lookup
Post.objects.filter(created_at__gte=datetime(2024, 1, 1))
Post.objects.exclude(status='draft')
Post.objects.get(id=1)                            # raises DoesNotExist if not found
Post.objects.get_or_create(slug='hello', defaults={'title': 'Hello'})

# Chaining
Post.objects.filter(status='published').order_by('-created_at')[:10]
Post.objects.select_related('author', 'category')  # JOIN (ForeignKey)
Post.objects.prefetch_related('tags')              # separate query (M2M)

# Aggregation
from django.db.models import Count, Avg, Sum, Max, Q

Post.objects.filter(status='published').count()
Post.objects.aggregate(avg_views=Avg('view_count'))

Post.objects.values('author__username').annotate(post_count=Count('id'))

# Q objects — complex queries
Post.objects.filter(Q(status='published') | Q(author=user))
Post.objects.filter(Q(status='published') & ~Q(category=None))

# Update
Post.objects.filter(author=user).update(status='published')
# post.save() — always triggers signals; .update() does not

# Delete
Post.objects.filter(status='draft', created_at__lt=cutoff).delete()

Views & URLs

# views.py (function-based)
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required

def post_list(request):
    posts = Post.objects.filter(status='published').select_related('author')
    return render(request, 'posts/list.html', {'posts': posts})

def post_detail(request, slug):
    post = get_object_or_404(Post, slug=slug, status='published')
    return render(request, 'posts/detail.html', {'post': post})

@login_required
def create_post(request):
    if request.method == 'POST':
        form = PostForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.save()
            return redirect('post_detail', slug=post.slug)
    else:
        form = PostForm()
    return render(request, 'posts/form.html', {'form': form})

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

urlpatterns = [
    path('', views.post_list, name='post_list'),
    path('<slug:slug>/', views.post_detail, name='post_detail'),
    path('create/', views.create_post, name='create_post'),
]

# myproject/urls.py
urlpatterns = [
    path('admin/', admin.site.urls),
    path('posts/', include('posts.urls')),
    path('api/', include('api.urls')),
]
Django

Django REST Framework

Django REST Framework Serializers from rest_framework import serializers from .models import Post, User class UserSerializer(serializers.ModelSerializer): class

Django REST Framework

Serializers

from rest_framework import serializers
from .models import Post, User

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'email', 'bio']
        read_only_fields = ['id']

class PostSerializer(serializers.ModelSerializer):
    author = UserSerializer(read_only=True)
    author_id = serializers.PrimaryKeyRelatedField(
        queryset=User.objects.all(), source='author', write_only=True
    )
    tags = serializers.SlugRelatedField(many=True, slug_field='name', queryset=Tag.objects.all())

    class Meta:
        model = Post
        fields = ['id', 'title', 'slug', 'body', 'status', 'author', 'author_id', 'tags', 'created_at']
        read_only_fields = ['id', 'slug', 'created_at']

    def validate_title(self, value):
        if len(value) < 3:
            raise serializers.ValidationError("Title must be at least 3 characters")
        return value

    def create(self, validated_data):
        tags = validated_data.pop('tags', [])
        post = Post.objects.create(**validated_data)
        post.tags.set(tags)
        return post

ViewSets & Routers

from rest_framework import viewsets, permissions, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.select_related('author').prefetch_related('tags')
    serializer_class = PostSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ['status', 'author']
    search_fields = ['title', 'body']
    ordering_fields = ['created_at', 'view_count']
    ordering = ['-created_at']

    def get_queryset(self):
        if self.action == 'list':
            return self.queryset.filter(status='published')
        return self.queryset

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

    @action(detail=True, methods=['post'])
    def publish(self, request, pk=None):
        post = self.get_object()
        post.status = 'published'
        post.save()
        return Response({'status': 'published'})

# urls.py
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('posts', PostViewSet, basename='post')
urlpatterns = [path('', include(router.urls))]
# Auto-generates: GET /posts/, POST /posts/, GET /posts/{id}/, PUT, PATCH, DELETE
# Custom: POST /posts/{id}/publish/

Authentication & Permissions

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 20,
}

# JWT with SimpleJWT
pip install djangorestframework-simplejwt

# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
    path('auth/login/', TokenObtainPairView.as_view()),
    path('auth/refresh/', TokenRefreshView.as_view()),
]

# Custom permission
from rest_framework.permissions import BasePermission

class IsOwnerOrReadOnly(BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in ['GET', 'HEAD', 'OPTIONS']:
            return True
        return obj.author == request.user
Django

Interview Questions

Django Interview Questions Q: What is Django's MTV architecture? Model-Template-View. Model defines data structure (ORM). Template is the HTML layer (Django tem

Django Interview Questions

Q: What is Django's MTV architecture?

Model-Template-View. Model defines data structure (ORM). Template is the HTML layer (Django template language or Jinja2). View is the business logic — processes requests and returns responses (equivalent to Controller in MVC). Django's "View" is closer to a controller; the "Template" is the view in MVC terms.

Q: What is the N+1 query problem and how does Django address it?

If you have 100 posts and access post.author for each, Django issues 101 queries. Fix with select_related() for ForeignKey/OneToOne (performs a SQL JOIN) and prefetch_related() for ManyToMany and reverse ForeignKey (issues separate optimized query). Use django-debug-toolbar to detect N+1 issues.

Q: What is the difference between FBV and CBV?

Function-Based Views (FBV) are simple Python functions — easy to understand, explicit. Class-Based Views (CBV) provide inheritance and mixins for code reuse — ListView, DetailView, CreateView, etc. handle common patterns. CBVs reduce boilerplate for CRUD but are harder to understand initially. DRF ViewSets extend CBV further for APIs.

Q: How do Django migrations work?

makemigrations compares the current models to previous migrations and generates a new migration file. migrate applies pending migration files to the database. Django tracks applied migrations in the django_migrations table. Migrations support forward and backward (rollback) operations. Always commit migration files to version control.

Q: What is Django's middleware?

Middleware is a layer of processing applied to every request/response. It's a hook system for adding behaviors globally: authentication, sessions, CSRF protection, security headers, GZIP compression. Middleware is configured in settings.MIDDLEWARE as an ordered list — request processing is top-to-bottom, response is bottom-to-top.

Q: What is CSRF and how does Django protect against it?

Cross-Site Request Forgery tricks users into making unintended requests using their authenticated session. Django's CsrfViewMiddleware generates a unique token per session, requires it in all POST/PUT/DELETE requests, and rejects requests without a valid token. For APIs using token auth (JWT), CSRF is less of a concern since tokens aren't sent automatically by browsers.

Keep your Django knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever