Разбить поле на собственный сериализатор в Django Rest Framework - PullRequest
0 голосов
/ 03 августа 2020

Допустим, у меня есть простая Product Django модель:

class Product:
    name = models.CharField(max_length=255, unique=True)
    created_on = models.DateField()

Я использую Django Rest Framework для сериализации этой модели. Я хотел бы разбить created_on на отдельный объект (включая ответ от запросов GET и полезную нагрузку в запросах POST):

{
    "name": "MyProduct",
    "created_on": {
        "year": 2020,
        "month": 1,
        "day": 24
    }
}

Вот что у меня есть на данный момент:

class DateSerializer(serializers.Serializer):
    year = serializers.IntegerField()
    month = serializers.IntegerField()
    day = serializers.IntegerField()

    def validate(data):
        return datetime.date(data["year"], data["month"], data["day"])

class ProductSerializer(serialzers.ModelSerializer):
    created_on = DateSerializer()

    class Meta:
        model = Friend
        fields = ("name", "created_on")

    def create(self, validated_data):
        return Product.objects.create(**validated_data)

class ProductViewset(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

Этот подход работает для запросов GET (то есть я получаю json выше). Однако он не работает для запросов POST (полезная нагрузка - json выше). Ответом будет 400 код состояния с сообщением {'created_on': [ErrorDetail(string='This field is required.', code='required')]}.

Если я передам required=False в DateSerializer, я увижу, что self.initial_data в методе create будет <QueryDict: {'name': ['MyProduct'], 'created_on': ['year', 'month', 'day']}>. Значит, значения по какой-то причине исчезают.

Есть идеи, что я здесь делаю не так и как я могу заставить это работать?

Ответы [ 4 ]

2 голосов
/ 09 августа 2020

Разобрался, мне нужно в запросе установить заголовок content_type=application/json. В противном случае по умолчанию используется content_type=multipart/form-data, что сглаживает полезную нагрузку до {'name': ['MyProduct'], 'created_on': ['year', 'month', 'day']}.

1 голос
/ 03 августа 2020

Заменить метод validate(...) класса ProductSerializer как,

<b>from datetime import date</b>


class DateSerializer(serializers.Serializer):
    year = serializers.IntegerField()
    month = serializers.IntegerField()
    day = serializers.IntegerField()


class ProductSerializer(serializers.ModelSerializer):
    created_on = DateSerializer()

    <b>def validate(self, attrs):
        super().validate(attrs)
        attrs['created_on'] = date(**attrs['created_on'])
        return attrs</b>

    class Meta:
        model = Product
        fields = ("name", "created_on")

Пример:

In [8]: payload = {"name": "MyProduct", "created_on": {"year": 2020, "month": 1, "day": 24}}                                                                                                                       

In [9]: serializer = ProductSerializer(data=payload)                                                                                                                                                               

In [10]: serializer.is_valid(True)                                                                                                                                                                                 
Out[10]: True

In [11]: serializer.validated_data                                                                                                                                                                                 
Out[11]: 
OrderedDict([('name', 'MyProduct'),
             ('created_on', datetime.date(2020, 1, 24))])

In [12]: product_instance = serializer.save()                                                                                                                                                                      

In [13]: product_instance.__dict__                                                                                                                                                                                 
Out[13]: 
{'_state': <django.db.models.base.ModelState at 0x7f75c0629978>,
 'id': 2,
 'name': 'MyProduct',
 'created_on': datetime.date(2020, 1, 24)}

In [14]: serializer.data                                                                                                                                                                                           
Out[14]: {'name': 'MyProduct', 'created_on': OrderedDict([('year', 2020), ('month', 1), ('day', 24)])}
0 голосов
/ 13 августа 2020

Зачем все усложнять. Вы можете сделать

class Product:
    name = models.CharField(max_length=255, unique=True)
    created_on = models.DateField()

Затем для сериализатора

class ProductSerializer(serialzers.ModelSerializer):

    def to_representation(self, instance):
       try:
         data = super().to_representation(instance)
         created_on = {}
         ### You should check on the syntax for this to get the right value
         created_on['year'] = data['created_on'].year 
         created_on['month'] = data['created_on'].month
         created_on['day'] = data['created_on'].day 
         data['created_on'] = created_on
       except :
          pass
       return data

    class Meta:
         model = Friend
         fields = ("name", "created_on")

или, может быть, так:

class ProductSerializer(serialzers.ModelSerializer):
    created_on_2 = serializers.SerializerMethodField()
    
    def get_created_on_2(self, obj):
         created_on = {}
         ### You should check on the syntax for this to get the right value
         created_on['year'] = data['created_on'].year 
         created_on['month'] = data['created_on'].month
         created_on['day'] = data['created_on'].day 
         return created_on

    class Meta:
         model = Friend
         fields = ("name", "created_on", "created_on_2)

Таким образом, вы все равно можете сделать запрос POST с обычное строковое значение даты для created_on и GET с желаемым результатом. Для получения дополнительной информации проверьте это to_respresentation () SerializerMethodField

0 голосов
/ 03 августа 2020
class DateSerializer(serializers.Serializer):
    year = serializers.IntegerField()
    month = serializers.IntegerField()
    day = serializers.IntegerField()

    def to_internal_value(data):
        return datetime.date(data["year"], data["month"], data["day"])
...