Pass context to included Jinja Template in Jinja Template - flask

I have common things in all Jinja files. I want to move all common content to one single Jinja file in Flask.
My app is written in Flask and I am passing context to Jinja template like
def f1(name): render_template('j1.jinja', name=name)
def f2(name): render_template('j2.jinja', name=name)
def f3(name): render_template('j3.jinja', name=name)
My j1.jinja file is:
{%block content %}
Name: {{ name }}
Hello
{% endblock %}
My j2.jinja file is:
{%block content %}
Name: {{ name }}
Bye
{% endblock %}
My j3.jinja file is:
{%block content %}
Name: {{ name }}
Howdy
{% endblock %}
I moved common content to single Jinja file common.jinja which is:
{% block content %}
Name : {{name}}
{% endblock %}
I am including it in all Jinja files like:
{% include 'common.jinja' with { "name": name } only %}
which doesn't work. I am getting the Exception:
Exception Occured. Explanation: expected token 'end of statement block', got 'with'
How can I pass context to included Jinja file?

Your code looks a bit weird. There is no separate with statement that specifies context, it is with context which is often used with import statement (see Import context behaviour).
To pass any context to an included template, simply render the parent template with context variables, e.g. in render_template():
render_template('j3.jinja', name=name)

Related

Nested variables in Django Templates

