Django Admin App

Today Admin sites are integral part of any website. Administrators, author and staff members uses admin site to manage the content of the main site. Django provides an admin app right out of the box for you, so can just focus on building features that need you instead of creating old boring admin sites. It doesn't mean that you can't roll out your own admin site. Infact, we will create our own admin site called cadmin. But first, we will learn how to use Django Admin panel. Let's get started.

Creating a superuser

To use admin site we first have to create a superuser. In the command prompt or terminal enter the following command.

(env) C:\Users\Q\TGDB\django_project>python manage.py createsuperuser

First you will be prompted to enter username.

Username (leave blank to use 'q'): admin

Next enter an email address

Email address: admin@overiq.com

Finally enter the password and confirm it by retyping.

Password:
Password (again):
Superuser created successfully.

Now you can login to the Django admin app.

Start the development server if not already running and point your browser to http://127.0.0.1:8000/admin. You will see a login page like this:

Enter username and password created in the above step and hit enter. On success you will see a page like this:

The Groups and Users under "Authentication and Authorization" is coming from django.contrib.auth app. Groups and Users are actually models. Django uses Groups to handle permissions. The Users refers to the users of the site. Click on the Users and you will see the user we created in the above step.

This page us allows to do the following tasks:

  1. Add new user
  2. Modify/Add the details of the current user
  3. Search and filter users
  4. Sort the user data by clicking on the column header
  5. Delete user

The admin site is designed in such a way that any non-technical user should be able to use the entire site without any problem. Try navigation admin site on your own. Add some new users and modify existing one, this will give you a better idea of how Django admin works.

Adding models to Django admin

I know some of you might be wondering Why Django admin is not showing the models we have created in the blog app ?

By default Django admin app doesn't load any model from the apps we create. To add a model to the Django admin you have to modify admin.py file available inside every app directory. Open admin.py inside the blog app i.e TGDB/django_project/blog. At this point your admin.py should look like this:

from django.contrib import admin

# Register your models here.

If this file is not available inside the blog app, just create a new file and call it admin.py then type the following code into it.

from django.contrib import admin
from blog import models

# Register your models here.

admin.site.register(models.Post)
admin.site.register(models.Category)
admin.site.register(models.Author)
admin.site.register(models.Tag)

In line 2 we are importing models from the blog app. To add a model to the Django admin, pass the name of the model to the admin.site.register() method.

Open your browser and visit http://127.0.0.1:8000/admin/. You should be able to see Post, Category, Author and Tag models in the Django admin as follows:

This is fully functional admin panel that you can use to list, create, edit and delete objects in a simple and easy way. I encourage to you take your time and populate your database with some more records. Use random text generator like http://www.lipsum.com/ to add at-least 4-5 paragraph long text to all the posts.

Important features of Django Admin

In this section we will discuss some important aspects of Django Admin. Our discussion will be restricted to following points:

  1. __str__() method
  2. Model Relationships
  3. Data validation
  4. Django tracks your changes
  5. different field for differnt data types

1) The __str__() method

Navigate to the Author list (http://127.0.0.1:8000/admin/blog/author/) page by clicking Authors link under the blog app in Django Admin home page. You will get a page which looks something like this:

So what makes Django admin to show only author name not the email, created_on or any other fields in the Author models ?

By default Django admin displays the value returned by the __str__() method defined in the Model class. For Author models, the method __str__() is defined as follows:

class Author(models.Model):
    name = models.CharField(max_length=50)
    ...
    last_logged_in = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name + ":" + self.email

Modify __str__() method in Author model as follows:

def __str__(self):
        return self.name + "-" + self.email

Refresh the author list page (http://127.0.0.1:8000/admin/blog/author/) and it should look like this:

As you can see Django admin has picked-up our changes in the __str__() method and started displaying name and email of author separated by a dash (-). We don't want these changed to be pemanent because in next section will display author's email in a separate column. So lets revert back to our original __str__() definition.

def __str__(self):
        return self.name + ":" + self.email

Refresh the author list page again and it should be back to the way it was.

2) Model Relationships in Django admin

This is one of the most important things that deserves some explantion. Recall that our Post model has following relationships:

1) one-to-many relationships between Post and Author
2) one-to-many relationships between Post and Category
3) many-to-many relationship between Post and Tags

