Creating custom model fields

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.

We need a field that allows us to define an order for objects. An easy way to specify an order for objects using existing Django fields is by adding a PositiveIntegerField to your models. Using integers, we can easily specify the order of objects. We can create a custom order field that inherits from PositiveIntegerField and provides additional behavior.

There are two relevant functionalities that we will build into our order field:

  • Automatically assign an order value when no specific order is provided: When saving a new object with no specific order, our field should automatically assign the number that comes after the last existing ordered object. If there are two objects with order 1 and 2 respectively, when saving a third object, we should automatically assign the order 3 to it if no specific order has been provided.
  • Order objects with respect to other fields: Course modules will be ordered with respect to the course they belong to and module contents with respect to the module they belong to.

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(OrderField, self).__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(OrderField,
self).pre_save(model_instance, add)

This is our custom OrderField. It inherits from the PositiveIntegerField field provided by Django. Our OrderField field takes an optional for_fields parameter that allows us to indicate the fields that the order has to be calculated with respect to.

Our field overrides the pre_save() method of the PositiveIntegerField field, which is executed before saving the field into the database. In this method, we perform the following actions:

  1. We check if a value already exists for this field in the model instance. We use self.attname, which is the attribute name given to the field in the model. If the attribute's value is different than None, we calculate the order we should give it as follows:
    1. We build a QuerySet to retrieve all objects for the field's model. We retrieve the model class the field belongs to by accessing self.model.
    2. We filter the QuerySet by the fields' current value for the model fields that are defined in the for_fields parameter of the field, if any. By doing so, we calculate the order with respect to the given fields.
    1. We retrieve the object with the highest order with last_item = qs.latest(self.attname) from the database. If no object is found, we assume this object is the first one and assign the order 0 to it.
    2. If an object is found, we add 1 to the highest order found.
    3. We assign the calculated order to the field's value in the model instance using setattr() and return it.
  2. If the model instance has a value for the current field, we don't do anything.
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/2.0/howto/custom-model-fields/.

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

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