How to test 404 NOT FOUND with django testing framework? - django

I am trying to automate 404 pages testing using Django 1.4's testing framework.
If I print 127.0.0.1:8000/something/really/weird/ in browser address bar with development server running, I see a 404 page, with correct "404 NOT FOUND" status (as firebug shows).
But if I try to use this code for testing:
from django.test import TestCase
class Sample404TestCase(TestCase):
def test_wrong_uri_returns_404(self):
response = self.client.get('something/really/weird/')
self.assertEqual(response.status_code, 404)
the test fails with this output:
$./manage.py test main
Creating test database for alias 'default'...
.F
======================================================================
FAIL: test_wrong_uri_returns_404 (main.tests.Sample404TestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File ".../main/tests.py", line 12, in test_wrong_uri_returns_404
self.assertEqual(response.status_code, 404)
*AssertionError: 200 != 404*
----------------------------------------------------------------------
Ran 2 tests in 0.031s
FAILED (failures=1)
Destroying test database for alias 'default'...
I'm seriously surprised with getting 200 code here. Anyone have any idea why on earth this is happening?
updated:
here lies urls.py: http://pastebin.com/DikAVa8T
and actual failing test is:
def test_wrong_uri_returns_404(self):
response = self.client.get('/something/really/weird/')
self.assertEqual(response.status_code, 404)
everything is happening in project https://github.com/gbezyuk/django-app-skeleton

Try
response = self.client.get('/something/really/weird/') # note the '/' before something
127.0.0.1:8000/something/really/weird/ is /something/really/weird/ in path relative to root, not
something/really/weird
something/really/weird/
/something/really/weird

The problem is that your ViewFor404 class returns a 200 status code. Look at Django's TemplateView definition:
class TemplateView(TemplateResponseMixin, View):
"""
A view that renders a template.
"""
def get_context_data(self, **kwargs):
return {
'params': kwargs
}
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
so all your class does is a render_to_response, which generates a '200' response.
If you need to override the 404 handler, you should do something more like this in the view:
return HttpResponseNotFound('<h1>Page not found</h1>')
(I don't know the equivalent in class-based views)
Or better yet, can you avoid customizing the View? To customize the 404 display, you can just create a 404.html template (in your site's templates/ directory), and it will be picked up by Django's error viewer.

Related

How to return 404 page intentionally in django

I made custom 404 page in django. And I'm trying to get 404 error page intentionally.
myproject/urls.py:
from website.views import customhandler404, customhandler500, test
urlpatterns = [
re_path(r'^admin/', admin.site.urls),
re_path(r'^test/$', test, name='test'),
]
handler404 = customhandler404
handler500 = customhandler500
website/views.py
def customhandler404(request):
response = render(request, '404.html',)
response.status_code = 404
return response
def customhandler500(request):
response = render(request, '500.html',)
response.status_code = 500
return response
def test(request):
raise Http404('hello')
But when I go 127.0.0.1:8000/test/ , It seems to return 500.html
And terminal says:
[24/Mar/2018 22:32:17] "GET /test/ HTTP/1.1" 500 128
How can I intentionally get 404 page?
When you set debug to False, you don't have a custom handler, and the status code of the response is 404, the 404.html (if present) in your base template directory is used. To return a response with a 404 status, you can simply return an instance of django.http.HttpResponseNotFound. The reason you got a 500 is because you raised an error instead of returning a response. So, your test function can be simply modified to this
from django.http import HttpResponseNotFound
def test(request):
return HttpResponseNotFound("hello")
Update:
So it turned out that the reason you are getting a 500 error was not that you raised an exception, but having incorrect function signatures. When I answered this question more than half a year ago I forgot that django catches HTTP404 exception for you. However, the handler view has different signatures than the normal views. The default handler for 404 is defaults.page_not_found(request, exception, template_name='404.html'), which takes 3 arguments. So your custom handler should actually be
def customhandler404(request, exception, template_name='404.html'):
response = render(request, template_name)
response.status_code = 404
return response
Although, in this case, you may as well just use the default handler.

custom exception handler for DRF response and Django response

I'd like to define a custom exception class and raise it.
Django rest framework has a hook where I can define custom exception handler,
(http://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling)
Django also provides a way to do it via middleware.
Question: If I want to handle exceptions in drf views and regular django views, would it be sufficient to handle them in django middleware?
Or do I need separate handler for DRF views?
In other words, does DRF request/response goes through django middleware as well or not?
Create a script called exception_middleware.py anywhere in the project directory (preferably in the main app directory) and add the following:
import logging
from django.shortcuts import render
from django.conf import settings
logger = logging.getLogger("StackDriverHandler")
class ExceptionMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if settings.DEBUG:
host = "http"
else:
# TODO: set for https
host = "http"
if response.status_code == 500:
return render(request, "error_templates/500.html")
if response.status_code == 404:
return render(request, "error_templates/404.html")
if response.status_code == 403:
return render(request, "error_templates/403.html")
return response
def process_exception(self, request, exception):
try:
logger.info(request, extra=exception)
except Exception as e:
logger.error(exception)
return None
def process_exception(self, request, exception):
try:
logger.info(request, extra=exception)
except Exception as e:
logger.error(exception)
return None
Add the path to this class in the MIDDLEWARE section in the settings.py file of your Django project (at the last).
That's it. This middleware will get executed during each of your requests and will check if you are in DEBUG and if there is an exception, this middleware will then create a message for you :) .

Django test print or log failure

I have a django_rest_framework test (the problem is the same with a regular django test) that looks like this:
from rest_framework.test import APITestCase
class APITests(APITestCase):
# tests for unauthorized access
def test_unauthorized(self):
...
for api in apipoints:
response = self.client.options(api)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
I have a url that fails, the terminal shows this:
FAIL: test_unauthorized (app.misuper.tests.APITests)
---------------------------------------------------------------------- Traceback (most recent call last): File
"/home/alejandro/...",
line 64, in test_unauthorized
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) AssertionError: 200 != 403
Ok, how can I know which url failed the test? I am iterating through all urls that require login, that is many urls, how can I print the one that failed the test?
For a simple quick-fix, you can pass the apipoint in the third parameter of the assertion method:
>>> from unittest import TestCase
>>> TestCase('__init__').assertEqual(1, 2, msg='teh thing is b0rked')
AssertionError: teh thing is b0rked
In the spirit of unit testing, these should really be each different test methods rather than only one test method with the loop. Check out nose_parameterized for help with making that more DRY. You'll decorate the test method like this:
from nose_parameterized import parameterized
#parameterized.expand(apipoints)
def test_unauthorized(self, apipoint):
response = self.client.options(apipoint)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
The decorator will generate different test methods for each endpoint, so that they can pass/fail independently of one another.
Although this package has nose in the name, it's also compatible with other runners such as unittest and py.test.

No forms exist in unit test with django-webtest

I want to write a test that will test change the password in the application. I use the django-allauth. For testing, I use django-WebTest.
When I run my code, I get the message:
FAILED (errors=1)
Destroying test database for alias 'default'...
mark#mariusz-K73E:~/myapp$ python manage.py test users
Creating test database for alias 'default'...
.E
======================================================================
ERROR: test_password_change_use_template (myapp.users.tests.ChangePasswordTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/mark/myapp/users/tests.py", line 16, in test_password_change_use_template
password_change.form['oldpassword'] = "test123"
File "/home/mark/.virtualenvs/urlop/local/lib/python2.7/site-packages/webtest/response.py", line 56, in form
"You used response.form, but no forms exist")
TypeError: You used response.form, but no forms exist
My code:
from django_webtest import WebTest
from django_dynamic_fixture import G
from users.models import User
from django.core.urlresolvers import reverse
class ChangePasswordTest(WebTest):
def setUp(self):
self.user = G(User)
def test_password_change_code(self):
password_change = self.app.get(reverse('account_change_password'), user=self.user)
def test_password_change_use_template(self):
password_change = self.app.get(reverse('account_change_password'), user=self.user)
password_change.form['oldpassword'] = "test123"
password_change.form['password1'] = "test456"
password_change.form['password2'] = "test456"
password_change.form.submit()
self.assertRedirects(password_change, reverse('change_password'))
WebTest tests the rendered content. There is no form found in the HTML (maybe the form tag is incorrect or missing).
If it's working in your manual test. You can try to print the response to see what's different:
def test_password_change_use_template(self):
response = self.app.get(reverse('account_change_password'), user=self.user)
print response

Django raising 404 with a message

I like to raise 404 with some error message at different places in the script eg: Http404("some error msg: %s" %msg)
So, in my urls.py I included:
handler404 = Custom404.as_view()
Can anyone please tell me how should I be handling the error in my views. I'm fairly new to Django, so an example would help a lot.
Many thanks in advance.
Generally there should not be any custom messages in 404 errors bu if you want to implement it you can do this using django middlewares.
Middleware
from django.http import Http404, HttpResponse
class Custom404Middleware(object):
def process_exception(self, request, exception):
if isinstance(exception, Http404):
# implement your custom logic. You can send
# http response with any template or message
# here. unicode(exception) will give the custom
# error message that was passed.
msg = unicode(exception)
return HttpResponse(msg, status=404)
Middlewares Settings
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'college.middleware.Custom404Middleware',
# Uncomment the next line for simple clickjacking protection:
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
This will do the trick. Correct me if I am doing any thing wrong. Hope this helps.
In general, 404 error is "page not found" error - it should not have customizable messages, simply because it should be raised only when a page is not found.
You can return a TemplateResponse with status parameter set to 404
Raise an Http404 exception inside a view. It's usually done when you catch a DoesNotExist exception. For example:
from django.http import Http404
def article_view(request, slug):
try:
entry = Article.objects.get(slug=slug)
except Article.DoesNotExist:
raise Http404()
return render(request, 'news/article.html', {'article': entry, })
Even better, use get_object_or_404 shortcut:
from django.shortcuts import get_object_or_404
def article_view(request):
article = get_object_or_404(MyModel, pk=1)
return render(request, 'news/article.html', {'article': entry, })
If you'd like to customize the default 404 Page not found response, put your own template called 404.html to the templates folder.
Yes we can show specific exception message when raise Http404.
Pass some exception message like this
raise Http404('Any kind of message ')
Add 404.html page into templates directory.
templates/404.html
{{exception}}
I figured out a solution for Django 2.2 (2019) after a lot of the middleware changed. It is very similar to Muhammed's answer from 2013. So here it is:
middleware.py
from django.http import Http404, HttpResponse
class CustomHTTP404Middleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before the view (and later middleware) are called.
response = self.get_response(request)
# Code to be executed for each request/response after the view is called.
return response
def process_exception(self, request, exception):
if isinstance(exception, Http404):
message = f"""
{exception.args},
User: {request.user},
Referrer: {request.META.get('HTTP_REFERRER', 'no referrer')}
"""
exception.args = (message,)
Also, add this last to your middleware in settings.py: 'app.middleware.http404.CustomHTTP404Middleware',
if you want to raise some sort of static messages for a particular view , you can do as follows:-
from django.http import Http404
def my_view(request):
raise Http404("The link seems to be broken")
You can return a plain HttpResponse object with a status code (in this case 404)
from django.shortcuts import render_to_response
def my_view(request):
template_context = {}
# ... some code that leads to a custom 404
return render_to_response("my_template.html", template_context, status=404)
In my case, I wanted to take some action (e.g. logging) before returning a custom 404 page. Here is the 404 handler that does it.
def my_handler404(request, exception):
logger.info(f'404-not-found for user {request.user} on url {request.path}')
return HttpResponseNotFound(render(request, "shared/404.html"))
Note that HttpResponseNotFound is required. Otherwise, the response's HTTP status code is 200.
The default 404 handler calls 404.html . You could edit that if you don't need anything fancy or can override the 404 handler by setting the handler404 view -- see more here