For reference this is how the Post model is defined.

class Post(models.Model):
    title = models.CharField(max_length=50)
    slug = models.SlugField(unique=True)
    content = models.TextField()
    pub_date = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey(Author)
    category = models.ForeignKey(Category)
    tags = models.ManyToManyField(Tag)

    def __str__(self):
        return self.title

We can see how Django admin manages model relationships by creating or editing a post. Lets create a new post by visiting http://127.0.0.1:8000/admin/blog/post/add/.

Django uses a select box(dropdown) to represent a one-to-many relationships. We have two one-to-many relationships. One is between Post and Category and the second is between Post and Author. Notice how Author and Category is represented in Add Post using select element.

To select post's author/category just select one from the dropdown list. You can also add a new author/category by clicking + icon in green. If you click the + sign, a popup window will open which allows you to add a new author/category.

Once an author or category is selected, you can also edit them by clicking yellow pencil icon beside the dropdown.

Similarly, Django uses multiple selection select box to represent a many-to-many relationships. As a post can have one or more than two tags, the multiple selection select box allows you to select more than one tags for a single post. To select multiple values hold Ctrl while selecting.

You can also add a new tag by clicking green + icon.

Another important point I want to mention here is that if you delete an object then any dependent objects will be deleted too. For example, If an author has created two categories, then deleting that Author, will also remove those two categories. However deleting a Category object will have no effect on Author.

3) Django admin handles validation

Django admin automatically provides input validation. Try saving a blank blog post or enter some in-valid characters in the slugfield. You will see errors as follows:

4) Django admin tracks your changes.

Django keeps track of the changes you have made on an object. When you edit an object, you get a link to HISTORY on the upper right corner of the page. Click on the HISTORY link to view list of the changes made on a object.

If you haven't made any changes on the object then you will get the following message.

This object doesn't have a change history. It probably wasn't added via this admin site.

5) Django provides widgets based on field types defined in the model. The widgets refer to the way a Model field is displayed in the Django admin. For example a CharField is displayed as input element, ForeignKey as select-box, BooleanField as checkbox and so on.

Customizing how models are displayed

Open you browser and visit Author list page i.e http://127.0.0.1:8000/admin/blog/author/.

This page is similar to User list page (http://127.0.0.1:8000/admin/auth/user/) but there are subtle differences.

  1. Unlike User list page there is no search bar.
  2. No filters on the right hand side.
  3. The Author model consists of 5 fields (name, email, active, created_on and last_logged_in) but only author name is visible.
  4. We can't sort the author name by clicking the column header.

To customize the appearance of models in Django admin, we create a new class AuthorAdmin which inherits from models.ModelAdmin.

class AuthorAdmin(admin.ModelAdmin):
    pass

The class models.ModelAdmin provides some attributes which allows you to change how a model is displayed in the list page. Here are some commonly used attributes of models.ModelAdmin.

1) list_display - It controls which model fields to display on the list page. It accepts a list or tuple of field names which you want to display. In addition to displaying fields, it also makes them sortable. For example, list_display = ('name', 'email', 'created_on',) will display three sortable columns namely: name, email and created_on.

2) search_fields - This attribute enables the search function on the list page. It accepts a list or tuple of field names where you want to search. It performs a case-insensitive search. For example, If search_fields = ('name', 'email',) then for the query "tom" Django will search name as well as email field.

3) ordering - It controls the ordering of the fields. Just like other attributes it accepts a list or tuple of field names to specify the order. For example, ordering = ['-name'] will display the name column in descending order.

4) list_filter - This attributes activates the filter on the right side of the list page. It accepts a list or tuple of field names. Django automatically provides different shortcuts to filter the objects based on the type of the field. For example: If the field is of type DateField then django provides "Today", "Past 7 days", "This month" and "This year" shortcuts to filter the objects.

5) date_hierarchy - This attribute is specially designed to provide an intelligent date based drill-down navigation just above Action select box. It takes a string not a list or tuple. As date_hierarchy creates date based filter, you are only allowed to specify field of type DateField or DateTimeField only. Here is how this attribute looks like in the list page.

Open admin.py inside the blog app and update the file as follows:

from django.contrib import admin
from blog import models

# Register your models here.

