Build a blog with Django: Add post admin

Entering blog content via the command-line is certainly possible but not very user-friendly. As Raymond Hettinger would prod you to shout, "There must be a better way."

There must be a better way

And, there is a better way. It's called the Django admin.

Django is able to automatically generate an admin interface based on the metadata it collects from your models.

I'll show you how to use the admin to manage your blog content.

N.B. The admin is best used only as an internal management tool. If you need an interface that's less model-centric and more process-centric then you should consider writing your own views. Buddy Lindsey from GoDjango gives a good list of 5 reasons not to use the Django admin.

Let's get started.

Accessing the admin

The admin is already enabled since we started with the default project template used by startproject.

N.B. If you're curious about the admin's requirements then you can check here.

To login to the admin you need to create a user. Use the createsuperuser command to do it.

(venv) $ python manage.py createsuperuser
Username (leave blank to use 'dwayne'):  
Email address: dwayne@simplydjango.com  
Password:  
Password (again):  
Superuser created successfully.  

Then, you can start up the server and navigate to http://127.0.0.1:8000/admin to login and view the admin site.

Django admin site

Take some time to explore the admin site before continuing. I can wait.

Manage posts via the admin

You'd notice that our posts app is nowhere to be found. Let's fix this by telling the admin about our Post model.

Edit posts/admin.py to contain the following:

from django.contrib import admin

from .models import Post


admin.site.register(Post)  

Refresh http://127.0.0.1:8000/admin to see the posts app.

Django admin site with posts app

That's it. We can now manage our posts via the admin.

Let me show you.

Click on the Add link next to Posts to view the add page for posts.

Django admin site add post

Enter some information into the required fields and click the Save button to persist your changes to the database and get redirected to the change list page.

Django admin site posts change list page

Finally, click on the post to be taken to its change page. Notice that it's similar to the add page. Make any edits you need and save it when you're done.

I'll now show you how easy it is to customize the pages you just saw.

Customize the change list page

Update posts/admin.py to contain the following:

from django.contrib import admin

from .models import Post


@admin.register(Post)
class PostAdmin(admin.ModelAdmin):  
    date_hierarchy = 'published_at'

    list_display = (
        'title',
        'is_published',
        'published_at',
        'created_at',
        'updated_at',
    )

    list_filter = ('is_published',)

    ordering = (
        '-published_at',
        '-created_at',
    )

    search_fields = (
        'title',
        'excerpt',
    )

Firstly, we create a new class, PostAdmin, that subclasses admin.ModelAdmin. It gives us a place to put our custom values.

Notice that I used the register decorator this time around to register the Post model with PostAdmin. However, doing it the previous way is perfectly fine as well. After the class definition you'd just put admin.site.register(Post, PostAdmin).

N.B. You can't use the register decorator if you have to reference your model admin class in its __init__() method, e.g. super(PersonAdmin, self).__init__(*args, **kwargs).

Here's what each class attribute does:

  • date_hierarchy: It makes the change list page use the published_at field of the Post model to display a date-based drilldown navigation.

  • list_display: It controls which fields are displayed as columns in the table shown on the change list page.

  • list_filter: It activates search filters in the right side bar of the change list page based on the elements of the tuple. In our case it will allow us to easily filter between draft and published posts.

  • ordering: It determines how the posts are ordered. First in descending order of published_at and then in descending order of created_at. This keeps draft posts at the top so they're right there in front of us when we go to the page to continue working on them. Then comes the most recent published posts. Finally, if two posts are published on the same day then the most recently created one appears first in the table.

  • search_fields: It enables a search box. I've set it to search the title and excerpt fields of the Post model whenever a search query is submitted.

Here's the final result of this customization:

Django admin site posts change list page after customizations

Customize the add/change pages

We're going to make the following changes:

  1. Re-label the is_published field to Is published?.

  2. Auto-populate the slug field when entering the title of the post.

  3. Exclude the published_at field from the form.

  4. Lastly, set the published_at field to an appropriate value when the is_published field is toggled. In particular, when is_published is true then published_at should contain the current date and time when the change is saved. And, when is_published is false then published_at should be set to None.

Here are the edits to posts/admin.py that will make all that possible:

from django import forms  
from django.contrib import admin  
from django.utils.timezone import now

from .models import Post


class PostAdminForm(forms.ModelForm):  
    class Meta:
        model = Post

        fields = ['is_published', 'title', 'slug', 'excerpt', 'body']

        labels = {
            'is_published': 'Is published?',
        }


@admin.register(Post)
class PostAdmin(admin.ModelAdmin):  
    # ...

    form = PostAdminForm

    prepopulated_fields = {
        'slug': ('title',)
    }

    def save_model(self, request, obj, form, change):
        if form.has_changed() and 'is_published' in form.changed_data:
            if obj.is_published:
                obj.published_at = now()
            else:
                obj.published_at = None

        super().save_model(request, obj, form, change)

I create a custom model form called PostAdminForm and then tell PostAdmin to use that form instead of the default it generates by setting the form class attribute.

The prepopulated_fields class attribute allows me to auto-populate the slug field when entering the post's title.

Finally, notice how I override the ModelAdmin's save_model method to perform a custom pre-save operation.

Here are the visible changes:

Django admin site posts change page customizations

Wrap up

That completes the customizations we'll be doing to the admin for now. I hope you're still tracking your changes with Git. If so, then now is the time to make your commits. Here are links to the ones I made:

Finally, check off the second task on your Trello card and bask in the joy of being 50% complete with the feature.

P.S. Let me know in the comments if you get into any problems. I'd be happy to help.

P.S.S. Subscribe to my newsletter if you're interested in getting exclusive Django content.