Jinja2 dynamic variable building - python

My jinja template gets an object which has many variable names, this attributes vary and so their names, I am looking for a way to access this attributes based on a prefix and a for loop:
{% for i in Object.vars %}
<h1> {{ Object.attribute_ + i }} </h1>
{% endfor %}
I'm trying to access Object.attribute_1, Object.attribute_2 and so on. the code above of course won't work, but I can't think on a way of doing this.

Keep in mind that doing too much logic in your template files will cause (long term) issues to maintain your code.
I would say, keep your logic outside of the template and create a list of your objects before rendering the template, using the getattr() function:
for i in Object.vars:
list_of_objects.append(getattr(Object, 'attribute_' + i))
Now when rendering the template pass the list to like that:
render_template('page.html', list_of_objects=list_of_objects)

The canonical way to solve problems like this is to pass a structure such as a list or dict. Dynamic variable names are almost always a terrible idea.

Related

Best way to use "context" on Django templates for many variables / dictionaries

I want to send a lot of data to 1 Django template (to show it on 1 specific page).
The data comes from 2 sources:
At least 1 list, with many dictionaries inside (I can't change any
of this, it comes from Google Ads API).
Many variables (I can
change them, for example, to turn them into dictionaries or tuples if
necessary).
The variables are very simple, something like "variable = other_variable / 100" (just 1 value, no need for a key), so I don't need the variables to be turned into a dictionary.
But I've understood (maybe wrong) that the "context" from Django's render needs dictionaries.
The variables' data comes from the list (I do some calculations using some of the list values), but maybe this is not relevant for my question.
Right now I'm doing something like this in my views.py:
campaign_data = []
CODE TO FILL THAT LIST WITH API DATA (MANY DICTIONARIES INSIDE)
clicks = sum(data['campaign_clicks'] for data in campaign_data)
sum_clicks = {"sum_clicks" : clicks}
context = {
'campaign_data' : campaign_data,
'sum_clicks' : sum_clicks
}
return render(request, 'testing.html', context)
And then on the Django template I use the data from views.py like this:
{% for element in campaign_data %}
{{ element.campaign_name }}
{{ element.campaign_budget }}
{% endfor %}
{% for key, value in sum_clicks.items %}
{{key}} - {{value}}
{% endfor %}
This works, but it seems like a lot of "extra steps" (create a dictionary I don't really need only to feed it to context, doing a loop for a dictionary that just has 1 key-value...).
And I'm going to use a lot of those variables or "extra dictionaries" (not just 1 like in this example), so it will matter.
Is there an easier and/or more efficient way to do this?
I'm a Python and Django beginner, so I don't really know what I'm doing.
But I've understood (maybe wrong) that the "context" from Django's render needs dictionaries.
The context is a dictionary, but it does not need dictionaries as values. You can pass any value to it.
So you can write this as:
campaign_data = []
# code to fill that list with API data (many dictionaries inside)
clicks = sum(data['campaign_clicks'] for data in campaign_data)
context = {
'campaign_data' : campaign_data,
'sum_clicks' : clicks
}
return render(request, 'testing.html', context)
and then render this as:
{% for element in campaign_data %}
{{ element.campaign_name }}
{{ element.campaign_budget }}
{% endfor %}
Total number of clicks - {{ sum_clicks }}
Context does indeed need to be in dictionary form, however you are also able to send object instances, such as simple classes or models. Take this example:
class Car:
colour: str
owner_names: List[str]
def __init__(self):
...
And you can send an instance as context:
car = Car(..)
return render(request, 'testing.html', {"car": car})
This is particularly useful if you are using models.py to create a database schema; you can send model instances!
What I found best is to create classes and treat them as data types, instead of sending custom built dictionaries in your view. Nesting classes is also really powerful to keep your code clean and organised. Either way, just using dictionaries is not bad, you just need to find a way that works for your project.

Django template execute on only first occurance of match in a for loop

I have a video table with Foreign Keys that point to a document (multiple videos to one doc). I would like to check every element of that list, and on the first match with a query, enable an element (button that leads elsewhere). My attempts have been to use a for loop and then an if statement such that:
{% for vid in doc.video_set.all %}
{% if vid.query_data == 'match_term' %}
<-- button/link stuff -->
{% initialize variable %}
{% endif %}
{% endfor %}
the idea being if I initialized a variable I could add if "and variable is None" to the if statement and prevent future displays. However, after trying to use "set" and "with" to intialize variables I have been greeted with little more than error messages that seem to indicate these methods dont exist. How would I effectively achieve this functionality.
Django template language does not allow you to set variables like this. Your question is a bit confusing because you are trying to show how you would implement it in the Django template, rather than showing what you want the template to display. Here's a couple of suggestions:
If match_term is constant, you could add a method to your model.
class Doc(models.Model):
def first_match(self):
return self.video_set.filter(query_data='match_term').first()
Then use {{ doc.first_match }} in the template.
If match_term changes, then you might have to write a custom template tag.

How to pass an argument to a function in html [duplicate]

I'm passing to Django's template a function, which returns some records.
I want to call this function and iterate over its result.
{% for item in my_func(10) %}
That doesn't work.
I've tried to set the function's return value to a variable and iterate over the variable, but there seems to be no way to set a variable in a Django template.
Is there any normal way to do it?
You cannot call a function that requires arguments in a template. Write a template tag or filter instead.
if you have an object you can define it as #property so you can get results without a call, e.g.
class Item:
#property
def results(self):
return something
then in the template:
<% for result in item.results %>
...
<% endfor %>
I'm passing to Django's template a function, which returns me some records
Why don't you pass to Django template the variable storing function's return value, instead of the function?
I've tried to set fuction's return value to a variable and iterate over variable, but there seems to be no way to set variable in Django template.
You should set variables in Django views instead of templates, and then pass them to the template.
By design, Django templates cannot call into arbitrary Python code. This is a security and safety feature for environments where designers write templates, and it also prevents business logic migrating into templates.
If you want to do this, you can switch to using Jinja2 templates (http://jinja.pocoo.org/docs/), or any other templating system you like that supports this. No other part of django will be affected by the templates you use, because it is intentionally a one-way process. You could even use many different template systems in the same project if you wanted.
What you could do is, create the "function" as another template file and then include that file passing the parameters to it.
Inside index.html
<h3> Latest Songs </h3>
{% include "song_player_list.html" with songs=latest_songs %}
Inside song_player_list.html
<ul>
{% for song in songs %}
<li>
<div id='songtile'>
<a href='/songs/download/{{song.id}}/'><i class='fa fa-cloud-download'></i> Download</a>
</div>
</li>
{% endfor %}
</ul>

Django Templates: How best can the output of executing python code in a Django template be suppressed?

Someone has probably encountered this before, and perhaps even the docs provide a solution already, but I couldn't find it yet. My situation is this:
Just to illustrate the REAL PROBLEM: Assuming I have a list that I pass to the template, and which list I iterate over, with a {% for... in one instance, and in the other, I only need to display its first 5 elements only (based on some condition for example, and not just the first 5 elements of the list). Both loops are being used to output a table dynamically. Now, it's the second instance that's tricky... I adopted the solution here, which utilizes a special Counter Class, passed to the template context, and on which one must invoke the Counter.increment method, to be able to increment the counter - which I then use in my conditional statement, to halt execution of the loop.
The challenge:
I currently have code like this:
<script>{{ Counter.reset }}</script>
<table>
...
{% for l in list %}
{%if Counter.counter <= 5 %}
<tr><td>{{ l.some_field }} <span style="display:none">{{ Counter.increment }}</span></td></tr>
{% endif %}
{% endfor %}
</table>
So, how can I just call the Counter.increment method, without needing the <span> inside which I encapsulate it (so the output from that code isn't sent to the browser)? Is it okay to just do:
<tr><td>{{ l.some_field }}{{ Counter.increment }}</td></tr>
The above would work, if Counter.increment doesn't return anything, but what if it does?!
How best can the output of executing python code in a Django template be suppressed then?
Also you can use with tag and ignore variable:
{% with ignorevar=Counter.increment %}{% endwith %}
This is a bit of a hack, but it would solve your problem:
{{ Counter.increment|yesno:"," }}
(See the documentation on the yesno filter)
If you only need the top five elements, then I think the right way is to send a list of only top 5 elements from your views to your your html templates in the very first place.
Also, if for some reason, you are not able to do that, then you should there is a thing in Django known as Template Tags where you do all your calculations.
See this -> https://docs.djangoproject.com/en/1.9/howto/custom-template-tags/
And finally if you still want to use Counter.increment, then simply put it inside a div say "count-flag" and using your javascript, hide that div forever on page load:
$(document).on('ready', function(){
$("#count-flag").hide();
}
So it will not be displayed on your html, but technically this is not the way to do it.

How to call function that takes an argument in a Django template?

I'm passing to Django's template a function, which returns some records.
I want to call this function and iterate over its result.
{% for item in my_func(10) %}
That doesn't work.
I've tried to set the function's return value to a variable and iterate over the variable, but there seems to be no way to set a variable in a Django template.
Is there any normal way to do it?
You cannot call a function that requires arguments in a template. Write a template tag or filter instead.
if you have an object you can define it as #property so you can get results without a call, e.g.
class Item:
#property
def results(self):
return something
then in the template:
<% for result in item.results %>
...
<% endfor %>
I'm passing to Django's template a function, which returns me some records
Why don't you pass to Django template the variable storing function's return value, instead of the function?
I've tried to set fuction's return value to a variable and iterate over variable, but there seems to be no way to set variable in Django template.
You should set variables in Django views instead of templates, and then pass them to the template.
By design, Django templates cannot call into arbitrary Python code. This is a security and safety feature for environments where designers write templates, and it also prevents business logic migrating into templates.
If you want to do this, you can switch to using Jinja2 templates (http://jinja.pocoo.org/docs/), or any other templating system you like that supports this. No other part of django will be affected by the templates you use, because it is intentionally a one-way process. You could even use many different template systems in the same project if you wanted.
What you could do is, create the "function" as another template file and then include that file passing the parameters to it.
Inside index.html
<h3> Latest Songs </h3>
{% include "song_player_list.html" with songs=latest_songs %}
Inside song_player_list.html
<ul>
{% for song in songs %}
<li>
<div id='songtile'>
<a href='/songs/download/{{song.id}}/'><i class='fa fa-cloud-download'></i> Download</a>
</div>
</li>
{% endfor %}
</ul>

Categories