How do I access Jinja2 for loop variables outside the loop? - python

I have a Jinja2 template page which contains two separate {% for %} loops. If neither of these loops contain any items, I want the page to redirect.
I'm trying to do something like this (pseudo-code):
loop1 = loop.length (in first loop)
loop2 = loop.length (in second loop)
if loop1 + loop2 == 0: redirect # (outside both loops)
Is this even possible? Is there a way to make the loop.length variables available outside their respective loops?

You can check your lists for truth, empty lists are false in Jinja2.
{% if things %}
{% for thing in things %} .... {% endfor %}
{% else %}
<!-- redirect here -->
{% endif %}

The simple answer is "no": You can't re-direct using a template -- it should be in the view logic of the controller/server.
Although technically one can, but not doing anyone any favours.

Assuming both things are lists, you can do this:
{% set all_things = thing1 + thing2 %}
{% if all_things %}
{# There is more than one thing in the two lists #}
{% else %}
{# redirect #}
That being said, this is not something that belongs at the template level - you are generating another list containing all of the things in thing1 and thing2 every time you hit the page, which will cost in terms of resources. You are putting application logic in the template level, which will not be maintainable. And finally, you are papering over the larger problem - that making changes to the back end code is costly. (Please understand that "you" in all these cases is the generic "you" - as in "the company you work for").
You (specifically) should raise these issues with those who are asking you to implement this hack and try to change the direction that this tool / product / company is taking before it becomes a piece of FrankenCode.
Good luck!

You could check the length things object(the object you are looping through) by using the length filter:
{{things|length}}
Now to answer your question. lets assume the objects you loop through is named t1 and t2, you could do:
{% if t1 | length == 0 and t2 | length == 0 %}
//use javascript to redirect(assuming you have the link)
{% endif %}
You could do this in your JavaScript block. I do not know if this is the most efficient way to do it, but, it should work.
I am posting this answer, because there is no up-voted or accepted answer to this question.
I do Hope this helps.

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.

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 can I use a JSON response in conditional python logic?

I'm using ajax to asynchronously update a boolean variable in my Django project. I'm able to successfully display this variable using the template but I'm not sure how I can use this variable in the template's embedded python logic.
Basically
{% if JSON_BOOL %}
<p>this</p>
{% else %}
<p>that</p>
{% endif %}
Where the JSON_BOOL is being supplied by an ajax function. What's the best way to make the variable available to the conditional logic? Thanks.
There are two ways to solve this. If I were you, I would just leave the variable in text format and in your code say:
{% if JSON_BOOL == "True" %}
...
{% else %}
...the rest of your code
Otherwise you could cast it to a boolean on the python side of things. This would look as follows in your view:
if JSON_BOOL == "True":
JSON_BOOL = True
else:
JSON_BOOL = False
I'm not aware of a way to cast variables to different types within the template itself..this seems to be outside of its scope of functionality, and either way it's better to keep functionality out of templates. Hope this helps.

Python Django: in view is it better to add properties to an object or make a dictionary of the data?

My models don't really matter in this case, this is a fundamental Python question, I suppose.
Say I have a queryset of items and I want to calculate some things for each one to be displayed in a template.
In my view, I can create a list of objects, and for each object I can set a property on that object for the calculation, then I can display that in the template. OR I can create a list of dictionaries and only get the fields I need to display in each dictionary along with the calculated field. Which is better for performance, and in general practice?
An overly-simplified example for clarity (I know I can call getAge() from the template, what I am really calculated is more complex and for performance I want to do the calculations in the view code):
models.py:
class Person(models.Model):
first_name = ...
last_name = ...
date_of_birth = ...
.
.
.
def getAge(self):
return ... # return the calculated years since date_of_birth
views.py:
def method1_object_property(request):
people = Person.objects.all()
for p in people:
p.age = p.getAge()
return render_to_response('template.htm', {'people': people})
def method2_dictionary(request):
people = Person.objects.all()
data = list()
for p in people:
row = dict()
row['first_name'] = p.first_name
row['last_name'] = p.last_name
row['age'] = p.getAge()
data.append(row)
return render_to_response('template.htm', {'people': data})
template.htm:
<ul>
{% for p in people %}
{{ p.first_name }} {{ p.last_name }} (Age: {{ p.age }})
{% endfor %}
</ul>
Both methods work just fine so far as I can tell, I was just curious what the preferred method would be and why. Are there performance issues assigning new fields dynamically to an existing object in memory using the object dot property method (object.new_field = 'some_detail')?
UPDATE:
Yes, I know in my example I can call getAge() from template, and yes, this is the incorrect naming standard for methods which should be lowercase with underscores. I think my example is too simple and is clouding what I really want to know.
What is the best way to add information to an object that I want displayed in the view that is not a part of the model layer. Say I get a QuerySet of Person objects and want to calculate how many times they have logged into my website in the last 30, 60 and 90 days. I want to create three "properties" for each Person object on the fly. I can set this in the view with
for p in people:
p.last_30 = Login.objects.filter(person=p, login_date__gt=date.today()-timedelta(days=30))
p.last_60 = Login.objects.filter(person=p, login_date__gt=date.today()-timedelta(days=60))
p.last_90 = Login.objects.filter(person=p, login_date__gt=date.today()-timedelta(days=90))
Then in my template I can display those "properties." I just wanted to make sure I'm not violating some Python standard or cheating the system. Alternately, I could store these other lookups in a dictionary with the object in one key/pair, and the various details in separate ones. This is a bit more work in the view, but I was curious if it is better for performance or compliance of standards to do so?
Sorry if my original question was not clear enough, or my example added confusion.
Definitely method 1.
Method 2 is pointless, you can iterate over the queryset directly in the template, there is no need to build up an intermediate 'list of dicts' in your view. eg you just can do:
def method2_dictionary(request):
people = Person.objects.all()
return render_to_response('template.htm', {'people': people})
in your template:
{% for p in people %}
{{ p.first_name }}
etc
{% endfor %}
Coming back to method 1...
This: p.age = p.getAge() is also pointless, you can directly call the method in your template as {{ p.getAge }} (as long as your method does not take arguments) see the docs here:
https://docs.djangoproject.com/en/dev/topics/templates/#accessing-method-calls
Note that in Python we generally prefer to use 'lower-case with underscores' for method names, eg def get_age(self) and {{ p.get_age }}
(see the official 'PEP8' style guide for Python here http://www.python.org/dev/peps/pep-0008/#function-names)
If your get_age method has no side-effects and takes no arguments you may like to make it a property which is Python's way of having a getter method you can access without the parentheses.
In this case it would make sense to name it just age:
#property
def age(self):
return ... # return the calculated years since date_of_birth
and in your template:
{% for p in people %}
{{ p.first_name }}
{{ p.age }}
etc
{% endfor %}
For more info about Python properties, see here:
http://docs.python.org/2/library/functions.html#property
Some more info in this SO question:
Real world example about how to use property feature in python?
UPDATE
Referring to your updated question... as a question of style I would still make these (last_30 etc) methods on the model rather than adding ad hoc properties onto each model instance in the view code.
From a performance perspective, the difference in memory, processing time etc of method lookup vs dictionaries etc is trivial in most real world situations ...by far the biggest performance consideration in this kind of code is usually the number of database queries.
If you know you're going to do an extra query (or three) for each item in your queryset it's worth looking for ways to get everything in one or more big queries.
In some cases you may be able to use the annotate() method:
https://docs.djangoproject.com/en/dev/ref/models/querysets/#annotate
(I don't think that's possible in your example)
In your specific code above you only need to query for the 90 days (oldest interval) and you could filter the 60 and 30 days sets from that in Python without querying the db again.
But that would still be doing one extra query per item in your people queryset. It'd be better to do one big query for the Login objects for all (or whatever subset) of the people. Since there is a foreign key relation for Person on Login we can use select_related() to get the Person instances in one big query when we query Login model:
def method3(request):
logins = Login.objects.filter(
person__in=Person.objects.all(),
login_date__gt=date.today() - timedelta(days=90)
).order_by('person', 'login_date').select_related()
return render_to_response('template.htm', {'logins': logins})
Note if you're really doing Person.objects.all() you wouldn't need the person__in filter above, only if you wanted to filter the Person set in some way.
Now that we got all the data in one big query we can do what we need to on the python side to display the data. eg in the template we could use the regroup tag:
{% regroup logins by person as people %}
{% for person in people %}
{% with person.grouper as p %}
{{ p.first_name }}
{% for login in person.list %}
{{ login.login_date }}
{% endfor %}
{% endwith %}
{% endfor %}
You could take this further and write a custom tag for the login date ranges... I won't detail that here but in your template it could look something like:
{% regroup logins by person as people %}
{% for person in people %}
{% with person.grouper as p %}
{{ p.first_name }}
{% logins_since person.list 60 as last_60_days %}
{% logins_since person.list 30 as last_30_days %}
{% for login in last_30_days %}
{{ login.login_date }}
{% endfor %}
{% for login in last_60_days %}
{{ login.login_date }}
{% endfor %}
{% endwith %}
{% endfor %}
Don't bother with dictionaries. By looking at those two methods, I'm failing to understand what real problem second one solves. From template's perspective both methods produce same outcome, but first one is far shorter than second one.
However, there are some issues I see in your code:
First, if you really care about performace, you should avoid performing needless work. The age-setting step in first method is not really best way to solve problem and it's memory usage will grow as you are adding new persons to database.
Did you know that you can use functions/methods that don't accept any arguments (or just "self" argument in case of methods) in templates like they were attributes? If you rename "getAge" to "age", you can simplify first method code down to this:
def method1_object_property(request):
people = Person.objects.all()
return render_to_response('template.htm', {'people': people})
Also please take while to familiarize yourself with PEP8 document that regulates conventions for writing python code: http://www.python.org/dev/peps/pep-0008/
As per PEP8 "getAge" is not correct function name, and underscore lowercase should be used, ergo "get_age" is good while "getAge" is "unpythonic". However because this function is basically dynamically counted attribute, you can leave it as "age" and optionally add #property decorator to it, giving it same behaviour it does in django template in python code.
Now about optimisation. Defaut behaviour for Django query set upon evaluation is to convert all results returned by database to python objects. So if you have 2 rows in table, Person.objects.all() will produce two Person objects. But if you have 9000 rows, it will produce 9000 python objects that will immediately consume large amounts of memory.
You have two ways to defend yourself against this:
First, you can limit queryset to specified number of items, either by hardcoding it to fetch, say 5 latest members, or by implementing pagination, or finally by makind display of profiles come after user enters search criteria for persons.
Limiting ("slicing") querysets is covered by Django docs here: https://docs.djangoproject.com/en/1.6/topics/db/queries/#limiting-querysets
Secondly, you can make django use lazy approach to turning database rows into python objects by adding .iterator() at the end of your query. This will make django turn rows into objects as they are returned by queryset which is more memory friendly, but imposes some limitations on your code because instead of list-like object it you will get generator object.
This change will make Django create Person object for result row once, use it to display row on list, and then throw it away.

Categories