Official original text link this series of articles github address reprint please indicate the source

The validator

In most cases, when you handle validation in the REST Framework, you simply rely on the default field validation or write explicit validation methods on serialized or field classes.

However, sometimes you will want to put validation logic into reusable components so that it can be easily reused throughout your code base. This can be done by using validator functions and validator classes.

Validation in the REST Framework

Validation in the Django REST Framework serializer works a little differently than validation in the Django ModelForm class.

With ModelForms, validation is performed partly on the form and partly on the model instance. With the REST Framework, validation is performed entirely on serialized classes. This has advantages for several reasons:

  • It separates the problem properly and makes the code behavior clearer.
  • Quick to useModelSerializerClass and use explicitSerializerClasses can be switched easily. Any forModelSerializerValidation behavior is easy to replicate.
  • Print the serialized class instancereprThe validation rules it applies will be displayed. There is no additional hidden validation behavior on the model instance (because it’s all on the serialized class).

All validations are handled automatically for you when you use ModelSerializer. If you want to use the Serializer class instead, you need to clearly define the validation rules.

Take a chestnut

As an example of how the REST Framework uses explicit validation, we’ll use a simple model class with uniquely constrained fields.

class CustomerReportRecord(models.Model):
    time_raised = models.DateTimeField(default=timezone.now, editable=False)
    reference = models.CharField(unique=True, max_length=20)
    description = models.TextField()
Copy the code

Here is a basic ModelSerializer that we can use to create or update instances of CustomerReportRecord:

class CustomerReportSerializer(serializers.ModelSerializer):
    class Meta:
        model = CustomerReportRecord
Copy the code

Now open the Django shell using manage.py shell

>>> from project.example.serializers import CustomerReportSerializer
>>> serializer = CustomerReportSerializer()
>>> print(repr(serializer))
CustomerReportSerializer():
    id = IntegerField(label='ID', read_only=True)
    time_raised = DateTimeField(read_only=True)
    reference = CharField(max_length=20, validators=[<UniqueValidator(queryset=CustomerReportRecord.objects.all())>])
    description = CharField(style={'type': 'textarea'})
Copy the code

The interesting thing here is the Reference field. We can see that the uniqueness constraint is explicitly enforced by the validator on the serialized field.

Because of this more explicit style, the REST Framework includes several validator classes that are not found in core Django. These classes are described in detail below.


UniqueValidator

This validator can be used to enforce the unique=True constraint on model fields. It takes a required argument and an optional messages argument:

  • queryset Must be– This is a set of queries that verify uniqueness.
  • message– Error message used when authentication failed.
  • lookup– Used to find existing instances of validated values. The default is'exact'.

This validator should be applied to serialized fields as follows:

from rest_framework.validators import UniqueValidator

slug = SlugField(
    max_length=100,
    validators=[UniqueValidator(queryset=BlogPost.objects.all())]
)
Copy the code

UniqueTogetherValidator

This validator can be used to enforce the Unique_together constraint on model instances. It has two required arguments and an optional messages argument:

  • queryset Must be– This is a set of queries that verify uniqueness.
  • fields Must be– A list or tuple of field names that must be unique (meaning that a set of values represented by a field in a collection cannot appear in two sets of data at the same time). These fields must all be in the serialized class.
  • message– Error message used when authentication failed.

Validators should be applied to serialized classes as follows:

from rest_framework.validators import UniqueTogetherValidator

class ExampleSerializer(serializers.Serializer):
    #...
    class Meta:
        # ToDo items belong to a parent list, and have an ordering defined
        # by the 'position' field. No two items in a given list may share
        # the same position.
        validators = [
            UniqueTogetherValidator(
                queryset=ToDoItem.objects.all(),
                fields=('list'.'position')))Copy the code

Note: The UniqueTogetherValidation class always imposes an implicit constraint that all the fields it applies are processed on demand. Fields with a default value are an exception because they always provide a value, even if it is omitted from the user input.


UniqueForDateValidator

UniqueForMonthValidator

UniqueForYearValidator

These validators can be used to enforce unique_FOR_date, UNIque_FOR_month, and UNIque_FOR_year constraints on model instances. They have the following parameters:

  • queryset Must be– This is a set of queries that verify uniqueness.
  • field Must be– The name of the field whose uniqueness needs to be verified within the given date range. The field must be a field in a serialized class.
  • date_field Must be– The name of the field that will be used to determine the date range of the uniqueness constraint. The field must be a field in a serialized class.
  • message– Error message used when authentication failed.

Validators should be applied to serialized classes as follows:

from rest_framework.validators import UniqueForYearValidator

class ExampleSerializer(serializers.Serializer):
    #...
    class Meta:
        # Blog posts should have a slug that is unique for the current year.
        validators = [
            UniqueForYearValidator(
                queryset=BlogPostItem.objects.all(),
                field='slug',
                date_field='published')]Copy the code

Let me explain that the above example means that the value of the slug field must be unique in the year in which the published date is published. Note that it is not the date that is exactly equal to the published date, but the year. The same applies to unique_for_date and unique_for_month.

The date field for validation should always exist in the serialized class. You can’t simply rely on the model class default=… Because the default values are not generated until the validation runs.