class AuthorAdmin(admin.ModelAdmin):
    list_display = ('name', 'email', 'created_on')
    search_fields = ['name', 'email']
    ordering = ['-name']
    list_filter = ['active']
    date_hierarchy = 'created_on'

admin.site.register(models.Post)
admin.site.register(models.Category)
admin.site.register(models.Author, AuthorAdmin)
admin.site.register(models.Tag)

Here we have created a new class AuthorAdmin which inherits from admin.ModelAdmin. Inside the class we have specified all the attributes we have just learned to customize the appearance of Author model. To register the changes outlined in the AuthorAdmin just pass the name of the new class as a second argument to the register() method.

Refresh the author list page and it will look something like this:

On visiting author list page you may get an error like this:

The error is about a library called pytz which is used by Django admin to perform some timezone related calculations. Open terminal or command prompt and install pytz using the following command.

(env) C:\Users\Q\TGDB\django_project>pip install pytz
Collecting pytz
  Using cached pytz-2016.10-py2.py3-none-any.whl
Installing collected packages: pytz
Successfully installed pytz-2016.10

(env) C:\Users\Q\TGDB\django_project>

Restart the server and refresh the author list page again, error should have gone by now.

Before we move ahead let's add some more classes in the admin.py file to modify the appearence of Post, Category and Tag models. Add the following classes after the AuthorAdmin class.

class AuthorAdmin(admin.ModelAdmin):
    ...
    date_hierarchy = 'created_on'

class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'pub_date', 'author', 'category',)
    search_fields = ['title', 'content']
    ordering = ['-pub_date']
    list_filter = ['pub_date']
    date_hierarchy = 'pub_date'    

class CategoryAdmin(admin.ModelAdmin):
    list_display = ('name', 'slug',)
    search_fields = ('name',)   

class TagAdmin(admin.ModelAdmin):
    list_display = ('name', 'slug',)
    search_fields = ('name',)

admin.site.register(models.Post, PostAdmin)
admin.site.register(models.Category, CategoryAdmin)
admin.site.register(models.Author, AuthorAdmin)
admin.site.register(models.Tag, TagAdmin)

Customizing add/edit forms

Django admin also allows you to change how add/edit forms are displayed. To customize add/edit forms, the class admin.ModelAdmin provides following attributes.

1) fields - By default the order of appearence of fields in the add/edit forms is same as that of fields defined in the models. To change the order, list the name of the fields in the order you want as a list or tuple. For example, fields = ['title', 'created_on', 'author'] will display "title" field followed by "created_on" field which is then followed by an "author" field.

In addition to ordering the field, you can also use this attribute to remove one or more fields from being edited/added entirely.

2) filter_horizontal - This attribute can only be used with ManyToManyField. By default ManyToManyField fields are displayed using multiple selection select-box. Selecting records from a small list is easy but what if there are hundreds or thousands of records ? To make the selection easier Django provides filter_horizontal. It accepts list or tuple of the names of fields of type ManyToManyField. It then creates a nice interface which allows to search through the records as well as view available records and choosen records.

At the end of PostAdmin class append filter_horizontal = ('tags',) as follows:

class PostAdmin(admin.ModelAdmin):
    ...
    filter_horizontal = ('tags',)

Visit Add Post/Change Post page. You will see a nice interface which allows you to view available and choosen tags and as well as search tags.

3) raw_id_fields - There is no doubt filter_horizontal makes record selection easier. What if you have hundreds or even thousands of records in the ManyToManyField ? Loading thousands of records at once could take a while. The solution is to use raw_id_fields instead of filter_horizontal.

It accepts list or tuple of the names of fields of type ManyToManyField or ForeignKey and creates an input box (input element <input type="text" ... />) where you can enter primary key of the record.

Open admin.py file and comment the filter_horizontal attribute we have added in the last step. Then add raw_id_fields  attribute just below it.

class PostAdmin(admin.ModelAdmin):
    ...
    # filter_horizontal = ('tags',)
    raw_id_fields = ('tags',)

Visit post list page again and create/edit a post. You will see a input element along with a search-icon.

So what do I need to fill in that input box ? The primary key of the record. Don't freak out you don't really need to memorize the primary key of records. Just click on the search-icon beside the input box and a pop-window will open which allows you to select and search through the records.

