Neural Architecture Hub
Python

Django

Django projects, models, views, templates, forms, authentication, testing, and deployment reference.

Django Developer Guide

Django is a batteries-included Python web framework for database-backed websites and applications. It provides URL routing, views, templates, forms, an ORM, migrations, authentication, an administration site, security protections, and testing tools.

Getting Started

Installation

Create an isolated environment and install Django:

python -m venv .venv

# macOS/Linux
source .venv/bin/activate

# PowerShell
.\.venv\Scripts\Activate.ps1

python -m pip install Django
python -m django --version

Create a Project and App

A project contains site-wide configuration. An app is a focused feature package such as blog, accounts, or payments.

django-admin startproject config .
python manage.py startapp blog
python manage.py migrate
python manage.py runserver

Typical structure:

manage.py
config/
  __init__.py
  settings.py
  urls.py
  asgi.py
  wsgi.py
blog/
  admin.py
  apps.py
  migrations/
  models.py
  tests.py
  views.py

Register the App

Add the application to INSTALLED_APPS:

# config/settings.py
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "blog",
]

Requests and Responses

First View

# blog/views.py
from django.http import HttpResponse


def home(request):
    return HttpResponse("Hello, Django")

Map a URL to the view:

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

from . import views

app_name = "blog"

urlpatterns = [
    path("", views.home, name="home"),
]

Include application URLs in the project:

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

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("blog.urls")),
]
python manage.py runserver

Open http://127.0.0.1:8000/ in a browser.

Route Parameters

# blog/urls.py
urlpatterns = [
    path("posts/<int:post_id>/", views.post_detail, name="post-detail"),
    path("authors/<slug:username>/", views.author_detail, name="author-detail"),
]
# blog/views.py
from django.http import JsonResponse


def post_detail(request, post_id):
    return JsonResponse({"post_id": post_id})

Common path converters include str, int, slug, uuid, and path.

Models and Databases

Define Models

Models describe stored data and relationships:

# blog/models.py
from django.conf import settings
from django.db import models
from django.urls import reverse


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

    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(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="posts",
    )
    category = models.ForeignKey(
        Category,
        on_delete=models.PROTECT,
        related_name="posts",
    )
    status = models.CharField(
        max_length=10,
        choices=Status.choices,
        default=Status.DRAFT,
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("blog:post-detail", kwargs={"slug": self.slug})

Migrations

Create and apply migration files whenever model structure changes:

python manage.py makemigrations
python manage.py migrate
python manage.py showmigrations
python manage.py sqlmigrate blog 0001

Commit migrations with the code change that requires them. Do not manually edit a production database schema instead of applying migrations.

QuerySets

Use the Django ORM to create, retrieve, update, and delete rows:

python manage.py shell
from blog.models import Category, Post

category = Category.objects.create(name="Python", slug="python")

Post.objects.all()
Post.objects.filter(status=Post.Status.PUBLISHED)
Post.objects.filter(title__icontains="django")
Post.objects.get(slug="getting-started")
Post.objects.order_by("-created_at")[:5]
Post.objects.filter(category__slug="python").count()

Post.objects.filter(status=Post.Status.DRAFT).update(status=Post.Status.PUBLISHED)
Post.objects.filter(slug="old-post").delete()

Efficient Relationships

Reduce repeated database queries when loading related objects:

posts = Post.objects.select_related("author", "category")
categories = Category.objects.prefetch_related("posts")

Use select_related() for foreign-key or one-to-one relationships and prefetch_related() for many-to-many or reverse relationships.

Templates and Rendering

Render HTML

# blog/views.py
from django.shortcuts import get_object_or_404, render

from .models import Post


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


def post_detail(request, slug):
    post = get_object_or_404(Post, slug=slug, status=Post.Status.PUBLISHED)
    return render(request, "blog/post_detail.html", {"post": post})
# blog/urls.py
urlpatterns = [
    path("posts/", views.post_list, name="post-list"),
    path("posts/<slug:slug>/", views.post_detail, name="post-detail"),
]

Place templates in an app-scoped directory:

blog/
  templates/
    blog/
      base.html
      post_list.html
      post_detail.html

Base Template

<!-- blog/templates/blog/base.html -->
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>{% block title %}Blog{% endblock %}</title>
  </head>
  <body>
    <nav><a href="{% url 'blog:post-list' %}">Posts</a></nav>
    <main>{% block content %}{% endblock %}</main>
  </body>
</html>
<!-- blog/templates/blog/post_list.html -->
{% extends "blog/base.html" %}

{% block title %}Posts{% endblock %}

{% block content %}
  <h1>Posts</h1>
  {% for post in posts %}
    <article>
      <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
    </article>
  {% empty %}
    <p>No posts yet.</p>
  {% endfor %}
{% endblock %}

Templates escape variable output by default. Avoid marking user-supplied content as safe unless it has been sanitized deliberately.

Forms and Validation

Model Form

# 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"]

    def clean_title(self):
        title = self.cleaned_data["title"].strip()
        if len(title) < 5:
            raise forms.ValidationError("Title must contain at least 5 characters.")
        return title

Create View

# blog/views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect, render

from .forms import PostForm


@login_required
def post_create(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)
    else:
        form = PostForm()

    return render(request, "blog/post_form.html", {"form": form})
<!-- blog/templates/blog/post_form.html -->
{% extends "blog/base.html" %}

{% block content %}
  <h1>New post</h1>
  <form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Save</button>
  </form>
{% endblock %}

For internal HTML forms that submit POST, include {% csrf_token %} and keep Django's CSRF middleware enabled.

Admin Site

Create an Administrator

python manage.py createsuperuser
python manage.py runserver

Visit http://127.0.0.1:8000/admin/.

Register and Customize Models

# blog/admin.py
from django.contrib import admin

from .models import Category, Post


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


@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("name",)}

