Конечное поле для несериализуемых данных сериализатора

У меня есть маршрут, в котором метаданные могут быть размещены. Если известные поля размещаются, я хотел бы хранить их в структурированном виде в моей БД, сохраняя только неизвестные поля или поля, которые не прошли проверку в формате 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

Поскольку вы ищете универсальное решение, есть несколько моментов, которые вам следует рассмотреть:

  1. Убедитесь, что вы не размещаете никаких model-level валидаций в вашей модели, так как вы хотите, чтобы она сохранялась независимо от статуса валидации.
  2. Выполняйте валидацию только на serializer-level с помощью пользовательских методов валидации.
  3. Сделайте поле unparseable_info только для чтения, так как это то, что мы не хотим, чтобы пользователь отправлял, но получал.
  4. Используйте словарь 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 вместе с любыми другими неразборчивыми данными.

Вернуться на верх