Django is a "batteries-included" web framework for Python. Out of the box it gives you URL routing, an ORM for working with databases, templates, forms, authentication and an auto-generated admin site. It follows a Model–View–Template pattern: models describe your data, views handle requests, and templates render HTML.
Use a virtual environment so each project has its own packages:
mkdir my-site
cd my-site
python -m venv .venv
# macOS / Linux
source .venv/bin/activate
# Windows
.venv\Scripts\activate
pip install django
python -m django --version
A project holds settings and top-level URLs. An app is a self-contained feature (a blog, a shop, user accounts). One project usually contains several apps.
django-admin startproject mysite .
python manage.py startapp blog
The . puts the project in the current folder. You now have:
my-site/
├── manage.py # command-line helper
├── mysite/
│ ├── settings.py # configuration
│ ├── urls.py # root URL routes
│ ├── asgi.py
│ └── wsgi.py
└── blog/
├── admin.py
├── apps.py
├── models.py
├── views.py
├── tests.py
└── migrations/
Register the app in mysite/settings.py:
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"blog", # add this
]
Start the development server and open http://127.0.0.1:8000:
python manage.py runserver
A view is a function that takes a request and returns a response. In blog/views.py:
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world!")
def post_detail(request, post_id):
return HttpResponse(f"You are looking at post {post_id}")
Create blog/urls.py to map paths to views:
from django.urls import path
from . import views
app_name = "blog"
urlpatterns = [
path("", views.index, name="index"),
path("<int:post_id>/", views.post_detail, name="post_detail"),
]
Include the app's URLs in the project's mysite/urls.py:
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("blog/", include("blog.urls")),
]
Now /blog/ shows "Hello, world!" and /blog/5/ shows post 5.
| Converter | Matches |
|---|---|
<int:id> | Zero or a positive integer |
<str:name> | Any text without / (the default) |
<slug:slug> | Letters, numbers, hyphens, underscores |
<uuid:id> | A UUID |
<path:p> | Any text, including / |
Templates keep HTML out of your Python code. Django looks inside each app's templates/ folder. Create blog/templates/blog/index.html:
<h1>{{ title }}</h1>
{% if posts %}
<ul>
{% for post in posts %}
<li>
<a href="{% url 'blog:post_detail' post.id %}">{{ post.title }}</a>
<small>{{ post.created|date:"j M Y" }}</small>
</li>
{% endfor %}
</ul>
{% else %}
<p>No posts yet.</p>
{% endif %}
Render it from a view with render():
from django.shortcuts import render
def index(request):
context = {"title": "My Blog", "posts": []}
return render(request, "blog/index.html", context)
| Syntax | Purpose |
|---|---|
{{ variable }} | Output a value (HTML-escaped automatically) |
{% tag %} | Logic: if, for, url, block… |
{{ value|filter }} | Transform a value, e.g. |upper, |date |
{# comment #} | Comment, not rendered |
A base layout, blog/templates/blog/base.html:
<!doctype html>
<html>
<head><title>{% block title %}My Blog{% endblock %}</title></head>
<body>
<main>{% block content %}{% endblock %}</main>
</body>
</html>
A page that extends it:
{% extends "blog/base.html" %}
{% block title %}Home{% endblock %}
{% block content %}
<h1>Welcome</h1>
{% endblock %}
A model is a Python class that maps to a database table. Django uses SQLite by default, so no setup is needed to start. In blog/models.py:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
published = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
author = models.CharField(max_length=100)
text = models.TextField()
Create and apply migrations, which turn your models into database tables:
python manage.py makemigrations
python manage.py migrate
Run these two commands every time you change a model.
Try queries interactively with python manage.py shell:
from blog.models import Post
# Create
post = Post.objects.create(title="Hello Django", body="My first post")
# Read
Post.objects.all()
Post.objects.get(id=1)
Post.objects.filter(published=True)
Post.objects.filter(title__icontains="django")
Post.objects.exclude(published=True).order_by("-created")[:5]
# Update
post.title = "Hello again"
post.save()
Post.objects.filter(published=False).update(published=True)
# Delete
post.delete()
# Related objects
post.comments.all()
post.comments.create(author="Sam", text="Nice post!")
Use it in a view, with get_object_or_404 to return a 404 page when nothing matches:
from django.shortcuts import get_object_or_404, render
from .models import Post
def index(request):
posts = Post.objects.filter(published=True).order_by("-created")
return render(request, "blog/index.html", {"title": "My Blog", "posts": posts})
def post_detail(request, post_id):
post = get_object_or_404(Post, id=post_id, published=True)
return render(request, "blog/detail.html", {"post": post})
Django generates a full admin interface for your models. Create a login first:
python manage.py createsuperuser
Register your models in blog/admin.py:
from django.contrib import admin
from .models import Post, Comment
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ["title", "published", "created"]
list_filter = ["published"]
search_fields = ["title", "body"]
admin.site.register(Comment)
Visit http://127.0.0.1:8000/admin/ to add, edit and delete posts.
A ModelForm builds a form, with validation, straight from a model. Create blog/forms.py:
from django import forms
from .models import Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ["author", "text"]
Handle it in a view:
from django.shortcuts import get_object_or_404, redirect, render
from .forms import CommentForm
from .models import Post
def add_comment(request, post_id):
post = get_object_or_404(Post, id=post_id)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
return redirect("blog:post_detail", post_id=post.id)
else:
form = CommentForm()
return render(request, "blog/add_comment.html", {"form": form, "post": post})
And in the template. {% csrf_token %} is required on every POST form:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Post comment</button>
</form>
For common patterns like "list objects" or "show one object", Django provides generic views that cut out boilerplate:
from django.views.generic import DetailView, ListView
from .models import Post
class PostListView(ListView):
model = Post
template_name = "blog/index.html"
context_object_name = "posts"
queryset = Post.objects.filter(published=True).order_by("-created")
class PostDetailView(DetailView):
model = Post
template_name = "blog/detail.html"
# blog/urls.py
urlpatterns = [
path("", views.PostListView.as_view(), name="index"),
path("<int:pk>/", views.PostDetailView.as_view(), name="post_detail"),
]
DetailView looks up objects by pk by default, so the URL parameter is named pk here.
For a simple API endpoint, use JsonResponse:
from django.http import JsonResponse
from .models import Post
def post_api(request):
posts = Post.objects.filter(published=True).values("id", "title", "created")
return JsonResponse({"posts": list(posts)})
For anything larger, use Django REST Framework, which adds serializers, authentication and browsable APIs.
tests.py and run them with python manage.py test.django.contrib.auth and the @login_required decorator.static/ folder with {% load static %}.DEBUG = False, configure ALLOWED_HOSTS and keep SECRET_KEY out of your code.| Command | What it does |
|---|---|
python manage.py runserver | Start the development server |
python manage.py startapp name | Create a new app |
python manage.py makemigrations | Generate migrations from model changes |
python manage.py migrate | Apply migrations to the database |
python manage.py createsuperuser | Create an admin login |
python manage.py shell | Python shell with your project loaded |
python manage.py test | Run tests |