jinja2: macro selecting macro or dynamic macro calls - python

I have a list of namedtuples I'm going through, each slightly differing in rendering requirements, so I want to call the proper macro based on an attribute. What I have is this:
{% macro format_item(item) %}
{% if item.type_of == 'a' %}
{{ format_a(item) }}
{% elif item.type_of == 'b' %}
{{ format_b(item) }}
{% elif item.type_of == 'c'%}
{{ format_c(item) }}
{% elif item.type_of == 'd'%}
{{ format_d(item) }}
{% else %}
{{ format_general(item) }}
{% endif %}
{% endmacro %}
but what I want is to something like:
...iterating through list of items
{{ call macro based off item.type_of }}
at this point in regular python I'd do something like
getattr(object_with_method_to_produce_templates, item)
but haven't figured out a way to use the attr filter effectively (if I can use it properly in this situation at all).
I've found flask.get_template_attribute looking elsewhere which might be interesting (if I can instead just do it all ahead of time and send a precalculated and preformatted item to the template). Maybe too much and beyond what I want to do at this time.
What is a better way to call from a varied listing of macros instead of a list of if then's that might get rather large in the future? Seems like a common question, but I have not stumbled on the exact answer I'm looking for.
EDIT:
I added this to what I was doing, trying to generate a callable macro as part of the item I want to render
from flask import get_template_attribute
from jinja2 import Template
test_template = Template('{% macro test_macro(item) %}<div id="test-div">sent to me: {{ item }}</div>{% endmacro %}')
...in item generation...
template = get_template_attribute(test_template, 'test_macro')
...in template...iterate items then for each item
{{ item.template("testing this method") }}
which sort of works but only generates the string letter for letter, not as a regular macro would(i.e. the divs aren't rendered as divs, only as text).
<div id="test-div">sent to me: testing this method</div>
So I need to give Template some context, or something this is closer to what was aiming for but doesn't seem right.
EDIT2:
{{ item.template("testing this method")|safe }}
returns what I was looking for, so this is passable, I might be able to bypass the namedtuple arrangement I had and just pass a macro with...more working on it I suppose. Is this optimal/preferable or a mess though?

You can create a Jinja2 filter which gets the Macro from the current context and then evaluates the Macro. The filter is:
#contextfilter
def call_macro_by_name(context, macro_name, *args, **kwargs):
return context.vars[macro_name](*args, **kwargs)
See the full answer here.

Related

django philosophy: when to include templates and when to have code generate html?

When using Django templates, should I have some templates that act like "subroutines", so to speak, or should I generate HTML from within my code in these cases?
For example, I have a template with several lists of names, each of which I want to turn into a select. Should I have a template that renders the name_list variable into a select, and do something like this:
#in the view:
return {'name_list_1': name_list_1,
'name_list_2': name_list_2,
'name_list_3': name_list_3}
#in the template:
{% with name_list_1 as name_list %}
{% include "sub_name_list_select.html" %}
{% endwith %}
{% with name_list_2 as name_list %}
{% include "sub_name_list_select.html" %}
{% endwith %}
{% with name_list_3 as name_list %}
{% include "sub_name_list_select.html" %}
{% endwith %}
Or should I have a function in my code, name_list_to_select_html, which does the same job, and do this:
return {'name_list_1_html': name_list_to_select_html(name_list_1),
'name_list_2_html': name_list_to_select_html(name_list_2),
'name_list_3_html': name_list_to_select_html(name_list_3)}
#in the template:
{{ name_list_1_html|safe }}
{{ name_list_2_html|safe }}
{{ name_list_3_html|safe }}
Or are both of these wrong and I am getting the philosophy totally wrong?
Additional question: in terms of speed, is it slow to constantly include templates? Is that a bonus point for the in-code html generation?
Generally, HTML should only be generated in the templating system or directly related code. That keeps the view of the data completely separate from the business and functional logic. I feel that's a proper separation of concerns. Go with your first solution.
As for performance, Django should probably take around the same amount of time running either code. But it has built-in view and template fragment caching if you know those segments of code don't need to be regenerated on every request.

Python/Django: Simple Django Template

Hi I am using App Engine/Python to do a simple website. I have some trouble with a Django template problem.
In short, I want to use a "ShortName" to access a "LongName".
The soource code:
LongName={"so":"stackoverflow","su":"superuser"}
ShortName=['so','su']
Then I pass these two parameters to the templates.
In the template I write:
{% for aname in ShortName %}
{{ aname }} stands for {{ LongName.aname }},
{% endfor %}
The output is:
so stands for, su stands for
No error is given. The LongName.aname wont work.
I have no idea whats wrong.
This is trying to access LongName['aname'], not LongName[aname].
You might have to write a custom template tag/filter to get this to work. This Django bug (marked WONTFIX) has a simple implementation:
def get(d, key):
return d.get(key, '')
register.filter(get)
which you would use by
{{ LongName|get:aname }}
after adding it to your app (that SO answer shows how to do it on GAE).
You could also pre-make a variable to loop over in the view, by passing in
# in view
name_abbrevs = [(k, LongName[k]) for k in ShortName]
# in template
{% for short_name, long_name in name_abbrevs %}
{{ short_name }} stands for {{ long_name }}
{% endif %}
If you really don't want to add a template tag -- which isn't that bad! you just make one file! :) -- or pass in an extra variable, Vic's approach will let you do this without touching the Python files at all. As he mentions, it involves a lot of pointless iteration, but it'll work fine for small lists.
Django templates have a drawback here. I've been in the same situation before. What you have to do is iterate over all the keys in LongName, and check if the key you're looking for matches the ShortName. Here you go:
{% for aname in ShortName %}
{% for short_version, long_version in LongName %}
{% if aname == short_version %}
{{ aname }} stands for {{ long_version }},
{% endif %}
{% endfor %}
{% endfor%}
It's inefficient, and essentially a pointless O(n^2) mechanism. However, there's no better way in pure Django templates to refer to entries of a dict by a variable name.

