Django: provide global method to all (class-based) views?

1.1k views Asked by At

I need a button, included in the header of every page in my Django application, to trigger some global method, to toggle a "mode" setting in the session. There are two modes: 'preview' and 'publish'.

One solution I've come up with: duplicate a post() method in every (class-based) view, to handle the mode change. This hardly seems DRY.

Another would be to inherit all of my CBVs from a single superclass, or use a mixin. I suppose this is a possibility.

A better solution perhaps: I've setup a context_processor to handle publishing the mode globally to my templates. This works fine. I've also setup a middleware class with process_request which could, theoretically, handle POST requests globally. But how do I call this process_request method from my templates?

My current stab at it follows. How do I toggle the "preview" and "publish" buttons in my template, and call the middleware?

template.html:

<html>
<head></head>
<body>
<header>
    <form method="post">
    {% csrf_token %}
    <!-- button toggle -->
    {% if mode == 'preview' %}
        <button name="mode" value="publish">Publish</button>
    {% else %}
        <button name="mode" value="preview">Preview</button>
    {% endif %}
    </form>
</header>
</body>
</html>

middleware.py:

class MyMiddleware(object):
    def process_request(self, request):
        update_mode(request)

def update_mode(request, new_mode=None): # how do I call this from template?
    modes = [
        'preview',
        'publish'
    ]

    # ensure default
    if not request.session.get('mode', None):
        request.session['mode'] = 'preview'

    # set new mode
    if new_mode and new_mode in modes:
        request.session['mode'] = new_mode

context_processor.py:

def template_mode(request):
    context = {
        'mode': request.session['mode']
    }
    return context
2

There are 2 answers

3
Daniel Roseman On BEST ANSWER

You don't "call" middleware: that's not at all how it works. Middleware is invoked on every request, so in your case the update_mode function would always run.

A better solution would be to get the form containing the button to post to a new URL, which invokes a view to update the mode. You could add a hidden field containing the current URL - which you can get from request.path - and the update mode view can redirect back to that URL after doing its work.

1
professorDante On

I wouldn't do it that way - how about making a template tag for the form?

In your templatetags.py:

def set_session_mode_form():
    return {'session_form': SessionForm()}

register.inclusion)tag("<path-to-your-template>",set_session_mode_form)

Then your session form sends to a view that updates the session variable you want. To use it, just load the tags on your page and use {% include %}. This way, its very easy to add to any page, and keeps it DRY.