Search code examples
djangodjango-templatesdjango-1.8

Django upgrade from 1.7 to 1.8: Need variable from contextprocessor in view


A number of variables must be determined during each page hit but used in various places in my project, not just templates but also in views. So far, I've been using a context processor (called 'globals') to achieve this result. Please note that in the context processor I'm doing actual computations and database calls, so I don't just need a settings variable.

Since upgrading from Django 1.7 to 1.8, the variables returned by the context processor still show up in the templates, which is good, but they no longer show up in the views, at least nowhere I can find them.

In my contextprocessor, I have the following code:

def globals(request):

    # NOTE: We DON'T simply need a variable from settings - in reality this is computed
    if_this_is_true_then_we_alter_text = True

    ctx = {
        'var_from_contextprocessor': if_this_is_true_then_we_alter_text,
    }

    return ctx

Then, in my view, I have:

from django.template import RequestContext
from django.shortcuts import render

def show_globals(request):
    ctx = RequestContext(request)
    ctx['var_from_view'] = 'YES, we found it!' if ctx.has_key('var_from_contextprocessor') else 'NO, it ain\'t there!'
    return render(request, 'show_globals.html', context_instance=ctx)

My template, show_globals.html is as follows:

var_from_view: {{ var_from_view }}
var_from_contextprocessor: {{ var_from_contextprocessor }}

When running Django 1.7, the output in the template from the view will be "YES, we found it!". However, once I've upgraded to 1.8, the variable returned by the context processor appears to be available to the view, and so the text changed to "NO, it ain't there!". In both cases, var_from_contextprocessor is however duly displayed in the template itself.

Is there still a way to retrieve variables from context processors in individual views? If not, any suggestions on how to achieve the same results without the use of a context processor?

Note that the basic problem I'm trying to solve is simply having variables calculated on the fly during each page hit, which are then available to both views and templates. I don't really care whether this is done by using a context processor or not.

Thanks in advance!


Solution

  • I've discovered a solution that helps, at least in my case.

    So the desired result is having code, which is run at every page load, with results available both in all views and all templates.

    A technical reasons for why this stopped working with a simple contextprocessor between Django versions 1.7 an 1.8 are quite well explained in a different answer by Daniel Roseman, so I won't go into them here.

    Long story short, the trick is to use a middleware instead of a contextprocessor, but then have the contextprocessor inherit the variables from the middleware.

    Considering the code in the original question, compare with the new code:

    Contextprocessor:

    def globals(request):
    
        ctx = request.extravars # See example.middleware.ExtraVarsMiddleware
    
        return ctx
    

    Middleware:

    class ExtraVarsMiddleware():
        # For handing certain variables over to the context processor for global display.
        def process_view(self, request, view_func, view_args, view_kwargs):
    
            # NOTE: We DON'T simply need a variable from settings
            if_this_is_true_then_we_alter_text = True
    
            request.extravars = {
                'var_from_contextprocessor': if_this_is_true_then_we_alter_text,
            }
    

    The "global" variables are hung on to the request object in an arbitrarily named dictionary called extravars. Note that while our "global" variable entry is still called var_from_contextprocessor, it's a misnomer at this point since it's from the middleware, no longer the contextprocessor.

    In order to activate this middleware, settings must be altered to include it, like so (defaults included):

    MIDDLEWARE_CLASSES = (
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    
        'example.middleware.ExtraVarsMiddleware',
    )
    

    The templates then have access to variables as previously, since the context processor has added the entirety of request.extravars to its resulting context, which is then returned as per the code above.

    Then, to access the variables in the view, we need a slight change from before:

    from django.shortcuts import render
    
    def show_globals(request):
        ctx = request.extravars
        ctx['var_from_view'] = 'YES, we found it!' if ctx.has_key('var_from_contextprocessor') else 'NO, it ain\'t there!'
        return render(request, 'show_globals.html', context=ctx)
    

    I've changed this code as little as possible to demonstrate the minimum changes needed. Only two things have changed, one is how the ctx dictionary is acquired, this time without using RequestContext. The second change is that we no longer pass context_instance to the render function, but simply context.

    For clarification, the corresponding file names in my example setup are:

    example/contextprocessors.py
    example/middleware.py
    example/views.py
    settings.py