As specified in the title, my concern is about how to pass a variable set in a parent Jinja2 template to its child template.
The configuration of the project is the following:
I'm using Tornado to build an API.
In the app.py script, I associated the endpoint /parent to the
class ParentHandler. When a curl GET method is performed, the get()
method of the class ParentHandler is executed and renders its result,
response (which is a dict) to the template parent.html. I would like to use the rendered HTML code as a header for
the child template, so at the end of parent.html, there is a block to
display the tags from the child template.
In app.py, I associated the endpoint '/child' to the class ChildHanlder. When a curl GET method is performed, the get() method of the class ChildHandler is executed and renders its result, child_content (which is a dict) to the template child.html (I'm not getting ParentHandler's response in ChildHandler, so ChildHandler renders only child_content). The template child.html includes parent.html, so the behavior I'm expecting is child.html to display the HTML code from parent.html (with the values from the dict response provided by ParentHandler, not ChildHandler) and to render its own dict, child_content.
Unfortunately, when I try to perform the described process above, child.html doesn't find response from parent.py.
Here's a code snippet:
app.py
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r'/parent', ParentHandler),
(r'/child', ChildHandler)
]
jinja_load = Jinja2Loader(os.path.join(PATH, '/templates'))
settings = {
'template_path': os.path.join(PATH, '/templates')
'template_loader': jinja_load
}
tornado.web.Application.__init__(self, handlers, **settings)
parent.py
class ParentHandler(tornado.web.RequestHandler):
def get(self):
response = {"status": "200", "val": "some_value"}
try:
self.render("parent.html", response=response)
except:
self.write(response)
child.py
class ChildHandler(tornado.web.RequestHandler):
def get(self):
response = {"status": "200", "data": "some_data"}
try:
self.render("child.html", child_content=response)
except:
self.write(response)
parent.html
<div>
{% if response['status'] == 200 %}
{% set val1 = response.get('val', 0) %}
<i>{{ val1 }}</i>
{% endif %}
</div>
{% block child_content %}{% endblock %}
child.html
{% include 'parent.html' %}
{% from 'parent.html' import val1 %}
{% block child_content %}
<table>
{% for d in data %}
<tr>
<td>{{ d }}</td>
</tr>
{% endfor %}
{% endblock %}
But I end up with this error when I try to render child.html:
UndefinedError: 'response' is undefined
Can anyone help me please?
You just need to add the with keyword to the include statement, like so:
{% include 'parent.html' with var1=value1, var2=value2, ... %}
In your case
{% include 'parent.html' with response=responseValue %}
I ended up giving up my original idea and decided to go with #Strinnityk 's solution.
I changed the output of child.py and updated it with the output of parent.py.
That way, I don't even need to use variables in the child.html template.
Thanks again!
Related
Say we have a flask template as such:
{% extends "layout.html" %}
{% block body %}
<div class="container page-container">
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
</div>
{% endblock %}
We can render this template using the flask.render_template() function, and thus display a flash message using code like this:
from flask import flash, render_template
#timesheet_billing.route('/timesheet-billing')
def timesheet_billing_select_job():
jobs = get_open_jobs()
flash('A job was not selected!')
return render_template('timesheet_billing/index.html', jobs = jobs)
However if we render it using Jinja2's template class function jinja2.Template.render() with code like this:
from flask import flash
import jinja2
env = jinja2.Environment(loader=jinja2.PackageLoader('templates'))
index_temp = env.get_template('index.html')
#timesheet_billing.route('/timesheet-billing')
def timesheet_billing_select_job():
jobs = get_open_jobs()
flash('A job was not selected!')
return index_temp.render(jobs = jobs)
We get the following error when trying to load the page:
jinja2.exceptions.UndefinedError: 'get_flashed_messages' is undefined
What is the difference here? The answer in this question suggests that they should be the same. However it seems in one we do not have access to flask methods.
I believe the difference here is how flask.get_flashed_messages works.
This webpage https://flask.palletsprojects.com/en/1.1.x/templating/ explains the scope of jinja2 context variables:
The Jinja Context Behavior:
These variables are added to the context of variables, they are not global variables. The difference is that by default these will not show up in the context of imported templates.
This is partially caused by performance considerations, partially to
keep things explicit.
Here is where flask's render_template makes a difference, when comparing it to jinja2.render (from the question link you referred to):
def render_template(template_name_or_list, **context):
ctx = _app_ctx_stack.top
ctx.app.update_template_context(context)
return _render(ctx.app.jinja_env.get_or_select_template(template_name_or_list),
context, ctx.app)
def _render(template, context, app):
before_render_template.send(app, template=template, context=context)
rv = template.render(context)
template_rendered.send(app, template=template, context=context)
return rv
by calling render directly, you're missing the application context update call, which will inject all the information a template context processor will need to make functions (like get_flashed_messages, in this case) available to templates.
parent.sql
I am in parent {{ itable }}
child.sql
{% extends 'test.sql' %}
{% set itable = "test" %}
When I run the my python code
template = env.get_template('child.sql')
data = {
'impressions_table': 'test'
}
j = jinjasql.JinjaSql(env=env)
query, bind_params = j._prepare_query(
template,
data
)
My output is
I am in test %s
This is a simple example for something complex I am doing I am looking for a way to propagate variable values up to parents.
I have also tried using
include
but I get an error saying expected end got with.
That's correct - variables are not propagated to their parent scope. Blocks are however used to override behavior in parents:
parent template:
I am in parent {% block itable %}{% endblock %}
child template:
{% extends 'test.sql' %}
{% block itable %}test{% endblock %}
This also allows you to set a default value in the parent by having the default value inside the block: {% block itable %}default_value{% endblock %}
I want to pass data from models to template using CMS_plugins.py
I've made an app and standalone works. When i open link manualy
localhost:port/en/post_list.html all values are shown and works.
If i go on admin page and add plugin, values in mysql are stored but not presended in my template.html . I want to pass values in template.html
EDIT:
I manage to pass values to template. I edited cms_plugins.py
How can i hook data with "for" ?.
In code blow, nothing isnt shown in my template.html
<p>{{instance.firstline}}</p> ->>>this works
{ %block content %}
{{ instance.post.offer_option}}
<p>{{post.firstline}}</p>
<p>{{post.secline}}</p>
{% endfor %}
{% endblock}
if i change to :
{% for post in posts %}
<p>{{post.firstline}}</p>
<p>{{post.secline}}</p>
{% endfor %}
{% endblock}
In upper case if i run manualy url: localhost:port/en/pricing , I can see the result i want to be rendered in template.In template nothing isnt shown neither.
cms_plugins.py
class pricing(CMSPluginBase):
model = Post
name = _("pricing")
render_template = "post_list.html"
cache = False
def render(self, context, instance, placeholder):
context.update({'instance': instance})
return context
plugin_pool.register_plugin(pricing)
I want to loop data taken from database in rendered template. Using cms plugin.
I dont have problem looping data in html template. But if i use CMSPlugin to insert new app in placeholder, nothing shows.
If i run url localhost:port/test.html.I got input what i want. But rendered template doesnt loop data.
{% for post in posts %}
{{ post.firstoption }}
{% endfor %}
if I use code below, nothing shows in my rendered template. Values are passed in rendered template. Because, if i try {{instance.firstoption}} i get value shown in template. Problem is i cant loop data with tag instance.
{% for post in instance.posts %}
{{ post.firstoption }}
{% endfor %}
I also tried {% for post in instance.posts_set.all %}
and {% for post in instance.posts.all %}
cms_plugins.py
class pricing(CMSPluginBase):
model = mymodel
name = _("myplugin")
render_template = "template.html"
cache = False
def render(self, context, instance, placeholder):
context.update({'instance': instance})
return context
models.py
class mymodel(CMSPlugin):
firstoption = models.CharField(max_length=200)
def __str__(self):
return self.firstoption
It is probably because you need to call all on your posts
{% for post in instance.posts.all %}
{{ post.firstoption }}
{% endfor }
I am using Django's Template Fragment Caching so in a template.html file
{% extends 'base.html' %}
{% load cache %}
{% block content %}
{% cache 500 "myCacheKey" %}
My html here...
{% endcache %}
{% endblock %}
This is working fine - I can see it's getting cached and hit but the view is doing something expensive to provide data to this view and thats getting called every time.
In views.py
def index(request)
data = api.getSomeExpensiveData()
return render_to_response('template.html', {'data':data} )
So how do I tell if the cache is avail before the call to api.getSomeExpensiveData()?
I can't use cache.get('myCacheKey') as the cache isn't found - does it use some naming scheme and if so can I either use something like
cache.get(cache.getTemplateFragmentKey("myCacheKey"))
or
cache.getTemplateFragment("myCacheKey")
If you do not use that data in your view, something as simple as this might work:
def index(request)
get_data = api.getSomeExpensiveData
return render_to_response('template.html', {'get_data':get_data} )
In template
{% block content %}
{% cache 500 "myCacheKey" %}
{{ get_data.something }}
Or maybe
{% for something in get_data %}
{% endfor %}
{% endcache %}
{% endblock %}
Django template automatically calls all callable objects.
EDIT:
If you need to use get_data more than once in your template you'll need some wrapper. Something similar to this:
def index(request)
class get_data(object):
data = False
def __call__(self):
if not self.data:
self.data = api.getSomeExpensiveData()
return self.data
return render_to_response('template.html', {'get_data':get_data()} )
I found this SO - How do I access template cache?
And adapted it to
from django.utils.hashcompat import md5_constructor
from django.utils.http import urlquote
from django.core.cache import cache
def hasFragmentCache(key, variables = []):
hash = md5_constructor(u':'.join([urlquote(var) for var in variables]))
return cache.has_key(cache_key)
Edit - I've accepted skirmantas answer as whilst this does exactly as asked its the better approach as then the template and view are more loosly coupled. Using this method you need to know the name of each cache fragment and whats used where. A designer moves things around and it would fall over.