Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to raise an error / return a {"foo":["This field is required."]} response in Django REST

Suppose I have a view and I need to check that a field is given before calling serializer.save to ensure I don't get a dictionary key error:

class BarView(CreateAPIView):
    serializer_class = BarSerializer
    queryset = Bar.objects.all()
    def perform_create(self, serializer):
        if 'foo' not in self.request.data:
            raise ParseError('foo field required.')
        foo = get_object_or_404(Foo, pk=self.request.data['foo'])
        if foo.counter == 10:
            raise ParseError('foo limit reached.')
        return serializer.save(user=self.request.user, foo=foo)

Instead of returning "foo field required." I would like to return a message the same as Django REST returns e.g. {"foo":["This field is required."]}

Is there a better way to do this? Perhaps validating the foo field alone with the serializer?

Update: I forgot to mention the user field is also required.

The model for Bar is:

class Bar(models.Model):
    user = models.ForeignKey(User, db_index=True, editable=False)
    foo = models.ForeignKey(Foo, db_index=True)
like image 552
gornvix Avatar asked Sep 04 '15 15:09

gornvix


People also ask

How do I create a custom exception in Django REST framework?

Custom exception handlingThe exception handler function should either return a Response object, or return None if the exception cannot be handled. If the handler returns None then the exception will be re-raised and Django will return a standard HTTP 500 'server error' response.

How to handle exception in Django?

Django Exception Example It raises a DoesNotExist exception if data not found. This is Django's built-in exception. It shows the following exception because no record is available at id 12. We can handle it by using try and except, now let's handle this exception.

What is Non_field_errors?

non_field_errors () This method returns the list of errors from Form. errors that aren't associated with a particular field. This includes ValidationError s that are raised in Form.


2 Answers

Yes,

Simply look at docs: Validation

(I assumed that field foo is part of Bar model, if not please add it to fields in Meta):

Add validation to BarSerializer:

class BarSerializer(serializers.ModelSerializer):
    def validate_foo(self, value):
        if not value:
            raise serializers.ValidationError("foo field required.")
        if Foo.objects.filter(pk=value, counter__gte=10).exists():
            raise serializers.ValidationError("foo limit reached.")
        return value

    class Meta:
        model = Bar

And then create Your View by extending this:

from rest_framework.exceptions import ValidationError

class MyCreateAPIView(CreateAPIView):        
    def post(self, request, *args, **kwargs):
        try:
             return super(BarView, self).post(request, *args, **kwargs)
        except ValidationError as e:
             return Response(e.detail, , status=status.HTTP_400_BAD_REQUEST)

    def create(self,request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        try:
            self.perform_create(serializer)       
        except DjangoValidationError as e:
            raise ValidationError(e.messages)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(serializer):
        # do your stuff
        serializer.save()
like image 104
WBAR Avatar answered Oct 04 '22 08:10

WBAR


Yes, the best way to do this is to make the foo field required in your serializer using extra_kwargs option in the Meta class.

DRF will automatically handle the validation for you. You don't need to raise this validation error yourself.

class Meta:
    ...
    extra_kwargs = {'foo': {'required':True}} # make 'foo' a required field.

Now, whenever the foo field is not passed in the request, there will be a key foo in the serializer.errors dictionary and its value will be This field is required.

Also, create a validate_foo() function which will validate for the limit of foo_object.counter.

def validate_foo(self, value):
    self.foo_object = get_object_or_404(Foo, pk=value) # get the 'foo' object
    if self.foo_object.counter == 10: # check for limits
        raise serializers.ValidationError('foo limit reached.') # raise error
    return value # must return the value at the end

FINAL CODE:

serializers.py

class BarSerializer(serializers.ModelSerializer):

    class Meta:
        ...
        extra_kwargs = {'foo': {'required':True}} # make 'foo' a required field.

    def validate_foo(self, value):
        self.foo_object = get_object_or_404(Foo, pk=value)
        if self.foo_object.counter == 10: 
            raise serializers.ValidationError('foo limit reached.') 
        return value

views.py

In your views, you need to override perform_create() and pass user and serializer.foo_object to serializer.save() function.

class BarView(CreateAPIView):
    serializer_class = BarSerializer
    queryset = Bar.objects.all()

    def perform_create(self, serializer):
        return serializer.save(user=self.request.user, foo=serializer.foo_object)
like image 36
Rahul Gupta Avatar answered Oct 04 '22 10:10

Rahul Gupta