[Fixed]-Is it possible to automatically inject the request into model methods?

1👍

It is possible, and it came to me in the form of a decorator.
It’s a little bit obscure, I know, but we are in Python, and Python allow for a lil bit of darkness 😉

This one here searches the stack frames for an instance of HttpRequest and if it finds it, it will inject it into the decorated function.
(If you are wondering, assertions are there to help you code defensively: a request object might not be found, or the function call could still fail in a template if extra arguments don’t have default values, etc.)

import inspect
from django.http import HttpRequest
from django.utils import six

def inject_request(func):
    argspec = inspect.getargspec(func)
    assert 'request' in argspec.args, \
        "There must be a 'request' argument in the function."
    index = argspec.args.index('request')
    assert index in [0, 1], \
        "The 'request' argument must be, at most, the second positional argument."
    assert len(argspec.args) - len(argspec.defaults or []) == index, \
        "All arguments after (and including) 'request' must have default values."
    def wrapper(*args, **kwargs):
        if (index < len(args) and args[index]) or kwargs.get('request', None):
            return func(*args, **kwargs)
        request = None
        frame = inspect.currentframe().f_back
        while frame and not request:
            for v_name, v_object in six.iteritems(frame.f_locals):
                if isinstance(v_object, HttpRequest):
                    request = v_object
                    break
            frame = frame.f_back
        if request:
            kwargs.setdefault('request', request)
        return func(*args, **kwargs)
    return wrapper

In your models, you can do this:

class SomeModel(models.Model):
    ...
    ...
    @inject_request
    def allow_editing(self, request=None):
        ...
    @inject_request
    def allow_super_awkward_action(self, request=None):
        ...

And we’re back to happiness using the plain method call in templates:

{% if object.allow_super_awkward_action %}
    place link to the action
{% endif %}

This will work!

Leave a comment