Sentry developer contribution Guide - Django Rest Framework(Serializers)

Posted by sanchan on Fri, 14 Jan 2022 04:02:34 +0100

Serializer is used to get complex Python models and convert them into json. The serializer can also be used to deserialize json back to the python model after validating the incoming data.

In Sentry, we have two different types of serializers: Django Rest Framework Serializer and Model Serializer.

Django Rest Framework

The Django Rest Framework serializer is used to handle input validation and transformation of data entering Sentry.

Example

In a typical serializer, fields are specified so that they validate the type and format of the data according to your specifications.
The Django Rest Framework serializer can also save the information to the database if the model is suitable for writing.

from rest_framework import serializers
from sentry.api.serializers.rest_framework import ValidationError

class ExampleSerializer(serializers.Serializer):
    name = serializers.CharField()
    age = serializers.IntegerField(required=False)
    type = serializers.CharField()

    def validate_type(self, attrs, source):
        type = attrs[source]
        if type not in ['bear', 'rabbit', 'puppy']:
            raise ValidationError('%s is not a valid type' % type)
	return attrs

Field check

In the example above,
The serializer will accept and validate a json with three fields: name, age, and type.
Where name and type must be strings,
age must be the recommended integer.
By default, fields are required, and if they are not provided, the serializer will mark them as invalid.
Note that the integer field age,required is set to False.
Therefore, it may not be included, and the serializer will still be considered valid.

Custom validation

For values that require custom validation (in addition to simple type checking),

def validate_<variable_name>(self, attrs, source)

You can create < variable_ Name > replace with the exact variable name of the given field.
So, for example, if I have a field name typename, the validation method name will be validate_typeName,
And if I have a name called type_name field, and the validation method name will be validate_type_name.
In the example given above, the type is checked and must be a string.
If a field does not match what your validation method expects, a ValidationError is thrown.

usage

In endpoint, this is the typical usage of Django Rest Framework Serializer

class ExampleEndpoint(Endpoint):
    def post(self, request):
        serializer = ExampleSerializer(request.DATA)
        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.object

        #Assuming Example is a model with the same fields 
        try:
            with transaction.atomic():
                Example.objects.create(
                    name=result['name'],
                    age=result.get('age'),
                    type=result['type'],
                )
        except IntegrityError:
            return Response('This example already exists', status=409)

        return Response(serialize(result, request.user), status=201)

Validation data

The Serializer from Django Rest Framework will be used for the methods of incoming data that need to be verified (i.e. put and post methods).
Once the serializer is instantiated, you can call serializer is_ Validate () to validate the data.
serializer.errors will give specific feedback that the given data is invalid.

For example, given input

{
	'age':5,
	'type':'puppy'
}

The serializer will return an error indicating that the required field name was not provided.

Save data

After confirming that the data is valid, you can save the data in one of the following two ways.
The example given above is the most common in sentry.
Get serializer Object, which is only validated data (None if serializer.is_valid() returns False)
And use < modelname > objects. Create saves the data directly in the model

Another method uses more features of Django Rest Framework,
ModelSerializer

from rest_framework import serializers
from sentry.api.serializers.rest_framework import ValidationError

class ExampleSerializer(serializer.ModelSerializer):
    name = serializers.CharField()
    age = serializers.IntegerField(required=False)
    type = serializers.CharField()

    class Meta:
        model = Example
 
    def validate_type(self, attrs, source):
        type = attrs[source]
        if type not in ['bear', 'rabbit', 'puppy']:
            raise ValidationError('%s is not a valid type' % type)
        return attrs

class ExampleEndpoint(Endpoint):
    def post(self, request):
        serializer = ExampleSerializer(request.DATA)
        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        example = serializer.save()
        return Response(serialize(example, request.user), status=201)

Model Serializer

Sentry's Model Serializers For outgoing data only. A typical model serializer is as follows:

@register(Example)
class ExampleSerializer(Serializer):
    def get_attrs(self, item_list, user):
        attrs = {}
        types = ExampleTypes.objects.filter(
            type_name__in=[i.type for i in item_list]
        )

        for item in item_list:
            attrs[item] = {}
            attrs[item]['type'] = [t for t in types if t.name == item.type_name]
	    return attrs

    def serialize(self, obj, attrs, user):
        return {
            'name':obj.name,
            'type':attrs['type'],
            'age': obj.age,
        }

Register Model Serializer

The decorator @ register is required to

`return Response(serialize(example, request.user), status=201)`

works. In this case, it will search for the matching model Example in the background,
The model type of the given variable example.
To match the model serializer to the Model, you simply execute

@register(<ModelName>)
class ModelSerializer(Serializer):
...

get_attrs method

Why do you do this when the Django Rest Framework has similar functionality?
get_ The attrs method is the reason. It allows you to perform batch queries instead of multiple queries.
In our example, I can filter the items I want and assign them to related items using python,
Instead of calling exampletypes objects. get(...) Multiple item s.
In the case of attr dictionary, the key is the item itself.
And value is a dictionary that contains the name of the attribute to be added and its value.

attrs[item] = {'attribute_name': attribute}

Serialize method

Finally, you return a dictionary with json serializable information that will be returned with the response.

more