[Django]-Django: raise BadRequest as exception?

14👍

You need custom middleware to handle exception what you raise.
Utilize custom exceptions to check for this condition in middleware.

class ErrorHandlingMiddleware(object):
    def process_exception(self, request, exception):
        if not isinstance(exception, errors.ApiException): # here you check if it yours exception
            logger.error('Internal Server Error: %s', request.path,
                exc_info=traceback.format_exc(),
                extra={
                    'request': request
                }
            )
        # if it yours exception, return response with error description
        try:
            return formatters.render_formatted_error(request, exception) # here you return response you need
        except Exception, e:
            return HttpResponseServerError("Error During Error Processing")

29👍

The other answers are explaining how to return an HTTP response with 400 status.

If you want to hook into Django’s 400 error handling, you can raise a SuspiciousOperation exception or a subclass of it.

See the docs here and here.

In your example it would look like:

from django.core.exceptions import SuspiciousOperation

def load_data_from_request(request):
    try:
        data_raw = json.loads(...)
    except ValueError:
        raise SuspiciousOperation('Invalid JSON')
    # ...
    return data
👤yprez

9👍

As an alternative to @coldmind’s answer (converting exceptions in a middleware layer), you could put a decorator on your view function which does the same thing. Personally I prefer this, because it’s just plain-old-Python, and doesn’t require me dust off my knowledge of how Django middleware works.

You don’t want to stream-of-conciousness inline all functionality in your view functions (this makes your view module depend on all your project’s other modules, leading to ‘everything depends on everything else’ architecture) Instead, it’s better if the view just knows about http. It extracts what you need from the request, delegates to some other ‘business logic’ function. The business logic might delegate to other modules (e.g. database code or interfaces to other external systems.) Then finally the return value from your business logic is converted into an http response by the view function.

But how to communicate errors back to the view function from the business logic (or whatever it delegates to)? Using return values is irksome for many reasons. For example, these error return values will have to be propogated back to the view from all through your whole codebase. This is often cripplingly messy because you will already be using the return values of functions for other purposes.

The natural way to deal with this is to use exceptions, but the Django view won’t, by itself, convert uncaught exceptions into returned HTTP status codes (except for a couple of special cases, as the OP says.)

So. I write a decorator to apply to my view. The decorator converts various raised exception types into different returned django.http.HttpResponseXXX values. e.g:

# This might be raised by your business logic or database code, if they get
# called with parameters that turn out to be invalid. The database code needs
# know nothing about http to do this. It might be best to define these exception
# types in a module of their own to prevent cycles, because many modules 
# might need to import them.
class InvalidData(Exception):
    pass

# This decorator is defined in the view module, and it knows to convert
# InvalidData exceptions to http status 400. Add whatever other exception types
# and http return values you need. We end with a 'catch-all' conversion of
# Exception into http 500.
def exceptions_to_http_status(view_func):
    @wraps(view_func)
    def inner(*args, **kwargs):
        try:
            return view_func(*args, **kwargs)
        except InvalidData as e:
            return django.http.HttpResponseBadRequest(str(e))   
        except Exception as e:
            return django.http.HttpResponseServerError(str(e))
     return inner

 # Then finally we define our view, using the decorator.

 @exceptions_to_http_status
 def myview(self, request):
     # The view parses what we need out of incoming requests
     data = request.GET['somearg']

     # Here in the middle of your view, delegate to your business logic,
     # which can just raise exceptions if there is an error.
     result = myusecase(data)

     # and finally the view constructs responses
     return HttpResponse(result.summary)

Depending on circumstance, you might find the same decorator could work on many, or all, of your view functions.

6👍

HttpResponseBadRequest is ready to use. It is implemented as:

class HttpResponseBadRequest(HttpResponse):
    status_code = 400

Edited due OP updated question.

You can create your own helper and encapsulate try-catch block into it.

def myJsonDec(str):
    try:
        ...

6👍

Since 3.2 Django provides BadRequest class. So you can now do

try:
    data_raw = json.loads(...)
except ValueError:
    raise BadRequest('Invalid JSON')

The problem with that is that for some reason the message ‘Invalid JSON’ doesn’t appear in the error response, only generic one is shown:

<!doctype html>
<html lang="en">
<head>
  <title>Bad Request (400)</title>
</head>
<body>
  <h1>Bad Request (400)</h1><p></p>
</body>
</html>

0👍

I’m not sure what you mean by raising BadRequest as an exception.

You can return a response with any status code you like, either by explicitly using the relevant subclass of HttpResponse, or by adding a status parameter to the normal response.

0👍

I think one of the simple way is to define your own BadRequestException and raise it in called function.

from django.http import HttpResponseBadRequest, HttpResponse

class BadRequestException(Exception):
    def __init__(self, message='', *args, **kwargs):
        self.message = message

def my_view(request):
    try:
        data = get_data_from_another_func()
    except BadRequestException as e:
        return HttpResponseBadRequest(e.message)
    process_data(data)
    return HttpResponse('Thank you')

def get_data_from_another_func():
    raise BadRequestException(message='something wrong')

def process_data(data):
    pass

0👍

The simpler solution I found is to raise the BadRequest exception. Here is a method I use to return Http400 if the required parameter is not passed in the request. Raising the BadRequest exception will make the request return a 400 status code.

from django.core.exceptions import BadRequest
from django.http import HttpResponse
from django.views import View

def get_param_or_return_400(kwargs: dict, key: str):
    if key in kwargs:
        return kwargs[key]
    raise BadRequest(f'The parameter "{key}" must be provided')

class MyView(View):

    def post(self, request, *args, **kwargs):
        query = get_param_or_return_400(kwargs, 'query')
        # At this point we know query is not None
        do_some_operation(query)
        return HttpResponse('OK')
👤mrj

Leave a comment