Конечное поле для несериализуемых данных сериализатора
У меня есть маршрут, в котором метаданные могут быть размещены. Если известные поля размещаются, я хотел бы хранить их в структурированном виде в моей БД, сохраняя только неизвестные поля или поля, которые не прошли проверку в формате JSONField
.
Предположим, что моя модель такова:
# models.py
from django.db import models
class MetaData(models.Model):
shipping_address_zip_code = models.CharField(max_length=5, blank=True, null=True)
...
unparseable_info = models.JSONField(blank=True, null=True)
Я хотел бы использовать встроенную логику сериализации для проверки того, является ли zip_code
правильным (5 букв или меньше). Если да, то я буду действовать нормально и сохранять его в поле shipping_address_zip_code
. Если же он не пройдет проверку, я хотел бы сохранить его как пару ключ-значение в поле unparseable_info
и при этом вернуть сообщение об успехе клиенту, вызывающему маршрут.
У меня гораздо больше полей, и я ищу общее решение, но включение только одного поля здесь, вероятно, поможет проиллюстрировать мою проблему.
def validate_shipping_address_zip_code(self, value):
if value >= 5:
return value
else:
raise serializers.ValidationError("Message Here")
в сериализаторе гораздо больше валидаторов смотрите подробнее https://www.django-rest-framework.org/api-guide/serializers/
from rest_framework import serializers
class MetaDataSerializer(serializers.ModelSerializer):
unparseable_info = serializers.JSONField(required=False)
class Meta:
model = MetaData
fields = ('shipping_address_zip_code', 'unparseable_info')
def validate_shipping_address_zip_code(self, value):
if len(value) > 5:
raise serializers.ValidationError("Zip code is too long")
return value
def create(self, validated_data):
unparseable_info = {}
for key, value in self.initial_data.items():
try:
validated_data[key] = self.fields[key].run_validation(value)
except serializers.ValidationError as exc:
unparseable_info[key] = value
validated_data.pop(key, None)
instance = MetaData.objects.create(**validated_data)
if unparseable_info:
instance.unparseable_info = unparseable_info
instance.save()
return instance
Вы можете использовать сериализатор Django, который сохраняет поля, не прошедшие валидацию, в JSONField.
Вот пример, который сработал для меня:
from rest_framework import serializers
class MetaDataSerializer(serializers.ModelSerializer):
class Meta:
model = MetaData
fields = 'all'
def validate_shipping_address_zip_code(self, value):
if len(value) > 5:
raise serializers.ValidationError("Zip code must be 5 characters or less.")
return value
def create(self, validated_data):
unparseable_info = {}
for field, value in self.initial_data.items():
try:
validated_data[field] = self.fields[field].run_validation(value)
except serializers.ValidationError as e:
unparseable_info[field] = value
instance = MetaData.objects.create(**validated_data)
if unparseable_info:
instance.unparseable_info = unparseable_info
instance.save()
return instance
Поскольку вы ищете универсальное решение, есть несколько моментов, которые вам следует рассмотреть:
- Убедитесь, что вы не размещаете никаких
model-level
валидаций в вашей модели, так как вы хотите, чтобы она сохранялась независимо от статуса валидации. - Выполняйте валидацию только на
serializer-level
с помощью пользовательских методов валидации. - Сделайте поле
unparseable_info
только для чтения, так как это то, что мы не хотим, чтобы пользователь отправлял, но получал. - Используйте словарь
errors
, предоставляемый сериализатором, поскольку он заполняется ошибками, специфичными для поля, когда мы вызываемis_valid
.
Вот как это можно перевести в код, внутри models.py
:
class MetaData(models.Model):
shipping_address_zip_code = models.CharField(blank=True, null=True)
...
unparseable_info = models.JSONField(blank=True, null=True)
then inside serializers.py
:
class MetaDataSerializer(serializers.ModelSerializer):
class Meta:
model = MetaData
read_only_fields = ('unparseable_info', )
fields = '__all__'
# Write validators for all of your fields.
и наконец, внутри вашего метода views.py
, что-то вроде этого (вы можете сделать это и внутри метода сериализатора save
):
meta_data = MetaDataSerializer(data=request.data)
if not meta_data.is_valid():
meta_data.unparseable_info = meta_data.errors
meta_data.save()
# Return meta_data.data in JSONResponse.
Вы можете создать пользовательский сериализатор для этого и использовать Django Rest Framework для проверки и хранения POST-данных.
Сначала создайте класс сериализатора для вашей модели:
# serializers.py
from rest_framework import serializers
from .models import MetaData
class MetaDataSerializer(serializers.ModelSerializer):
class Meta:
model = MetaData
fields = '__all__'
Затем, в вашем представлении, вы можете проверить POST-данные с помощью сериализатора:
# views.py
from rest_framework import views, status
from rest_framework.response import Response
from .serializers import MetaDataSerializer
from .models import MetaData
class MetaDataView(views.APIView):
def post(self, request, format=None):
serializer = MetaDataSerializer(data=request.data)
if serializer.is_valid():
shipping_address_zip_code = serializer.validated_data.get('shipping_address_zip_code', None)
if shipping_address_zip_code and len(shipping_address_zip_code) <= 5:
# store the data in the shipping_address_zip_code field
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
# store the unparseable data in the unparseable_info field
unparseable_data = {'shipping_address_zip_code': shipping_address_zip_code}
meta_data = MetaData.objects.create(unparseable_info=unparseable_data)
return Response({'id': meta_data.id}, status=status.HTTP_201_CREATED)
else:
# store the unparseable data in the unparseable_info field
unparseable_data = request.data
meta_data = MetaData.objects.create(unparseable_info=unparseable_data)
return Response({'id': meta_data.id}, status=status.HTTP_201_CREATED)
Таким образом, если поле shipping_address_zip_code является действительным, оно будет сохранено в поле shipping_address_zip_code, иначе оно будет сохранено в поле unparseable_info вместе с любыми другими неразборчивыми данными.