Does a ModelSerializer catch "django.core.exceptions.ValidationError"s and turn them to an HTTP response with a 400 status code?

Let's say this is my model:

from django.core.exceptions import ValidationError


class MyModel(models.Model):
    value = models.CharField(max_length=255)

    def clean(self):
        if self.value == "bad":
            raise ValidationError("bad value")

    def save(self):
        self.full_clean()
        return super().save()

And I have this serializer:

from rest_framework.serializers import ModelSerializer

class MyModelSerializer(ModelSerializer):
    class Meta:
        model = MyModel
        fields = ["value"]

And this was my viewset

from rest_framework.viewsets import ModelViewSet

class MyModelViewSet(ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer

My question is: what should happen when a bad value is submitted for value?

  • Should it be caught by DRF and turned into an HTTP response with a 400 status code?

  • Or should it be treated like a regular exception and crash the server?

I'm asking this because when I submit invalid data in DRF's browsable API, instead of catching the ValidationError and returning an HTTP response, DRF stops the application entirely.

Is that normal, or am I doing something wrong?

I don't want to repeat my validation logic again in the serializer, so what's the correct approach here?

I assume there are a few stimulus requests being used to test this:

  1. "good"
  2. "bad"
  3. "x" * 256 # greater than max_length
    if self.value == "bad":
        raise ValidationError("bad value")

The OP does not explicitly mention it, but I'm going to assume that test case (2.) does in fact trigger that raise, and Django turns it into a "client error" 400 http response. If you were to add a logging statement within the if, we should see a log entry prior to the raise happening.

when I submit invalid data in DRF's browsable API

I assume "invalid" is the "too long" test case (3.)

DRF stops the application entirely.

That is not normal, as the webserver should continue to listen and serve requests no matter what. The OP does not include a stack trace, so it is impossible to tell where the error originated. Django should trap whatever the exception was and return a "server error" 500 response to the web client, and append an entry to its local error log file.

I don't want to repeat my validation logic again

Indeed. Keep it DRY!

The code you posted looks good. There's something in your environment you're not telling us about, perhaps in your settings.py. Posting a GitHub repo link can be helpful, but posting a full stack trace is essential when tracking down such errors.

Please verify that you have not set DEBUG = True. I assume you're reporting bad behavior that happened in a production environment.

If you change the validation error class from Django's to DRF's it should work. DRF converts its own exceptions to 400, not the ones from Django.

The one you're looking for is rest_framework.exceptions.ValidationError , here is your updated snippet:


from rest_framework.exceptions import ValidationError


class MyModel(models.Model):
    value = models.CharField(max_length=255)

    def clean(self):
        if self.value == "bad":
            raise ValidationError("bad value")

    def save(self):
        self.full_clean()
        return super().save()

I'm asking this because when I submit invalid data in DRF's browsable API, instead of catching the ValidationError and returning an HTTP response, DRF stops the application entirely.

Is that normal, or am I doing something wrong?

That's right, DRF doesn't handle Django's ValidationError, it only handles its own rest_framework.exceptions.ValidationError. As you might have guessed, it's not good practice to raise the DRF exception from a model...

I don't want to repeat my validation logic again in the serializer, so what's the correct approach here?

Yes, that's fair... This post might provide a solution for you, using a custom exception handler (if that's something you do a lot). Another alternative is to do it at the serializer level:

from django.core.exceptions import ValidationError as DjangoValidationError
from rest_framework.exceptions import ValidationError as DRFValidationError
from rest_framework.serializers import as_serializer_error


class MyModelSerializer(ModelSerializer):
    class Meta:
        model = MyModel
        fields = ["value"]

    def save(self, **kwargs):
        try:
            return super().save(**kwargs)
        except DjangoValidationError as exc:
            raise DRFValidationError(as_serializer_error(exc))

This might not give you very good errors back though...

Вернуться на верх