pyguides

Django Basics: Models, Views, and Templates

Django is a high-level Python web framework that includes everything you need to build web applications. It follows the “batteries included” philosophy, providing authentication, admin interface, database ORM, and form handling out of the box.

This tutorial covers creating a Django project, defining models, writing views, and rendering templates. By the end, you will have a working Django application.

Installing Django

Django installs via pip. Create a virtual environment first.

python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
pip install django

Verify the installation.

django-admin --version

Creating a Django Project

Use the django-admin command to create a new project. This sets up the directory structure and configuration files.

django-admin startproject myproject
cd myproject

Here is what the project structure looks like:

myproject/
├── manage.py
└── myproject/
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    ├── asgi.py
    └── wsgi.py

The outer myproject/ is the project root. The inner myproject/ contains the Python package. manage.py is the command-line utility for running Django commands.

Run the development server.

python manage.py runserver

Visit http://127.0.0.1:8000 in your browser. You should see the Django welcome page.

Creating a Django App

Django projects contain one or more apps. An app is a web application that does something. Create an app for a simple blog.

python manage.py startapp blog

Your structure now includes the blog/ directory:

blog/
├── __init__.py
├── admin.py
├── apps.py
├── migrations/
├── models.py
├── tests.py
└── views.py

Open myproject/settings.py and add blog to the INSTALLED_APPS list.

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "blog",
]

Defining Models

Models define your database schema. Each model is a Python class that inherits from django.db.models.Model.

Open blog/models.py and create a simple Post model.

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title

Each field in the model maps to a database column. CharField is for short text with a maximum length and maps to a VARCHAR column in SQL, TextField is for long text with no length limit, and DateTimeField stores timestamps with timezone support built in. The auto_now_add parameter sets the field once when the object is first created, while auto_now updates it every time the object is saved — together they give you both creation and modification timestamps without writing any save-override logic. Django also provides field types for integers, floats, booleans, file uploads, emails, URLs, and relationships like foreign keys and many-to-many associations, each mapping to an appropriate database column type in the underlying SQL schema.

Create the database tables.

python manage.py makemigrations
python manage.py migrate

The first command creates migration files describing the schema changes. The second command applies those changes to the database. SQLite is the default database, so no additional setup is required. Django’s migration system tracks which migrations have been applied in a special database table, so you can run migrate repeatedly and it only applies new ones. This makes schema evolution safe to automate in deployment pipelines — you run migrate as part of your deploy step and Django figures out what schema changes are needed. If you later switch to PostgreSQL or MySQL, you only need to change the DATABASES setting in settings.py and the same migration files will produce equivalent SQL for the new backend.

Creating the Admin Interface

Django provides an automatic admin interface for managing your models. Register your model in blog/admin.py.

from django.contrib import admin
from .models import Post

admin.site.register(Post)

Create a superuser to log in.

python manage.py createsuperuser

Follow the prompts to set a username, email, and password.

Run the server and visit http://127.0.0.1:8000/admin. Log in with your superuser credentials. You can now create, edit, and delete posts through the admin interface.

Writing Views

Views handle the logic for your web pages. Each view is a Python function (or class-based view) that receives a web request and returns a response.

Open blog/views.py and create views for listing and showing posts.

from django.shortcuts import render, get_object_or_404
from .models import Post

def post_list(request):
    posts = Post.objects.all().order_by("-created_at")
    return render(request, "blog/post_list.html", {"posts": posts})

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

The render function combines a template with context data to produce an HTML response. get_object_or_404 retrieves an object or returns a 404 error if not found. The queryset method order_by with a minus sign prefix reverses the sort direction so newest posts appear at the top of the list. Django querysets are lazy — the database query does not execute until the results are actually used, which in this case happens when the template iterates over the posts variable. You can chain additional filter and exclude calls to narrow results further before the query runs.

Creating Templates

Templates define the HTML structure of your pages. Django looks for templates in a templates directory within each app.

Create the template directories and files.

mkdir -p blog/templates/blog
touch blog/templates/blog/post_list.html
touch blog/templates/blog/post_detail.html

Create blog/templates/blog/post_list.html:

<!DOCTYPE html>
<html>
<head>
    <title>Blog</title>
</head>
<body>
    <h1>My Blog</h1>
    {% for post in posts %}
        <article>
            <h2><a href="{% url post_detail post.id %}">{{ post.title }}</a></h2>
            <p>{{ post.created_at|date:"F j, Y" }}</p>
            <p>{{ post.content|truncatewords:30 }}</p>
        </article>
    {% empty %}
        <p>No posts yet.</p>
    {% endfor %}
</body>
</html>

Django templates use template tags like {% for %} and template filters like |truncatewords:30. The {% url %} tag generates URLs by name, which means you can reorganize your URL structure later without updating every link in every template — as long as the URL name stays the same, Django resolves the correct path at render time. The truncatewords filter cuts content to a specific word count and appends an ellipsis, and the date filter formats datetime objects using a human-readable format string. Django ships with dozens of built-in template tags and filters, and you can write custom ones when the built-in set does not cover your needs.

Create blog/templates/blog/post_detail.html:

<!DOCTYPE html>
<html>
<head>
    <title>{{ post.title }}</title>
</head>
<body>
    <h1>{{ post.title }}</h1>
    <p>Published {{ post.created_at|date:"F j, Y" }}</p>
    <div>
        {{ post.content|linebreaks }}
    </div>
    <p><a href="{% url post_list %}">Back to list</a></p>
</body>
</html>

Configuring URLs

URL configuration maps URLs to views. Create blog/urls.py:

from django.urls import path
from . import views

urlpatterns = [
    path("", views.post_list, name="post_list"),
    path("post/<int:post_id>/", views.post_detail, name="post_detail"),
]

Include these URLs in the project-level myproject/urls.py:

from django.contrib import admin
from django.urls import path, include

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

The include function delegates URL matching to the app-level configuration. This pattern keeps URL configuration close to the views it references, so a blog app carries its own URL definitions rather than crowding the project-level urls.py with every route in the application. The empty string path at the blog level catches requests to the site root and routes them to the post list view, while the named capture group <int:post_id> in the detail pattern extracts a numeric ID from the URL and passes it as a keyword argument to the view function. Django’s URL dispatcher resolves patterns in order, so more specific patterns should appear before catch-all patterns to avoid swallowing requests intended for other views.

Testing Your Application

Run the development server.

python manage.py runserver

Visit http://127.0.0.1:8000. You should see the post list. Since you have no posts yet, log in at /admin and create some posts through the admin interface. Then return to the list page to see them displayed.

Click on a post title to view the detail page.

Conclusion

You created a Django project, defined a model with fields and timestamps, registered it with the admin interface, wrote views to handle requests, and created templates to render HTML. These components form the foundation of every Django application.

The next tutorial in this series covers authentication with FastAPI. For now, explore the Django documentation to learn about forms, class-based views, and the testing framework.

See Also