Context behavior for jinja2 blocks with included files - python

I have difficulties understanding the context behavior when including files in Jinja2 blocks. See this minimal example:
base.jinja2
{% set foo="bar" %}
{% block content %}
{% include 'include.jinja2' %}
{% endblock content %}
include.jinja2
{{ foo }}
Rendering base.jinja2 produces the error foo' is undefined.
When I move the declaration of foo into the content block, the code renders correctly. So does it, when I move the include statement outside the content block, when I remove the content block around the include statement, or when I replace the include statement with the file's contents.
Why is that? How can I use the global foo variable in the include file inside the content block?

Look at the link, section Block Nesting and Scope:
Blocks can be nested for more complex layouts. However, per default
blocks may not access variables from outer scopes:
The reason for this is that if the block is replaced by a child
template, a variable would appear that was not defined in the block or
passed to the context.
According to the document and starting with Jinja 2.2 you can change this behavior using scoped.
{% set foo="bar" %}
{% block content scoped %}
{% include 'include.jinja2' %}
{% endblock content %}
I agree that this is somehow weird, compared to what we are used to. But this is an intended feature.
Generally speaking, include is only passed the context. Since 2.1 it is
passed a derived context that is the original context + all the local
variables used before the include statement. This also means that if
you just use a variable from an outer scope, Jinja2 will track it as
local now and pass it to the derived context.

Related

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 have variable defaults in templates?

I want to pass variable defaults to my templates in case of the actual variable is None.
Here is what I mean:
{{ value|default:"Default" }} ## General case.
How can I replace "Default" with my custom variable ?
Is it possible to replace it with another variable ?
Something like :
{{ value|default:{{value2}} }} ## OR Something similar
You can just put another variable instead of constant string like this:
{{ value|default:backup_var }}
I've just tested this, it works fine. Here is my template piece:
<i>{{ my_var|default:user.username}}</i>
I have no my_var defined, so it evaluates to:
<i>admin</i>
Summing up, django parser accepts not only constant values, but also variables as parameters to filters also. But not expressions (I couldn't figure out how to make my expression parsed. But if you need it, you may alsways use {% with var=expr %} ... {% endwith %} for complex situations)

Django: specifying a base template by directory

I'm working on a Django site that has multiple sections and subsections. I'd like to have several depths of template inheritance: a base template for the whole site, one base template for each section that inherits from the root base template, and so on. Here's a simplified version of my desired directory structure:
base.html
section1/
base.html
section2/
base.html
section3/
base.html
What I would desire is for all the files under section1/ to contain something like {% extends "base.html" %}, meaning they would extend section1/base.html. section1/base.html would contain something like {% extends "../base.html" %}, meaning that it would extend the root-level base file. However, I couldn't find anything in the documentation suggesting this was possible, and I couldn't get Django to distinguish between "../base.html" and "base.html". ({% extends "../base.html" %} throws an error.) I suppose one workaround would be to rename all base files base_SECTIONNAME.html, and update all the files that inherit from them, but I am concerned that this might become difficult to maintain as my site becomes bigger and sections change names, etc. I would prefer a solution that takes advantage of the natural hierarchy specified by directories and subdirectories.
Any ideas?
May be I oversee something, but all you want can be accomplished with the django template system. All extends calls are relative to template directories.
In order for all base.html files in subdirectories to extend base.html, you just have to put a {% extends "base.html" %} into the files. section1/base.html would would look like that.
{% extends "base.html" %}
{# ... rest of your code ...#}
Now, to get the files from section1 to extend section1/base.html you just have to put {% extends "section1/base.html" %} at the top of them. Same for section2, section3 and so on.
It is just that simple, but might not totally obvious in the documentation.
I hope, I understood your question.
The accepted answer will work, but I do recommend using variable names to keep track of section structure. My personal preference would be a context processor. If, for example, your site's section organization is transparently reflected in the url, try something like:
# It may be convenient to make this function live in or near your url conf.
def convert_url_path_to_folder_path(path):
# fill in the magic here
def sub_folder_available(request):
folder = convert_url_path_to_folder_path(request.path)
return {'subsection': folder, 'local_base':folder+'/base.html'}
Then in your template, just call
{% extends local_base %}
There are probably a dozen other ways to do this, but the main thing is to think about avoiding hard-coding the folder name into the template. This will get you a lot of mileage, especially since you can just drag and drop template between sections if they happen to be similar enough. Another thing you might add insert is:
def sub_folder_available(request):
folder = convert_url_path_to_folder_path(request.path)
# Check if local base exists:
if os.access(folder+'/base.html',os.F_OK):
base = folder+'/base.html'
else:
# revert to your global base
base = 'base.html'
return {'subsection': folder, 'base':base}
The nice advantage of this strategy is of course that you can get a fly-weight section up and running without any local base template at all.
You can use this library: https://github.com/vb64/django.templates.relative.path
Just write in your templates as follows:
{% load relative_path %}
{% extends ".base.html" %}
this will extend template "base.html", located in the same folder, where your template placed
{% load relative_path %}
{% extends "...base.html" %}
extend template "base.html", located at two levels higher
same things works with 'include' tag.

Categories