ghazi-git / drf-standardized-errors

Standardize your DRF API error responses
https://drf-standardized-errors.readthedocs.io/en/latest/
MIT License
232 stars 13 forks source link

internal unhandled error when raising validation error from model clean() #78

Open codeforcesmeme opened 1 week ago

codeforcesmeme commented 1 week ago

I have got a semi-complex case of model inheritance and need to do some validation check on instance update/create and the best way for me to avoid duplication is to put it in the model clean (one shared at the top abstract parent) and others optional at the children, just to avoid some big code duplications on the serializer level per polymorphic child etc..

This worked flawlessly before but since integrating drf-standardized-errors I've been getting the following error trace of internal error with no easy way to grasp what's happening because I can't tell which function called from DRF is being masked from this package.

class Order(TimeStampedModel):
    class Meta:
        abstract = True

    _safedelete_policy = SOFT_DELETE_CASCADE
    related = models.ForeignKey(Related, on_delete=models.CASCADE, null=False)
    all_objects = Manager()

    def clean(self):
        if not self.deleted and self.related.sub.is_condition:
            raise ValidationError(BusinessErrors.ORDER_IS_AFTER_DATE)
    def save(self, **kwargs):
        self.full_clean()
        super(Order, self).save(**kwargs)

class BuyOrder(Order):
    some_prop = models.IntegerField(null=False)
    def clean(self):
        super(Order, self).clean()
        if not some_condition:
            raise ValidationError(BusinessErrors.EXCEEDS_CREDIT_LIMIT)

Full trace:

ERROR    django.request:log.py:241 Internal Server Error: /orders/2/related/
Traceback (most recent call last):
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/sentry_sdk/integrations/django/views.py", line 90, in sentry_wrapped_callback
    return callback(request, *args, **kwargs)
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/django/views/decorators/csrf.py", line 56, in wrapper_view
    return view_func(*args, **kwargs)
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "/home/myownproject/api/orders/views.py", line 80, in update_related
    buy_orders.save(related=related)
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/rest_framework/serializers.py", line 731, in save
    self.instance = self.create(validated_data)
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/rest_framework/serializers.py", line 703, in create
    return [
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/rest_framework/serializers.py", line 704, in <listcomp>
    self.child.create(attrs) for attrs in validated_data
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/rest_polymorphic/serializers.py", line 79, in create
    return serializer.create(validated_data)
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/rest_framework/serializers.py", line 962, in create
    instance = ModelClass._default_manager.create(**validated_data)
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/django/db/models/manager.py", line 87, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/django/db/models/query.py", line 658, in create
    obj.save(force_insert=True, using=self.db)
  File "/home/myownproject/api/orders/models.py", line 90, in save
    self.full_clean()
  File "/opt/pysetup/.venv/lib/python3.9/site-packages/django/db/models/base.py", line 1502, in full_clean
    raise ValidationError(errors)
django.core.exceptions.ValidationError: {'__all__': ['ORDER_IS_AFTER_DATE']}
GiancarloFusiello commented 1 week ago

@ghazi-git correct me if I'm wrong. @codeforcesmeme The problem looks like a Django core ValidationError is being raise rather than a DRF ValidationError, hence why it's not handling the exception. Was DRF catching the Django core exception previously?

codeforcesmeme commented 1 week ago

@GiancarloFusiello Thank you!! that's exactly it! Silly thing I missed it since I am not the one who wrote the code but had to deal with this after tests failing when I integrated drf-standardized-errors. Answering your question, yes it was handled before with vanilla DRF. I will leave the ticket up to the maintainers if they think code validation exceptions should be handled as well or not, otherwise, feel free to close it.

GiancarloFusiello commented 1 week ago

@codeforcesmeme happy to help. FWIW, looking at DRF exception_handler it doesn't look like Django core validation errors are handled directly.