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
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
I am writing Rest API in tastypie and creating custom resource as follows
class Myresource(Resource):
def obj_create(self, request, **kwargs):
# logic when POST request is called
# here if some error occurs while inserting data I want to add my custom error message and return it response.
return bundle
How can I return custom error message from obj_create message. right now I am handling exception but tastypie always return 201.
I think you need to override the wrap_view method in your resource as mentioned here .
This is how its should work.
class YourResource(ModelResource):
def wrap_view(self, view):
"""
Wraps views to return custom error codes instead of generic 500's
"""
#csrf_exempt
def wrapper(request, *args, **kwargs):
try:
callback = getattr(self, view)
response = callback(request, *args, **kwargs)
if request.is_ajax():
patch_cache_control(response, no_cache=True)
# response is a HttpResponse object, so follow Django's instructions
# to change it to your needs before you return it.
# https://docs.djangoproject.com/en/dev/ref/request-response/
return response
except (BadRequest, ApiFieldError), e:
return HttpBadRequest({'code': 666, 'message':e.args[0]})
except ValidationError, e:
# Or do some JSON wrapping around the standard 500
return HttpBadRequest({'code': 777, 'message':', '.join(e.messages)})
except Exception, e:
# Rather than re-raising, we're going to things similar to
# what Django does. The difference is returning a serialized
# error message.
return self._handle_500(request, e)
return wrapper
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')
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.