Translated article, original address: medium.com/profil-soft…

1. ViewSets

The benefit of viewsets is that they keep your code consistent and free of duplication. If you’re writing views to do more than one thing, then viewsets are what you want.

For example, if you have a Model called Tag and you need list, create, and detail functions, you can define a viewset:

from rest_framework import mixins, permissions
from rest_framework.viewsets import GenericViewSet


class TagViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, GenericViewSet) :
    """ The following endpoints are fully provided by mixins: * List view * Create view """
    queryset = Tag.objects.all()
    serializer_class = TagSerializer
    permission_classes = (permissions.IsAuthenticated,)
Copy the code

Viewset mixins can be combined freely. You can define your own mixins or use ModelViewSet.

ModelViewset can provide you with the following methods:.list(),.retrieve(),.create(),.update(),.partial_update(),.destroy().

Also, when you use viewsets, it will make your routing configuration much clearer.

from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter


api_router = DefaultRouter()
api_router.register(r'tag', TagViewSet, 'tag')

urlpatterns = [
   url(r'^v1/', include(api_router.urls, namespace='v1')))Copy the code

Now, your viewSet can do the following for you:

  • GET a list of tags and send a GET request tov1/tag/
  • Create a Tag and send a POST request tov1/tag/
  • GET a specific Tag and send a GET request tov1/tag/<tag_id>

You can even add custom routes to the viewSet using the @action decorator.

2. Understand different types of Serializers

As a DRF user, you don’t have to care much about views or routing configurations, so you’ll probably put most of your energy into serializers.

Serializers are the ones that act as translators between Django’s Models and their representations, such as JSON. Each Serializer can be used as both read and write, and the way it is initialized determines the actions it will perform. Three different types of Serializer can be distinguished: create, Update, and Retrieve.

If you want to transfer data outside the serializer, here’s an example:

def retrieve(self, request, *args, **kwargs) :
    instance = self.get_object()
    serializer = ProfileSerializer(instance=instance)
    return Response(serializer.data)
Copy the code

But when you create it, you need to write it another way:

def create(self, request, *args, **kwargs) :
    serializer = ProfileSerializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    serializer.save()
    return Response(serializer.data)
Copy the code

Finally, when updating an instance, you need to provide both instance and date:

def update(self, request, *args, **kwargs) :
    instance = self.get_object()
    serializer = ProfileSerializer(
        instance=instance,
        data=request.data
    )
    serializer.is_valid(raise_exception=True)
    serializer.save()
    return Response(serializer.data)
Copy the code

Serializer.save () calls the appropriate internal methods based on the parameters passed during initialization.

3. Use SerializerMethodField

SerializerMethodField is a read-only field that is computed when a request is processed by calling the corresponding method on the Serializer classs attached to it.

For example, if you have a Model with a dateTime field that stores models.DateTimeField, but you want to get timestamp when serializing:

from rest_framework import serializers


class TagSerializer(serializers.ModelSerializer) :
    created = serializers.SerializerMethodField()
    
    class Meta:
        model = Tag
        fields = ('label'.'created')
        
    def get_created(self, obj) :
        return round(obj.created.timestamp())
Copy the code

SerializerMethodField accepts method_NAME, but it’s usually easier to use the default naming method, such as get_

. Also, you want to make sure that you’re not burdening the method fields for any heavy lifting.

4. Use the source parameter

In many cases, the fields defined in your Model are different from the ones you want to serialize. You can easily solve this problem by using the source parameter.

Here’s an example:

from rest_framework import serializers
class TaskSerializer(serializers.ModelSerializer) :
    job_type = serializers.CharField(source='task_type')

    class Meta:
        model = Task
        fields = ('job_type'.)Copy the code

Task_type in the model is converted to job_type. This operation works not only for reading, but also for writing.

In addition, you can use dot syntax to retrieve fields from the associated model.

owner_email = serializers.CharField(source='owner.email')
Copy the code

5. Validation of serialized fields

In addition to the validators parameter that can be passed when initializing the Serializer field and serializer.validate () hook, there is a field-level validation that allows you to define their own validation method for each individual field.

I find it useful for two reasons: First, it allows for validation and decoupling of specific fields. Second, it produces a structured error response.

The use of this method is very similar to that of SerializerMethodField, except that the function name is formatted with a font like def Validate_

. Here’s an example:

from rest_framework import serializers

class TransactionSerializer(serializers.ModelSerializer):
    bid = serializers.IntegerField()

    def validate_bid(self, bid: int) -> int:
        if bid > self.context['request'].user.available_balance:
            raise serializers.ValidationError(
                _('Bid is greater than your balance')
            )
        return bid
Copy the code

If the validation fails, the output will look like this:

{
   "bid": ["Bid is greater than your balance"]
}
Copy the code

The validation method must return a value, which is then passed to the Model instance.

Also keep in mind that field-level validation will be called by serializer.to_internal_value() before serializer.validate().

6. Pass the value directly to the save method

