Django templatetag scope forcing me to do extra queries

帅比萌擦擦* 提交于 2019-12-05 09:24:50
Peter Rowell

You said, "This templatetag is called in a base template which is extended by many other templates."

The question is: is this tag called from within a named block? If it is then you have a couple of potential problems.

  1. {% block %} pushes a new dict on the Context stack and pops it off when it reaches the matching `{% endblock %}'. This means any context value created while in the block has essentially gone out of scope on block exit.

  2. If this block is overridden by some other template that extends the base template, the value may not be available at all unless you do a {{block.super}}, and even then I'm not certain the value will be available to the template doing the extending.

If the tag is not called from within a {% block %} then the context value should be available to all of the code that follows it, either in the base template, any included templates and (I think) any extending templates.

This is one of those cases where building a set of careful tests will probably save you time and tears.

Alternatively, if you are always accessing this value, you could just put it in a context processor so that its availability is guaranteed.

Update for comments: OK, time to bring in the big guns! One of the most irritating, long-standing bugs in Django templates is that callables (ie. functions) that are top-level context values (as opposed to functions that are dict-values/methods of context values) are not called! This ticket is over 2 years old and takes about 10 lines of code to fix. We have several heavy-weight DB calls that we only want to happen if the template cache has expired. So we a) MonkeyPatched the template _resolve_lookup() code to fix the callable problem, and then b) curry functions to have all of the necessary parameters if needed, because you can't pass params to functions in the template "language".

I think you've accurately described the limitations in this situation. The most maintainable solutions will likely involve some restructuring of your template inheritance chain, though its hard to say without knowing the details. Can you introduce a new template in the inheritance hierarchy, probably somewhere near the top of the pyramid but so it only is inherited by templates that need this data, with a single block that encompasses the entire region within which you need this data? That big block can then be subdivided into smaller blocks that inheriting templates will override. If you call your templatetag at the beginning of that block, all blocks within it (including in inheriting templates) will have access to the data.

Update: I can't say much without seeing your templates, but introducing a new template in the middle of an inheritance chain very rarely involves "changing all the templates," in a sane inheritance structure it often can be done with changes to only one or two other templates. And I think what I am suggesting is actually not a hack, it's just better design. If you need a certain piece of data in certain parts of your site and not other parts, there should be a specific single template you can point to and say "this template represents the logical layer at which this piece of data is introduced, and encompasses the parts of the site where that data is needed."

Are you just trying to keep down the number of database queries or are you looking for a clever solution?

If it's the former, I would definitely go with caching. Would fragment caching work in your case? If not, perhaps you could put the caching in the template tag code (assuming it's not one of Django's own template tags your using)?

Just came across this trick from Liviu, Agile Bear (all credit goes to him)

Instead of doing

context['some_var']='some value'

do

context.dicts[0]['some_var']='some value'

May not be a by-the-book-coding-practice but works well enough

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!