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, который позволяет динамически указывать, какие поля будут использоваться в сериализаторе, какие будут исключены, а при необходимости какие станут обязательными!
- Создать миксин
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
- Инициализируйте/настройте ваш сериализатор, добавив DynamicSerializerMixin в наследство
class UserProfileSerializer(DynamicSerializerMixin, serializers.ModelSerializer):
class Meta:
model = User
fields = (
"id",
'first_name', 'last_name'
"email",
"is_staff",
)
- Используйте это :)
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)