In some cases, it is convenient to pass values directly from outside the serializer to its save () method.

This method takes arguments that can be equivalent to the serialized object. Values passed in this way will not be validated. It can be used to force overwriting of the initial data.

serializer = EmailSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(owner_id=request.user.id)
Copy the code

7. Use CurrentUserDefault

If you need to set the user, better than the example above, use CurrentUserDefault, and you don’t have to override the view.

from rest_framework import serializers

class EmailSerializer(serializers.ModelSerializer) :
    owner = serializers.HiddenField(
        default=serializers.CurrentUserDefault()
    )
Copy the code

This will do two things. First, set the user authenticated in the request object as the default user. Second, because HiddenField is used, no incoming data is taken into account, so it is impossible to set it to a different user.

8. Initial serializers

Sometimes you need to get the raw data for the Serializer. This is because the data has been modified by running serializer.is_valid (), or the value of another field in the validation method needs to be compared when validATED_data is not yet available.

Data can be obtained by using serializer. Initial_data in the dict format. For example:

from rest_framework import serializers


class SignupSerializer(serializers.ModelSerializer) :
    password1 = serializers.CharField()
    password2 = serializers.CharField()

    def validate_password1(self, password1) :
        ifpassword1 ! = self.initial_data['password2'] :raise serializers.ValidationError(
                'Passwords do not match'
            )
Copy the code

9. Handle multiple create/update/delete in nested serializer

Most of the time, serializers are completely simple, and with some experience, nothing can go wrong. However, there are some limitations. Things can get a little tricky when you have to support multiple create, update, and delete operations in nested serializers in a single high-level serializer.

There is a tradeoff: choose between processing a large number of requests and processing a long time within a single request.

By default, the DRF does not support multiple updates at all. It’s hard to imagine how it supports all possible nested insert and delete types. That’s why the creators of the DRF chose flexibility over a ready-made “one size fits all” solution, and left the privilege to us.

In this case, there are two paths you can follow:

  • Use the popular third-party library DRF Writable Desktop
  • Do your own

I recommend choosing the second option at least once, so you know what it means.

After analyzing the incoming data, in most cases, we can make the following assumptions:

  • All instances that should be updated have ids,
  • All instances that should be created have no IDS,
  • All instances that should be deleted exist in the data store (such as a database), but do not appear in the incoming request.data.

Based on this, we know how to handle specific instances in the list. Here is a code snippet that shows this process in detail:

class CUDNestedMixin(object) :
    @staticmethod
    def cud_nested(queryset: QuerySet,
                   data: List[Dict],
                   serializer: Type[Serializer],
                   context: Dict) :
        """ Logic for handling multiple updates, creates and deletes on nested resources. :param queryset: queryset for objects existing in DB :param data: initial data to validate passed from higher level serializer to nested serializer :param serializer: nested serializer to use :param context: context passed from higher level serializer :return: N/A """
        updated_ids = list()
        for_create = list(a)for item in data:
            item_id = item.get('id')
            if item_id:
                instance = queryset.get(id=item_id)
                update_serializer = serializer(
                    instance=instance,
                    data=item,
                    context=context
                )
                update_serializer.is_valid(raise_exception=True)
                update_serializer.save()
                updated_ids.append(instance.id)
            else:
                for_create.append(item)

        delete_queryset = queryset.exclude(id__in=updated_ids)
        delete_queryset.delete()

        create_serializer = serializer(
            data=for_create,
            many=True,
            context=context
        )
        create_serializer.is_valid(raise_exception=True)
        create_serializer.save()
Copy the code

Here’s a simplified version of how the advanced serializer takes advantage of this mixin:

from rest_framework import serializers

class AccountSerializer(serializers.ModelSerializer, CUDNestedMixin) :
    phone_numbers = PhoneSerializer(
        many=True,
        source='phone_set'.)class Meta:
        model = User
        fields = ('first_name'.'last_name'.'phone_numbers')

    def update(self, instance, validated_data) :
        self.cud_nested(
            queryset=instance.phone_set.all(),
            data=self.initial_data['phone_numbers'],
            serializer=PhoneSerializer,
            context=self.context
        )
        ...
        return instance
Copy the code

Remember that you should use initial_data instead of validated_data for nested objects.

That’s because running validation calls field.to_internal_value () on each field of the serializer, which may modify the data stored in a particular field (for example, by changing the primary key to a model instance).

10. Overwrite data to force sort

Sorting list views is easy by adding sorting to the QuerySet on the View, but it’s not that easy when you should also be sorting nested resources.

For read-only fields, you can do this in SerializerMethodField, but what if you have to write the field?

In this case, you can override the data property of the serializer, as shown in the following example:

@property
def data(self) :
    data = super().data
    data['phone_numbers'].sort(key=lambda p: p['id'])
    return data
Copy the code

Conclusion:

Hopefully, you found some interesting new technologies in this article. Have new DRF use tips or ideas, welcome to share!