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

### 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

```py
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`

```py
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`

```py
# 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](https://github.com/django/django/blob/main/django/db/models/base.py#L1397) 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

```py
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`

```py
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
```py
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

```py
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
    ```py
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
    ```py
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







