Перейти к содержанию

Сериализаторы

Перед тем как писать представления, нам нужно создать для них сериализаторы. Для этого создадим файл serializers.py в наших приложениях.

Адаптеры
from rest_framework import serializers

from apps.adapter.models import Adapter


class AdapterSerializer(serializers.ModelSerializer):
    class Meta:
        model = Adapter
        fields = ["id", "full_name"]

    full_name = serializers.SerializerMethodField()

    def get_full_name(self, obj):
        return f"{obj.last_name} {obj.first_name}"

Один из самых базовых сериализаторов, основанных на модели.

Создадим атрибут full_name со значением serializers.SerializerMethodField(), и затем создадим метод с тем же названием и приставкой get. В этом методе будем возвращать фамилию и имя.

Сделано это для того, чтобы выводить их в одном поле в итоговом JSON. Также будем возвращать id адаптера.

JWT токены
from rest_framework import serializers


class CustomJWTSerializerWithExpiration(serializers.Serializer):
    access = serializers.CharField()
    refresh = serializers.CharField()
    access_expiration = serializers.DateTimeField()
    refresh_expiration = serializers.DateTimeField()

В этом сериализаторе определим четыре поля: два текстовых и два с датой и временем. Этот сериализатор нужен для возврата информации о JWT токенах после авторизации, и его нужно указать в настройках, пункт об этом есть здесь.

from rest_framework import serializers

from apps.survey.models import SurveyQuestion
from apps.adapter.models import Adapter
from apps.adapter.serializers import AdapterSerializer
from apps.oidc.models import ItmoIdProfile


class SurveyQuestionSerializer(serializers.ModelSerializer):
    class Meta:
        model = SurveyQuestion
        fields = ["id", "component", "text", "help_text", "training_text", "training_help_text", "adapters"]

    component = serializers.SerializerMethodField()
    adapters = serializers.SerializerMethodField()

    def get_component(self, obj: SurveyQuestion) -> SurveyQuestion.Type:
        return obj.get_component_display()

    def get_adapters(self, obj: SurveyQuestion) -> list[dict[str, str | int]]:
        request = self.context.get("request")
        if request and hasattr(request, "user") and obj.component == SurveyQuestion.Type.ADAPTER_SLIDER:
            group = ItmoIdProfile.objects.filter(user=request.user).values_list("group", flat=True)[0]
            adapters = Adapter.objects.filter(groups__name=group)
            return AdapterSerializer(adapters, many=True).data
        return []

В этом сериализаторе необходимо возвращать всю информацию, необходимую для генерации "формы" опроса: все нужные компоненты, id вопросов, их названия, а также список привязанных к группе адаптеров при необходимости.

Для этого создадим два новых поля с помощью методов: в первом будем просто возвращать человекочитаемое название компонента (которое соответствует названию файла с компонентом, чтобы на фронте было проще), а во втором из контекста (который в дальнейшем при использовании сериализатора "прокинем" из представления) получим запрос пользователя, чтобы идентифицировать его.

Если тип используемого для этого вопроса компонента является AdapterSlider, то мы должны по учебной группе пользователя получить список привязанных к ней адаптеров (используя ранее созданный сериализатор для адаптеров), в ином случае будем просто возвращать пустой список.

from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from apps.survey.models import SurveyQuestion


class AdapterSliderValueSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    value = serializers.IntegerField()

    class Meta:
        fields = ("id", "value")


class SurveyAnswerSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    value = serializers.JSONField()

    class Meta:
        fields = ("id", "value")

    def validate_id(self, value):
        question_exists = SurveyQuestion.objects.filter(id=value).exists()
        if not question_exists:
            raise ValidationError("The question ID does not exist!")
        return value

    def validate_value(self, value):
        if isinstance(value, int) or isinstance(value, str):
            return value
        elif isinstance(value, list) and all(isinstance(item, dict) for item in value):
            value_serializer = AdapterSliderValueSerializer(data=value, many=True)
            if value_serializer.is_valid(raise_exception=True):
                return value_serializer.validated_data
            else:
                raise ValidationError("Invalid value for Adapter Slider!")
        else:
            raise ValidationError(
                "The value must be an integer, a string, or a list of dictionaries with integer keys and values!"
            )

Особенностью сериализатора SurveyAnswerSerializer является то, что он будет использоваться не для выходных данных, а для входных.

Использовать будем всего 2 поля: id (id вопроса) и value (полученный ответ для этого вопроса). Первое поле будет численным, а второе - JSON, так как JSON позволяет принимать практически любые встроенные типы данных, что нам и нужно.

Для каждого из этих полей напишем валидатор. В случае id будем проверять существование вопроса с таким id.

В случае value будем проверять его тип и в зависимости от этого варьировать действия. Если получена строка или число, вернём, как есть. Если получен список, то проверим, что каждый элемент этого списка является словарём и дополнительно провалидируем этот список с помощью ещё одного сериализатора, специально написанного для AdapterSlider компонента, возвращающего данные по-особенному. Если валидация успешна, то вернём данные, в любом ином случае (в том числе при получении других типов данных) вернём ошибку валидации.