Django Tutorial #6: Create Models and Setup Admin Panel

Django Tutorial #6: Create Models and Setup Admin Panel

Please note that this post may contain affiliate links, and for every purchase you make, at no extra cost to you, a commission fee will be rewarded to me.

You can download the source code of this tutorial here.

Design Database Structure

For a simple blogging system, we need at least 4 database tables: UsersCategoriesTags, and Posts. If you want other functions for your blog, comments, for example, you can add other tables yourself. To keep this tutorial short and easy to understand, these four are all we need.

Users Table

idintegerauto increment
namestringcannot be empty
emailstringunique, cannot be empty
passwordstring

The users table is already included in Django and we don’t need to do anything about it.

Categories Table

namestringcannot be empty
slugstringunique, cannot be empty
descriptiontextcan be empty

Tags Table

namestringcannot be empty
slugstringunique, cannot be empty
descriptiontextcan be empty

Posts Table

titlestringcannot be empty
slugstringunique, cannot be empty
featured imagestring or textcan be empty
contenttextcannot be empty
publishedboolean
featuredboolean

Relations

This is the most important part of this tutorial, but unfortunately it is also the most confusing part. Here I would like to introduce three different types of relationships, and they are sufficient to create any kind of website you want.

One-to-one

This is the most basic relation. For example, each User is associated with one Phone.

from django.conf import settings
from django.db import models


class Phone(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
    )

To do that, we create a Phone model, and inside that model, we use OneToOneField to define this relationship. This will create a user_id column in this table, and this column will be used to store the id of the user that this phone belongs to.

Many-to-one

The many-to-one relationships are more complicated. For instance, we have a Category model and a Post model, and each category has multiple posts. This is an example of a many-to-one relationship.

from django.db import models

class Category(models.Model):
    # …
    pass

class Post(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    # …

ForeignKey is how we can define this relationship, and this will create a category_id column in the post table, which will be used to store the id of the category that this post belongs to.

Many-to-many

The many-to-many relationships are the most complicated. Again, as an example, we have a Post model and a Tag model. Each post can have multiple tags and each tag can belong to multiple posts.

from django.db import models

class Post(models.Model):
    # …
    pass

class Tag(models.Model):
    # …
    posts = models.ManyToManyField(Post)

This is how we can define this relationship. Django will create a pivot table in the form of post_tag_xxxx, the xxxx is a random string to make sure this table is unique. And in this table, there will be two columns post_id and tag_id. This way Django will be able to find any associated tags of a post and vice versa.

To make thing clearer, imaging we have a table like this:

post_id tag_id
11
21
32
12
23

For a post with id=1, there are two tags, each with id=1 and id=2. If we want to do things backward, find posts through a tag, we can see that for a tag with id=2, there are two posts, each with id=3 and id=1.

Another point worth mentioning is that it does not matter which model has the ManyToManyField, which means the following code will work just the same.

from django.db import models

class Post(models.Model):
    # …
    tags = models.ManyToManyField(Tag)

class Tag(models.Model):
    # …
    pass

Design Relationships

For our blog website project. There are six relationships we need to take care of.

  • Each user has multiple posts
  • Each category has many posts
  • Each tag belongs to many posts
  • Each post belongs to one user
  • Each post belongs to one category
  • Each post belongs to many tags

Create Models

Now, it’s time for us to implement that design.

Category Model

class Category(models.Model):
    name = models.CharField(max_length=200)
    slug = models.SlugField()
    description = models.TextField()

    class Meta:
        verbose_name_plural = "categories"

    def __str__(self):
        return self.name

The first four lines of code should be easy to understand. What I want to talk about is the Meta class inside our Category model. This is how we add metadata to our models.

Model metadata is “anything that’s not a field”, such as ordering options, database table name etc. In this case, we use verbose_name_plural to define the plural form of “category”. Unfortunately, Django is not as “smart” as Laravel in this particular aspect, if we do not give Django the correct plural form, it will use “categorys”. You can test this in the admin panel.

Here is a list of all Meta options that you can use: https://docs.djangoproject.com/en/3.1/topics/db/models/#meta-options

Tag Model

class Tag(models.Model):
    name = models.CharField(max_length=200)
    slug = models.SlugField()
    description = models.TextField()

    def __str__(self):
        return self.name

This model should be quite easy to understand for you.

Post Model

from ckeditor.fields import RichTextField


class Post(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField()
    content = RichTextField()
    featured_image = models.ImageField(upload_to='images/')
    is_published = models.BooleanField(default=False)
    is_featured = models.BooleanField(default=False)
    created_at = models.DateField(auto_now=True)

    def __str__(self):
        return self.title

Line 1, if you just copy and pasted this code, you editor will tell you that it cannot find the RichTextField and ckeditor. That is because it is not included in the Django framework.

The rich text editor or WYSIWYG HTML editor allows you to edit HTML pages directly without writing the code. In this tutorial, I am using the CKEditor as an example.

To install CKEditor, run the following code:

pip install django-ckeditor

After that, register ckeditor in settings.py:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
    'ckeditor',
]

Relationship

Now, we add relationships to our models. We only need to add three lines of code in the Post model, which is a lot easier compared to Laravel.

category = models.ForeignKey(Category, on_delete=models.CASCADE)
tag = models.ManyToManyField(Tag)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

And since we are using the built in User model, remember the import the settings module.

from django.conf import settings

Last but not least, we need to generate the migration files and apply them.

python manage.py makemigrations
python manage.py migrate

Setup Admin Panel

Our next step would be to setup our admin panel:

from django.contrib import admin
from .models import General, Category, Tag, Post


# Register your models here.
class CategoryAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("name",)}


class TagAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("name",)}


class PostAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("title",)}


admin.site.register(General)
admin.site.register(Category, CategoryAdmin)
admin.site.register(Tag, TagAdmin)
admin.site.register(Post, PostAdmin)

Here we use prepopulated_fields to generate slugs for all categories, tags and posts. Test this by creating a post, as well as some categories and tags in the admin.

Everything should work smoothly. However, our work is not done here. Open the category panel, you will notice that we can access categories from the post page but there is no way to access corresponding posts from the category page. To solve this problem, we need to use InlineModelAdmin.

class PostInlineCategory(admin.StackedInline):
    model = Post
    max_num = 2


class CategoryAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("name",)}
    inlines = [
        PostInlineCategory
    ]

We first create a PostInlineCategory, and then use it in the CategoryAdmin. max_num = 2 means only two posts will be shown on the category page. This is how it looks:

Next, we do something similar to the TagAdmin.

class PostInlineTag(admin.TabularInline):
    model = Post.tag.through
    max_num = 5


class TagAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("name",)}
    inlines = [
        PostInlineTag
    ]

The code is very similar, but notice the model is not just Post, it is Post.tag.through. This is because the relation between Post and Tag is a many-to-many relationship. This is the final result.

Leave a Reply

Your email address will not be published. Required fields are marked *