Adding Models to Django Admin

There’s one crucial part we haven’t done yet. Let’s add our own models to the admin site, so we can add, change and delete objects in our custom database tables using this nice interface. We’ll continue the books example from Chapter 4, where we defined three models: Publisher, Author and Book. Within the books directory (mysite_project\mysite\books), startapp should have created a file called admin.py, if not, simply create one yourself and type in the following lines of code:

from django.contrib import admin
from .models import Publisher, Author, Book

admin.site.register(Publisher)
admin.site.register(Author)
admin.site.register(Book)

This code tells the Django admin site to offer an interface for each of these models. Once you’ve done this, go to your admin home page in your web browser (http://127.0.0.1:8000/admin/), and you should see a "Books" section with links for Authors, Books and Publishers. You might have to stop and start the development server for the changes to take effect.

Django Admin with Books models added
You now have a fully functional admin interface for each of those three models. That was easy!

Take some time to add and change records, to populate your database with some data. If you followed Chapter 4’s examples of creating Publisher objects (and you didn’t delete them), you’ll already see those records on the publisher change list page.

One feature worth mentioning here is the admin site’s handling of foreign keys and many-to-many relationships, both of which appear in the Book model. As a reminder, here’s what the Book model looks like:

class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField()

    def __str__(self):
        return self.title 

On the Django admin site’s "Add book" page (http://127.0.0.1:8000/admin/books/book/add/), the publisher (a ForeignKey) is represented by a select box, and the authors field (a ManyToManyField) is represented by a multiple-select box. Both fields sit next to a green plus sign icon that lets you add related records of that type.

Django Books admin field select widgets
For example, if you click the green plus sign next to the "Publisher" field, you’ll get a pop-up window that lets you add a publisher. After you successfully create the publisher in the pop-up, the "Add book" form will be updated with the newly created publisher. Slick.

Making Fields Optional

After you play around with the admin site for a while, you’ll probably notice a limitation – the edit forms require every field to be filled out, whereas in many cases you’d want certain fields to be optional. Let’s say, for example, that we want our Author model’s email field to be optional – that is, a blank string should be allowed. In the real world, you might not have an e-mail address on file for every author.

To specify that the email field is optional, edit the Author model (which, as you’ll recall from Chapter 4, lives in mysite/books/models.py). Simply add blank=True to the email field, like so:

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField(blank=True)

This tells Django that a blank value is indeed allowed for authors’ e-mail addresses. By default, all fields have blank=False, which means blank values are not allowed.

There’s something interesting happening here. Until now, with the exception of the __str__() method, our models have served as definitions of our database tables – Pythonic expressions of SQL CREATE TABLE statements, essentially. In adding blank=True, we have begun expanding our model beyond a simple definition of what the database table looks like.

Now, our model class is starting to become a richer collection of knowledge about what Author
objects are and what they can do. Not only is the email field represented by a VARCHAR
column in the database; it’s also an optional field in contexts such as the Django admin site.

Once you’ve added that blank=True, reload the "Add author" edit form (http://127.0.0.1:8000/admin/books/author/add/), and you’ll notice the field’s label – "Email" – is no longer bolded. This signifies it’s not a required field. You can now add authors without needing to provide e-mail addresses; you won’t get the loud red "This field is required" message anymore, if the field is submitted empty.

Making Date and Numeric Fields Optional

A common gotcha related to blank=True has to do with date and numeric fields, but it requires a fair amount of background explanation. SQL has its own way of specifying blank values – a special value called NULL. NULL could mean "unknown," or "invalid," or some other application-specific meaning. In SQL, a value of NULL is different than an empty string, just as the special Python object None is different than an empty Python string ("").

This means it’s possible for a particular character field (e.g., a VARCHAR column) to contain both NULL values and empty string values. This can cause unwanted ambiguity and confusion: "Why does this record have a NULL but this other one has an empty string? Is there a difference, or was the data just entered inconsistently?" And: "How do I get all the records that have a blank value – should I look for both NULL records and empty strings, or do I only select the ones with empty strings?"

To help avoid such ambiguity, Django’s automatically generated CREATE TABLE statements (which were covered in Chapter 4) add an explicit NOT NULL to each column definition. For example, here’s the generated statement for our Author model, from Chapter 4:

CREATE TABLE "books_author" (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(40) NOT NULL,
    "email" varchar(75) NOT NULL
);

In most cases, this default behavior is optimal for your application and will save you from data-inconsistency headaches. And it works nicely with the rest of Django, such as the Django admin site, which inserts an empty string (not a NULL value) when you leave a character field blank.

But there’s an exception with database column types that do not accept empty strings as valid values – such as dates, times and numbers. If you try to insert an empty string into a date or integer column, you’ll likely get a database error, depending on which database you’re using. (PostgreSQL, which is strict, will raise an exception here; MySQL might accept it or might not, depending on the version you’re using, the time of day and the phase of the moon.)

In this case, NULL is the only way to specify an empty value. In Django models, you specify that NULL is allowed by adding null=True to a field. So that’s a long way of saying this – if you want to allow blank values in a date field (e.g., DateField, TimeField, DateTimeField) or numeric field (e.g., IntegerField, DecimalField, FloatField), you’ll need to use both null=True and blank=True.

For example, let’s change our Book model to allow a blank publication_date. Here’s the revised code:

class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField(blank=True, null=True)

Adding null=True is more complicated than adding blank=True, because null=True changes the semantics of the database – that is, it changes the CREATE TABLE statement to remove the NOT NULL from the publication_date field. To complete this change, we’ll need to update the database.

For a number of reasons, Django does not attempt to automate changes to database schemas, so it’s your own responsibility to execute the python manage.py migrate command whenever you make such a change to a model. Bringing this back to the admin site, now the "Add book" edit form should allow for empty publication date values.

Customizing Field Labels

On the admin site’s edit forms, each field’s label is generated from its model field name. The algorithm is simple – Django just replaces underscores with spaces and capitalizes the first character, so, for example, the Book model’s publication_date field has the label "Publication date."

However, field names don’t always lend themselves to nice admin field labels, so in some cases you might want to customize a label. You can do this by specifying verbose_name in the appropriate model field. For example, here’s how we can change the label of the Author.email field to "e-mail," with a hyphen:

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField(blank=True, verbose_name ='e-mail')

Make that change and reload the server, and you should see the field’s new label on the author edit form. Note that you shouldn’t capitalize the first letter of a verbose_name unless it should always be capitalized (e.g., "USA state"). Django will automatically capitalize it when it needs to, and it will use the exact verbose_name value in other places that don’t require capitalization.

Custom ModelAdmin classes

The changes we’ve made so far – blank=True, null=True and verbose_name – are really model-level changes, not admin-level changes. That is, these changes are a fundamental part of the model and just so happen to be used by the admin site; there’s nothing admin-specific about them.

Beyond these, the Django admin site offers a wealth of options that let you customize how the admin site works for a particular model. Such options live in ModelAdmin classes, which are classes that contain configuration for a specific model in a specific admin site instance.

<<< The Django Admin Site | Table of Contents | Customizing Change Lists and Forms >>>