The admin is excellent for trusted internal management. Build dedicated public views and authorization rules for end users.

Class-Based Views

Generic views reduce repeated list, detail, create, update, and delete logic:

# blog/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from django.views.generic import CreateView, DetailView, ListView, UpdateView

from .models import Post


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

    def get_queryset(self):
        return Post.objects.filter(status=Post.Status.PUBLISHED)


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


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

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)
# blog/urls.py
from .views import PostCreateView, PostDetailView, PostListView

urlpatterns = [
    path("posts/", PostListView.as_view(), name="post-list"),
    path("posts/new/", PostCreateView.as_view(), name="post-create"),
    path("posts/<slug:slug>/", PostDetailView.as_view(), name="post-detail"),
]

Use function views when they make behavior clearer; use generic views when their lifecycle fits the feature naturally.

Authentication and Permissions

Built-In Authentication URLs

# config/urls.py
urlpatterns = [
    path("accounts/", include("django.contrib.auth.urls")),
    path("", include("blog.urls")),
    path("admin/", admin.site.urls),
]

Provide templates such as registration/login.html, then use authentication helpers:

from django.contrib.auth.decorators import login_required, permission_required


@login_required
def profile(request):
    ...


@permission_required("blog.change_post", raise_exception=True)
def moderate_post(request, post_id):
    ...
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin


class PostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
    permission_required = "blog.change_post"

Custom User Models

For a new project that may need additional user fields, define a custom user model before the first migration:

# accounts/models.py
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    pass
# config/settings.py
AUTH_USER_MODEL = "accounts.User"

Changing the user model after tables and relationships already exist is considerably more involved.

Static and Uploaded Files

Static Assets

# config/settings.py
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
{% load static %}
<link rel="stylesheet" href="{% static 'blog/site.css' %}">
python manage.py collectstatic

In production, serve collected static assets through an appropriate static-file strategy rather than relying on Django's development server.

Media Uploads

# config/settings.py
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"
class Post(models.Model):
    image = models.ImageField(upload_to="posts/", blank=True)
python -m pip install Pillow             # Required for ImageField processing
<form method="post" enctype="multipart/form-data">
  {% csrf_token %}
  {{ form.as_p }}
  <button type="submit">Save</button>
</form>
form = PostForm(request.POST, request.FILES)

Validate uploaded file types and sizes, and use a suitable storage backend in production.

Settings and Environment Variables

Keep secrets outside source control:

# config/settings.py
import os

SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = os.environ.get("DJANGO_DEBUG", "false").lower() == "true"
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",")

Database example:

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.environ["POSTGRES_DB"],
        "USER": os.environ["POSTGRES_USER"],
        "PASSWORD": os.environ["POSTGRES_PASSWORD"],
        "HOST": os.environ.get("POSTGRES_HOST", "localhost"),
        "PORT": os.environ.get("POSTGRES_PORT", "5432"),
    }
}

SQLite is convenient locally. Production applications commonly use a database server such as PostgreSQL.

