Подтвердить что ты не робот

Контекст вложенных сериализаторов django rest framework

Если у меня есть вложенный сериализатор:

class ChildSerializer(ModelSerializer):
    class Meta:
        fields = ('c_name', )
        model = Child


class ParentSerializer(ModelSerializer):

    child = ChildSerializer(many=True, read_only=True)

    class Meta:
        model = Parent
        fields = ('p_name', 'child')

И я хочу получить доступ к контексту в вложенном сериализаторе, как я могу это сделать? Насколько я могу судить, контекст не передается Ребенку.

Я хочу иметь возможность реализовать модель разрешений для каждого пользователя в полях, потому что я переопределил метод get_fields() ModelSerializer:

def get_fields(self):
    fields = super().get_fields()
    ....
    for f in fields:
        if has_rights(self.context['request'].user, f, "read"):
            ret_val[f] = fields[f]
    ....
    return ret_val

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

4b9b3361

Ответ 1

Хорошо, я нашел рабочее решение. Я заменил назначение ChildSerializer в классе Parent с помощью SerializerMethodField, который добавляет контекст. Затем это передается методу get_fields в моем CustomModelSerializer:

class ChildSerializer(CustomModelSerializer):
    class Meta:
        fields = ('c_name', )
        model = Child


class ParentSerializer(CustomModelSerializer):

    child = serializers.SerializerMethodField('get_child_serializer')

    class Meta:
        model = Parent
        fields = ('p_name', 'child')

    def get_child_serializer(self, obj):
        serializer_context = {'request': self.context.get('request') }
        children = Child.objects.all().filter(parent=obj)
        serializer = ChildSerializer(children, many=True, context=serializer_context)
        return serializer.data

и в моем CustomModelSerializer:

class CustomModelSerializer(rest_serializer_classes.HyperlinkedModelSerializer):

    def __init__(self, *args, **kwargs):
        """
            Make sure a user is coupled to the serializer (needed for permissions)
        """
        super().__init__(*args, **kwargs)
        if not self.context:
            self._context = getattr(self.Meta, 'context', {})
        try:
            self.user = self.context['request'].user
        except KeyError:
            self.user = None


    def get_fields(self):
        ret = OrderedDict()

        if not self.user:
            print("No user associated with object")
            return ret

        fields = super().get_fields()

        # Bypass permission if superuser
        if self.user.is_superuser:
            return fields

        for f in fields:
            if has_right(self.user, self.Meta.model.__name__.lower(), f, "read"):
                ret[f] = fields[f]

        return ret

Кажется, что это нормально, и поля дочернего элемента отбрасываются в сериализаторе, когда я либо отменю права чтения на Child.c_name, либо на Parent.child

Ответ 2

Вместо этого вы можете использовать serialziers.ListField. ListField автоматически передает контекст ему дочерний элемент. Итак, вот ваш код

class ChildSerializer(ModelSerializer):
    class Meta:
        fields = ('c_name', )
        model = Child


class ParentSerializer(ModelSerializer):
    child = serializers.ListField(read_only=True, child=ChildSerializer())

    class Meta:
        model = Parent
        fields = ('p_name', 'child')

Ответ 3

Я знаю, что это старый вопрос, но у меня был тот же вопрос в 2019 году. Вот мое решение:

class MyBaseSerializer(serializers.HyperlinkedModelSerializer):

    def get_fields(self):
        '''
        Override get_fields() method to pass context to other serializers of this base class.

        If the context contains query param "omit_data" as set to true, omit the "data" field
        '''
        fields = super().get_fields()

        # Cause fields with this same base class to inherit self._context
        for field_name in fields:
            if isinstance(fields[field_name], serializers.ListSerializer):
                if isinstance(fields[field_name].child, MyBaseSerializer):
                    fields[field_name].child._context = self._context

            elif isinstance(fields[field_name], MyBaseSerializer):
                fields[field_name]._context = self._context

        # Check for "omit_data" in the query params and remove data field if true
        if 'request' in self._context:
            omit_data = self._context['request'].query_params.get('omit_data', False)

            if omit_data and omit_data.lower() in ['true', '1']:
                fields.pop('data')

        return fields

Выше я создаю базовый класс сериализатора, который переопределяет get_fields() и передает self._context любому дочернему сериализатору, имеющему тот же базовый класс. Для ListSerializer я присоединяю контекст к его потомку.

Затем я проверяю параметр запроса "omit_data" и удаляю поле "данные", если оно запрашивалось.

Я надеюсь, что это полезно для тех, кто все еще ищет ответы на этот вопрос.