I'm not sure if it is variable or tag.
I have two apps (different styles, different navbars) but want to print the same document from db.
I created base.html, templates with extends and everything works perfectly. But.
The body of template is filled from database, which is a part of html code.
And in this code there's <p>The contact email is: blabla#firstapp.example.com</p>
Of course this is wrong for secondapp.
I tried to change body with <p>The contact email is: {{ app_email }}</p>, and set it with context
But it doesn't work - it prints
The contact email is: {{ app_email }}
template/document.html:
{% extends base %}
{% load crispy_forms_tags %}
{% load static %}
{% block head %}
<title>{{ title }}</title>
{% endblock %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-9">
<h1 class="mt-2">{{ document.header }} !!!</h1>
<hr class="mt-0 mb-4">
{% autoescape off %}
{{ document.body }}
{% endautoescape %}
</div>
</div>
</div>
{% endblock %}
firstapp.views.py:
def myview(request):
context = {
'app_title' : 'First',
'title' : 'First - document',
'app_name' : 'first',
'app_email' : 'contact#first.example.com',
'document' : get_document('document1'),
'base' : 'first/base.html'
secondapp.views.py:
def myview(request):
context = {
'app_title' : 'Second',
'title' : 'Second - document',
'app_name' : 'second',
'app_email' : 'contact#second.example.com',
'document' : get_document('document1'),
'base' : 'second/base.html'
Is it possible this way? Mayby some filter?
edited:
Now I know, that I have to prerender it in get_document. But how to pass unknown parameters?
This function works - but have to add sth do parameters (*args? **kwargs?)
and redefine Context{{ *args? **kwargs?? }}
def get_document(name):
doc = Doc.objects.get(name=name)
doc.body = Template(doc.body).render(Context{{'app-email':'contact#first.example.com'}})
return doc
The problem is you are treating the content of document1 in this case as a context variable itself. It never gets parsed by the django templating engine and so the {{ app_email }} variable is never converted.
I see two options:
if the document is a file from disk (seems like that's not the case based on your description) then you need to figure out how to load the document into your template as another template. I know there are tags for loading another template based on the content of a variable. So you would pass template_name = "template/path/to/document" in your view and then in your template include it with something like {% include template_name %}. Actually, even if the template isn't on disk, you can write a template loader that loads it from where ever.
Alternatively, you can send the results of get_document(...) through the template engine independently. So in the view.py you would render it separately before adding it to the template context. I think there used to be a django.shortcuts.render_string function you could pass it through, though I think that might have changed in newer Django's. Update from OP: Template(<str>).render(<context>) is the way to do it.
Thanks to #saquintes
def get_document(name,**kwargs):
doc = Doc.objects.get(name=name)
doc.body = Template(doc.body).render(Context(kwargs))
return doc
and in first.views.py:
(...)
def myview(request):
context = {
'app_title' : 'First',
'title' : 'First - document',
'app_name' : 'first',
'document' : get_document('document1', app_email = 'contact#first.example.com'),
'base' : 'first/base.html'
(...)

Django conditional javascript file in template causes missing staticfile error in production only

I have a conditional in a template used by several views which will include a js file if passed in by the view:
in the template:
{% if js_file %}
{% block inner_js %}
<script defer type="text/javascript" src="{% static js_file %}"></script>
{% endblock inner_js %}
{% endif %}
which is eg used by a view by:
.......
context = {'title': 'Sign up',
'form': form,
'js_file': 'js/supplier.js'}
return render(request, 'pages/signup.html', context)
This produces no errors in development, but when pushed to production I get the error:
ValueError at /users/example/
Missing staticfiles manifest entry for ''
If I remove the template if block above then this error dissapears (and works fine for those views which use the template without a js file).
I would rather not have to create a new version of the template for every view which uses a js file, is their a better way to fix this? (seems a bit of a weird error).
I don't believe you can conditionally include/exclude a block as you're doing. If you put the if tags inside the block, it will only include the <script> tag when the variable js_file is populated.
If you're conditionally including this block to override another block called inner_js in a template higher up, you can do something like this to achieve the same results:
{% block inner_js %}
{% if js_file %}
<script ...></script>
{% else %}
{{ block.super }}
{% endif %}
{% endblock inner_js %}
{{ block.super }} is the equivalent of calling Python's super in a class, and allows for block extensions in templates that extend a base.

Django custom template tag is not being executed

I have a custom template tag that looks like this:
{% extends 'forms/base_template.html' %}
{% load mytags %}
{% mytag user 'hello' as greeting %}
{% block additional_info %}
{{ greeting }}
{% endblock %}
My tags are something like this:
from django import template
register = template.Library()
#register.assignment_tag(takes_context=False)
def mytag(user, what_to_say):
return "{what_to_say} {user}".format(
what_to_say=what_to_say,
user=user.name
)
But the code is never executed and the greeting variable is empty.
Any ideas what may be going on?
OK, I found out just before I published. Thought to share just in case someone else gets bitten by this.
Apparently the tags need to be included within the same block they are being used. Couldn't find any relevant docs. So if the template looks like this:
{% extends 'forms/base_template.html' %}
{% load mytags %}
{% block additional_info %}
{% mytag user 'hello' as greeting %}
{{ greeting }}
{% endblock %}
it will work as expected. Nothe the {% mytag %} call is now included within the block.
UPDATE: Found some relevant info (kind of hidden) in the docs.
Variable scope in context
Any variable set in the context will only be available in the same block of the template in which it was assigned. This behavior is intentional; it provides a scope for variables so that they don’t conflict with context in other blocks.

How to access an attribute of an object using a variable in django template?

How can I access an attribute of an object using a variable? I have something like this:
{% for inscrito in inscritos %}
{% for field in list_fields_inscrito %}
{{inscrito.field}} //here is the problem
{% endfor %}
{% endfor %}
For example Inscrito have: inscrito.id, inscrito.Name and inscrito.Adress and I only want to print inscrito.id and inscrito.Name because id and Name are in the list_fields_inscrito.
Does anybody know how do this?
You can write a template filter for that:
myapp/templatetags/myapp_tags.py
from django import template
register = template.Library()
#register.filter
def get_obj_attr(obj, attr):
return getattr(obj, attr)
Then in template you can use it like this:
{% load myapp_tags %}
{% for inscrito in inscritos %}
{% for field in list_fields_inscrito %}
{{ inscrito|get_obj_attr:field }}
{% endfor %}
{% endfor %}
You can read more about writing custom template tags.
Fixed answer for non string attributes
The selected answer don't cover cases where you need to access non string attributes.
If you are trying to access an attribute that isn't a string, then you must use this code:
from django import template
register = template.Library()
#register.filter
def get_obj_attr(obj, attr):
return obj[attr]
For this, create a folder named templatetags on your app's folder, then create a python file with whatever name you want and paste the code above
inside.
Inside your template load your brand new filter using the {% load YOUR_FILE_NAME %}, be sure to change YOUR_FILE_NAME to your actual file name.
Now, on your template you can access the object attribute by using the code bellow:
{{ PUT_THE_NAME_OF_YOUR_OBJECT_HERE|get_obj_attr:PUT_THE_ATTRIBUTE_YOU_WANT_TO_ACCESS_HERE }}

Why is this django template tag failing to display?

I have a template tag located in catalog/templatetags/catalog_tags.py, which looks like this:
register = template.Library()
#register.inclusion_tag("tags/navigation.html")
def nav_links():
flatpage_list = FlatPage.objects.all()
return {'flatpage_list': flatpage_list }
I have a catalog.html which has {% load catalog_tags %}, to load that tag, and is followed by an inclusion tag for my navigation, {% include "tags/navigation.html" %}.
navigation.html contains the following:
{% with flatpage_list as pages %}
{% for page in pages %}
{{ page.title }}
{% endfor %}
{% endwith %}
But the list of flat_pages is not appearing in my navigation section. Why is that?
If I understand right, with your current state you have something liek this in catalog.html template:
{% load catalog_tags %}
.....
{% include "tags/navigation.html" %}
What this code does, is just renders the "tags/navigation.html" template, nothing more. So your custom template tag is not hit at all. To fix it, you should replace include with nav_links:
{% load catalog_tags %}
.....
{% nav_links %}
See Django docs for reference.
Not sure if it's just a copy paste error or not but return {'flatpage_list': flatpage_list isn't closed properly return {'flatpage_list': flatpage_list}
Also could this be something more suited for a context processor?
EDIT: After reading the other answer, I realized what you are trying to do, when you were using the {% include ... %} tag it seemed like you just wanted to populate the flatpage_list