Accessing flask methods in Jinaj2.Template.render() - python

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.

Related

Creating a custom template tag to replace the for loop - Django

I am trying to simplify my code by creating a custom template tag for a 'for loop' that use frequently on my Django web application. I thought it would be a simple straight process, but something isn't working right... I can use some assistance in catching my error.
Here is my code.
views.py
class ArticleView(DetailView):
model = Articles
def get_context_data(self, **kwargs):
context = super(ArticleView, self).get_context_data(**kwargs)
context['s_terms'] = scientific_terms.objects.all()
return context
template tag
#register.filter(name='term')
def term(value):
{% for term in s_terms %}
{{ term.short_description }}
{% endfor %}
template.html
{% Neurons|term %}
Thank you for your assistance, in advance.
You are mixing Python code with the Django Template Language. The template tags are plain Python code, as they are defined inside a Python module. A working example would be:
#register.filter(name='term')
def term(terms):
output = ''
for term in terms:
output = '{0} {1}'.format(output, term.short_description)
return output
Then you could use it like this:
{{ s_terms|term }}
Maybe what you want is simply to create a reusable Django template.
For example, create a new template named terms.html:
templates/terms.html
{% for term in terms %}
<p>{{ term.short_description }}</p>
{% endfor %}
Then, in another template, you could include this partial template:
templates/index.html (name is just an example)
{% extends 'base.html' %}
{% block content %}
<h1>My application</h1>
{% include 'terms.html' with terms=s_terms %}
{% endblock %}

How to pass variables from one template to another in Jinja2

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!

simple loop in rendered template django cms plugin

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 }

{% something %} syntax in django tutorial

In the Django tutorial (part 3), they have the following syntax for a template:
{% if latest_poll_list %}
<ul>
{% for poll in latest_poll_list %}
<li>{{ poll.question }}</li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
Can someone explain exactly hwo the {% something %} syntax is supposed to work? Thank you for your help!
They are template-tags. In the relevant part of the actual Python-code the tutorial does this:
from django.http import HttpResponse
from django.template import RequestContext, loader
from polls.models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = RequestContext(request, {
'latest_question_list': latest_question_list,
})
return HttpResponse(template.render(context))
This is the view. In this view they populate latest_question_list with the five newest questions from the database. Then the view is told to render the output with these values.
The view then loads the template (the code you posted) and replaces the tags/placeholders with actual data from the corresponding object.
The template-tags should be pretty self-explainable. The first line checks if there are any questions provided. If it is, loop them and {{ poll.id }} etc will then be replaced by the actual data from the query.
This is more or less the "core" of this type of design. The idea is to separate the logic and the markup as much as possible. You should have all your code in the views and just the plain markup and template-tags in the template-files. It provides a much cleaner environment and makes it much easier to maintain.

Django - how to tell if a template fragment is already cached?

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.

Categories