Add extra data before serializer is saved

1,043 views
Skip to first unread message

Vlad Lep

unread,
Mar 23, 2015, 7:41:23 AM3/23/15
to django-res...@googlegroups.com
Hello,

I am using viewset, the default viewsets.ModelViewSet. 
I am trying to find the best way to hook yourself before the serializer save is called. I want to initialize some variables before serialization is done. 
As an example, let's say we have a model: 

class Book(models.Model):
    number = models.CharField(_("Book number"), max_length=50)

the number is a required field on the model level. At the API level, it either needs to be passed on the request OR the system generates a unique next number for the book.

Option 1: 
If i try to overwrite the serialize field:
number = serializers.CharField(max_length=50, required=False)
 
and just  overwrite the create view method: 

 def perform_create(self, serializer):
        if number_is_not set on request:
 serializer.save(number=new_generated_number)
                   else: 
 serializer.save() 

This would be most normal in my view, but it does not work. It gives me a field required error (for the number). The required=False is not taken into consideration for me. 

Option 2:
i wanted to add the number on the request before validation is done. Found this option, by overwriting the viewset method:

 def initialize_request(self, request, *args, **kwargs):     
        request = super(BookViewSet, self).initialize_request(request, *args, **kwargs)
        number = request.DATA.get('number', None)
        #Set number in case it is not sent, when creating a new book.
        if not number and request.method == 'POST':
            request.DATA['number'] = "generated number"
        return request

Option 2 works, but somehow it does not feel to be the most proper way. Is there other ways to do this? 

Cheers,
Vlad

Tim Laurence

unread,
Mar 23, 2015, 10:56:15 AM3/23/15
to django-res...@googlegroups.com
I have a very similar problem. I am trying to build a system that does copy-on-write. It easy to make copies, zero out the pk, but hard to increament the revision number that I use to revisions of the copies. Here is my code below. I tried poking at the various data structures in the serializer but none prevent is_valid from failing.

class Manufacturer(models.Model):

    MAX_MANUFACTURER_NAME_LENGTH = 50

    class Meta:
        unique_together = ('name_normalized', 'revision')


    name = models.CharField(max_length=MAX_MANUFACTURER_NAME_LENGTH,
                            blank=False,
                            null=False,
                            verbose_name="Manufacturer Name")

    name_normalized = models.CharField(max_length=MAX_MANUFACTURER_NAME_LENGTH,
                            blank=False,
                            null=False)

    revision = models.IntegerField(default=0)

    def save(self, *args, **kwargs):

    # This doesn't work
    # self.revision += 1

        super(Manufacturer, self).save(*args, **kwargs)

class ManufacturerDetail(generics.UpdateAPIView,
                         generics.RetrieveAPIView):
    queryset = Manufacturer.objects.all()
    serializer_class = ManufacturerSerializer
    permission_classes = (AuthenticatedOrReadOnly,)

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
      
    ....specialized data validation....

        # We aim to copy on write so zero out PK
        instance.pk = None

        serializer = self.get_serializer(instance, data=request.data, partial=partial)

    ## Can in increament revision here??
    ## This returns an error becuase of unique_together constraint
        serializer.is_valid(raise_exception=True)

        self.perform_update(serializer)
        return Response(serializer.data)


class ManufacturerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Manufacturer
        fields = ('id', 'name', "revision", "name_normalized")
        read_only_fields = ('id', "revision", "name_normalized")

    name_normalized = serializers.CharField(required=False)
    revision = serializers.IntegerField(required=False)

Ion Scerbatiuc

unread,
Mar 23, 2015, 11:03:05 AM3/23/15
to django-res...@googlegroups.com
Vlad,

The problem is that the field is required at the database level. Therefore even if you specified `required=False` on the serializer field, a non-specified value will still not be valid.

You could try using the `default=` argument on the number serializer field. Just pass a callable and if the number is not specified in the POST data, the default will be used.

number = serializers.CharField(max_length=50, required=False, default=generate_number)

Would that work for you?
-Ion


--
You received this message because you are subscribed to the Google Groups "Django REST framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-rest-fram...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Ion Scerbatiuc

unread,
Mar 23, 2015, 11:10:33 AM3/23/15
to django-res...@googlegroups.com
Oh, the `required=True` is implied when you use `default=...` so you'll get an error with my example. I didn't tested it, but I have strong feelings that the default argument is exactly what you need.

-Ion

Vlad Lep

unread,
Apr 8, 2015, 9:18:07 AM4/8/15
to django-res...@googlegroups.com
Thanks for the replies and sorry for the very late replies.

@ Ion: I was reading somewhere in the docs and was hoping that the serializer can hold the ultimate validations (they wanted to make it easy to change from modelserializer to serialzier). I can not really find where now, maybe i was wrong. Somehow i wanted to let me make sure there is a number and just trust what i say on the serializer.

I tried setting an initial value, in the serializer but then i get some strange code. I needed to test if that initial value was not changed and if it was not changed then generate a new number.  It looked hacky and also had some errors in some cases depending the encoding. 

Currently  I use version 2 and works but i do not like it that much. except that now, i get a strange error when there is a url-encoding.  When i try to set: "request.DATA['number'] = "generated number" i  get: 'This QueryDict instance is immutable'.

Vlad Lep

unread,
Apr 8, 2015, 9:25:40 AM4/8/15
to django-res...@googlegroups.com
@Tim: I might not understand that well the entire problem but it feels to me that the revision is incremented too late.
You increment it in the model, in the save method, which is called after the is_valid. You need to change the request.data before you call the is_valid method from what i know. 

Hope that helps you, 
Vlad
Reply all
Reply to author
Forward
0 new messages