How can I override handler404 to pass arguments into a view?
I need to pass the arguments text and error
# views.py
def handler404(request, error, text):
response = render_to_response('error/40X.html', {'error': error, 'text':text})
response.status_code = error
return response
The 404 handler code in urls.py is:
handler404 = 'app.views.handler404'
I am using Django v1.10 and Python v3.5, and would prefer avoiding having to create a function for each error.
The question was translated, and I would be grateful for corrections, original
I don't think you need a custom handler404 view here. Django's page_not_found view does what you want.
In Django 1.9+, you can include {{ exception }} in the 404 template.
In your view, you could set the message when raising the exception, for example:
from django.http import Http404
def my_view(request):
raise Http404('custom error message')
It doesn't make sense to set response.status_code in the handler - if it's handler404, then the status code should always be 404.
Related
Is there any way to have Django Rest Framework automatically respond with HTTP_400_STATUS's when there are database exceptions?
(IntegrityError and so on)
Example: I have a model with a unique username field and I'm trying to use a generic rest_framework.ListCreateAPIView. HTTP_400_STATUS's are normally thrown automatically if serializer validation fails but this is actually valid input, just not valid in the db. What should I do here?
While overriding the generic view is a completely valid solution, I think that a better solution is to make use of Django REST Frameworks's option to implement custom exception handling. You do this by creating a handler function that converts exceptions raised in your API views into response objects. To do this, all you have to do is tell Django REST Framework where your custom handler is by overriding it in the settings:
REST_FRAMEWORK = {'EXCEPTION_HANDLER':'my_project.my_app.utils.custom_exception_handler'}
Inside the file specified (my_project/my_app/utils.py in this case) you would then do something like the following:
from __future__ import unicode_literals
from django.db import IntegrityError
from rest_framework.views import Response, exception_handler
from rest_framework import status
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 there is an IntegrityError and the error response hasn't already been generated
if isinstance(exc, IntegrityError) and not response:
response = Response(
{
'message': 'It seems there is a conflict between the data you are trying to save and your current '
'data. Please review your entries and try again.'
},
status=status.HTTP_400_BAD_REQUEST
)
return response
As the docs say, it's worth noting "that the exception handler will only be called for responses generated by raised exceptions." (i.e. only when you do the following: serializer.is_valid(raise_exception=True)). However, this only matters if you are calling serializer.is_valid() yourself since "the generic views use the raise_exception=True flag, which means that you can override the style of validation error responses globally in your API. To do so, use a custom exception handler, as described above." Also, I just want to point out that if you wish to specify a custom IntegrityError message in a given view later on then you can always override the generic view as the other answers demonstrate and the custom exception handler will not insert the default message since response will no longer be None.
to do this using rest_framework proper (with a rest framework style response):
from django.db import IntegrityError
from rest_framework import status
from rest_framework.generics import ListCreateAPIView
from rest_framework.response import Response
class MyListCreateAPIView(ListCreateAPIView):
def create(self, request, *args, **kwargs):
try:
return super(ListCreateAPIView, self).create(request, *args, **kwargs)
except IntegrityError:
content = {'error': 'IntegrityError'}
return Response(content, status=status.HTTP_400_BAD_REQUEST)
Here is a list of available HTTP 400 status codes
You should extend ListCreateAPIView and catch the IntegrityError and handle it by returning a bad_request:
from django.views.defaults import bad_request
from rest_framework.generics import ListCreateAPIView
class MyListCreateAPIView(ListCreateAPIView):
def create(self, request, *args, **kwargs):
try:
return super(ListCreateAPIView,self).create(request, *args, **kwargs)
except IntegrityError:
return bad_request(request)
Interestingly you could raise a SuspiciousOperation instead of returning the bad_request explicitly:
except IntegrityError:
from django.core.exceptions import SuspiciousOperation
raise SuspiciousOperation
Then django will return a 400 BAD REQUEST.
This is how I solve this problem for a CreateView
class MyModelCreate(CreateView):
model = MyModel
fields = '__all__'
success_url = reverse_lazy('MyModels') # In case of success the user will be redirect to the listview. This is defined on your urls.py
def form_valid(self, form):
try:
return super(MyModelCreate, self).form_valid(form)
except IntegrityError as e:
messages.error(self.request, 'Integrity error. The name already exists. ' + str(e)) # You don't need to use the str(e). It is just for you to know what is going on.
return HttpResponseRedirect(reverse('MyModel_create',))
On your template, all you have to do is add the following lines
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
I am using Django Rest Framework 3.2.3 (DRF) and Django Rest Framework JWT 1.7.2 (DRF-JWT, https://github.com/GetBlimp/django-rest-framework-jwt) to create Login tokens.
I need to change the Status Code for invalid credentials when issuing a JWT from 400 to 202 (FYI: my client can't read the body of non-200 responses). I use a custom exception handler as described by Django Rest Framework to achieve it: http://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling
restapi/custom_exception.py
from rest_framework.views import exception_handler
from rest_framework import status
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)
print ('Exception raised: ' + str(response.status_code))
# Now add the HTTP status code to the response.
if response is not None:
if response.status_code != status.HTTP_403_FORBIDDEN:
response.status_code = status.HTTP_202_ACCEPTED
return response
And in the config:
'EXCEPTION_HANDLER': 'restapi.custom_exception.custom_exception_handler',
The DRF-JWT should raise ValidationError, when invalid credentials are used. I still get a 400 Bad Request response code, when posting invalid credentials to JWT token-auth interface.
With every other DRF interface I am getting the 202 status code as expected.
How can I get DRF-JWT to use the custom exception handler for their ValidationErrors?
Why we are not able to use custom exception handler here?
This is happening because raise_exception flag has not been passed to the JSONWebTokenSerializer when calling .is_valid() on it. ( JSONWebTokenSerializer is the serializer class used to validate a username and password.)
DRF-JWT source code for post() method:
def post(self, request):
serializer = self.get_serializer(
data=get_request_data(request)
)
if serializer.is_valid(): # 'raise_exception' flag has not been passed here
user = serializer.object.get('user') or request.user
token = serializer.object.get('token')
response_data = jwt_response_payload_handler(token, user, request)
return Response(response_data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Now, we can see that raise_exception flag has not been passed to is_valid(). When this happens, ValidationError is not raised thereby resulting in non-execution of your custom_exception_handler code.
As per the DRF section on Raising an exception on invalid data:
The .is_valid() method takes an optional raise_exception flag that
will cause it to raise a serializers.ValidationError exception if
there are validation errors.
These exceptions are automatically dealt with by the default exception
handler that REST framework provides, and will return HTTP 400 Bad
Request responses by default.
SOLUTION:
If you pass the raise_exception flag as True when calling the .is_valid() function, the code for custom_exception_handler will be executed.
You will need to create a CustomObtainJSONWebToken view which will inherit from the default ObtainJSONWebToken view. In this, we will override the .post() method to pass the raise_exception flag. Then will specify this view in our urls.
my_app/views.py
from rest_framework_jwt.views import ObtainJSONWebToken
class CustomObtainJSONWebToken(ObtainJSONWebToken):
def post(self, request):
serializer = self.get_serializer(
data=get_request_data(request)
)
serializer.is_valid(raise_exception=True) # pass the 'raise_exception' flag
user = serializer.object.get('user') or request.user
token = serializer.object.get('token')
response_data = jwt_response_payload_handler(token, user, request)
return Response(response_data)
urls.py
# use this view to obtain token
url(r'^api-token-auth/', CustomObtainJSONWebToken.as_view())
I think maybe a cleaner way to do it that doesn't involve overriding the post method is
serializers.py
from rest_framework_jwt import serializers as jwt_serializers
class JSONWebTokenSerializer(jwt_serializers.JSONWebTokenSerializer):
"""
Override rest_framework_jwt's ObtainJSONWebToken serializer to
force it to raise ValidationError exception if validation fails.
"""
def is_valid(self, raise_exception=None):
"""
If raise_exception is unset, set it to True by default
"""
return super().is_valid(
raise_exception=raise_exception if raise_exception is not
None else True)
views.py
from rest_framework_jwt import views as jwt_views
from .serializers import JSONWebTokenSerializer
class ObtainJSONWebToken(jwt_views.ObtainJSONWebToken):
"""
Override the default JWT ObtainJSONWebToken view to use the custom serializer
"""
serializer_class = JSONWebTokenSerializer
urls.py
from django.conf.urls import url
from .views import ObtainJSONWebToken
urlpatterns = [
...
url(r'^api-token-auth/', ObtainJSONWebToken.as_view(), name='jwt-create'),
]
Well, at this moment I know how to make a custom error 404 page (and it actually works in my app). I followed the same strategy step by step to make a 403 page, but to no avail. So, this is what I have done:
#root urls.py
handler403 = 'blog.views.custom_403'
#blog views.py
def custom_403(request):
template = loader.get_template('blog/403.html')
context = RequestContext(request,{})
return HttpResponse(template.render(context))
Then, in one of my functions I have this code:
return HttpResponseForbidden()
I thought, it would render 403.html, but what I see is just a message 403 FORBIDDEN in the console.
The trouble is, you are not raising a 403 exception (as you did for the 404), but simply returning a response with a status code of 403. As the docs show, you need to raise django.views.defaults.permission_denied to trigger the 403 page.
from django.core.exceptions import PermissionDenied
...
raise PermissionDenied
For a web app, I have a method get_stuff_for_payment that raises PermissionDenied for which Django generates a HTTP 403 to the user.
I am now asked to show a friendly error page instead of the generic 403. The following code does it. But how do I retain the stacktrace when I do this?
try:
stuff = get_stuff_for_payment(id)
except PermissionDenied:
return render(request, 'friendly_error_page.html', context)
If you want just to log it, then do so. The logging.exception method will log a message and append a traceback automatically:
except PermissionDenied as err:
logger.exception('Permission was denied: %s', err)
StackTrace of Errors on our custom Template in python
except PermissionDenied:
return render(request, 'friendly_error_page.html', context)
is not a good way in django gor dealing with 403 , 4xx or 5xx errors.
If we want to show exceptions which are generated , on ur template(403.html) then we could write your own 403 view, grabbing the exception and passing it to your 403 template.
Steps:
#.In views.py:
import sys,traceback
def custom_403(request):
t = loader.get_template('403.html')
logger.info("custom msg",sys.exc_info()) // for traceback
type, value, tb = sys.exc_info()
return HttpResponseServerError(t.render(Context({
'exception_value': value,
'value':type,
'tb':traceback.format_exception(type, value,
tb)
},RequestContext(request))))
#.In Main Urls.py:
from django.conf.urls.defaults import *
handler403 = 'Project.web.services.views.custom_403'
and if you want to show stacktrace to user use this template otherwise your custom template
#.In Template(403.html):
{{ exception_value }}{{value}}{{tb}}
I’m trying to create a custom error response from the REST Django framework.
I’ve included the following in my views.py,
from rest_framework.views import exception_handler
def custom_exception_handler(exc):
"""
Custom exception handler for Django Rest Framework that adds
the `status_code` to the response and renames the `detail` key to `error`.
"""
response = exception_handler(exc)
if response is not None:
response.data['status_code'] = response.status_code
response.data['error'] = response.data['detail']
response.data['detail'] = "CUSTOM ERROR"
return response
And also added the following to settings.py.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
'EXCEPTION_HANDLER': 'project.input.utils.custom_exception_handler'
}
Am I missing something as I don’t get the expected response. i.e a custom error message in the 400 API response.
Thanks,
As Bibhas said, with custom exception handlers, you only can return own defined errors when an exception is called. If you want to return a custom response error without exceptions being triggered, you need to return it in the view itself. For example:
return Response({'detail' : "Invalid arguments", 'args' : ['arg1', 'arg2']},
status = status.HTTP_400_BAD_REQUEST)
From the documentation -
Note that the exception handler will only be called for responses generated by raised exceptions. It will not be used for any responses returned directly by the view, such as the HTTP_400_BAD_REQUEST responses that are returned by the generic views when serializer validation fails.