Sorting related items in a Django template

Is it possible to sort a set of related items in a DJango template?
That is: this code (with HTML tags omitted for clarity):
{% for event in eventsCollection %}
{{ event.location }}
{% for attendee in event.attendee_set.all %}
{{ attendee.first_name }} {{ attendee.last_name }}
{% endfor %}
{% endfor %}
displays almost exactly want I want. The only thing I want to change is I the list of attendees to be sorted by last name. I've tried saying something like this:
{% for event in events %}
{{ event.location }}
{% for attendee in event.attendee_set.order_by__last_name %}
{{ attendee.first_name }} {{ attendee.last_name }}
{% endfor %}
{% endfor %}
Alas, the above syntax doesn't work (it produces an empty list) and neither does any other variation I have thought of (lot's of syntax errors reported, but no joy).
I could, of course, produce some kind of array of sorted attendee lists in my view, but that is an ugly and fragile (and did I mention ugly) solution.
Needless to say, but I'll say it anyway, I have perused the on-line docs and searched Stack Overflow and the archives of django-user without finding anything helpful (ah, if only a query set were a dictionary dictsort would do the job, but it's not and it doesn't)
==============================================
Edited to add additional thoughts
after accepting Tawmas's answer.
Tawmas addressed the issue exactly as I presented it -- although the solution was not what I expected. As a result I learned a useful technique that can be used in other situations as well.
Tom's answer proposed an approach I had already mentioned in my OP and tentatively rejected as being "ugly".
The "ugly" was a gut reaction, and I wanted to clarify what was wrong with it. In doing so I realized that the reason it was an ugly approach was because I was hung up on the idea of passing a query set to the template to be rendered. If I relax that requirement, there is an un-ugly approach that should work.
I haven't tried this yet, but suppose that rather than passing the queryset, the view code iterated through the query set producing a list of Events, then decorated each Event with a query set for the corresponding attendees which WAS sorted (or filtered, or whatever) in the desired way. Something like so:
eventCollection = []
events = Event.object.[filtered and sorted to taste]
for event in events:
event.attendee_list = event.attendee_set.[filtered and sorted to taste]
eventCollection.append(event)
Now the template becomes:
{% for event in events %}
{{ event.location }}
{% for attendee in event.attendee_list %}
{{ attendee.first_name }} {{ attendee.last_name }}
{% endfor %}
{% endfor %}
The downside is the view has to "actualize" all of the events at once which could be a problem if there were large numbers of events. Of course one could add pagination, but that complicates the view considerably.
The upside is the "prepare the data to be displayed" code is in the view where it belongs letting the template focus on formatting the data provided by the view for display. This is right and proper.
So my plan is to use Tawmas' technique for large tables and the above technique for small
tables, with the definition of large and small left to the reader (grin.)
You can use template filter dictsort https://docs.djangoproject.com/en/dev/ref/templates/builtins/#std:templatefilter-dictsort
This should work:
{% for event in eventsCollection %}
{{ event.location }}
{% for attendee in event.attendee_set.all|dictsort:"last_name" %}
{{ attendee.first_name }} {{ attendee.last_name }}
{% endfor %}
{% endfor %}
You need to specify the ordering in the attendee model, like this. For example (assuming your model class is named Attendee):
class Attendee(models.Model):
class Meta:
ordering = ['last_name']
See the manual for further reference.
EDIT. Another solution is to add a property to your Event model, that you can access from your template:
class Event(models.Model):
# ...
#property
def sorted_attendee_set(self):
return self.attendee_set.order_by('last_name')
You could define more of these as you need them...
One solution is to make a custom templatag:
#register.filter
def order_by(queryset, args):
args = [x.strip() for x in args.split(',')]
return queryset.order_by(*args)
use like this:
{% for image in instance.folder.files|order_by:"original_filename" %}
...
{% endfor %}
regroup should be able to do what you want, but is there a reason you can't order them the way you want back in the view?

Polymorphic macros in Jinja

