Django’s built-in shortcut get_object_or_404 is a handy way to catch exceptions when a record isn’t found in the database (maybe the user messed with the URL params). But the generic 404 page isn’t always what you want to show. Sometimes it makes a bit more sense to show the user a separate template instead.
To solve this here’s a simple replacement function which makes use of a custom exception and some new middleware.
First the custom exception:
app/exceptions/custom.py
class NotFound(Exception): | |
def __init__(self, template): | |
# call the base class constructor | |
Exception.__init__(self, 'Record not found') | |
# set the template | |
self.template = template |
Next the middleware:
app/middleware/exceptions.py
from django.shortcuts import render | |
from app.exceptions.custom import NotFound | |
class ExceptionMiddleware(object): | |
def process_exception(self, request, exception): | |
if type(exception) == NotFound: | |
return render(request, exception.template) | |
return None |
Make sure you add the middleware to your settings:
MIDDLEWARE_CLASSES = ( | |
… | |
'app.middleware.exceptions.ExceptionMiddleware', |
And finally the replacement function:
app/lib/shortcuts.py
from django.shortcuts import _get_queryset | |
from app.exceptions.custom import NotFound | |
def get_object_or_template(klass, template, *args, **kwargs): | |
# replacement for django.shortcuts.get_object_or_404() | |
# allows a template to be supplied instead of a 404 | |
""" | |
Uses get() to return an object, or raises a Http404 exception if the object | |
does not exist. | |
klass may be a Model, Manager, or QuerySet object. All other passed | |
arguments and keyword arguments are used in the get() query. | |
Note: Like with get(), an MultipleObjectsReturned will be raised if more than one | |
object is found. | |
""" | |
queryset = _get_queryset(klass) | |
try: | |
return queryset.get(*args, **kwargs) | |
except queryset.model.DoesNotExist: | |
raise NotFound(template) |
You can now use this anywhere you were using get_object_or_404:
# get the product or show the not found template | |
product = get_object_or_template(Product, 'templates/product/not_found.html', pk=1) |
Using this same design pattern you could easily create a get_object_or_redirect shortcut which redirects the user to another page instead of showing a template.