4) prepopulated_fields - This attribute automatically add content to the field. It accepts a dictionary mapping field names to the fields it should prepopulate form. It is commonly used to create slugs. For example, prepopulated_fields = {'slug': ('title',)} , this code will prepopulate slug field from the title field using some javascript.

Open admin.py and add prepopulated_fields attribute as follows:

class PostAdmin(admin.ModelAdmin):
    ...
    raw_id_fields = ('tags',)
    prepopulated_fields = {'slug': ('title',)}

Visit Add Post page again and enter some data in the title field, you will that notice slug field automatically gets populated in real time.

prepopulated_fields only prepopulate field while adding new a record, it will not prepopulate the field when you update a record. We are discussing how to update slug from title in the upcoming sections.

5) readonly_fields - This attribute makes the field read only. Although, the field will appear on the add/edit form but you can't enter anything to it. It accepts name of the field names as list or tuple.

At this point add/edit post form allows users to enter slug in the slug field. It would be much better if we automatically create slug from the title. To do that we must first make the slug field readonly.

Once again open admin.py and comment out prepopulated_fields attribute, then add readonly_fields attribute to PostAdmin like this:

class PostAdmin(admin.ModelAdmin):
    ...    
    #prepopulated_fields = {'slug': ('title',)}
    readonly_fields = ('slug',)

Save the file, visit post list page and click on "ADD POST". You will see a form like this:

Notice that the slug field is pushed at the end of the page, this happens because Django first displays fields which are editable followed by readonly fields. We can easily change this behavior using fields attribute discussed above. Just after readonly_fields attribute in the PostAdmin, add the fields attribute as follows:

class PostAdmin(admin.ModelAdmin):
    ...
    readonly_fields = ('slug',)
    fields = ('title', 'slug', 'content', 'author', 'category', 'tags',)

Refresh the page again and you will see readonly slug field right after the title field.

Another important point I want to mention here is that DateField or DateTimeField whose parameter is set to auto_now_add or auto_now to True will not appear in Django admin panel, even after you add their name to the fields attribute.

    fields = ('title', 'slug', 'pub_date', 'content', 'author', 'category', 'tags',)

In fact, doing so raises an FieldError exception.

Updating slug

So how do we automatically create slug from the title of the post ?

To automatically create slug from the title we override the save() method of the models.Model class. We will then use slugify() method to generate slug from the title. Here is the actual code.

from django.db import models
from django.template.defaultfilters import slugify

class Post(models.Model):
    ...

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        self.slug = slugify(self.title)
        super(Post, self).save(*args, **kwargs)

In line 2 we are importing slugify() method from django.template.defaultfilters. And in line 41, we are overriding save() method. If the string is "The Great Django Blog" then slugify() method will return
"the-great-django-blog".

Start the server if not already running and create a new post by visiting http://127.0.0.1:8000/admin/blog/post/add/. After entering data in all the fields hit "Save and continue editing" button.

Notice how the slug is automatically generated from the title. The save() method is run every time you create/update a Post object.

Creating Optional Fields

Most of the fields defined in our models require you to enter some kind of data (except fields like Post's pub_date, and Author's created_on and last_logged_in, Django automatically provides values to these fields because of auto_now and auto_now_add parameters).

What if you want to make some fields optional ?

To make fields optional pass blank=True while defining field in the models.py file. By default blank is set to False that's why django admin requires you to fill those fields before submitting the form. For example, to make the author's email optional add blank=True as follows:

class Author(models.Model):
    ...
    email = models.EmailField(unique=True, blank=True)
    ...

Here we are changing fields, so we must run makemigrations command to create migration file.

(env) C:\Users\Q\TGDB\django_project>python manage.py makemigrations
Migrations for 'blog':
  blog\migrations\0002_auto_20170329_1526.py:
    - Alter field email on author

Next, run the migration command to commit changes to the database.    

(env) C:\Users\Q\TGDB\django_project>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
  Applying blog.0002_auto_20170329_1526... OK

(env) C:\Users\Q\TGDB\django_project>

Open your browser and and navigate to the Django admin and create a new author by visiting http://127.0.0.1:8000/admin/blog/author/add/. Submit the form without entering anything. This time the form will report error only for the author's name field not for the email field.

