user1601871
user1601871

Reputation: 101

Django 1.11 context processor error: TypeError: context must be a dict rather than RequestContext'

I cannot figure out why I'm encountering a problem with Django 1.11 and RenderContext. I really need help here. This is the code I've been playing with from the official documentation for 1.11:

from django.http import HttpResponse
from django.template import RequestContext, Template
from django.template import loader

def ip_address_processor(request):
    return {'ip_address': request.META['REMOTE_ADDR']}


def view_2(request):
    template = loader.get_template('template2.html')
    ctx = RequestContext(request, {
        'title': 'Your IP Address',
    }, [ip_address_processor])
    return HttpResponse(template.render(ctx))

And my simple template:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
Test
{{ title }}: {{ ip_address }}
</body>
</html>

This results in the following error:

Internal Server Error: /view2/
Traceback (most recent call last):
  File "C:\Python27\lib\site-packages\django\core\handlers\exception.py", line 41, in inner
    response = get_response(request)
  File "C:\Python27\lib\site-packages\django\core\handlers\base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "C:\Python27\lib\site-packages\django\core\handlers\base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\null\PycharmProjects\project1\project1\views.py", line 48, in view_2
    return template.render(c)
  File "C:\Python27\lib\site-packages\django\template\backends\django.py", line 64, in render
    context = make_context(context, request, autoescape=self.backend.engine.autoescape)
  File "C:\Python27\lib\site-packages\django\template\context.py", line 287, in make_context
    raise TypeError('context must be a dict rather than %s.' % context.__class__.__name__)
TypeError: context must be a dict rather than RequestContext.
[07/Aug/2017 23:52:49] "GET /view2/ HTTP/1.1" 500 72701

Which is just odd to me because the following code works:

from django.http import HttpResponse
from django.template import RequestContext, Template
from django.template import loader

def ip_address_processor(request):
    return {'ip_address': request.META['REMOTE_ADDR']}


def view_2(request):

    template = Template('{{ title }}: {{ ip_address }}')
    ctx = RequestContext(request, {
        'title': 'Your IP Address',
    }, [ip_address_processor])
    return HttpResponse(template.render(ctx))

Hard coding a template by overriding Template works fine, but importing it with django.template.loader.get_loader does not??? I'm really at a loss here.

What am I doing wrong? The templates are doing exactly the same thing. This is really putting me back from 1.11. It used to be you could pass a context_instance in Django 1.8 and it just worked. I can't seem to get any Context Processers running in 1.11, even using the example as documented on docs.djangoproject.com. It only ever works if I call Template and pass in my template through that by hard coding it.

Upvotes: 4

Views: 8998

Answers (3)

Stephen G Tuggy
Stephen G Tuggy

Reputation: 1141

Try this:

from django.http import HttpResponse
from django.template import Template
from django.template import loader

def ip_address_processor(request):
    return {'ip_address': request.META['REMOTE_ADDR']}


def view_2(request):
    template = loader.get_template('template2.html')
    ctx = request.GET.copy().dict()
    ctx.update({
        'title': 'Your IP Address',
    })
    ctx.update(ip_address_processor(request))
    return HttpResponse(template.render(ctx))

request.GET now returns a read-only QueryDict object. In order to make it modifiable, you have to get a read-write copy using .copy(). Then you have to convert it to a regular Python dictionary using .dict(). The render() method won't accept it otherwise.

Upvotes: 0

lmiguelvargasf
lmiguelvargasf

Reputation: 69675

Basically, the problem you are facing is that in Django 1.11 prohibits non-dict context.

For compatibility with multiple template engines, django.template.backends.django.Template.render() must receive a dictionary of context rather than Context or RequestContext. If you were passing either of the two classes, pass a dictionary instead – doing so is backwards-compatible with older versions of Django.

You should use render instead of returning an HttpResponse, so you can update your code as follows. Notice that it is passing a dict as context instead of an object of type RequestContext.

def ip_address_processor(request):
    return {'ip_address': request.META['REMOTE_ADDR']
            'ua': request.META['HTTP_USER_AGENT']}


def view_2(request):
    context = ip_address_processor(request)
    context.update({'title': 'test2'})
    return render(request, 'template2.html', context)

render is a shortcut that will take a template name as an argument, then render this template with the given parameters, and then return an HttpResponse with the rendered body.

HttpResponse does not do the stuff Django behind the scenes, so in case you want to return a rendered Django template you would need to do this manually and pass that result to the HttpResponse before returning. (for more check this question and its answers)

Upvotes: 1

user1601871
user1601871

Reputation: 101

As The_Cthulhu_Kid said in a comment, Django 1.11 deprecated passing in non-dict Contexts:

https://docs.djangoproject.com/en/1.11/releases/1.11/#django-template-backends-django-template-render-prohibits-non-dict-context

I came up with a quick example, if anyone is figuring how you'd do content processors in 1.11

I changed the example code above to:

def ip_address_processor(request):
    return {'ip_address': request.META['REMOTE_ADDR'], 'ua': request.META['HTTP_USER_AGENT']}


def view_2(request):
    template = loader.get_template('template2.html')
    proc_ex = ip_address_processor(request)
    context = {'ua': proc_ex.get('ua'),
               'ip_address': proc_ex.get('ip_address'),
               'title': 'TEST'}
    return HttpResponse(template.render(context))

And the template:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{{ title }}: {{ ip_address }}
User-Agent: {{ ua }}
</body>
</html>

You could also do this, to save from having to align keys up with the ip_address_processor function:

def ip_address_processor(request):
    return {'ip_address': request.META['REMOTE_ADDR'], 'ua': request.META['HTTP_USER_AGENT']}


def view_2(request):
    template = loader.get_template('template2.html')
    proc_ex = ip_address_processor(request)
    proc_ex.update({'title': 'test2'})
    return HttpResponse(template.render(proc_ex))

Looks like the key thing here is to just feed it a dict and it's happy.

Upvotes: 3

Related Questions