Django Tutorial #6: Create Models and Setup Admin Panel

    Django Tutorial #6: Create Models and Setup Admin Panel

    More Tutorials on Web Development


    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 *