For now we want to users to enter email address, so remove the blank=True from the Author's model. Then use makemigrations and migrate command once again to commit the changes to the database.

(env) C:\Users\Q\my_workspace\django_project>python manage.py makemigrations
Migrations for 'blog':
  blog\migrations\0003_auto_20170329_1708.py:
    - Alter field email on author

(env) C:\Users\Q\my_workspace\django_project>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
  Applying blog.0003_auto_20170329_1708... OK

(env) C:\Users\Q\my_workspace\django_project>

Changing field labels

The Form labels in Django admin uses the field names from the models. If the field name in the model is name then form label will be Name. If label consist of multiple words separated by underscore like pub_date, then form label will be Pub Date.

To explicitly specify form label for a field use verbose_name parameter in the models as follows:

class Author(models.Model):
    name = models.CharField(max_length=50, verbose_name="Author Name")
    ...

To commit the changes use makemigrations and migrate commands as usual.

(env) C:\Users\Q\TGDB\django_project>python manage.py makemigrations
Migrations for 'blog':
  blog\migrations\0004_auto_20170329_1729.py:
    - Alter field name on author

(env) C:\Users\Q\TGDB\django_project>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
  Applying blog.0004_auto_20170329_1729... OK

(env) C:\Users\Q\TGDB\django_project>

help_text parameter

help_text is another useful parameter to display useful information in the form. Let's see how it works. Navigate to the post list page in Django admin (http://127.0.0.1:8000/admin/blog/post/) and click on "ADD POST" button. You will see form like this.

At this point our form shows an uneditable slug field. First time visitor to this page may get confused about how to use this field.

As we are generating slug field automatically we should inform user that this field is automatically generated from the title of the post. To add help_text parameter open models.py file in the blog app and modify Post model as follows:

class Post(models.Model):
    ....
    slug = models.SlugField(unique=True, help_text="Slug will be generated automatically from the title of the post")
    ...

Once again run makemigrations and migrate command to commit changes. Refresh the add post form and you will see updated form like this:

Fixing the typo in Django admin

Did you notice anything odd in the django admin ? If not then point your browser again to http://127.0.0.1:8000/admin/.

Notice the word "Categorys" under the Blog app. By default Django displays plural of model name in Django admin. So "Author" becomes "Authors" and "Category" becomes "Categorys".

Django uses something called Meta class to add metadata to the model class. Meta class is defined as a nested class in the Model.

So what is Metadata ? Metadata is just additional options/settings that are not specific to any fields. Here are some additional options that we can specify in the Meta class.

ordering

This option specifies the default ordering scheme for the object. If specified this ordering scheme will be used every time we access data using Django ORM. It accepts one or more field names as string in a list or tuple. For example:

ordering = ['-pub_date'] - this will order the objects by pub_date in descending order.

ordering = ['-pub_date', 'name'] - this will order the objects in first by pub_date in descending order then by name in ascending order.

unique_together

This option specifies the unique constraint on two or more field names when taken together. It accepts two or more field names as a tuple of tuple. For example:

unique_together = (('name', 'email'),) 

Due to this constraint no two rows will have same name and email field when taken together.

verbose_plural_name

Use this option to specify the pluralized name of the model. It accepts a string. We will use this option to fix the typo. For example:

class Post(models.Model):
   ...
   class Meta:
       verbose_plural_name = "Post"

This will cause the Post model to appear in Django Admin under name Post instead of Posts.

db_table

By default Django automatically create table names for us. For example, If we have an app called forum and a model class named Bounty, then the table name would be forum_bounty. If for some reason you want to override this naming convetion use db_table. It accepts a string as argument. For example:

db_table = "bounties"

For a full list of Meta options visit https://docs.djangoproject.com/en/1.11/ref/models/options/.

Lets fix the typo now. Open models.py and add the following Meta class to Category model after the field declarations as follows:

class Category(models.Model):
    name = models.CharField(max_length=50, unique=True)
    slug = models.SlugField(max_length=255, unique=True)

    class Meta:
        verbose_name_plural = "Categories"

    ...

Save the file, then run makemigrations followed by migrate command to commit changes.    

Visit http://127.0.0.1:8000/admin/ and the typo should have gone.