I use require_http_methods for restricting access to views. Here example my code:
# myapp/views.py
#require_http_methods(["POST"])
def my_view(request):
return HttpResponse('my_view')
But when I go to url /myapp/my_view I see white page. So, my question is: How I can set default view if method not match rules? For example I want to show 404, 403 or something else. It is possible? Can you provide me a small example? Thank you!
In your case 405 HTTP error is returned.
You could try to create middleware for custom errors:
from django.http import HttpResponseNotAllowed
class CustomHTTPErrorsMiddleware(object):
def process_response(self, request, response):
if isinstance(response, HttpResponseNotAllowed):
# Custom response for `HttpResponseNotAllowed`.
pass
return response
Also, Django's error handling may be useful.
Related
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!
During development, I am running Django in Debug mode and I am posting data to my application using a text mode application. Ideally, I need to receive a plain text response when I get an http error code 500 so I don't have to look for the real error inside all that HTML and Javascript.
Is it possible to obtain a Django 500 Internal Server Error as plain text?
If you are looking for a way to get a plain text error page when using curl, you
need to add the HTTP header X-Requested-With with value XMLHttpRequest, e.g.
curl -H 'X-Requested-With: XMLHttpRequest' http://example.com/some/url/
Explanation: this is because Django uses the is_ajax method to determine whether or not to return as plain text or as HTML. is_ajax in turn looks at X-Requested-With.
Update
Since django version 3.1, error reporting ignores the X-Requested-With header. Instead, set the Accept header on the request to any valid value which does not include the text/html mime type.
e.g.
curl -H 'Accept: application/json;charset=utf-8' http://example.comp/some/url
I think to write a middleware, because otherwise the exception isn't available in the 500.html
http://docs.djangoproject.com/en/dev/topics/http/middleware/#process-exception
class ProcessExceptionMiddleware(object):
def process_exception(self, request, exception):
t = Template("500 Error: {{ exception }}")
response_html = t.render(Context({'exception' : exception }))
response = http.HttpResponse(response_html)
response.status_code = 500
return response
There's a setting DEBUG_PROPAGATE_EXCEPTIONS which will force Django not to wrap the exceptions, so you can see them, e.g. in devserver logs.
This is an improvement on Yuji's answer, which provides a stacktrace, more instructions (for us django newbies) and is simpler.
Put this code in a file somewhere in your application, e.g. PROJECT_ROOT/MAIN_APP/middleware/exceptions.py, and make sure you have an empty __init__.py in the same directory.
import traceback
from django.http import HttpResponse
class PlainExceptionsMiddleware(object):
def process_exception(self, request, exception):
return HttpResponse(traceback.format_exc(exception), content_type="text/plain", status=500)
Now edit your settings.py and find MIDDLEWARE_CLASSES = (. Add another entry so it is like this:
MIDDLEWARE_CLASSES = (
# (all the previous entries)
# Plain text exception pages.
'MAIN_APP.middleware.exceptions.PlainExceptionsMiddleware',
)
Restart django and you are good to go!
User-agent aware formatting.
If you're like me and developing an app and a website both backed by django, you probably want to show plain text error pages to the app, and the nice formatted ones to the browser. A simple way to to that is to check the user agent:
import traceback
from django.http import HttpResponse
class PlainExceptionsMiddleware(object):
def process_exception(self, request, exception):
if "HTTP_USER_AGENT" in request.META and "chrome" in request.META["HTTP_USER_AGENT"].lower():
return
return HttpResponse(traceback.format_exc(exception), content_type="text/plain", status=500)
Building off of Timmmm's answer, I had to make several modifications for it to work in Django 3.1:
Create a file somewhere in your application, such as YOUR_APP_NAME/middleware/exceptions.py and paste the following code:
import traceback
from django.http import HttpResponse, HttpRequest
class PlainExceptionsMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request: HttpRequest, exception: Exception):
if "HTTP_USER_AGENT" in request.META and "chrome" in request.META["HTTP_USER_AGENT"].lower():
return
print(traceback.format_exc())
return HttpResponse(repr(exception), content_type="text/plain", status=500)
It is not necessary to create an __init__.py file in the middleware folder.
In settings.py, add the following item to the end of the MIDDLEWARE variable, so that it looks like:
MIDDLEWARE = [
# ...
'YOUR_APP_NAME.middleware.exceptions.PlainExceptionsMiddleware'
]
Now, if "HTTP_USER_AGENT" and "chrome" are in the request header, this middleware doesn't do anything, so Django returns an HTML response as usual. Otherwise, it returns a plain-text representation of the error as a response (e.g., ValueError("Field 'id' expected a number but got 'undefined'.")) and prints out the traceback to the Django console, as Django normally would. Of course, you can instead return the full traceback as your response.
I have a routing rule in my Django app for downloads that redirect to a external CDN. I am now writing tests for my app, and I want to test that the route does successfully redirect to the configured url in my Django settings. Here is a simplified example that should help explain what I'm trying to do:
from django.test import SimpleTestCase
from django.test.client import Client
from django.conf import settings
class MyTestCase(SimpleTestCase):
def setUp(self):
self.client = Client()
def test_download(self):
response = self.client.get('/download/mac/')
self.assertRedirects(response, settings.URLS.get('mac'))
Now this doesn't work, the redirect gets a 404 even though when I print settings.DOWNLOAD_URL in this method it is correct, and a copy/paste into the browser proves it works. I started to look into why it wasn't working, and I noticed this in the Django source code:
Note that assertRedirects won't work for external links since it uses
TestClient to do a request.
So then, how does one test these redirects? I'm not looking for anything super fancy, what I expect to check is the response's status_code and location. I saw that response has a follow parameter, and tried something like this, but it still didn't work:
def test_download(self):
response = self.client.get('/download/mac/', follow=True)
self.assertEqual(response.status_code, 302)
self.assertEqual(response['Location'], settings.URLS.get('mac')
It was requested that I include the relevant parts from my urls.py and views.py, here they are:
# urls.py
from django.conf.urls import patterns, url
urlpatterns = patterns('myapp.views',
url(r'^download/(?P<platform>\w+)/$', 'download_app', name='download'),
)
#views.py
from django.conf import settings
from django.shortcuts import redirect
from django.http import Http404
def download_app(request, platform):
if platform in settings.URLS:
return redirect( settings.URLS.get(platform) )
else:
raise Http404
Any help in solving this would be much appreciated. Thanks in advance.
Probably the original poster has solved his problems and moved on long ago, but the solution here is to not specify follow=True as he did in the alternative proposal that didn't work either.
The following will simply check that the view being tested redirects as expected, without relying on the external resources.
def test_download(self):
response = self.client.get('/download/mac')
self.assertEqual(response.status_code, 302)
self.assertEqual(response['Location'], 'http://external/resource')
In Django 1.7 assertRedirect got a new parameter fetch_redirect_response which can be set to False to get the same effect, but I haven't tried it myself.
I am creating a custom HTTP 500 error template. Why is it Django shows it when i raise an exception and not when I return HttpResponseServerError (I just get the default browser 500 error)? I find this behaviour strange...
The HttpResponseServerError inherits from HttpResponse and is actually quite simple:
class HttpResponseServerError(HttpResponse):
status_code = 500
So let's look at the HttpResponse constructor:
def __init__(self, content='', *args, **kwargs):
super(HttpResponse, self).__init__(*args, **kwargs)
# Content is a bytestring. See the `content` property methods.
self.content = content
As you can see by default content is empty.
Now, let's take a look at how it is called by Django itself (an excerpt from django.views.defaults):
def server_error(request, template_name='500.html'):
"""
500 error handler.
Templates: :template:`500.html`
Context: None
"""
try:
template = loader.get_template(template_name)
except TemplateDoesNotExist:
return http.HttpResponseServerError('<h1>Server Error (500)</h1>')
return http.HttpResponseServerError(template.render(Context({})))
As you can see when you produce a server error, the template named 500.html is used, but when you simply return HttpResponseServerError the content is empty and the browser falls back to it's default page.
Put this below in the urls.py.
#handle the errors
from django.utils.functional import curry
from django.views.defaults import *
handler500 = curry(server_error, template_name='500.html')
Put 500.html in your templates. Just as simple like that.
Have you tried with another browser ? Is your custom error page larger than 512 bytes ? It seems some browsers (including Chrome) replace errors page with their own when the server's answer is shorter than 512 bytes.
As of Django 2+ all you need to do is put the corresponding error templates in your base templates folder. Django will automatically render them before rendering the default.
https://docs.djangoproject.com/en/2.0/ref/views/#error-views
In your case just drop your template '500.html' (or '404.html') into /templates
I have a custom 404 view defined in my Pyramid app:
#view_config(context=HTTPNotFound, renderer='404.pt')
def not_found(self, request):
return {}
It works fine, except that the HTTP status code sent with the content is 200 OK, which is not OK by any means. I'm having the same problem with 403 Forbidden. How can I get Pyramid to send the correct status code?
The exception view is a separate view that provides a spot for you to do whatever you want. Just like any view that uses a renderer, you can affect the response object via request.response to modify its behavior. The renderer then fills in the body.
#view_config(context=HTTPNotFound, renderer='404.pt')
def not_found(self, request):
request.response.status = 404
return {}
Actually, in pyramid 1.3 There's a new decorator #notfound_view_config.
#notfound_view_config(renderer = '404_error.jinja2')
def notfound(request):
request.response.status = 404
The best way to do this is to override the default Not Found View:
http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/narr/hooks.html#changing-the-not-found-view
Even in this scenario, you need to return a proper response object which has a status of 404:
def notfound(request):
return Response('Not Found, dude', status='404 Not Found')
To take the example from the page linked above
Here is how you can directly use the 404 hook and render a template while doing so.
In your init.py:
config.add_notfound_view(not_found)
In your view.py:
from pyramid.view import notfound_view_config
from pyramid.renderers import render_to_response
def not_found(request):
request.response.status = 404
t = 'talk_python_to_me_com:templates/errors/404.pt'
return render_to_response(t, {}, request)
I did this for Talk Python To Me: http://www.talkpythontome.com/, here's an invalid page to see a custom template rendered.
http://www.talkpythontome.com/there_is_no_cat