17
votes

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)
4

4 Answers

11
votes

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

7
votes

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.

4
votes

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?

0
votes

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.