Adding ordering to module and content objects

Let's add the new field to our 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'])

We name the new field order, and we 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 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 '{}. {}'.format(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, we 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 '{}. {}'.format(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:

You are trying to add a non-nullable field 'order' to content without a default; we can't do that (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 let me add a default in models.py
Select an option:

Django is telling us that we have to provide a default value for the new order field for existing rows in the database. If the field had null=True, it would accept null values and Django would create the migration automatically instead of asking for a default value. We 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 now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
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_auto_20180326_0704.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_auto_20180326_0704... OK

Let's test our 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')

We have created a course in the database. Now, let's add modules to the course and see how their order is automatically calculated. We 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. Now, we 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 we specify a custom order, the OrderField field does not interfere and the value given to the order is used.

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. Our 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 we specified for_fields=['course'] in the order field of the Module model.

Congratulations! You have successfully created your first custom model field.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset