We plan to add different types of content to the course modules such as texts, images, files, and videos. We need a versatile data model that allows us to store diverse content. In Chapter 6, Tracking User Actions, you have learned about the convenience of using generic relations to create foreign keys that can point to objects of any model. We are going to create a Content
model that represents the modules contents and define a generic relation to associate any kind of content.
Edit the models.py
file of the courses
application and add the following imports:
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey
Then add the following code to the end of the file:
class Content(models.Model): module = models.ForeignKey(Module, related_name='contents') content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() item = GenericForeignKey('content_type', 'object_id')
This is the Content
model. A module contains multiple contents, so we define a ForeignKey
field to the Module
model. We also set up a generic relation to associate objects from different models that represent different types of content. Remember that we need three different fields to set up a generic relationship. In our Content
model, these are:
content_type
: A ForeignKey
field to the ContentType
modelobject_id
: This is PositiveIntegerField
to store the primary key of the related objectitem
: A GenericForeignKey
field to the related object by combining the two previous fieldsOnly the content_type
and object_id
fields have a corresponding column in the database table of this model. The item
field allows you to retrieve or set the related object directly, and its functionality is built on top of the other two fields.
We are going to use a different model for each type of content. Our content models will have some common fields, but they will differ in the actual contents they can store.
Django supports model inheritance. It works in a similar way to standard class inheritance in Python. Django offers the following three options to use model inheritance:
Let's take a closer look at each of them.
An abstract model is a base class in which you define fields you want to include in all child models. Django doesn't create any database table for abstract models. A database table is created for each child model, including the fields inherited from the abstract class and the ones defined in the child model.
To mark a model as abstract, you need to include abstract=True
in its Meta
class. Django will recognize that it is an abstract model and will not create a database table for it. To create child models, you just need to subclass the abstract model. The following is an example of an abstract Content
model and a child Text
model:
from django.db import models class BaseContent(models.Model): title = models.CharField(max_length=100) created = models.DateTimeField(auto_now_add=True) class Meta: abstract = True class Text(BaseContent): body = models.TextField()
In this case, Django would create a table for the Text
model only, including the title
, created
, and body
fields.
In multi-table inheritance, each model corresponds to a database table. Django creates a OneToOneField
field for the relationship in the child's model to its parent.
To use multi-table inheritance, you have to subclass an existing model. Django will create a database table for both the original model and the sub-model. The following example shows multi-table inheritance:
from django.db import models class BaseContent(models.Model): title = models.CharField(max_length=100) created = models.DateTimeField(auto_now_add=True) class Text(BaseContent): body = models.TextField()
Django would include an automatically generated OneToOneField
field in the Text
model and create a database table for each model.
Proxy models are used to change the behavior of a model, for example, including additional methods or different meta options. Both models operate on the database table of the original model. To create a proxy model, add proxy=True
to the Meta
class of the model.
The following example illustrates how to create a proxy model:
from django.db import models from django.utils import timezone class BaseContent(models.Model): title = models.CharField(max_length=100) created = models.DateTimeField(auto_now_add=True) class OrderedContent(BaseContent): class Meta: proxy = True ordering = ['created'] def created_delta(self): return timezone.now() - self.created
Here, we define an OrderedContent
model that is a proxy model for the Content
model. This model provides a default ordering for QuerySets and an additional created_delta()
method. Both models, Content
and OrderedContent
, operate on the same database table, and objects are accessible via the ORM through either model.
The Content
model of our courses
application contains a generic relation to associate different types of content to it. We will create a different model for each type of content. All content models will have some fields in common, and additional fields to store custom data. We are going to create an abstract model that provides the common fields for all content models.
Edit the models.py
file of the courses
application and add the following code to it:
class ItemBase(models.Model): owner = models.ForeignKey(User, related_name='%(class)s_related') title = models.CharField(max_length=250) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) class Meta: abstract = True def __str__(self): return self.title class Text(ItemBase): content = models.TextField() class File(ItemBase): file = models.FileField(upload_to='files') class Image(ItemBase): file = models.FileField(upload_to='images') class Video(ItemBase): url = models.URLField()
In this code, we define an abstract model named ItemBase
. Therefore, we have set abstract=True
in its Meta
class. In this model, we define the owner
, title
, created
, and updated
fields. These common fields will be used for all types of content. The owner
field allows us to store which user created the content. Since this field is defined in an abstract class, we need different related_name
for each sub-model. Django allows us to specify a placeholder for the model
class name in the related_name
attribute as %(class)s
. By doing so, related_name
for each child model will be generated automatically. Since we use '%(class)s_related'
as related_name
, the reverse relation for child models will be text_related
, file_related
, image_related
, and video_related
respectively.
We have defined four different content models, which inherit from the ItemBase
abstract model. These are:
Text
: To store text content.File
: To store files, such as PDF.Image
: To store image files.Video
: To store videos. We use an URLField
field to provide a video URL in order to embed it.Each child model contains the fields defined in the ItemBase
class in addition to its own fields. A database table will be created for the Text
, File
, Image
, and Video
models respectively. There will be no database table associated to the ItemBase
model since it is an abstract model.
Edit the Content
model you created previously and modify its content_type
field as follows:
content_type = models.ForeignKey(ContentType, limit_choices_to={'model__in':('text', 'video', 'image', 'file')})
We add a limit_choices_to
argument to limit the ContentType
objects that can be used for the generic relationship. We use the model__in
field lookup to filter the query to the ContentType
objects with a model
attribute that is 'text'
, 'video'
, 'image'
or, 'file'
.
Let's create a migration to include the new models we have added. Run the following command from the command line:
python manage.py makemigrations
You should see the following output:
Migrations for 'courses': 0002_content_file_image_text_video.py: - Create model Content - Create model File - Create model Image - Create model Text - Create model Video
Then, run the following command to apply the new migration:
python manage.py migrate
The output you see should end as follows:
Running migrations: Rendering model states... DONE Applying courses.0002_content_file_image_text_video... OK
We have created models that are suitable to add diverse content to the course modules. However, there is still something missing in our models. The course modules and contents should follow a particular order. We need a field that allows us to order them easily.