You may need to use several styles, depending on how you want the API to be presented. If you use ModelSerializer, you may just rely on the default values generated for you by the REST Framework, but if you use Serializer or need more explicit controls, use the styles shown below.

Used with writable date fields.

If you want the date field to be writable, the only thing worth noting is that you should make sure it is always available in the input data, either by setting the default parameter or by setting Required =True.

published = serializers.DateTimeField(required=True)
Copy the code

Used with read-only date fields.

If you want the date field to be visible but the user cannot edit it, set read_only=True and set default=… Parameters.

published = serializers.DateTimeField(read_only=True, default=timezone.now)
Copy the code

This field is not writable to the user, but the default value will still be passed to validATED_data.

Use with hidden date fields.

If you want the date field completely hidden from the user, use HiddenField. This field type does not accept user input, but always returns its default value to validATED_data in the serialized class.

published = serializers.HiddenField(default=timezone.now)
Copy the code

Note: The UniqueFor

Validation class always imposes an implicit constraint that all fields it applies are processed on demand. Fields with a default value are an exception because they always provide a value, even if it is omitted from the user input.


Advanced field defaults

Validators applied to multiple fields of a serialized class sometimes do not require field input provided by the API client, but it can be used as input to the validator.

There are two modes that might require this validation:

  • useHiddenField. The field will appear invalidated_dataIn, but not in the serialized output representation.
  • useread_only=TrueThe standard field also containsdefault=...Parameters. This field will be used in the serialized output representation but cannot be set directly by the user.

The REST framework contains some defaults that might be useful in this case.

CurrentUserDefault

A default class that can be used to represent the current user. To use it, ‘request’ must be provided as part of the context dictionary when instantiating the serialized class.

owner = serializers.HiddenField(
    default=serializers.CurrentUserDefault()
)
Copy the code

CreateOnlyDefault

A default class that can be used to set only default parameters during the CREATE operation. During the update, this field is omitted.

It takes one parameter, which is the default or callable parameter that should be used during the CREATE operation.

created_at = serializers.DateTimeField(
    read_only=True,
    default=serializers.CreateOnlyDefault(timezone.now)
)
Copy the code

The limitations of the validator

In some ambiguous cases, you need to display the processing validation instead of relying on the default serialization class generated by ModelSerializer.

In these cases, you may want to disable auto-generated validators by specifying an empty list for the serialized class meta.validators property.

Optional fields

By default, “unique Together “validation enforces all fields to be required=True. In some cases, you may want to explicitly apply Required =False to one of the fields, in which case the behavior required for validation is unclear.

In this case, you usually need to exclude validators from serialized classes and explicitly write validation logic either in the.validate() method or in the view.

Such as:

class BillingRecordSerializer(serializers.ModelSerializer):
    def validate(self, data):
        # Apply custom validation either here, or in the view.

    class Meta:
        fields = ('client'.'date'.'amount')
        extra_kwargs = {'client': {'required': 'False'}}
        validators = []  # Remove a default "unique together" constraint.
Copy the code

Update nested serialized classes

When updates are applied to existing instances, the uniqueness validator excludes the current instance from the uniqueness check. The current instance is available in the context of uniqueness checking because it exists as an attribute in the serializer, and instance=… was originally used when instantiating the serialized class. Pass.

This exclusion cannot be applied when updating a nested serialized class because the instance is not available.

Once again, you may need to explicitly remove the validator from the serialized class and explicitly write the code for the validation constraint to the.validate() method or view.

Debug complex cases

If you’re not sure of the default behavior of the ModelSerializer class, it’s usually a good idea to run manage.py shell and print an instance of the serialized class so you can check the fields and validators it automatically generates.

>>> serializer = MyComplexModelSerializer()
>>> print(serializer)
class MyComplexModelSerializer:
    my_fields = ...
Copy the code

Also keep in mind that in complex cases, it is often better to explicitly define the serialization class rather than rely on the default ModelSerializer behavior. While this will write more code, it ensures that the resulting behavior is more transparent.


Write a custom validator

You can use Django’s existing validators, or you can write a custom one.

Based on the function

The validator can be any callable object, when the failure cause serializers. ValidationError.

def even_number(value):
    if value % 2! =0:
        raise serializers.ValidationError('This field must be an even number.')
Copy the code

Field-level validation

You can specify custom field-level validation by adding the.validate_

method to the Serializer subclass.

Based on the class

To write a class-based validator, use the __call__ method. Class-based validators are useful because they allow parameterization and reuse behavior.

class MultipleOf(object):
    def __init__(self, base):
        self.base = base

    def __call__(self, value):
        ifvalue % self.base ! =0:
            message = 'This field must be a multiple of %d.' % self.base
            raise serializers.ValidationError(message)
Copy the code

useset_context()

In some advanced cases, you may want to retrieve the serialized field being validated in the validator. You can do this by declaring the set_context method on a class-based validator.

def set_context(self, serializer_field):
    # Determine if this is an update or a create operation.
    # In `__call__` we can then use that information to modify the validation behavior.
    self.is_update = serializer_field.parent.instance is not None
Copy the code