开发者

Django : template dictionary/object cross-referencing

In Django, a dot in a template variable theoretically signifies a lookup, based on the following order of resolution:

  • Dictionary lookup. Example: foo["bar"]
  • Attribute lookup. Example: foo.bar
  • Method call. Example: foo.bar()
  • List-index lookup. Example: foo[bar]

from rendering a context in django 1.2 (docs.djangoproject.com).

In reality:

view:

...
class Veggie(object):
    def __init__(self, name):
        self.name = name
veggies = {'a': Veggie('carrot'), 'b': Veggie('lettuce')}

basket = ['a', 'c', 'b']

return render_to_response('tpls/veggies.txt', 
        {'veggies': veggies, 'basket': basket},
        mimetype="text/plain")

template:

Veggie by basket order, showing name

{% for veg in basket %}
    {{ veg }}
    {{ veggies.veg.name }}
{% endfor %}

output:

Veggie by basket order, showing name

    a

    c

    b

No veggie names in that output.

I think this isn't what Django says it does on the tin. I appreciate that I can, a la Peter Harkins "Django Template Tag for Dictionary Access", write a tag to solve the issue. That's ugly.

This is a no fix for the Django team, as per Django ticket 12486 because "The intention is that Django's templates can be read and written by non-programmers, so complex logic should be view code or custom template tags." (Luke Plant)

I'd be grateful for some practical advice on writing legible template code in this situation (I've got a lot of "vegetables" of different sorts to dereference). Is it worth steppin开发者_JAVA技巧g away from Django if we've got template-writers who -- ahem -- know how to do dictionary lookups?


Django's template engine is treating veg in your for loop as a string, but you are trying to use it as part of an expression. While Django's template engine is an obstacle at times, what you are doing just doesn't validate. What you want to do is get the attribute of veggies with the name contained in the object veg, and dot notation doesn't work that way either in Python or in Django's templates. When you say veggies.veg, it is looking for an attribute on veggies named veg, not named a, b, or c. This isn't a flaw of the Django template engine; this is just how Python (and every language I'm aware of) works.

Instead, just suck it up and write a template tag to access an attribute, by name, of an arbitrary object, or perhaps pass the template a basket that doesn't require that kind of logic in the template.


Django way to do this is to prepare the data structures for rendering in a view (I think that's one of the reasons why is "view" called "view" in django), e.g.

basket = [veggies[veg_id] for veg_id in basket_id_list]

and then

{% for veg in basket %}
    {{ veg.name }}
{% endfor %}

So pass structures you want to render, not the structures you get somewhere. It is not true that django consider template writers dumb people unable to understand the dictionary lookup (or e.g. a bit of python code), django considers variable dictionary lookups (as well as arbitrary python code) too complex for template logic, and there are reasons for that. I personally didn't find your template code snippet very readable, but that's personal preferences.

If you disagree with this ideology or it is limiting for your task then it is easy to use a template filter (there is no way to distinguish between dct[var] and dct['var'] with dot-syntax anyway, and the filter is just a one-liner, there is no gain in having it in django itself) or, if this ideology is really disgusting, switch to a different template engine like jinja2.


Django is preventing you from putting a quick hack in a template to solve your problem because it enforces a separation between the template authors and the view authors. Imagine you sold 10,000 different vegetables and you kept them stored in a database table. Would you really want to pass a dictionary of all 10,000 veggies into the template to enable what you describe?

If you were building this example with real Models instead of classes and dictionaries you would probably already have what you want: A "basket" would be an object with a foreign key reference to a "veggie" so that basket_item.veggie would refer to a row in the Veggie table (ugh, no pun intended). You could simply pass in the basket and the veggie items would be populated on demand when referenced from the template. For efficiency you might eventually use a .select_related when fetching the basket items to pre-populate the veggie items in a single SQL query.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