django: raise BadRequest as exception? - python

Is it possible to raise BadRequest as exception in django?
I have seen that you can raise a 404 [1].
Use case: in a helper method I load a json from request.GET. If the json was cut since the browser (IE) cut the url, I would like to raise a matching exception.
A BadRequest exception looks appropriate, but up to now there seems to no such exception in django.
In 1.6 there is a SuspiciousOperation exception. But this does not match in my case, since it is not security related.
Of course I could put a try..except around my helper method in the view method, but this is not DRY.
Has someone a solution where I don't need a try..exception around every call of my helper method?
[1] https://docs.djangoproject.com/en/1.6/ref/exceptions/#django.core.urlresolvers.Resolver404
Update
Code example:
def my_view(request):
data=load_data_from_request(request) # I don't want a try..except here: DRY
process_data(data)
return django.http.HttpResponse('Thank you')
def load_data_from_request(request):
try:
data_raw=json.loads(...)
except ValueError, exc:
raise BadRequest(exc)
...
return data

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

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")

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.

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:
...

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>

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.

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

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')

Related

How can I reference a user globally in Django for all Exceptions?

I have two questions that correlate.
1) Does django-rest-framework have a way to reference a user globally?
2) Does django / python allow me to change the generic exception class to include this user ID as meta every time it throws?
I know I can create custom exception classes and raise them in code, but what about an exception I don’t correctly handle? For example, let’s say a divide by zero exception is thrown but I didn’t correctly handle it, right now my logs just say “Divide by zero exception”.
Is there a way to update this globally so if a user is logged in it says “Divide by zero exception for user_id {id}“?
class SomeExternalApiHelper:
#staticmethod
def do_api_call():
url = 'https://example.com'
# do api request
try:
home_value = 100 / 0
except Exception as e:
# Exception occurs here, I want to be able to reference user_id, without having
# to pass the user_object all the way down into this call
raise Exception("Something went wrong for user ID {0}".format(user_id))
class AddNewHouse(APIView):
def post(self, request, format=None):
# I can call request.user here and access the user object
SomeExternalApiHelper.do_api_call()
You can create a custom middleware that catches all exceptions, you can then log the exception along with the user
def exception_middleware(get_response):
def middleware(request):
try:
response = get_response(request)
except Exception as e:
# You now have the exception and request.user and can log how you like
raise
return response
return middleware

Django rest_framework: How to pass additional attribute to exception / error response?

I'm using Python 3.6 with Django 1.11.9 and rest_framework 3.6.2.
I have a view (APIView) which can only be accessed by some users who successfully pass a given HasUnlimitedAccesPermission check. In case of failure to pass the latter, I raise a PermissionDenied with a detailed message of my choice about the error to pass on to front-end. So far so good, all of that is easily achievable by applying the HasUnlimitedAccessPermission to my view thanks to the "permission_classes" decorator (yeah, I'm using a function_based view, here).
Now, what I would like to achieve is passing an additional attribute to my error response JSON (when the user fails to pass the permission test). This attribute would be an "error_id" attribute that would give the front-end developer the ability to adapt the error display depending on the "error_id" value. An example of the response JSON would be :
{
"error": "To enjoy this feature, go pick one of our pay offer!",
"error_id": "no_unlimited_access"
}
Any idea on how to achieve this?
Your problem can be solved using a middleware issue.
I would recommend you to build a custom middleware. So the custom middleware can help you to create the response as per your need. Just plug the middleware in your django application.
You can read more about it in this blog or this blog
Define a Custom Exception class as,
from rest_framework.serializers import ValidationError
from rest_framework import status
class CustomAPIException(ValidationError):
"""
raises API exceptions with custom messages and custom status codes
"""
status_code = status.HTTP_400_BAD_REQUEST
default_code = 'error'
def __init__(self, detail, status_code=None):
self.detail = detail
if status_code is not None:
self.status_code = status_code
and use in views as,
from rest_framework import status
def my_view(request):
if some_condition:
error_msg = {
"error": "To enjoy this feature, go pick one of our pay offer!",
"error_id": "no_unlimited_access"
}
raise CustomAPIException(error_msg)
OK, thanks to Bear Brown's comment and Jerin Peter George's answer (completed by rest_framework source code), I've done as follows:
1) Created a custom PermissionDenied exception:
class CustomPermissionDenied(PermissionDenied):
def __init__(self, detail=None, code=None, error_id=None):
super().__init__(detail=detail, code=code)
self.error_id = error_id
which is raised in HasUnlimitedAccessPermission, for instance, this way:
raise CustomPermissionDenied(detail=_("To use this feature, subscribe to one of our plans."),
error_id="no_unlimited_access")
2) In a custom_exception_handler (that I already had for other purpose) I added the lines between the ellipses
def custom_exception_handler(exc, context):
...
error_id = getattr(exc, "error_id", None)
if error_id is not None:
new_response_data["error_id"] = error_id
...
response.data = new_response_data
return response
And here it is, I have the error response format I was looking for. Thanks to all of you for your help!

