Django REST Framework - Сериализация необязательных полей

У меня есть объект с необязательными полями. Я определил свой сериализатор следующим образом:

class ProductSerializer(serializers.Serializer):
    code = serializers.Field(source="Code")
    classification = serializers.CharField(source="Classification", required=False)

I мысль required=False выполнит задачу обхода поля, если оно не существует. Однако в документации упоминается, что это влияет на десериализацию, а не на сериализацию.

Я получаю следующую ошибку:

'Product' object has no attribute 'Classification'

Что происходит, когда я пытаюсь получить доступ к .data сериализованного экземпляра. (Разве это не означает десериализацию, которая поднимает это?)

Это случается для экземпляров, которые не имеют Classification. Если я опускаю Classification из класса сериализатора, он отлично работает.

Как правильно это сделать? Сериализуйте объект с необязательными полями.

Ответы

Ответ 1

Сериализаторы намеренно предназначены для использования фиксированного набора полей, поэтому вам нелегко будет опционально отказаться от одного из ключей.

Вы можете использовать SerializerMethodField, чтобы вернуть значение поля или None, если это поле не существует, или вы можете вообще не использовать сериализаторы и просто написать представление, которое возвращает ответ напрямую.

Обновление для REST framework 3.0 serializer.fields может быть изменено на экземпляре serializer. Когда требуются классы динамического сериализатора, я бы предложил изменить поля в пользовательском методе Serializer.__init__().

Ответ 2

Django REST Framework 3.0 +
Динамические поля теперь поддерживаются, см. http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields - этот подход определяет все поля в сериализаторе, а затем позволяет выборочно удалять их вы не хотите.

Или вы также можете сделать что-то подобное для сериализатора моделей, где вы запутались с Meta.fields в инициализации сериализатора:

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ('code',)

    def __init__(self, *args, **kwargs):
        if SHOW_CLASSIFICATION: # add logic here for optional viewing
            self.Meta.fields = list(self.Meta.fields)
            self.Meta.fields.append('classification')
        super(ProductSerializer, self).__init__(*args, **kwargs)

Вы должны спросить Тома, хотя, если это "правильный путь", поскольку он может не соответствовать долгосрочному плану.

Django REST Framework < 3,0
Попробуйте что-то вроде этого:

class ProductSerializer(serializers.Serializer):
    ...
    classification = serializers.SerializerMethodField('get_classification')

    def get_classification(self, obj):
        return getattr(obj, 'classification', None)

Несколько сериализаторов

Другим подходом было бы создание нескольких сериализаторов с разными наборами полей. Один сериализатор наследует от другого и добавляет дополнительные поля. Затем вы можете выбрать соответствующий сериализатор в представлении с помощью метода get_serializer_class. Вот пример того, как я использую этот подход для вызова разных сериализаторов для представления разных пользовательских данных, если пользовательский объект совпадает с запросом пользователя.

def get_serializer_class(self):
    """ An authenticated user looking at their own user object gets more data """
    if self.get_object() == self.request.user:
        return SelfUserSerializer
    return UserSerializer

Удаление полей из представления

Другим подходом, который я использовал в контексте безопасности, является удаление полей в методе to_representation. Определите метод, подобный

def remove_fields_from_representation(self, representation, remove_fields):
    """ Removes fields from representation of instance.  Call from
    .to_representation() to apply field-level security.
    * remove_fields: a list of fields to remove
    """
    for remove_field in remove_fields:
        try:
            representation.pop(remove_field)
        except KeyError:
            # Ignore missing key -- a child serializer could inherit a "to_representation" method
            # from its parent serializer that applies security to a field not present on
            # the child serializer.
            pass

а затем в вашем сериализаторе вызовите этот метод, например

def to_representation(self, instance):
    """ Apply field level security by removing fields for unauthorized users"""
    representation = super(ProductSerializer, self).to_representation(instance)
    if not permission_granted: # REPLACE WITH PERMISSION LOGIC
        remove_fields = ('classification', ) 
        self.remove_fields_from_representation(representation, remove_fields)
    return representation

Этот подход является простым и гибким, но это связано с сериализацией полей, которые иногда не отображаются. Но это, вероятно, хорошо.

Ответ 3

Метод, описанный ниже, сделал работу для меня. Довольно просто, легко и сработало для меня.

Используемая версия DRF = djangorestframework (3.1.0)

class test(serializers.Serializer):
  id= serializers.IntegerField()
  name=serializers.CharField(required=False,default='some_default_value')

Ответ 4

Для этой цели сериализаторы имеют аргумент partial. Если при инициализации сериализатора вы можете пройти partial=True. Если вы используете generics или mixins, вы можете перекрыть функцию get_serializer следующим образом:

def get_serializer(self, *args, **kwargs):
    kwargs['partial'] = True
    return super(YOUR_CLASS, self).get_serializer(*args, **kwargs)

И это сделает трюк.

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

Ответ 5

Из "ужасного взлома, основанного на конкретных деталях реализации как DRF, так и Django, но он работает (по крайней мере на данный момент)", здесь подход, который я использовал для включения некоторых дополнительных отладочных данных в ответ от "создать" реализацию метода в сериализаторе:

def create(self, validated_data)
    # Actual model instance creation happens here...
    self.fields["debug_info"] = serializers.DictField(read_only=True)
    my_model.debug_info = extra_data
    return my_model

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

Ответ 6

DynamicSerializer for DRF 3, который позволяет динамически указывать, какие поля будут использоваться в сериализаторе, какие будут исключены, а при необходимости какие станут обязательными!

  1. Создать миксин
    class DynamicSerializerMixin:
        """
        A Serializer that takes an additional 'fields' argument that
        controls which fields should be used.
        """

        def __init__(self, *args, **kwargs):
            # Don't pass the 'fields' arg up to the superclass
            fields = kwargs.pop("fields", None)
            excluded_fields = kwargs.pop("excluded_fields", None)
            required_fields = kwargs.pop("required_fields", None)

            # Instantiate the superclass normally
            super().__init__(*args, **kwargs)

            if fields is not None:
                # Drop any fields that are not specified in the 'fields' argument.
                allowed = set(fields)
                existing = set(self.fields)
                for field_name in existing - allowed:
                    self.fields.pop(field_name)

                if isinstance(fields, dict):
                    for field, config in fields.items():
                        set_attrs(self.fields[field], config)

            if excluded_fields is not None:
                # Drop any fields that are not specified in the 'fields' argument.
                for field_name in excluded_fields:
                    self.fields.pop(field_name)

            if required_fields is not None:
                for field_name in required_fields:
                    self.fields[field_name].required = True
  1. Инициализируйте/настройте ваш сериализатор, добавив DynamicSerializerMixin в наследство

class UserProfileSerializer(DynamicSerializerMixin, serializers.ModelSerializer):

    class Meta:
        model = User
        fields = (
            "id",
            'first_name', 'last_name'
            "email",
            "is_staff",
        )
  1. Используйте это :)
class RoleInvitationSerializer(serializers.ModelSerializer):
    invited_by = UserProfileSerializer(fields=['id', 'first_name', 'last_name'])

или в действии apis

    @action(detail=True, serializer_class=YourSerialzierClass)
    def teams_roles(self, request, pk=None):
        user = self.get_object()
        queryset = user.roles.all()
        serializer = self.get_serializer(queryset, many=True, excluded_fields=['user'])
        return Response(data=serializer.data)