Django Models Part 4 (Model validation, Model Meta, Abstract Model class)

Django Models Part 4 (Model validation, Model Meta, Abstract Model class)

ยท

6 min read

Model Field Validation

To validate a model field on application level we can create up a validator which is a callable that take the value to be validated and raises error (ValidationError to be precise) if the value doesn't meet the criteria or it is invalid

The ValidationError it raises can be imported as from django.core.exceptions import ValidationError

1. Individual Field validation

We can add callable validators to our model field through validators arguments

Let's take a simple example of a model having a field email which requires @djangotherightway.com emails to be valid

from django.core.exceptions import ValidationError
from django.db import models

def django_the_right_way_email_validator(email):
    if not email.endswith('@djangotherightway.com'):
        raise ValidationError('Email must end with @djangotherightway.com')

class Info(models.Model):
    email = models.EmailField(
        validators=[django_the_right_way_email_validator]
    )

You can add as many validator function/callables to a field

2. Validating multiple fields together

Sometime we come up for a need to validate two or more field together or are dependent on each other, in such scenario we can use clean() method provided by models.Model

Let's add up simple requirements on our previous model example. Let's add up phone_number and age to our model, and imagine a situation where phone_number is required if age>20

from django.core.exceptions import ValidationError
from django.db import models

def django_the_right_way_email(email):
    if not email.endswith('@djangotherightway.com'):
        raise ValidationError('Email must end with @djangotherightway.com')

class Info(models.Model):
    email = models.EmailField(
        validators=[django_the_right_way_email]
    )
    # make phone nullable, since phone_number is
    # required only if age>20
    phone_number = models.CharField(
        max_length=10, null=True, blank=True
    )
    age = models.IntegerField()

    def clean(self):
        if self.age > 20:
            if not self.phone_number:
                raise ValidationError(
                    'Phone number is required if age is greater than 20'
                )

Running Validators

These validators we created will not run automatically when you create/update object/row. To make this validators run, we need to call full_clean() method which under the hood runs all the validator

Note: Validators run automatically when using ModelForm

# this code can be on your view func or ...
# ...for this example you can use django shell 
#  Open Django shell using: python manage.py shell
obj = Info()
obj.email = "example@djangotherightway.com"
obj.age = 21
obj.phone_number = '1234567890'
try:
    obj.full_clean()
except ValidationError as e:
    print(e)
else:
    obj.save()

Have a look into full_clean() Django code:Github where it runs all individual field validators like django_the_right_way_email in our case and even runs clean() method we implemented for validations

You can call full_clean() from your models save() method, so that you don't have to manually call full_clean() everytime while validating objects, but this might bring up another issue of full_clean() being called two times in case using ModelForm.

Built-in validators

Django already has a collection of validators used for general purpose that we can use openly as a function/callable validator to our Model field or Forms or you own data validation

These built-in validators can be imported from django.core.validators module

Some list of commonly used built-in validators are validate_email, validate_slug, validate_ipv4_address, validate_ipv6_address, int_list_validator, validate_image_file_extension e.t.c

A simple example to demonstrate use of built-in validators

from django.db import models
from django.core.validators import validate_email

class Info(models.Model):
    # we didn't make email=models.EmailField() 
    #  because models.EmailField() would already contain
    #  validate_email validator that checks for 
    #  valid email addresses
    email = models.CharField(validators=[validate_email])

Django models Meta class

Model Meta class in Django is a way to provide some more information regarding table_name whether model is abstract singular or plural names app_label ordering and many more....

Python metaclass is different than that of Django model meta class. Metaclass in Python are class factory where as meta class in Django is completely different which is used to provide additional information about a model to Django. Don't get confused on they sharing the same name during our pronouncing speaking listening but they are way different on writings.

Basic example of creating Meta options class in Django

Django automatically create a database table for every models in the format of {app_name}_{model_name}, so lets customize this behaviour and provide it with a custom table name

from django.db import models

class Address(models.Model):
    name = models.CharField(max_length=100)

    class Meta:
        db_table = 'address'

Now, when we create migrations file and run our migrations through migrate command, django will create a table named address instead of its default {app_name}_address

Available Models Meta Options

  1. abstract
    • if abstract=True this model will be a abstract model class
  2. db_table
    • if db_table is set to a value, Django will take this value to create database table like we saw in previous example
  3. managed
    • if managed=True (default behaviour) Django will create and manage database table for this model
    • if managed=False table is not created. This might be useful to integrate already existing table into Django
  4. ordering
    • default ordering when retrieving list of objects/rows
  5. verbose_name
    • Human readable name for the singular object
  6. verbose_name_plural
    • Human readable plural name for the object

Abstract Model Base class (abstract=True)

Abstract model base class is also a model class which is used to place common model fields used in other models into a single model so that it can be used/inherited into other model.

  • This type of model will not be used to create and Database Table by the migrations framework
  • Since, this type of model doesn't create database table, we cannot perform any queries on this model
  • This type of models are inherited on other models to obtain features/fields from abstract model to normal model
  • So, this abstract base class is useful when you want to group common logics/fields of models into a single model but do not want to create it's corresponding database table
Let's take example of simple Information abstract base class
from django.db import models

class InfoBase(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField()

    class Meta:
        abstract = True
Let's use this abstract class to form other normal model class
class User(InfoBase):
    address = models.CharField(max_length=100)

So, the User model will now have fields name email address, where name and email is inherited from the InfoBase abstract model class

  1. Override fields in child model

    Some time, we may need to override some fields from the base class so that it gets changed in our child class. let's take an example we need to change max_length of name in User, we can do it by

    class User(InfoBase):
         name = models.CharField(max_length=50) # changed
         address = models.CharField(max_length=100)
    
  2. Remove/delete fields in child model

    Some time, there may occur a case when we need to delete/remove some fields in our child class, we can do it by simply overriding the fields with None value

    class User(InfoBase):
         name = models.CharField(max_length=50)
         email = None
         address = models.CharField(max_length=100)
    

    So, considering this example, User model will only have name and address fields