Raise 404 instead of 500 when using an invalid uuid

Viewed 413

I have a uuid field in my Document model

If I get() my model with a bad uuid, I got a error 500. This line throw an error:

x = Document.objects.get(uuid=object_uuid)

Instead of a 500 I want a 404 error

I wrote this, but I don't want to put my function everywhere it's so redundant (I'm lazy)

def is_valid_uuid_or_404(object_uuid):
    try:
        return uuid.UUID(object_uuid)
    except ValueError:
        raise Http404("Invalid uuid")

x = Document.objects.get(uuid=is_valid_uuid_or_404(object_uuid)) #Boring

Someone has a good idea to make it better ?

EDIT 1 :

I get my object in my admin's change_view method, I override the change_view to add extra_context, this follows the recommendation of django

def change_view(self, request, object_id, form_url="", extra_context=None):
    extra_context = extra_context or {}
    extra_context["my_field"] = Document.objects.get(uuid=object_id).my_field
    return super().change_view(request, object_id, form_url, extra_context=extra_context)
2 Answers

You can create a custom model manager and then add a custom method to that

from django.db import models

class DocumentManager(models.Manager):

    def get_by_uuid(self, uuid, **kwargs):
            
        try:
             return uuid.UUID(object_uuid)
        except ValueError:
            raise Http404("Invalid uuid")
    
        return self.get(uuid=uuid, **kwargs)

class Document(models.Model):
      objects = DocumentManager()


x = Document.objects.get_by_uuid(object_uuid)

Since you are trying to get this object in the model admin simply use the get_object method the model admin already has to get the object, this way you won't need to handle the errors and all you would need to handle would be the method returning a None since there is no matching object:

from django.contrib.admin.utils import unquote


def change_view(self, request, object_id, form_url="", extra_context=None):
    extra_context = extra_context or {}
    
    to_field = request.POST.get('_to_field', request.GET.get('_to_field'))
    object = self.get_object(request, unquote(object_id), to_field)
    
    extra_context["my_field"] = object.my_field if object else None
    return super().change_view(request, object_id, form_url, extra_context=extra_context)

General Solution:

A bad (invalid) uuid will cause this error, and this gives you a 500 response instead of which you want a 404 response. Instead of catching this error, the better thing to do is perform validation beforehand. There are few areas where you might have gotten the UUID from, keeping things simple we'll assume you either got it from a url parameter or from a form. Both of these of course have proper ways to validate a UUID.

For a url pattern, you should use the uuid path converter, if the passed uuid is not a valid uuid the request won't even be routed to the view and you'll get a 404:

path('some-view/<uuid:object_uuid>/', views.some_view),

Next for a form you should use a UUIDField [Django-docs] and make use of the forms cleaning and validation:

from django import forms


class SomeForm(forms.Form):
    object_uuid = forms.UUIDField()


# In your view
def some_view(request):
    ...
    form = SomeForm(request.POST)
    if form.is_valid():
        object_uuid = form.cleaned_data['object_uuid']
    ...
Related