Toolmingo
Guides9 min read

Django Cheat Sheet: The Complete Quick Reference

A complete Django cheat sheet — models, views, URLs, templates, forms, Django REST Framework, authentication, admin, ORM queries, and common patterns.

The Django patterns you need every day — models, ORM queries, views, serializers, authentication, and DRF — with copy-ready code in every section.

Quick reference

Task Code
Create project django-admin startproject mysite
Create app python manage.py startapp blog
Run dev server python manage.py runserver
Make migrations python manage.py makemigrations
Apply migrations python manage.py migrate
Create superuser python manage.py createsuperuser
Open Django shell python manage.py shell
Run tests python manage.py test
Collect static files python manage.py collectstatic
Dump data python manage.py dumpdata app.Model
Load data python manage.py loaddata fixture.json
List routes python manage.py show_urls

Project setup

pip install django djangorestframework
django-admin startproject mysite .
python manage.py startapp blog

Add your app to settings.py:

INSTALLED_APPS = [
    # built-in apps...
    "blog",
    "rest_framework",
]

Models

# blog/models.py
from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()

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):
    class Status(models.TextChoices):
        DRAFT = "draft", "Draft"
        PUBLISHED = "published", "Published"

    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    body = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts")
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)
    tags = models.ManyToManyField("Tag", blank=True)
    status = models.CharField(max_length=10, choices=Status, default=Status.DRAFT)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    published_at = models.DateTimeField(null=True, blank=True)

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

    def __str__(self):
        return self.title


class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)

    def __str__(self):
        return self.name

Field types quick reference

Field Use case
CharField(max_length=n) Short text, required
TextField() Long text
SlugField() URL-friendly string
IntegerField() Integer
FloatField() / DecimalField(max_digits, decimal_places) Floats / money
BooleanField() True/False
DateField() / DateTimeField() Date / datetime
auto_now_add=True Set once on create
auto_now=True Update on every save
EmailField() Validated email
URLField() Validated URL
JSONField() JSON (PostgreSQL recommended)
ImageField(upload_to="images/") File path, requires Pillow
ForeignKey(Model, on_delete=...) Many-to-one
ManyToManyField(Model) Many-to-many
OneToOneField(Model, ...) One-to-one

on_delete options: CASCADE, SET_NULL, SET_DEFAULT, PROTECT, DO_NOTHING.

ORM queries

from blog.models import Post

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

# Save after modifying
post.title = "Updated"
post.save()

# Bulk create (no signals, no save())
Post.objects.bulk_create([Post(title="A"), Post(title="B")])

# --- Read ---
Post.objects.all()                          # all rows
Post.objects.filter(status="published")    # WHERE status = 'published'
Post.objects.exclude(status="draft")       # NOT draft
Post.objects.get(slug="hello")             # single row, raises DoesNotExist / MultipleObjectsReturned
Post.objects.first()                       # first per ordering
Post.objects.last()
Post.objects.count()

# Lookups
Post.objects.filter(title__icontains="django")   # LIKE %django% (case-insensitive)
Post.objects.filter(title__startswith="How")
Post.objects.filter(created_at__year=2026)
Post.objects.filter(author__username="alice")    # JOIN via FK
Post.objects.filter(tags__name="python")         # JOIN via M2M

# --- Select related (avoid N+1) ---
Post.objects.select_related("author", "category")   # FK / O2O  → SQL JOIN
Post.objects.prefetch_related("tags")               # M2M / reverse FK → separate query

# --- Ordering & slicing ---
Post.objects.order_by("-created_at")[:10]   # latest 10

# --- Update ---
Post.objects.filter(status="draft").update(status="published")   # bulk update

# --- Delete ---
Post.objects.filter(status="draft").delete()   # bulk delete

# --- Aggregation ---
from django.db.models import Count, Avg, Max, Sum
Post.objects.aggregate(total=Count("id"), avg_len=Avg("body__length"))

# --- Annotation ---
from django.db.models import Count
Category.objects.annotate(post_count=Count("post"))

# --- Q objects (OR / NOT) ---
from django.db.models import Q
Post.objects.filter(Q(status="published") | Q(author=user))
Post.objects.filter(~Q(status="draft"))   # NOT draft

# --- F objects (reference another field) ---
from django.db.models import F
Post.objects.filter(updated_at__gt=F("created_at"))

# --- Values ---
Post.objects.values("id", "title")          # list of dicts
Post.objects.values_list("id", flat=True)   # flat list of ids

Views

Function-based views

# blog/views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse, HttpResponse
from .models import Post

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

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

def create_post(request):
    if request.method == "POST":
        # handle form
        return redirect("post_list")
    return render(request, "blog/create.html")

Class-based views

from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin

class PostListView(ListView):
    model = Post
    template_name = "blog/post_list.html"
    context_object_name = "posts"
    paginate_by = 10

    def get_queryset(self):
        return Post.objects.filter(status="published").select_related("author")

