-1

Let's say I have simple Product Django model:

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

I'm using Django Rest Framework to serialize this model. I'd like to break up created_on into its own object (including the response from GET requests and the payload in POST requests):

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

Here's what I have so far:

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

This approach works for the GET requests (that is, I get the json above). However, it doesn't work for POST requests (payload being the json above). The response is 400 status code with the message {'created_on': [ErrorDetail(string='This field is required.', code='required')]}.

If I pass required=False into DateSerializer, I see self.initial_data in the create method is <QueryDict: {'name': ['MyProduct'], 'created_on': ['year', 'month', 'day']}>. So the values disappear for some reason.

Any idea what I'm doing wrong here and how I can get this to work?

Johnny Metz
  • 5,977
  • 18
  • 82
  • 146

4 Answers4

2

Figured it out, I need to set the content_type=application/json header in the request. Otherwise it defaults to content_type=multipart/form-data which flattens the payload to {'name': ['MyProduct'], 'created_on': ['year', 'month', 'day']}.

Johnny Metz
  • 5,977
  • 18
  • 82
  • 146
1

Override the validate(...) method of ProductSerializer class as,

from datetime import date


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


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

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

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

Example:

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)])}
JPG
  • 82,442
  • 19
  • 127
  • 206
0
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"])
rgermain
  • 708
  • 4
  • 11
0

Why make it so complicated. You can do

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

Then for the serializer

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")

or maybe this way:

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)

This way you can still make a POST request with a regular Date string value for "created_on" and GET with your desired output For more information, check this to_respresentation() SerializerMethodField

Kyle.Ng
  • 96
  • 1
  • 4