Custom exception handler not working as documented in django-rest-framework

I'm trying to write a custom exception handler in django-rest-framework, and the code is the same as given in the example:
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
# Now add the HTTP status code to the response.
if response is not None:
response.data['status_code'] = response.status_code
return response
But on raising an exception from the view, this does not work, it instead throws this message:
custom_exception_handler() missing 1 required positional argument: 'context'
I've tried setting the first argument to None, like so:
def custom_exception_handler(exc, context=None):
But this happens:
exception_handler() takes 1 positional argument but 2 were given
So it seems rest_framework.views.exception_handler takes only one argument.
Indeed this is the case:
def exception_handler(exc):
"""
Returns the response that should be used for any given exception.
By default we handle the REST framework `APIException`, and also
Django's built-in `ValidationError`, `Http404` and `PermissionDenied`
exceptions.
Any unhandled exceptions may return `None`, which will cause a 500 error
to be raised.
"""
So my question is, is this a bug? Or am i missing something, and there is another way to do this?..
EDIT:
This has been confirmed officially by the rest_framework team. This has been added in the latest version so it seems using v3.0.2 will not reflect the new documentation.
https://github.com/tomchristie/django-rest-framework/issues/2737
We can raise exception from API View using APIException or create custom Exception class that extends APIException.
raise APIException("My Error Message")
Now Custom Exception handler is
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
if isinstance(exc, APIException):
response.data = {}
response.data['error'] = str(exc)
elif isinstance(exc, MyCustomException):
response.data = {}
response.data['error'] = str(exc)
return response

How to catch any exception in python and print stack trace (DJANGO)

I have like this:
try:
#bunch of code
except:
return HttpResponse....
I want to send an error to client and to print stack trace on console. How can i do that?
You can do something like this
import traceback
from django.http import HttpReponse
def view(request):
try:
#throw exception
except:
tb = traceback.format_exc()
return HttpResponse(tb)
# normal flow
You can create a custom middleware class where you can catch all exceptions:
class ErrorMiddleware(object):
def process_exception(self, request, exception):
# send error
return HttpResponse(...) # or None
and put it on first place to MIDDLEWARE_CLASSES tuple in settings.py.
From middleware process_exception docs:
Django calls process_exception() when a view raises an exception.
process_exception() should return either None or an HttpResponse
object. If it returns an HttpResponse object, the template response
and response middleware will be applied, and the resulting response
returned to the browser. Otherwise, default exception handling kicks
in.
You might want to enable logging of all exceptions How do you log server errors on django sites
and make a custom 500 django error page https://docs.djangoproject.com/en/dev/topics/http/views/#the-500-server-error-view
and you should also make a 500 error page at web server level (apache / nginx) just in case the framework doesn't work.
That way you can "catch" all errors and show a nice error message to the client.

Does Django have exception for an immediate http response?

Django-Tastypie has ImmediateHttpResponse exception which allow to return to the client an immediate response:
raise ImmediateHttpResponse(response='a message')
Django has Http404, but i couldn't find a more universal exception like ImmediateHttpResponse.
What technique do you use to return to client an immediate 400 response?
For example having model:
class Subscriber(Model):
def delete(self, *args, **kwargs):
raise ImmediateHttpResponse('Deleting subcribers is not allowed!')
and trying to delete an object would return to the client a 400 response with the given message.
I think what you want is a middleware which implements a process_exception.
It works like this: you raise an Exception on you view (e.g. ImmediateHttpResponse). If the exception is catched by your middleware, the middleware returns a response, in your case the with a status 400. If you don't catch the exception, Django will catch it in the end, returning a status 500 (server error), or other status.
The simplest example is a middleware that catches Http404. If you catch the exception Http404 in your middleware, you can return any response you want (status 200, 400, etc). If you don't catch (i.e. the process_exception method of you middleware returns None), Django will catch it for you and return a Response with status 404. This is actually the standard way of having your own custom exceptions that you want to respond in a custom way.
It is not an exception, but there is HttpResponseBadRequest, which is your normal HttpResponse but with 400.
The Http404 exception is simply an empty Exception class, there is nothing special about it:
class Http404(Exception):
pass
So you can easily create your own if you must:
class Http400(Exception):
pass
ImmediateHttpResponse isn't that much different than Http404 in that its also a generic Exception but with a specific property, which makes it more like HttpResponseBadRequest:
class ImmediateHttpResponse(TastypieError):
"""
This exception is used to interrupt the flow of processing to immediately
return a custom HttpResponse.
Common uses include::
* for authentication (like digest/OAuth)
* for throttling
"""
_response = HttpResponse("Nothing provided.")
def __init__(self, response):
self._response = response
#property
def response(self):
return self._response

Categories