8

I made a custom handler404 for a authenticated Django website to avoid information leakage.

def check_logged_in_404(request):
    """ Custom 404. Show friendly 404 when logged in and redirect to /login
    when not logged in.
    """
    if request.user.is_authenticated():
        return render_to_response('404.html')
    else:
        return HttpResponseRedirect('/login')

Functionally it does exactly what I want. However the 404 return page has a status 200, which is correct code-wise. But this obviously needs to be a 404 return status.

A raise404 doesn't work because, if not ending in a infinite recursion, it comes back here and thus results in the same issue.

I tried a HttpResponseNotFound, but this only takes a string as a argument and not a template, which is not to DRY-ish.

And I manually tried to set the header with:

    response = render_to_response('404.html')
    response['Status'] = "Not Found - 404"
    return response

Then the status header is indeed set but the browser still shows up a 200.

I'm out of options .. Anybody that has tips, please be my hero ... :)

Thanx and regards,

Gerard.

Edit: I tried the status field value in all sort btw, but no luck :(

GerardJP
  • 985
  • 2
  • 9
  • 20

6 Answers6

21

I'd use render_to_string and HttpResponseNotFound, e.g. return HttpResponseNotFound(render_to_string('404.html')).

Cat Plus Plus
  • 125,936
  • 27
  • 200
  • 224
  • Hmm didn't realise about "render_to_string". Still curious about why the set status codes are not honoured. Anyway ... you definitely earned the "somebody's hero" badge .. ;) .. thanx a lot! – GerardJP Nov 28 '09 at 13:30
12

I finally found why the returned status code didnt work. Instead of setting a header message, it simply is:

response.status_code = 404

Nevertheless, the code suggested by PiotrLegnica definitely wins on simplicity, readability and beauty .. The badge still stands ;)

Regards,

Gerard.

GerardJP
  • 985
  • 2
  • 9
  • 20
8

Based on suggestions above, here is my short version of 404, 500 handlers:

def handler404(request):
    response = render_to_response('404.html', {},
                                  context_instance=RequestContext(request))
    response.status_code = 404
    return response


def handler500(request):
    response = render_to_response('500.html', {},
                                  context_instance=RequestContext(request))
    response.status_code = 500
    return response
alex_koval
  • 1,646
  • 1
  • 12
  • 5
3

You can use render method:

from django.shortcuts import render

Returns a HttpResponse whose content is filled with the result of calling django.template.loader.render_to_string() with the passed arguments.

Uses a RequestContext by default.

Example:

return render(request, '404.html', status=404)

And with keywords:

return render(request, '404.html', {'data': 'some data'}, status=404)
Omid Raha
  • 9,862
  • 1
  • 60
  • 64
2

Why don't you just use Http404 exception?

if request.user.is_authenticated():
    raise Http404
else:
    return HttpResponseRedirect('/login')

That should be just fine for you.

gruszczy
  • 40,948
  • 31
  • 128
  • 181
  • Somehow I endup with a 500 response when raising the Http404. Nevertheless, I think there needs to be a distinction within the function otherwise I end up with a recursion error, or I need to write a page_not_found() view. Thanx anyway. – GerardJP Nov 30 '09 at 13:35
  • Have you imported Http404 from django.http? – gruszczy Dec 01 '09 at 13:03
  • Emphh, when you raise it you get redirected to your 404 handler. Some people want them to be custom. – DataGreed Sep 30 '10 at 16:07
  • I still get a status 200 when using `raise Http404` – CutePoison Jul 31 '21 at 21:17
1

You could do something like the example below.

Into your application's urls.py add:

# Imports
from django.conf.urls.static import static
from django.conf.urls import handler404
from django.conf.urls import patterns, include, url
from yourapplication import views

##
# Handles the URLS calls
urlpatterns = patterns('',
    # url(r'^$', include('app.homepage.urls')),
)

handler404 = views.error404

Into your application's views.py add:

# Imports
from django.shortcuts import render
from django.http import HttpResponse
from django.template import Context, loader


##
# Handle 404 Errors
# @param request WSGIRequest list with all HTTP Request
def error404(request):

    # 1. Load models for this view
    #from idgsupply.models import My404Method

    # 2. Generate Content for this view
    template = loader.get_template('404.htm')
    context = Context({
        'message': 'All: %s' % request,
        })

    # 3. Return Template for this view + Data
    return HttpResponse(content=template.render(context), content_type='text/html; charset=utf-8', status=404)

The secret is in the last line: status=404

Hope it helped!

I look forward to see the community inputs to this approach. =)

Fabio Nolasco
  • 7,122
  • 6
  • 35
  • 32