5

I'm having a problem with django rest framework.
My front is posting data to drf, and one of the fields could be null or an empty string "".

# models.py
class Book(models.Model):
    title = models.CharField(max_length=100)
    publication_time = models.TimeField(null=True, blank=True)


# serializers.py
from rest_framework import serializers
from .models import Book

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ('id', 'title', 'publication_time')

publication_time could either be blank or "".

The blank case works, in fact when I post a json {"title": "yeah a book", "publication_time": none} everything is fine.

When I send {"title": "yeah a book", "publication_time":""} I do get a validation error "Time has wrong format. Use one of these formats instead: hh:mm[:ss[.uuuuuu]]."

I've tried to add a field validator to the serializer class:

def validate_publication_time(self, value):
    if not value:
        return None

Or even using the extra_kwargs

# ....
def empty_string_to_none(value):
    if not value:
        return None    

# ....
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ('id', 'title', 'publication_time')
        extra_kwargs = {'publication_time': {'validators' : [empty_string_to_none]} }

What I am trying to do is to transform an empty string to None (that should be accepted by the serializer and the model) before any validation occurs or as the first validation rule.

PROBLEM:
The problem is that the validate_publication_time is never called and I get a validation error before even hitting the function. As I've understood there is a specific order in which the validators run, but now I have no idea how to solve my issue.

QUESTION:
What I want to do is to actually clean the data in order to transform "" into None before any validation is run. Is it possible? How?

EDIT: This is the representation of my serializer:

# from myapp.serializers import BookSerializer
# serializer = BookSerializer()
# print repr(serializer)
# This is the print result:
BookSerializer():
    id = IntegerField(label='ID', read_only=True)
    title = CharField(max_length=100)
    publication_time = TimeField(allow_null=True, required=False)

So as you can see the publication_time field could be null, isn't it?

Community
  • 1
  • 1
Leonardo
  • 4,046
  • 5
  • 44
  • 85

5 Answers5

11

I had the same problem and finally found a solution.

In order to deal with '' before the error occurs, you need to override the to_internal_value method:

class BookSerializer(serializers.ModelSerializer):
    def to_internal_value(self, data):
        if data.get('publication_time', None) == '':
            data.pop('publication_time')
        return super(BookSerializer, self).to_internal_value(data)
Ledorub
  • 354
  • 3
  • 9
Ivan Blinov
  • 121
  • 1
  • 5
  • Great answer, thanks! I would only make the following editions: turn the if statement to `if 'publication_time' in data and data['publication_time'] == ''` and set `data['publication_time'] = None` if it passes. As the author wants to convert an empty string to None, I think this way it would be more precise. – Diego Aragão Aug 21 '19 at 18:09
5

Have you tried to override serialization behavior? What you need is override .to_internal_value(self, data)

Gabriel Muj
  • 3,682
  • 1
  • 19
  • 28
4

the kwarg is allow_null and allow_blank not null and blank.

Rex Salisbury
  • 478
  • 4
  • 13
  • I don't think so, those are django model fields, not serializer fields – Leonardo May 12 '16 at 09:13
  • I've edited the question so you see also the serializer instance representation – Leonardo May 12 '16 at 09:19
  • I think this should be the correct answer. This worked for me. – eman.lodovice Aug 05 '19 at 13:45
  • 1
    @Leonardo What he means is that when you declare the variable in meta data in serializer you can say sometext= serializers.CharField(allow_blank=True) not in model,py but in serializer py . This would overwrite the default behavior as well. https://www.django-rest-framework.org/api-guide/fields/ – Evren Bingøl Jan 28 '21 at 22:14
1

You can override serializer's save method where you would check if the value is an empty string and if it is then set it to Null.

In your serializer (untested):

def save(self, *args, **kwargs)
    if self.publication_time == "":
        self.publication_time = Null
    super.save(*args, **kwargs)

Or, you can do like that in a view(this is how I do that):

def perform_update(self, serializer):
    publication_time = self.kwargs['publication_time']
    if publication_time == "":
        publication_time = Null
    serializer.save(publication_time=publication_time)

only then you'll also need to overwrite perform_create if you also need this when you POST, not only when PUT

0

Answer of @Ivan Blinov is correct, except you should allow the data to be mutable, otherwise you get this error:

 AttributeError: This QueryDict instance is immutable

so the complete answer is:

class BookSerializer(serializers.ModelSerializer):
    def to_internal_value(self, data):
        if data.get('publication_time', None) == '':
            data._mutable = True
            data.pop('publication_time')
    return super(BookSerializer, self).to_internal_value(data)