Updated Jun 22, 2026

The Django Admin

Here's the trick that made people fall in love with Django in the first place. You spent Phase 3 defining two models — Post and Comment — describing your blog's data as Python classes. Django already turned those into database tables for you. Now it's about to do something that feels almost unfair: it reads those same model definitions and hands you a complete, working web application for managing that data. A real back-office, with login, list pages, search boxes, filters, edit forms, delete buttons — and you write essentially none of it.

📝 The mental model: the admin is a reflection of your models. It doesn't have its own idea of what your data looks like — it asks your models. Each field becomes a form input. Each model becomes a list page. Change the model, and the admin changes with it. That's why the rest of this phase is short: you're not building the admin, you're describing how you want Django to render the one it already built.

This is the single feature that sells Django to teams. "We need an internal tool so the content team can publish posts" is normally a week of CRUD-screen drudgery. In Django it's about four lines of code.

The auto-admin: a back-office for free

Think about what a content manager actually needs to do with a blog: see all the posts, find a specific one, create a new one, fix a typo, unpublish something, delete spam comments. That's the classic CRUD loop — Create, Read, Update, Delete — wrapped in a usable interface.

💡 Writing those screens by hand is the most repetitive work in web development. Django noticed that the information needed to build them — the field names, their types, which fields are required — already lives in your models. So it reads your models (the ones from Phase 3) and builds the whole interface from them automatically.

You get all of this with zero screen-building on your part:

  • A list page for each model, showing every row
  • Create and edit forms, with the right input type per field (a date picker for DateField, a dropdown for foreign keys, a checkbox for BooleanField)
  • Delete with a confirmation step
  • Search, filters, and pagination once you ask for them
  • A login screen and a permission system, so only trusted staff get in

The admin isn't a toy demo, either — real teams run their entire content operation through it for years.

Enabling it

Good news: the admin is already switched on. When you ran startproject back in Phase 1, Django included the admin app in INSTALLED_APPS and wired its URLs. The admin lives at /admin/ right now. You just need two things: a user who's allowed in, and a line telling the admin which models to show.

First, create a superuser — an account with full admin access:

python manage.py createsuperuser
Username: nika
Email address: [email protected]
Password:
Password (again):
Superuser created successfully.

What just happened: Django walked you through creating a privileged account and saved it to the User table (one of the tables you got from the auth app's migrations). This is the account you'll log in with. Start the server (python manage.py runserver), visit http://127.0.0.1:8000/admin/, and you can log in — though the dashboard is nearly empty, because you haven't told the admin about your models yet.

Now open blog/admin.py (Django created this empty file for your app) and register your models:

from django.contrib import admin

from .models import Post, Comment

admin.site.register(Post)
admin.site.register(Comment)

What just happened: admin.site.register(Post) says "add Post to the admin." Refresh /admin/ and you'll see a Blog section with Posts and Comments, each clickable. You can now create posts, edit them, delete them, and manage comments — a full CRUD interface, for those two lines. Nobody wrote a single form or template.

💡 Remember the __str__ method you added to your models in Phase 3? This is where it pays off. The admin's list page shows each row using __str__. Without it, every post shows up as the useless Post object (1). With a good __str__ returning the post's title, the list is actually readable. That wasn't busywork in Phase 3 — it was setting up for today.

Customizing with ModelAdmin

The default admin is functional but plain — a list of posts shown only by title, with no way to search or filter. You shape it with a ModelAdmin class: a small configuration object that tells the admin how to present one model. You attach it with the @admin.register decorator (a tidier replacement for the admin.site.register call above).

from django.contrib import admin

from .models import Post, Comment


@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ("title", "author", "published", "created_at")
    list_filter = ("published", "created_at")
    search_fields = ("title", "body")
    ordering = ("-created_at",)
    prepopulated_fields = {"slug": ("title",)}

What just happened: each attribute reshapes the admin for Post:

  • list_display — which columns show on the list page. Instead of one title column, you now see title, author, published-state, and date side by side. (Each entry is a field name from your model.)
  • list_filter — adds a sidebar of filters. Now a content manager can click "show only published" or filter by date with one click.
  • search_fields — adds a search box at the top that searches across the named fields.
  • ordering — the default sort. "-created_at" means newest first (the minus sign reverses it).
  • prepopulated_fields — as you type a post's title, Django auto-fills the slug field with a URL-friendly version. A small touch that saves real keystrokes. (This assumes your Post has a slug field; drop the line if it doesn't.)

💡 Notice the pattern: you didn't write any UI. You wrote configuration — a handful of tuples naming fields — and Django translated it into search boxes, filter sidebars, and sortable columns. That's the admin's whole philosophy: describe what you want, don't build it.

Inlines: editing related objects together

