Skip to the resource

Day 5 of 7 · 90–120 minutes

Save real posts with a model and admin

Create a Post model, run migrations, add an admin account, and replace the temporary list with database records.

Today you finish withPosts saved in SQLite and managed through Django admin.

Today’s goal

Move the posts out of views.py and save them in Django’s SQLite database. You will create and edit posts through Django admin, then display them on the homepage.

The change is:

Before: views.py → temporary list → template
After:  SQLite → Post objects → view → template

Step 1 — Create the Post model

Replace blog/models.py with:

from django.db import models


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

    class Meta:
        ordering = ("-created_at",)

    def __str__(self):
        return self.title

Each Post object becomes one database row:

FieldWhat it stores
titleShort text, up to 200 characters
bodyThe full article text
created_atThe date and time Django creates the post

Django also creates an integer id automatically.

Step 2 — Create and apply the migration

The model is Python code. A migration turns that code into a database change.

python manage.py makemigrations
python manage.py migrate

Remember the order:

  1. Change models.py.
  2. Run makemigrations to create a recipe.
  3. Run migrate to apply that recipe.

Step 3 — Register Post in admin

Replace blog/admin.py with:

from django.contrib import admin

from .models import Post


@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ("title", "created_at")
    search_fields = ("title", "body")

Step 4 — Create an admin account

python manage.py createsuperuser

Choose a username and password. The terminal does not display password characters while you type; that is normal.

Run the server:

python manage.py runserver

Open http://127.0.0.1:8000/admin/, sign in, open Posts, and create at least three posts.

You can reuse these ideas:

  • Learning Python one step at a time
  • My first Django page
  • Why HTML matters

Step 5 — Query the database in the view

Replace blog/views.py with:

from django.db.models import Q
from django.shortcuts import render

from .models import Post


def home(request):
    query = request.GET.get("q", "").strip()
    posts = Post.objects.all()

    if query:
        posts = posts.filter(
            Q(title__icontains=query) | Q(body__icontains=query)
        )

    context = {
        "site_title": "My First Blog",
        "posts": posts,
        "query": query,
    }

    return render(request, "blog/index.html", context)

Post.objects.all() asks for every post. Django returns a QuerySet, which behaves like a list in the template.

icontains means “contains this text, ignoring case.” Q(...) | Q(...) means the title or body can match.

Step 6 — Update the post cards

Your template already loops over posts. Change the inside of each card so it uses model fields:

<article class="post-card">
    <h3>{{ post.title }}</h3>
    <p>Published {{ post.created_at|date:"j F Y" }}</p>
    <p>{{ post.body|truncatewords:25 }}</p>
</article>

truncatewords:25 shows a preview instead of the whole body.

Step 7 — Prove the database is now the source

  1. Create a new post in /admin/.
  2. Return to / and refresh.
  3. The new post should appear without changing Python.
  4. Edit its title in admin and refresh again.
  5. Search for a word inside its body.

Delete the old hardcoded list from views.py. There should now be only one source of posts: the database.

Checkpoint

  • makemigrations and migrate finish without errors.
  • Posts appear in /admin/.
  • Creating a post in admin makes it appear on /.
  • Newest posts appear first.
  • Search matches both the title and body.
  • No temporary post dictionaries remain in views.py.

If it does not work

What you seeCheck this
no such table: blog_postRun python manage.py migrate.
Post is missing from adminRegister Post in blog/admin.py.
NameError: Q is not definedImport Q from django.db.models.
Homepage shows nothingCreate posts in admin and confirm context uses "posts".
Search only matches titlesInclude Q(body__icontains=query).

Stop here

Today is complete when admin can create a post and the homepage displays it. Tomorrow each title will open its own page with the complete article.