class PostDetailView(DetailView):
    model = Post
    template_name = "blog/post_detail.html"
    slug_field = "slug"

class PostCreateView(LoginRequiredMixin, CreateView):
    model = Post
    fields = ["title", "slug", "body", "category", "status"]
    success_url = reverse_lazy("post_list")

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

class PostUpdateView(LoginRequiredMixin, UpdateView):
    model = Post
    fields = ["title", "body", "status"]
    success_url = reverse_lazy("post_list")

class PostDeleteView(LoginRequiredMixin, DeleteView):
    model = Post
    success_url = reverse_lazy("post_list")

URLs

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

app_name = "blog"

urlpatterns = [
    path("", views.PostListView.as_view(), name="post_list"),
    path("<slug:slug>/", views.PostDetailView.as_view(), name="post_detail"),
    path("create/", views.PostCreateView.as_view(), name="post_create"),
    path("<slug:slug>/edit/", views.PostUpdateView.as_view(), name="post_update"),
    path("<slug:slug>/delete/", views.PostDeleteView.as_view(), name="post_delete"),
]

# mysite/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("blog/", include("blog.urls", namespace="blog")),
    path("api/", include("blog.api_urls")),
]

Use in templates: {% url 'blog:post_list' %} or in Python: reverse("blog:post_list").

Templates

{# blog/templates/blog/post_list.html #}
{% extends "base.html" %}

{% block content %}
  <h1>Posts</h1>

  {% for post in posts %}
    <article>
      <h2><a href="{% url 'blog:post_detail' post.slug %}">{{ post.title }}</a></h2>
      <p>By {{ post.author.get_full_name }} on {{ post.created_at|date:"N j, Y" }}</p>
      <p>{{ post.body|truncatewords:30 }}</p>
    </article>
  {% empty %}
    <p>No posts yet.</p>
  {% endfor %}

  {# Pagination #}
  {% if is_paginated %}
    {% if page_obj.has_previous %}
      <a href="?page={{ page_obj.previous_page_number }}">Previous</a>
    {% endif %}
    Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
    {% if page_obj.has_next %}
      <a href="?page={{ page_obj.next_page_number }}">Next</a>
    {% endif %}
  {% endif %}
{% endblock %}

Common template filters

Filter Example Output
date {{ dt|date:"Y-m-d" }} 2026-07-14
truncatewords {{ text|truncatewords:20 }} First 20 words…
truncatechars {{ text|truncatechars:100 }} First 100 chars…
linebreaks {{ text|linebreaks }} <p> tags
safe {{ html|safe }} Bypasses escaping
default {{ val|default:"N/A" }} Fallback value
length {{ list|length }} Count
lower / upper {{ s|lower }} case conversion
slugify {{ title|slugify }} url-safe-slug
add {{ num|add:5 }} num + 5

Forms

# blog/forms.py
from django import forms
from .models import Post

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ["title", "slug", "body", "category", "status"]
        widgets = {
            "body": forms.Textarea(attrs={"rows": 10}),
        }

    def clean_slug(self):
        slug = self.cleaned_data["slug"]
        if Post.objects.filter(slug=slug).exclude(pk=self.instance.pk).exists():
            raise forms.ValidationError("A post with this slug already exists.")
        return slug


# In a view:
def create_post(request):
    form = PostForm(request.POST or None)
    if form.is_valid():
        post = form.save(commit=False)
        post.author = request.user
        post.save()
        form.save_m2m()   # save ManyToMany after commit=False
        return redirect("blog:post_list")
    return render(request, "blog/create.html", {"form": form})
{# template #}
<form method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <button type="submit">Save</button>
</form>

Django REST Framework

pip install djangorestframework
# blog/serializers.py
from rest_framework import serializers
from .models import Post, Category

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ["id", "name", "slug"]

class PostSerializer(serializers.ModelSerializer):
    author_name = serializers.CharField(source="author.get_full_name", read_only=True)
    category = CategorySerializer(read_only=True)
    category_id = serializers.PrimaryKeyRelatedField(
        queryset=Category.objects.all(), source="category", write_only=True
    )

    class Meta:
        model = Post
        fields = ["id", "title", "slug", "body", "author_name", "category", "category_id", "status", "created_at"]
        read_only_fields = ["id", "created_at"]

    def validate_slug(self, value):
        qs = Post.objects.filter(slug=value)
        if self.instance:
            qs = qs.exclude(pk=self.instance.pk)
        if qs.exists():
            raise serializers.ValidationError("Slug already in use.")
        return value
# blog/api_views.py
from rest_framework import viewsets, permissions, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Post
from .serializers import PostSerializer

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.select_related("author", "category").prefetch_related("tags")
    serializer_class = PostSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    filter_backends = [filters.SearchFilter, filters.OrderingFilter]
    search_fields = ["title", "body"]
    ordering_fields = ["created_at", "title"]

    def get_queryset(self):
        qs = super().get_queryset()
        status = self.request.query_params.get("status")
        if status:
            qs = qs.filter(status=status)
        return qs

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

    @action(detail=False, methods=["get"])
    def published(self, request):
        posts = self.get_queryset().filter(status="published")
        serializer = self.get_serializer(posts, many=True)
        return Response(serializer.data)
# blog/api_urls.py
from rest_framework.routers import DefaultRouter
from .api_views import PostViewSet

router = DefaultRouter()
router.register("posts", PostViewSet, basename="post")

urlpatterns = router.urls
# GET    /api/posts/           → list
# POST   /api/posts/           → create
# GET    /api/posts/{id}/      → retrieve
# PUT    /api/posts/{id}/      → update
# PATCH  /api/posts/{id}/      → partial update
# DELETE /api/posts/{id}/      → destroy
# GET    /api/posts/published/ → custom action

Authentication

# settings.py
AUTH_USER_MODEL = "accounts.User"  # custom user (recommended)

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.SessionAuthentication",
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ],
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticatedOrReadOnly",
    ],
    "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("api/token/", TokenObtainPairView.as_view()),
    path("api/token/refresh/", TokenRefreshView.as_view()),
]
# Decorators for function views
from django.contrib.auth.decorators import login_required, permission_required
from rest_framework.decorators import permission_classes
from rest_framework.permissions import IsAuthenticated

