Written by Super User.

8- Creating Django Models (Database Tables)

It is time to create our database tables. We already have a user table. When we design our database we should plan carefully. We need to follow normalization rules. You can read more about normalization on https://www.w3schools.in/dbms/database-normalization

We also need the determine the relationships between tables; One to One relationship, One to Many relationship or Many to Many relationship.

 

One to One Relationship: One record in a table is associated with one and only one record in another table. (We can't use this because that means, A user can only watch 1 specific movie, nothing else).

One to Many Relationship: One record in a table can be associated with one or more records in another table. (A user can watch one or more movies but movies cannot be watched by many different users)

Many To Many Relationship: Multiple records in a table are associated with multiple records in another table. (Multiple different users can watch multiple different movies and multiple different movies can be watched by multiple different users.)

 

Here is my database design and I will be using many to many relationships between tables. In many to many design, generally we have 2 main table (users and movies in this case ) and another table (Watched table or Comments table in my example) in the middle that has both user_id and movies_id. Also I have many to many relationship between Movies and Actors table. MovieAct table sits in the middle and has both movie_id and actor_id.

Notice that Watched and Comments Tables has extra data other than user_id and movie_id. So I will manually create models for Watched and Comments tables. On the other hand, MovieAct has no extra data. Therefore, Django will automatically generate a table to manage many-to-many relationship between Movies and Actors.

 

USER Model:

First I will add city and country fields to Users model.

models.py

from django.db import models
from django.contrib.auth.models import AbstractUser

class MyCustomUser(AbstractUser):
    pass
    dateofbirth = models.DateField(blank=True, null=True)
    sex = models.CharField(max_length=10, blank=True, null=True)
    city = models.CharField(max_length=20, blank=True, null=True)
    country= models.CharField(max_length=20, blank=True, null=True)

    def __str__(self):
        return self.username

 

To see these newly added fields in admin panel, I add these fields in list_display like below.

from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin

from .forms import MyCustomUserCreationForm, MyCustomUserChangeForm
from .models import MyCustomUser

class MyCustomUserAdmin(UserAdmin):
    add_form = MyCustomUserCreationForm
    form = MyCustomUserChangeForm
    model = MyCustomUser
    list_display = ['email', 'username', 'is_superuser', 'date_joined', 'dateofbirth', 'sex', 'city', 'country']



admin.site.register(MyCustomUser, MyCustomUserAdmin)

 

Now run makemigrations and migrate command to modify the User table. Go ahead check the admin panel, now you should see these new fields in Users section.

Now I will create the other models; Movie, Actor, Watched and UserComment.

 

MOVIE Model:

models.py

class Movie(models.Model):
    title= models.CharField(max_length=100)
    gender= models.CharField(max_length=20)
    description= models.CharField(max_length=255, blank=True, null=True)
    moviefile= models.FileField(upload_to='movie', blank=True, null=True)
    photo= models.FileField(upload_to='photo',blank=True, null=True)
    publishDate= models.DateField(blank=True, null=True)
    imdbID= models.CharField(max_length=10, blank=True, null=True)
    movieapp_score= models.FloatField(blank=True, null=True)
    date_added= models.DateField(auto_now=True)
    numberOfViews= models.IntegerField(default=0)
    duration= models.FloatField(blank=True, null=True)
    resolution= models.CharField(max_length=5)
    users = models.ManyToManyField(MyCustomUser, through='UserComment')
    
    
    def __str__(self):
        return self.title
    class Meta:
        ordering = ('id',)

 

 

USERCOMMENT Model:  

In many to many relationship, Django normally handles and creates the through table (middle table) internally between user and movie models. We do not need to define a middle model unless we have some extra fields on the middle table (comment table)

However, I need some extra fields in comment table. Therefore I have to explicitly define comment model like below. Note that, I use "models.ForeignKey" for both MyCustomUser and Movie tables (Not ManyToMany).  In Movie table note that, user field is defined as ManyToMany and I specify this relation will be made through "UserComment" middle table.

class UserComment(models.Model):
    user = models.ForeignKey('MyCustomUser', blank=True, null=True, related_name='Comment_User', on_delete=models.CASCADE)
    movie = models.ForeignKey('Movie',blank=True, null=True, related_name='Comment_Movie', on_delete=models.CASCADE)
    comment = models.CharField(max_length=255, blank=True, null=True)
    numberOfLikes = models.IntegerField(blank=True, null=True)
    numberOfDislikes = models.IntegerField(blank=True, null=True)
    
    def __str__(self):
        return self.comment
    class Meta:
        ordering = ('id',)

 

Register Movie and Comment models in admin.py and then run migration commands.

admin.py

from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin

from .forms import MyCustomUserCreationForm, MyCustomUserChangeForm
from .models import  MyCustomUser, Movie, UserComment

class MyCustomUserAdmin(UserAdmin):
    add_form = MyCustomUserCreationForm
    form = MyCustomUserChangeForm
    model = MyCustomUser
    list_display = ['email', 'username', 'is_superuser', 'date_joined', 'dateofbirth', 'sex', 'city', 'country']



admin.site.register(MyCustomUser, MyCustomUserAdmin)
admin.site.register(Movie)
admin.site.register(UserComment)

 

I have already created some movies on admin panel. Now I create a comment for the movie Brave Heart.

 

WATCHED Model:

This table has also 1 extra field. Therefore I have to define this model, as well. datewatched field will be filled with the current time automatically. Note that, I use "models.ForeignKey" for both MyCustomUser and Movie tables (Not ManyToMany). 

class Watched(models.Model):
    user = models.ForeignKey('MyCustomUser', blank=True, null=True, related_name='Watched_User', on_delete=models.CASCADE)
    movie = models.ForeignKey('Movie',blank=True, null=True, related_name='Watched_Movie', on_delete=models.CASCADE) 
    date_watched = models.DateField(auto_now=True)
    def __str__(self):
        return self.user.username
    class Meta:
        ordering = ('id',)

 

Register this model to admin.py and run migration commands. Watched table returns the user's username, So we can see the username on Admin Panel.

 

 

Actor Model:

Because I will not have any extra field on in the middle table it is enought to create only Actor model and Django will create the through table byitself.

class Actor(models.Model):
    actorname = models.CharField(max_length=25, blank=True, null=True)
    movies = models.ManyToManyField(Movie)
    def __str__(self):
        return self.actorname

Register this model to admin.py and run migration commands.

from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin

from .forms import MyCustomUserCreationForm, MyCustomUserChangeForm
from .models import  MyCustomUser, Movie, UserComment, Watched, Actor

class MyCustomUserAdmin(UserAdmin):
    add_form = MyCustomUserCreationForm
    form = MyCustomUserChangeForm
    model = MyCustomUser
    list_display = ['email', 'username', 'is_superuser', 'date_joined', 'dateofbirth', 'sex', 'city', 'country']



admin.site.register(MyCustomUser, MyCustomUserAdmin)
admin.site.register(Movie)
admin.site.register(UserComment)
admin.site.register(Watched)
admin.site.register(Actor)

 

 

 

actor_movies table is automatically created by Django, as we can see below 

I think we are done with the database design for now. I might be adding, removing or modifying some fields as it might require. 

 

 

 

 

Customizing Django Admin Panel:

When I check the database records for Movies on Admin Panel, I can only see the returned value title. I want see more fields on this page. Also I want to be able to filter by gender for example and I want to search movie titles.

 

Open admin.py and add a new class (MovieAdmin in my case) , define what fields you want to display (list_display) and register this new class (MovieAdmin) like below.

list_filter will create a filter option on the admin panel (gender, imdbID, resolution)

search_fields will place a search box that I search whatever field I defined (movie title in my case)

from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin

from .forms import MyCustomUserCreationForm, MyCustomUserChangeForm
from .models import  MyCustomUser, Movie, UserComment, Watched, Actor

class MyCustomUserAdmin(UserAdmin):
    add_form = MyCustomUserCreationForm
    form = MyCustomUserChangeForm
    model = MyCustomUser
    list_display = ['email', 'username', 'is_superuser', 'date_joined', 'dateofbirth', 'sex', 'city', 'country']

class MovieAdmin(admin.ModelAdmin):
    list_display = ['title', 'gender', 'imdbID', 'resolution', 'publishDate', 'date_added']
    list_filter = ['gender', 'imdbID', 'resolution']
    search_fields = ['title']

admin.site.register(MyCustomUser, MyCustomUserAdmin)
admin.site.register(Movie, MovieAdmin)
admin.site.register(UserComment)
admin.site.register(Watched)
admin.site.register(Actor)

 

Here is the result:

 

You can create a new class for each table and customize them for your Admin Panel just like that.