The Django Admin: Free CRUD for Your Models
Use the Django admin to get a free CRUD interface for every model — createsuperuser, register models, customise list_display and search_fields, and add inline admins.
What you'll learn
- ✓How to create a superuser and log into the admin
- ✓How to register a model in three lines
- ✓How to customise the list, search, and filter UI with ModelAdmin
- ✓How readonly_fields and fieldsets shape the edit form
- ✓How inline admins let you edit related rows on the same page
- ✓When the admin is the right tool — and when it isn’t
Prerequisites
- •A registered model — see Django Models and the ORM
- •A running dev server — see Install Django and Start Your First Project
The Django admin is the feature that sells Django to most teams. Define a model, write three lines, and you get a polished CRUD interface with list views, search, filters, pagination, and edit forms — all for free. Newsrooms ship it as their CMS. SaaS companies ship it as their back-office. Internal tools that would take a week to build elsewhere take an afternoon.
This post covers the admin from createsuperuser to inline admins, plus an honest section on when it isn’t the right tool.
If you don’t yet have a model to register, work through Django Models and the ORM first.
Step 1: Create a superuser
The admin is permission-gated. You need an account with staff and superuser flags to log in:
python manage.py createsuperuser
You’ll be prompted for a username, email, and password. Pick something memorable; this is local-development credentials, not production.
If the command errors with “no such table: auth_user,” you skipped python manage.py migrate. Run it and try again.
Step 2: Log in
Start the dev server:
python manage.py runserver
Open http://127.0.0.1:8000/admin/. You’ll see the Django admin login page. Sign in with the superuser you just created and you’ll land on the admin index — currently empty except for “Groups” and “Users” sections from django.contrib.auth.
Step 3: Register a model
Open blog/admin.py:
# blog/admin.py
from django.contrib import admin
from .models import Post
admin.site.register(Post)
Save and refresh /admin/. A “Blog” section now appears, with a “Posts” link. Click it: a list of every post in your database. Click any one: an edit form with every field. Click “Add post” in the top right: an empty create form.
Three lines. Real CRUD UI. No HTML, no JavaScript, no SQL.
The list page is sortable, paginated, and searchable. The edit form respects every field’s choices, max_length, and default. ForeignKey fields render as searchable dropdowns. DateTime fields get a date picker and a now-button. Boolean fields get a checkbox.
Step 4: Customise with ModelAdmin
The default admin is functional but plain. Customisation lives in a ModelAdmin subclass:
# blog/admin.py
from django.contrib import admin
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ("title", "author", "status", "created_at")
list_filter = ("status", "created_at", "author")
search_fields = ("title", "body")
ordering = ("-created_at",)
prepopulated_fields = {"slug": ("title",)}
readonly_fields = ("created_at", "updated_at")
A walkthrough of each attribute:
list_display— columns on the list page. Default is just__str__. Adding more is the single biggest UX improvement you can make.list_filter— a right-hand sidebar with clickable filters. Works on booleans, choices, dates, and ForeignKeys.search_fields— the search box at the top. Supports field lookups:("title", "body", "author__username").ordering— default sort on the list page.prepopulated_fields— auto-generate a slug from the title as you type. Pure magic for content editors.readonly_fields— fields shown but not editable. Perfect for timestamps and computed values.
Save the file, reload /admin/blog/post/, and the difference is immediate: real columns, useful filters, working search.
The @admin.register decorator
The decorator form is equivalent to:
class PostAdmin(admin.ModelAdmin):
list_display = ("title", "author", "status", "created_at")
admin.site.register(Post, PostAdmin)
Pick whichever style your team prefers. The decorator is slightly tidier for one-off registrations.
Custom columns and computed values
list_display can include methods on the model or the ModelAdmin:
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ("title", "author", "status", "short_body", "is_recent")
@admin.display(description="Body preview")
def short_body(self, obj):
return obj.body[:60] + ("..." if len(obj.body) > 60 else "")
@admin.display(boolean=True, description="Recent?")
def is_recent(self, obj):
from django.utils import timezone
from datetime import timedelta
return obj.created_at >= timezone.now() - timedelta(days=7)
The @admin.display decorator gives the column a friendly header and tells Django to render booleans as green/red icons.
Fieldsets
By default the edit form lists every field in declaration order. For larger models, group them:
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
fieldsets = (
("Content", {
"fields": ("title", "slug", "body"),
}),
("Publishing", {
"fields": ("author", "status"),
"description": "Who wrote it and whether it’s live.",
}),
("Timestamps", {
"fields": ("created_at", "updated_at"),
"classes": ("collapse",), # collapsed by default
}),
)
readonly_fields = ("created_at", "updated_at")
Grouped, labelled, optionally collapsed. Content editors notice. The screenshot you’ll show to non-technical stakeholders just got a lot more shippable.
Try it yourself. Take a ModelAdmin you’ve written and add list_display, list_filter, and search_fields to it. Then add a custom method column that returns “Yes” or “No” based on whether the row is published. Reload /admin/ and admire how close to “production-quality” three lines of declaration got you.
Inline admins
Inline admins let you edit related rows on the parent’s edit page. If you added a Comment model with a ForeignKey to Post:
# blog/models.py
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
author = models.CharField(max_length=100)
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
You can edit a post’s comments from the post’s own page:
# blog/admin.py
class CommentInline(admin.TabularInline):
model = Comment
extra = 1 # one blank row by default
readonly_fields = ("created_at",)
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ("title", "author", "status", "created_at")
inlines = [CommentInline]
Now opening any post shows a compact table of its comments at the bottom of the edit form, with rows you can add, edit, or delete. TabularInline renders rows in a table; StackedInline renders them as full forms stacked vertically — use it when each related row has many fields.
Customising the admin index
A couple of one-liners that matter for polish:
# mysite/urls.py (or anywhere it runs early)
admin.site.site_header = "MySite Admin"
admin.site.site_title = "MySite Admin"
admin.site.index_title = "Welcome — pick a section to manage."
Tiny change, big perceived professionalism for stakeholders peeking at the back-office.
Permissions
Every model gets four auto-generated permissions: add, change, delete, and view. Use Django’s auth Group model to bundle them and assign groups to users from the admin’s “Authentication and Authorization” section.
A common setup for a content team:
- Editors —
add/change/view/deleteonPostandComment. - Reviewers —
change/viewonPost; no delete. - Readers —
viewonly.
Granular per-row permissions (Editor X can edit only their own posts) need custom logic in the ModelAdmin via get_queryset and has_change_permission. Reachable, but plan for it.
Customising actions
Admin actions are bulk operations available from the list page’s dropdown. Adding one is short:
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ("title", "status", "created_at")
actions = ["mark_published"]
@admin.action(description="Mark selected posts as published")
def mark_published(self, request, queryset):
updated = queryset.update(status="published")
self.message_user(request, f"{updated} posts published.")
Select rows on the list page, pick “Mark selected posts as published” from the dropdown, hit Go. The action runs server-side on the selected queryset.
Try it yourself. Add a mark_draft admin action that does the opposite. Then try giving your superuser a second account that belongs to a “Readers” group with view-only permission on Post. Log out, log in as the new user, and confirm the admin enforces the restriction — edit buttons disappear, the action dropdown shrinks.
When the admin is the right tool
Strong fits:
- Internal back-office work. Customer support agents managing accounts. Ops staff inspecting orders. Editors approving content. The admin pays for itself the first afternoon.
- CMS for content sites. Many newsrooms ship Django admin as their entire publishing tool.
- Bootstrapping data. Even for projects with a separate end-user UI, the admin is the fastest way to seed and inspect data during development.
- Read-only dashboards with custom list_display and filters can stand in for a reporting tool.
When it isn’t
- End-user UI. The admin is for staff. Don’t put customers in it. Build the public site separately.
- Highly custom workflows. When every screen is bespoke — a quoting tool, a complex onboarding flow — the admin’s structure works against you. Build a dedicated UI.
- Public APIs. The admin renders HTML. For programmatic access, use Django REST Framework or FastAPI.
- Mobile-first experiences. The admin is responsive but not designed for phones-as-primary-device.
The honest framing: the admin is the answer for staff CRUD. It’s an answer for prototype dashboards. It is not the answer for product UI.
Recap
You now know:
createsuperusergives you an account that can log into/admin/admin.site.register(Model)is a complete CRUD UI in three linesModelAdmincustomises the list, search, filter, and edit viewslist_display,list_filter,search_fields, andreadonly_fieldsare the day-one customisations- Inline admins let you edit related rows on the parent’s page
- Admin actions add bulk operations to the list view
- The admin is fantastic for back-office work and wrong for customer-facing product UI
Next steps
You now have the four pillars of a Django site — models, views and templates, the admin, and the project structure that holds them together. The natural next steps are forms, user authentication flows, and deployment, which we’ll cover in future posts.
Related: What Is Django?, What Is FastAPI?.
Questions or feedback? Email codeloomdevv@gmail.com.