Testing

Model and View Tests

# blog/tests.py
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse

from .models import Category, Post


class PostTests(TestCase):
    def setUp(self):
        user = get_user_model().objects.create_user(
            username="author",
            password="test-password",
        )
        category = Category.objects.create(name="Python", slug="python")
        self.post = Post.objects.create(
            title="Django basics",
            slug="django-basics",
            body="A post",
            author=user,
            category=category,
            status=Post.Status.PUBLISHED,
        )

    def test_post_string(self):
        self.assertEqual(str(self.post), "Django basics")

    def test_published_post_is_listed(self):
        response = self.client.get(reverse("blog:post-list"))

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "Django basics")
python manage.py test
python manage.py test blog

Useful Test Client Requests

response = self.client.get("/posts/")
response = self.client.post("/posts/new/", {"title": "New post"})
self.client.login(username="author", password="test-password")
self.client.force_login(user)

Management Commands

Common built-in commands:

python manage.py check                 # Validate project configuration
python manage.py shell                 # Interactive shell with Django configured
python manage.py dbshell               # Open the database command-line client
python manage.py dumpdata blog         # Export app data as JSON
python manage.py loaddata seed.json    # Load fixture data
python manage.py changepassword admin
python manage.py clearsessions

Create a custom command for repeatable operational tasks:

blog/
  management/
    __init__.py
    commands/
      __init__.py
      publish_scheduled.py
# blog/management/commands/publish_scheduled.py
from django.core.management.base import BaseCommand


class Command(BaseCommand):
    help = "Publish scheduled posts"

    def handle(self, *args, **options):
        self.stdout.write(self.style.SUCCESS("Scheduled posts published"))
python manage.py publish_scheduled

Performance and Advanced ORM

Aggregation and Transactions

from django.db import transaction
from django.db.models import Count

popular_categories = Category.objects.annotate(post_count=Count("posts"))


@transaction.atomic
def publish_post(post):
    post.status = Post.Status.PUBLISHED
    post.save(update_fields=["status"])

Pagination

from django.core.paginator import Paginator


def post_list(request):
    posts = Post.objects.filter(status=Post.Status.PUBLISHED)
    page = Paginator(posts, 20).get_page(request.GET.get("page"))
    return render(request, "blog/post_list.html", {"page": page})

Optimize after inspecting query behavior: reduce unnecessary columns, avoid repeated related-object queries, add indexes for real lookup patterns, and paginate large result sets.

Security and Deployment

Deployment Checklist

Do not deploy with development defaults:

DEBUG = False
ALLOWED_HOSTS = ["www.example.com"]
CSRF_TRUSTED_ORIGINS = ["https://www.example.com"]
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

Before a release:

python manage.py check --deploy
python manage.py migrate
python manage.py collectstatic --noinput
python manage.py test

Use an ASGI or WSGI production server, configure HTTPS at the application or reverse-proxy layer, keep SECRET_KEY private, and arrange logging, backups, and error monitoring.

ASGI and WSGI Entry Points

The generated project includes:

config/asgi.py   # ASGI application for asynchronous-capable servers
config/wsgi.py   # WSGI application for traditional servers

Run through an installed production server according to the chosen hosting stack; python manage.py runserver is only for development.

Common Mistakes

ProblemBetter approach
Editing models without migrationsRun makemigrations and commit migration files
Hardcoding secrets in settings.pyLoad secrets from environment or secret storage
Using DEBUG=True in productionSet production security settings and run check --deploy
Submitting forms without CSRF tokensInclude {% csrf_token %} for internal POST forms
Querying related data in a loopUse select_related() or prefetch_related()
Trusting admin access for public permissionsAdd explicit authentication and authorization checks

Quick Reference

python -m pip install Django             # Install
django-admin startproject config .       # Create project
python manage.py startapp blog           # Create app
python manage.py makemigrations          # Generate schema migration
python manage.py migrate                 # Apply migrations
python manage.py createsuperuser         # Create admin login
python manage.py runserver               # Development server
python manage.py shell                   # Django shell
python manage.py test                    # Test suite
python manage.py collectstatic           # Gather static files for deployment
python manage.py check --deploy          # Production setting checks
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import path, reverse

from .models import Post

Post.objects.create(...)
Post.objects.filter(status="published")
Post.objects.get(pk=1)
Post.objects.select_related("author")
Post.objects.prefetch_related("tags")

Further Reading

On this page