In the previous chapter, you learned the basics of the internationalization and localization of Django projects. You added internationalization to your online shop project. You learned how to translate Python strings, templates, and models. You also learned how to manage translations, and you created a language selector and added localized fields to your forms.
In this chapter, you will start a new Django project that will consist of an e-learning platform with your own content management system (CMS). Online learning platforms are a great example of applications where you need to provide tools to generate content with flexibility in mind.
In this chapter, you will learn how to:
The source code for this chapter can be found at https://github.com/PacktPublishing/Django-4-by-example/tree/main/Chapter12.
All the Python modules used in this chapter are included in the requirements.txt
file in the source code that comes with this chapter. You can follow the instructions to install each Python module below, or you can install all the requirements at once with the command pip install -r requirements.txt
.
Your final practical project will be an e-learning platform. First, create a virtual environment for your new project within the env/
directory with the following command:
python -m venv env/educa
If you are using Linux or macOS, run the following command to activate your virtual environment:
source env/educa/bin/activate
If you are using Windows, use the following command instead:
.enveducaScriptsactivate
Install Django in your virtual environment with the following command:
pip install Django~=4.1.0
You are going to manage image uploads in your project, so you also need to install Pillow
with the following command:
pip install Pillow==9.2.0
Create a new project using the following command:
django-admin startproject educa
Enter the new educa
directory and create a new application using the following commands:
cd educa
django-admin startapp courses
Edit the settings.py
file of the educa
project and add courses
to the INSTALLED_APPS
setting, as follows. The new line is highlighted in bold:
INSTALLED_APPS = [
'courses.apps.CoursesConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
The courses
application is now active for the project. Next, we are going to prepare our project to serve media files, and we will define the models for the courses and course contents.
Before creating the models for courses and course contents, we will prepare the project to serve media files. Course instructors will be able to upload media files to course contents using the CMS that we will build. Therefore, we will configure the project to serve media files.
Edit the settings.py
file of the project and add the following lines:
MEDIA_URL = 'media/'
MEDIA_ROOT = BASE_DIR / 'media'
This will enable Django to manage file uploads and serve media files. MEDIA_URL
is the base URL used to serve the media files uploaded by users. MEDIA_ROOT
is the local path where they reside. Paths and URLs for files are built dynamically by prepending the project path or the media URL to them for portability.
Now, edit the main urls.py
file of the educa
project and modify the code, as follows. New lines are highlighted in bold:
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
We have added the static()
helper function to serve media files with the Django development server during development (that is, when the DEBUG
setting is set to True
).
Remember that the static()
helper function is suitable for development but not for production use. Django is very inefficient at serving static files. Never serve your static files with Django in a production environment. You will learn how to serve static files in a production environment in Chapter 17, Going Live.
The project is now ready to serve media files. Let’s create the models for the courses and course contents.
Your e-learning platform will offer courses on various subjects. Each course will be divided into a configurable number of modules, and each module will contain a configurable number of contents. The contents will be of various types: text, files, images, or videos. The following example shows what the data structure of your course catalog will look like:
Subject 1
Course 1
Module 1
Content 1 (image)
Content 2 (text)
Module 2
Content 3 (text)
Content 4 (file)
Content 5 (video)
...
Let’s build the course models. Edit the models.py
file of the courses
application and add the following code to it:
from django.db import models
from django.contrib.auth.models import User
class Subject(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
class Meta:
ordering = ['title']
def __str__(self):
return self.title
class Course(models.Model):
owner = models.ForeignKey(User,
related_name='courses_created',
on_delete=models.CASCADE)
subject = models.ForeignKey(Subject,
related_name='courses',
on_delete=models.CASCADE)
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
overview = models.TextField()
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created']
def __str__(self):
return self.title
class Module(models.Model):
course = models.ForeignKey(Course,
related_name='modules',
on_delete=models.CASCADE)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
def __str__(self):
return self.title
These are the initial Subject
, Course
, and Module
models. The Course
model fields are as follows:
owner
: The instructor who created this course.subject
: The subject that this course belongs to. It is a ForeignKey
field that points to the Subject
model.title
: The title of the course.slug
: The slug of the course. This will be used in URLs later.overview
: A TextField
column to store an overview of the course.created
: The date and time when the course was created. It will be automatically set by Django when creating new objects because of auto_now_add=True
.Each course is divided into several modules. Therefore, the Module
model contains a ForeignKey
field that points to the Course
model.
Open the shell and run the following command to create the initial migration for this application:
python manage.py makemigrations
You will see the following output:
Migrations for 'courses':
courses/migrations/0001_initial.py:
- Create model Course
- Create model Module
- Create model Subject
- Add field subject to course
Then, run the following command to apply all migrations to the database:
python manage.py migrate
You should see output that includes all applied migrations, including those of Django. The output will contain the following line:
Applying courses.0001_initial... OK
The models of your courses
application have been synced with the database.
Let’s add the course models to the administration site. Edit the admin.py
file inside the courses
application directory and add the following code to it:
from django.contrib import admin
from .models import Subject, Course, Module
@admin.register(Subject)
class SubjectAdmin(admin.ModelAdmin):
list_display = ['title', 'slug']
prepopulated_fields = {'slug': ('title',)}
class ModuleInline(admin.StackedInline):
model = Module
@admin.register(Course)
class CourseAdmin(admin.ModelAdmin):
list_display = ['title', 'subject', 'created']
list_filter = ['created', 'subject']
search_fields = ['title', 'overview']
prepopulated_fields = {'slug': ('title',)}
inlines = [ModuleInline]
The models for the course application are now registered on the administration site. Remember that you use the @admin.register()
decorator to register models on the administration site.
Sometimes, you might want to prepopulate your database with hardcoded data. This is useful for automatically including initial data in the project setup, instead of having to add it manually. Django comes with a simple way to load and dump data from the database into files that are called fixtures. Django supports fixtures in JSON, XML, or YAML formats. You are going to create a fixture to include several initial Subject
objects for your project.
First, create a superuser using the following command:
python manage.py createsuperuser
Then, run the development server using the following command:
python manage.py runserver
Open http://127.0.0.1:8000/admin/courses/subject/
in your browser. Create several subjects using the administration site. The change list page should look as follows:
Figure 12.1: The subject change list view on the administration site
Run the following command from the shell:
python manage.py dumpdata courses --indent=2
You will see an output similar to the following:
[
{
"model": "courses.subject",
"pk": 1,
"fields": {
"title": "Mathematics",
"slug": "mathematics"
}
},
{
"model": "courses.subject",
"pk": 2,
"fields": {
"title": "Music",
"slug": "music"
}
},
{
"model": "courses.subject",
"pk": 3,
"fields": {
"title": "Physics",
"slug": "physics"
}
},
{
"model": "courses.subject",
"pk": 4,
"fields": {
"title": "Programming",
"slug": "programming"
}
}
]
The dumpdata
command dumps data from the database into the standard output, serialized in JSON format by default. The resulting data structure includes information about the model and its fields for Django to be able to load it into the database.
You can limit the output to the models of an application by providing the application names to the command, or specifying single models for outputting data using the app.Model
format. You can also specify the format using the --format
flag. By default, dumpdata
outputs the serialized data to the standard output. However, you can indicate an output file using the --output
flag. The --indent
flag allows you to specify indentations. For more information on dumpdata
parameters, run python manage.py dumpdata --help
.
Save this dump to a fixtures file in a new fixtures/
directory in the courses
application using the following commands:
mkdir courses/fixtures
python manage.py dumpdata courses --indent=2 --output=courses/fixtures/subjects.json
Run the development server and use the administration site to remove the subjects you created, as shown in Figure 12.2:
Figure 12.2: Deleting all existing subjects
After deleting all subjects, load the fixture into the database using the following command:
python manage.py loaddata subjects.json
All Subject
objects included in the fixture are loaded into the database again:
Figure 12.3: Subjects from the fixture are now loaded into the database
By default, Django looks for files in the fixtures/
directory of each application, but you can specify the complete path to the fixture file for the loaddata
command. You can also use the FIXTURE_DIRS
setting to tell Django additional directories to look in for fixtures.
Fixtures are not only useful for setting up initial data, but also for providing sample data for your application or data required for your tests.
You can read about how to use fixtures for testing at https://docs.djangoproject.com/en/4.1/topics/testing/tools/#fixture-loading.
If you want to load fixtures in model migrations, look at Django’s documentation about data migrations. You can find the documentation for migrating data at https://docs.djangoproject.com/en/4.1/topics/migrations/#data-migrations.
You have created the models to manage course subjects, courses, and course modules. Next, you will create models to manage different types of module contents.
You plan to add different types of content to the course modules, such as text, images, files, and videos. Polymorphism is the provision of a single interface to entities of different types. You need a versatile data model that allows you to store diverse content that is accessible through a single interface. In Chapter 7, Tracking User Actions, you learned about the convenience of using generic relations to create foreign keys that can point to the objects of any model. You are going to create a Content
model that represents the modules’ contents and define a generic relation to associate any object with the content object.
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',
on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType,
on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
item = GenericForeignKey('content_type', 'object_id')
This is the Content
model. A module contains multiple contents, so you define a ForeignKey
field that points to the Module
model. You can also set up a generic relation to associate objects from different models that represent different types of content. Remember that you need three different fields to set up a generic relation. In your Content
model, these are:
content_type
: A ForeignKey
field to the ContentType
model.object_id
: A PositiveIntegerField
to store the primary key of the related object.item
: A GenericForeignKey
field to the related object combining the two previous fields.Only 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.
You are going to use a different model for each type of content. Your Content
models will have some common fields, but they will differ in the actual data they can store. This is how you will create a single interface for different types of content.
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 the fields you want to include in all child models. Django doesn’t create any database tables 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 example shows 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 between the child model and its parent model. 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 will include an automatically generated OneToOneField
field in the Text
model and create a database table for each model.
A proxy model changes the behavior of a model. 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, you 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 your courses
application contains a generic relation to associate different types of content with it. You 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. You 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',
on_delete=models.CASCADE)
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, you define an abstract model named ItemBase
. Therefore, you set abstract=True
in its Meta
class.
In this model, you define the owner
, title
, created
, and updated
fields. These common fields will be used for all types of content.
The owner
field allows you to store which user created the content. Since this field is defined in an abstract class, you need a different related_name
for each sub-model. Django allows you to specify a placeholder for the model class name in the related_name
attribute as %(class)s
. By doing so, the related_name
for each child model will be generated automatically. Since you are using '%(class)s_related'
as the related_name
, the reverse relationship for child models will be text_related
, file_related
, image_related
, and video_related
, respectively.
You have defined four different Content
models that inherit from the ItemBase
abstract model. They are as follows:
Text
: To store text contentFile
: To store files, such as PDFsImage
: To store image filesVideo
: To store videos; you use an URLField
field to provide a video URL in order to embed itEach 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 with 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,
on_delete=models.CASCADE,
limit_choices_to={'model__in':(
'text',
'video',
'image',
'file')})
You add a limit_choices_to
argument to limit the ContentType
objects that can be used for the generic relation. You 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 you have added. Run the following command from the command line:
python manage.py makemigrations
You will see the following output:
Migrations for 'courses':
courses/migrations/0002_video_text_image_file_content.py
- Create model Video
- Create model Text
- Create model Image
- Create model File
- Create model Content
Then, run the following command to apply the new migration:
python manage.py migrate
The output you see should end with the following line:
Applying courses.0002_video_text_image_file_content... OK
You have created models that are suitable for adding diverse content to the course modules. However, there is still something missing in your models: the course modules and contents should follow a particular order. You need a field that allows you to order them easily.
Django comes with a complete collection of model fields that you can use to build your models. However, you can also create your own model fields to store custom data or alter the behavior of existing fields.
You need a field that allows you to define an order for the objects. An easy way to specify an order for objects using existing Django fields is by adding a PositiveIntegerField
to your models. Using integers, you can easily specify the order of the objects. You can create a custom order field that inherits from PositiveIntegerField
and provides additional behavior.
There are two relevant functionalities that you will build into your order field:
1
and 2
respectively, when saving a third object, you should automatically assign order 3
to it if no specific order has been provided.Create a new fields.py
file inside the courses
application directory and add the following code to it:
from django.db import models
from django.core.exceptions import ObjectDoesNotExist
class OrderField(models.PositiveIntegerField):
def __init__(self, for_fields=None, *args, **kwargs):
self.for_fields = for_fields
super().__init__(*args, **kwargs)
def pre_save(self, model_instance, add):
if getattr(model_instance, self.attname) is None:
# no current value
try:
qs = self.model.objects.all()
if self.for_fields:
# filter by objects with the same field values
# for the fields in "for_fields"
query = {field: getattr(model_instance, field)
for field in self.for_fields}
qs = qs.filter(**query)
# get the order of the last item
last_item = qs.latest(self.attname)
value = last_item.order + 1
except ObjectDoesNotExist:
value = 0
setattr(model_instance, self.attname, value)
return value
else:
return super().pre_save(model_instance, add)
This is the custom OrderField
. It inherits from the PositiveIntegerField
field provided by Django. Your OrderField
field takes an optional for_fields
parameter, which allows you to indicate the fields used to order the data.
Your field overrides the pre_save()
method of the PositiveIntegerField
field, which is executed before saving the field to the database. In this method, you perform the following actions:
self.attname
, which is the attribute name given to the field in the model. If the attribute’s value is different from None
, you calculate the order you should give it as follows:self.model
.for_fields
attribute of the field, you filter the QuerySet by the current value of the model fields in for_fields
. By doing so, you calculate the order with respect to the given fields.last_item = qs.latest(self.attname)
from the database. If no object is found, you assume this object is the first one and assign order 0
to it.1
to the highest order found.setattr()
and return it.When you create custom model fields, make them generic. Avoid hardcoding data that depends on a specific model or field. Your field should work in any model.
You can find more information about writing custom model fields at https://docs.djangoproject.com/en/4.1/howto/custom-model-fields/.
Let’s add the new field to your models. Edit the models.py
file of the courses
application, and import the OrderField
class and a field to the Module
model, as follows:
from .fields import OrderField
class Module(models.Model):
# ...
order = OrderField(blank=True, for_fields=['course'])
You name the new field order
and specify that the ordering is calculated with respect to the course by setting for_fields=['course']
. This means that the order for a new module will be assigned by adding 1
to the last module of the same Course
object.
Now, you can edit the __str__()
method of the Module
model to include its order, as follows:
class Module(models.Model):
# ...
def __str__(self):
return f'{self.order}. {self.title}'
Module contents also need to follow a particular order. Add an OrderField
field to the Content
model, as follows:
class Content(models.Model):
# ...
order = OrderField(blank=True, for_fields=['module'])
This time, you specify that the order is calculated with respect to the module
field.
Finally, let’s add a default ordering for both models. Add the following Meta
class to the Module
and Content
models:
class Module(models.Model):
# ...
class Meta:
ordering = ['order']
class Content(models.Model):
# ...
class Meta:
ordering = ['order']
The Module
and Content
models should now look as follows:
class Module(models.Model):
course = models.ForeignKey(Course,
related_name='modules',
on_delete=models.CASCADE)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
order = OrderField(blank=True, for_fields=['course'])
class Meta:
ordering = ['order']
def __str__(self):
return f'{self.order}. {self.title}'
class Content(models.Model):
module = models.ForeignKey(Module,
related_name='contents',
on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType,
on_delete=models.CASCADE,
limit_choices_to={'model__in':(
'text',
'video',
'image',
'file')})
object_id = models.PositiveIntegerField()
item = GenericForeignKey('content_type', 'object_id')
order = OrderField(blank=True, for_fields=['module'])
class Meta:
ordering = ['order']
Let’s create a new model migration that reflects the new order fields. Open the shell and run the following command:
python manage.py makemigrations courses
You will see the following output:
It is impossible to add a non-nullable field 'order' to content without specifying a default. This is because the database needs something to populate existing rows.
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit and manually define a default value in models.py.
Select an option:
Django is telling you that you have to provide a default value for the new order
field for existing rows in the database. If the field includes null=True
, it accepts null values and Django creates the migration automatically instead of asking for a default value. You can specify a default value, or cancel the migration and add a default
attribute to the order
field in the models.py
file before creating the migration.
Enter 1
and press Enter to provide a default value for existing records. You will see the following output:
Please enter the default value as valid Python.
The datetime and django.utils.timezone modules are available, so it is possible to provide e.g. timezone.now as a value.
Type 'exit' to exit this prompt
>>>
Enter 0
so that this is the default value for existing records and press Enter. Django will ask you for a default value for the Module
model too. Choose the first option and enter 0
as the default value again. Finally, you will see an output similar to the following one:
Migrations for 'courses':
courses/migrations/0003_alter_content_options_alter_module_options_and_more.py
- Change Meta options on content
- Change Meta options on module
- Add field order to content
- Add field order to module
Then, apply the new migrations with the following command:
python manage.py migrate
The output of the command will inform you that the migration was successfully applied, as follows:
Applying courses.0003_alter_content_options_alter_module_options_and_more... OK
Let’s test your new field. Open the shell with the following command:
python manage.py shell
Create a new course, as follows:
>>> from django.contrib.auth.models import User
>>> from courses.models import Subject, Course, Module
>>> user = User.objects.last()
>>> subject = Subject.objects.last()
>>> c1 = Course.objects.create(subject=subject, owner=user, title='Course 1', slug='course1')
You have created a course in the database. Now, you will add modules to the course and see how their order is automatically calculated. You create an initial module and check its order:
>>> m1 = Module.objects.create(course=c1, title='Module 1')
>>> m1.order
0
OrderField
sets its value to 0
, since this is the first Module
object created for the given course. You can create a second module for the same course:
>>> m2 = Module.objects.create(course=c1, title='Module 2')
>>> m2.order
1
OrderField
calculates the next order value, adding 1
to the highest order for existing objects. Let’s create a third module, forcing a specific order:
>>> m3 = Module.objects.create(course=c1, title='Module 3', order=5)
>>> m3.order
5
If you provide a custom order when creating or saving an object, OrderField
will use that value instead of calculating the order.
Let’s add a fourth module:
>>> m4 = Module.objects.create(course=c1, title='Module 4')
>>> m4.order
6
The order for this module has been automatically set. Your OrderField
field does not guarantee that all order values are consecutive. However, it respects existing order values and always assigns the next order based on the highest existing order.
Let’s create a second course and add a module to it:
>>> c2 = Course.objects.create(subject=subject, title='Course 2', slug='course2', owner=user)
>>> m5 = Module.objects.create(course=c2, title='Module 1')
>>> m5.order
0
To calculate the new module’s order, the field only takes into consideration existing modules that belong to the same course. Since this is the first module of the second course, the resulting order is 0
. This is because you specified for_fields=['course']
in the order
field of the Module
model.
Congratulations! You have successfully created your first custom model field. Next, you are going to create an authentication system for the CMS.
Now that you have created a polymorphic data model, you are going to build a CMS to manage the courses and their contents. The first step is to add an authentication system for the CMS.
You are going to use Django’s authentication framework for users to authenticate to the e-learning platform. Both instructors and students will be instances of Django’s User
model, so they will be able to log in to the site using the authentication views of django.contrib.auth
.
Edit the main urls.py
file of the educa
project and include the login
and logout
views of Django’s authentication framework:
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from django.contrib.auth import views as auth_views
urlpatterns = [
path('accounts/login/', auth_views.LoginView.as_view(),
name='login'),
path('accounts/logout/', auth_views.LogoutView.as_view(),
name='logout'),
path('admin/', admin.site.urls),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
Create the following file structure inside the courses
application directory:
templates/
base.html
registration/
login.html
logged_out.html
Before building the authentication templates, you need to prepare the base template for your project. Edit the base.html
template file and add the following content to it:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>{% block title %}Educa{% endblock %}</title>
<link href="{% static "css/base.css" %}" rel="stylesheet">
</head>
<body>
<div id="header">
<a href="/" class="logo">Educa</a>
<ul class="menu">
{% if request.user.is_authenticated %}
<li><a href="{% url "logout" %}">Sign out</a></li>
{% else %}
<li><a href="{% url "login" %}">Sign in</a></li>
{% endif %}
</ul>
</div>
<div id="content">
{% block content %}
{% endblock %}
</div>
<script>
document.addEventListener('DOMContentLoaded', (event) => {
// DOM loaded
{% block domready %}
{% endblock %}
})
</script>
</body>
</html>
This is the base template that will be extended by the rest of the templates. In this template, you define the following blocks:
title
: The block for other templates to add a custom title for each page.content
: The main block for content. All templates that extend the base template should add content to this block.domready
: Located inside the JavaScript event listener for the DOMContentLoaded
event. It allows you to execute code when the Document Object Model (DOM) has finished loading.The CSS styles used in this template are located in the static/
directory of the courses
application in the code that comes with this chapter. Copy the static/
directory into the same directory of your project to use them. You can find the contents of the directory at https://github.com/PacktPublishing/Django-4-by-Example/tree/main/Chapter12/educa/courses/static.
Edit the registration/login.html
template and add the following code to it:
{% extends "base.html" %}
{% block title %}Log-in{% endblock %}
{% block content %}
<h1>Log-in</h1>
<div class="module">
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% else %}
<p>Please, use the following form to log-in:</p>
{% endif %}
<div class="login-form">
<form action="{% url 'login' %}" method="post">
{{ form.as_p }}
{% csrf_token %}
<input type="hidden" name="next" value="{{ next }}" />
<p><input type="submit" value="Log-in"></p>
</form>
</div>
</div>
{% endblock %}
This is a standard login template for Django’s login
view.
Edit the registration/logged_out.html
template and add the following code to it:
{% extends "base.html" %}
{% block title %}Logged out{% endblock %}
{% block content %}
<h1>Logged out</h1>
<div class="module">
<p>
You have been successfully logged out.
You can <a href="{% url "login" %}">log-in again</a>.
</p>
</div>
{% endblock %}
This is the template that will be displayed to the user after logging out. Run the development server with the following command:
python manage.py runserver
Open http://127.0.0.1:8000/accounts/login/
in your browser. You should see the login page:
Figure 12.4: The account login page
Open http://127.0.0.1:8000/accounts/logout/
in your browser. You should see the Logged out page now, as shown in Figure 12.5:
Figure 12.5: The account logged out page
You have successfully created an authentication system for the CMS.
The following resources provide additional information related to the topics covered in this chapter:
In this chapter, you learned how to use fixtures to provide initial data for models. By using model inheritance, you created a flexible system to manage different types of content for the course modules. You also implemented a custom model field on order objects and created an authentication system for the e-learning platform.
In the next chapter, you will implement the CMS functionality to manage course contents using class-based views. You will use the Django groups and permissions system to restrict access to views, and you will implement formsets to edit the content of courses. You will also create a drag-and-drop functionality to reorder course modules and their content using JavaScript and Django.
Read this book alongside other users and the author.
Ask questions, provide solutions to other readers, chat with the author via Ask Me Anything sessions, and much more. Scan the QR code or visit the link to join the book community.