I am looking for a way to have a Jinja macro that calls different implementations depending on the type of object that is being passed. Basically, standard Python method polymorphism. Right now, I'm using an ugly workaround similar to this:
{% macro menuitem(obj) %}
{% set type = obj.__class__.__name__ %}
{% if type == "ImageMenuItem" %}
{{ imagemenuitem(obj) }}
{% elif type == "FoobarMenuItem" %}
{{ foobarmenuitem(obj) }}
{% else %}
{{ textmenuitem(obj) }}
{% endif %}
{% endmacro %}
In pure Python, one can muck around with the module environment, e.g. globals()[x+'menuitem'], which isn't pretty but works very well. I've tried something similar using the Jinja context, but the latter doesn't seem to contain the macro definitions.
What better ways are there to achieve what I'm seeking?
The essence of OOP: polymorphism.
Create a presentation Layer for your objects:
class MenuPresentation:
def present(self):
raise NotImplementedException()
class ImageMenuPresentation(MenuPresentation):
def present(self):
return "magic url "
class TextMenuPresentation(MenuPresentation):
def present(self):
return "- text value here"
And then will be just a matter of:
{% macro menuitem(obj) %}
{{ obj.present() }}
{% endmacro %}
I have now solved my problem similarly to how fabrizioM suggested, with one notable difference: Since the menu item presentation can (and most of the time, does) contain HTML, I don't want mess around with HTML markup directly in the present methods. So I ended up implementing the menu definitions in Python, the presentation in Jinja, with mutual recursion bridging the gap.
Different types of menu items are represented by different subclasses:
class MenuItem(object):
def present(self, macromap):
return macromap[type(self).__name__](self, macromap)
class TextLink(MenuItem):
def __init__(self, url, text):
self.url, self.text = url, text
class Section(MenuItem):
def __init__(self, text, items):
self.text, self.items = text, items
class ImageLink(MenuItem):
...
The macromap referenced above is a dict mapping the type of menu item to the macro implementing its represenation. It's all defined in Jinja:
{% macro TextLink(l, macromap) %}
<a class="menuitem" href="{{l.url|escape}}">
{{ l.text|escape }}
</a>
{% endmacro %}
{% macro Section(s, macromap) %}
<div class="heading">{{s.text}}</div>
<ul class="items">
{% for item in s.items %}
<li>{{ item.present(macromap) }}</li>
{% endfor %}
</ul>
{% endmacro %}
{% set default_map = {'TextLink': TextLink, 'Section': Section, ...}
The actual menu definitions are cleanly expressed as trees of MenuItem subclasses:
main_menu = section("Main Menu", [
section("Product Line 1", [
TextLink("/products/...", "A product"),
...
]),
section(...),
])
To kick off the presentation, a template has to call the top level section's present method, passing a macro map to specify how to present the menu, e.g. main_menu.present(default_map). As can best be seen in the Section macro, menu items can then ask their children to present themselves, whose present method will call yet another Jinja macro, and so on, recursively.
Having to explicitly pass around the macro map is not very pretty, but it grants a valuable benefit: One can now easily render different representations of the menu data without touching the menu definitions at all. For example, macro maps may be defined to render the main website menu, or a variant for mobile devices (in case CSS doesn't suffice), or an XML sitemap, or even a plain text version. (We actually ended up using this system for the website menu and sitemap cases.)

how to run this code in django template

this is my code :
{% for i,j in enumerate(a) %}
{{i}} ,{{j}}
{% endfor%}
but , it show a error , i think it cant run the enumerate method ,
so how to run the enumerate in django template ,
thanks
The template subsystem has some special constructs built into the for/endfor block that allows you to access the current index of the loop without having to call enumerate.
{% for j in a %}
{{ forloop.counter0 }}, {{ j }}
{% endfor %}
While this snippet solves your immediate problem, if you're expecting to have access to Python builtins and other Python constructs inside your Django templates, you may be misunderstanding the sandbox that it provides/enforces.
you can use {{ forloop.counter }} or {{ forloop.counter0 }} for the same effect, the latter is 0-indexed, thus more like enumerate.
{% for item in a %}
{{ forloop.counter }}, {{ item }}
{% endfor %}
Link related
Django template makes up the presentation layer and are not meant for logic. From the docs
If you have a background in programming, or if you’re used to languages which mix programming code directly into HTML, you’ll want to bear in mind that the Django template system is not simply Python embedded into HTML. This is by design: the template system is meant to express presentation, not program logic.
Now to get the same functionality in Django, you will have to complete your logic in the views.
views.py
def my_view(request, ...):
....
enumerated_a = enumerate(a);
....
return render_to_response('my_template.html', {'enumerated_a ': enumerated_a }..)
Now enumerate function returns an enumerate object which is iterable.
my_template.html
{% for index, item in enumerated_a %}
{{ index }},{{ item }}
{% endfor %}
Although I think you can probably change it to an enumerated list and use it like that as well.
If however you need to use a function within a template, i suggest you create a filter or a tag instead. For reference, check out http://docs.djangoproject.com/en/1.2/howto/custom-template-tags/

Categories

Resources