Django - accessing the RequestContext from within a custom filter
Asked Answered
G

4

21

I've got a filter currency, which takes a value in USD and converts it to a currency (either USD or GBP). The currency to convert to is stored in the session, but filters don't take RequestContext, so I can't grab it straight from there.

Is there a better way than passing the relevant session element into the template, and from the template into the filter as an argument? Whilst this approach is working, it seems fairly horrible, and I'm likely to end up passing the currency to (almost) every template.

My filter currently looks something like this:

def currency(value, currency):
    if currency == 'usd':
       val = '$%.2f' % value
       return mark_safe(val)

    d = Decimal(value)
    val = '£%.2f' % (d*Decimal('0.63'))

    return mark_safe(val)
Genitor answered 29/9, 2009 at 17:18 Comment(0)
I
14

If you create a template tag instead of a filter, you are given the context to work with (which contains the request). http://docs.djangoproject.com/en/dev/howto/custom-template-tags/#writing-custom-template-tags

Improbable answered 29/9, 2009 at 17:45 Comment(4)
@Improbable - maybe I'm being thick, but I can't see in those docs how to get access to session variables from within custom template tags. Could you give me an example or point out what I'm missing?Genitor
Ah - digging around it appears to be passed as the first argument to the tag (just found an example which adds a class to links matching a regular expression, which looks (a) useful and (b) adaptable - gnuvince.wordpress.com/2007/09/14/…).Genitor
Right, the context is a standard parameter of the render method on the template tag node. If you need a further specific example, I'm sure I could muster one up.Improbable
Well, looking at the example in the URL posted, it's not quite the same since the author is explicitly passing the request. If you write the template tag as a mixture of a compilation and renderer methods, the context is passed without explicitly doing anything from the template.Improbable
W
8

I would have to agree with Adam that migrating the code to a custom tag is the best way.

However, a client needed to record the use of certain filters only when a page was published and had a HUGE inventory of templates that used the existing filter syntax. It would have been a costly undertaking to rewrite all the templates. So, I came up with this simple function that extracts the context from the call stack:

https://gist.github.com/drhoden/e05292e52fd5fc92cc3b

def get_context(max_depth=4):
    import inspect
    stack = inspect.stack()[2:max_depth]
    context = {}
    for frame_info in stack:
        frame = frame_info[0]
        arg_info = inspect.getargvalues(frame)
        if 'context' in arg_info.locals:
            context = arg_info.locals['context']
            break
    return context

Be sure to read my warnings, but this DOES give standard filters access to the context (when it is available) WITHOUT having to turn your filter into a tag.

Warmedover answered 22/1, 2015 at 20:40 Comment(2)
It's awesome, not sure if awesome in good or bad way, but awesome ;)Heinrike
I am both extremely impressed and extremely horrified. Excellent. :DIndividualism
S
5

This can be done using a filter. First make sure that you have "django.core.context_processors.request" in you TEMPLATE_CONTEXT_PROCESSORS. If you don't, you can add this to your settings.py file:

TEMPLATE_CONTEXT_PROCESSORS += (
    "django.core.context_processors.request"
)

Then in your template, your filter will look like this (assuming your session variable is named 'currency_type'):

{{value|currency:request.session.currency_type}}

Or is something like this what you are considering fairly horrible?

Syl answered 2/10, 2009 at 19:49 Comment(2)
He is; Look at what he is doing, that filter describes exactly what you are telling.Eppie
I thought he might be complaining about the process of having to explicitly pass the variable through the view. This will make it available automatically. I can also post an example of using a template tag with takes_context=True in the decoratorSyl
W
1

A somehow less hacky solution to Daniel Rhoden's proposal is, to use threading.local(). Define a middleware class, which stores your request as a global object inside your local thread, and add that class to your MIDDLEWARE_CLASSES.

Now a template filter can easily access that request object.

Wylde answered 18/2, 2015 at 10:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.