There's one rough edge so far. Comments belong to posts (that's the foreign key from Phase 3), but in the admin they live on a totally separate page. To read a post and moderate its comments, you'd bounce between two screens. What you actually want is to see — and edit — a post's comments right there on the post's edit page.

That's an inline. You define a small inline class for the related model, then attach it to the parent's ModelAdmin:

from django.contrib import admin

from .models import Post, Comment


class CommentInline(admin.TabularInline):
    model = Comment
    extra = 1


@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ("title", "author", "published", "created_at")
    list_filter = ("published", "created_at")
    search_fields = ("title", "body")
    ordering = ("-created_at",)
    inlines = [CommentInline]

What just happened: CommentInline tells the admin "Comments are related to Post — render them inside the Post form." Now when you open a post to edit it, its comments appear as editable rows beneath the post's fields, and extra = 1 leaves one blank row so you can add a new comment without leaving the page. Django knew how to connect them because of the ForeignKey from Comment to Post — you just told it where to display the relationship.

💡 TabularInline lays the related rows out as a compact table (great for short records like comments). Its sibling StackedInline stacks each related object as a full form block — better when the related model has many fields and a table would be too cramped. Same idea, two layouts; pick whichever reads better.

What the admin is for (and what it isn't)

The admin feels so powerful that it's tempting to reach for it everywhere. So let's be clear about its job, because misusing it is a genuine security mistake.

💡 The admin is for trusted internal staff — your team, your content editors, your moderators. It's an internal back-office and content-management tool, and at that job it's a massive productivity win. Spin one up and your colleagues can manage data on day one.

⚠️ The admin is not your public, user-facing interface, and it is not a public API. Don't point your blog's readers at /admin/ to write comments, and don't let untrusted users near it. It exposes raw database editing with delete buttons everywhere — in the wrong hands that's a disaster. In production, lock it down: serve it over HTTPS, give staff strong unique passwords, and consider moving it off the obvious /admin/ URL or restricting it by IP. Treat it like the keys to the database, because that's roughly what it is.

What your public visitors see — the actual blog pages with their own design — is a separate thing you build deliberately. That's the next two phases: templates render the public pages (Phase 5), and forms let visitors safely submit comments (Phase 6).

💡 Step back and notice what just happened across Phases 3 and 4. You defined Post and Comment once, as Python classes. From that single definition Django gave you the database schema (Phase 3), and now a complete admin interface (Phase 4) — and next it'll generate forms for you too (Phase 6). Define the model once, get the rest for free. That "don't repeat yourself" payoff is the heart of why Django feels the way it does.

Recap

  • Django generates a full CRUD back-office — list, search, filter, create, edit, delete — automatically from your models. It's the framework's signature feature.
  • The admin is already enabled. Create access with python manage.py createsuperuser, then register each model with admin.site.register(Model) (or the @admin.register decorator) in admin.py.
  • A model's __str__ controls how its rows appear in the admin — which is why you wrote good __str__ methods in Phase 3.
  • A ModelAdmin class customizes one model's admin via list_display, list_filter, search_fields, ordering, and prepopulated_fields — configuration, not UI code.
  • Inlines (TabularInline / StackedInline) let you edit related objects, like a post's comments, on the parent's edit page.
  • The admin is for trusted staff only — an internal tool, never a public UI or API. Lock it down in production.

Quick check

[
  {
    "q": "Where does the Django admin get the information it needs to build its forms and list pages?",
    "choices": ["From a separate config file you write by hand", "From your model definitions", "From the database's raw column metadata at runtime"],
    "answer": 1,
    "explain": "The admin reflects your models — each field becomes a form input and each model becomes a list page. Change the model and the admin changes with it."
  },
  {
    "q": "What does a ModelAdmin's `list_display` attribute control?",
    "choices": ["Which fields are required when creating a record", "Which columns appear on the model's list page in the admin", "Which users are allowed to see the model"],
    "answer": 1,
    "explain": "`list_display` is a tuple of field names shown as columns on the list page, so you see more than just the `__str__` value."
  },
  {
    "q": "Which statement about the Django admin is correct?",
    "choices": ["It's meant to be your public, user-facing interface", "It's an internal back-office for trusted staff and should be locked down in production", "It replaces the need to ever write templates or forms"],
    "answer": 1,
    "explain": "The admin is a powerful internal tool for trusted staff. It is not a public UI or API — expose it carefully, and build public pages with templates and forms instead."
  }
]

← Phase 3: Models & the ORM · Guide overview · Phase 5: Templates & the MTV Pattern →

Check your understanding

1. Where does the Django admin get the information it needs to build its forms and list pages?

2. What does a ModelAdmin's `list_display` attribute control?

3. Which statement about the Django admin is correct?

Was this page helpful?