@login_required
def my_view(request): ...

@permission_required("blog.add_post", raise_exception=True)
def create_post(request): ...

Admin

# blog/admin.py
from django.contrib import admin
from .models import Post, Category, Tag

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ["title", "author", "status", "created_at"]
    list_filter = ["status", "category", "created_at"]
    search_fields = ["title", "body"]
    prepopulated_fields = {"slug": ("title",)}
    date_hierarchy = "created_at"
    ordering = ["-created_at"]
    raw_id_fields = ["author"]
    filter_horizontal = ["tags"]
    readonly_fields = ["created_at", "updated_at"]

    fieldsets = [
        ("Content", {"fields": ["title", "slug", "body", "author"]}),
        ("Meta", {"fields": ["category", "tags", "status"]}),
        ("Timestamps", {"fields": ["created_at", "updated_at"], "classes": ["collapse"]}),
    ]

    def get_queryset(self, request):
        return super().get_queryset(request).select_related("author", "category")

admin.site.register(Category)
admin.site.register(Tag)

Signals

# blog/signals.py
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from django.utils.text import slugify
from .models import Post

@receiver(post_save, sender=Post)
def auto_slugify(sender, instance, created, **kwargs):
    if created and not instance.slug:
        instance.slug = slugify(instance.title)
        instance.save(update_fields=["slug"])

# blog/apps.py
class BlogConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "blog"

    def ready(self):
        import blog.signals  # noqa: F401

Common mistakes

Mistake Fix
Post.objects.get() without try/except Use get_object_or_404() or catch DoesNotExist
N+1 queries in views Use select_related() / prefetch_related()
Putting business logic in views Move to model methods or service layer
Not using commit=False before saving M2M Call form.save_m2m() after saving instance
auto_now_add field in update_fields Omit it — Django ignores it anyway
Missing {% csrf_token %} in forms Always include in any POST form
Using request.user without @login_required Check request.user.is_authenticated first

6 FAQ

Q: When should I use function-based views vs class-based views?

Use FBVs for simple, one-off endpoints or when the flow has many branches (complex conditionals). Use CBVs for CRUD operations — they eliminate boilerplate and mixins (LoginRequiredMixin, etc.) compose cleanly.

Q: What's the difference between null=True and blank=True?

null=True adds NULL at the database level (for non-string fields). blank=True allows empty values in Django form validation. For strings, use only blank=True; Django stores empty strings, not NULL. For non-string fields (integers, dates, FKs), use null=True, blank=True together when the field is optional.

Q: How do I avoid N+1 queries?

Use select_related("author") for ForeignKey / OneToOne (generates a single JOIN) and prefetch_related("tags") for ManyToMany / reverse FK (runs a separate optimised query). Check queries in the shell with django-debug-toolbar or connection.queries.

Q: How do I create a custom user model?

Create it before the first migration. Subclass AbstractUser (keeps existing fields) or AbstractBaseUser (full control). Set AUTH_USER_MODEL = "accounts.User" in settings.py. Reference it in code via get_user_model(), never import directly.

Q: What is makemigrations vs migrate?

makemigrations creates Python migration files by comparing your models to the current migration history. migrate actually runs those SQL statements against the database. Always run makemigrations first, commit the generated files, then run migrate in each environment.

Q: How do I handle file uploads?

Add MEDIA_URL = "/media/" and MEDIA_ROOT = BASE_DIR / "media" to settings. Use ImageField or FileField on your model. In urls.py serve with + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) in development. In production, serve media files via Nginx or an object store (S3/Cloudflare R2) — never via Django directly.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools