If I return a Jinja2 template like so:
return render_response('home.htm', **context)
How do then get a list of the variables in context from within the template?
Technically, because context is not passed as a named dictionary, a little work is required to generate a list of the context variables from inside a template. It is possible though.
Define a Jinja context function to return the jinja2.Context object, which is essentially a dictionary of the global variables/functions
Make that function available in the global namespace; i.e. a jinja2.Environment or jinja2.Template globals dictionary
Optionally, filter objects from the context; for instance, use callable() to skip Jinja's default global helper functions (range, joiner, etc.). This may be done in the context function or the template; wherever it makes the most sense.
Example:
>>> import jinja2
>>>
>>> #jinja2.contextfunction
... def get_context(c):
... return c
...
>>> tmpl = """
... {% for key, value in context().items() %}
... {% if not callable(value) %}
... {{ key }}:{{ value }}
... {% endif %}
... {% endfor %}
... """
>>>
>>> template = jinja2.Template(tmpl)
>>> template.globals['context'] = get_context
>>> template.globals['callable'] = callable
>>>
>>> context = {'a': 1, 'b': 2, 'c': 3}
>>>
>>> print(template.render(**context))
a:1
c:3
b:2
[Alternately, call render_response with ('home.htm', context=context) to make the other solution work.]
Here's how to get #crewbum's answer working from a Flask app:
import jinja2
#jinja2.contextfunction
def get_context(c):
return c
app.jinja_env.globals['context'] = get_context
app.jinja_env.globals['callable'] = callable
I found #Garrett's answer to be a bit more complex than what I wanted. I found that I can easily inspect the context by adding a clone of the context to the context itself:
contextCopy = dict(context)
context['all'] = contextCopy
And then pretty printing that into my Jinja template with <pre>{{ all|pprint }}</pre>.
Related
I'm returning a record set from MongoDB, parsing this as JSON and pushing this into a view and attempting to access dictionary values of each record in a template. I can print the records (as an individual record), but I cannot access the structure of each record as a dictionary. How can I get at the values?
def index(request):
results = settings.vali_collection.find({'index': [] })
json_docs = [json.dumps(doc, default=json_util.default) for doc in results]
return render(request, 'db.html', {'results': json_docs[0:3]})
In my template:
{% for result in results %}
{{ result.name}}
{{ result.items.name}}
{% endfor %}
My JSON looks like:
{"name": "Travel & Leisure", .., ..}
I can print the records in my template with {{record}}, but how do I get at the record as a dictionary? What I have above in the template doesn't work and returns nothing. But when I use:
{% for result in results %}
{{ result}}
{% endfor %}
I can get the records printed out to screen in JSON format. If I print out json_docs I get the following:
['{"name": "random", "sector": "random"}', {"name": "random", "sector": "random"}', {"name": "random", "sector": "random"}']
If the json is a dictionary itself, you need to have nested loops. Something like the following:
{%for i in gme%}
{%for l, k in i.items%}
<p> {{l}} {{k}} </p>
{%endfor%}
{%endfor%}
gme looks like this:
gme = [{"sdfje": 'sdfs',"sdfds": "sdf"},...]
The output is:
sdfje sdfs
sdfds sdf
Use json.loads(...) instead of json.dumps.
json.dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, encoding="utf-8", default=None, sort_keys=False, **kw)
Serialize obj to a JSON formatted str
json.loads(s[, encoding[, cls[, object_hook[, parse_float[, parse_int[, parse_constant[, object_pairs_hook[, **kw]]]]]]]])
Deserialize s (a str or unicode instance containing a JSON document) to a Python object using this conversion table.
What you are doing is serializing python dictionary to JSON formatted str, so you get string instead of dictionary.
You need to deserialize and for that, you need json.loads
If you can't use json.loads, as you said in comments section, then the results variable is not a json string. Maybe it is what you are looking for? Try to debug and see what is inside results
If you're using PyMongo 2.8 find returns a Cursor that can be sliced. This should work:
db.test.find()[20:25]
And this should also work:
result = db.test.find()
...
sliced_result = result[20:25]
You don't need to transform the result to JSON, you can transform the Cursor to a list and pass that directly to the template, like that:
def index(request):
results = settings.vali_collection.find({'index': []})[:3]
return render(request, 'db.html', {'results': list(results)})
You need to use list(results) to force the Cursor to execute the query. In your template you'll have a list of dicts, so this should work:
{% for result in results %}
{{ result.name }}
{{ result.items.name }}
{% endfor %}
result should be a dict. The only problem I see is that items is a template function in Django, this might confuse people that are reading your template, and would prevent you from doing something like this:
{% for result in results %}
{% for attr_name, value in result.items %}
{{ attr_name }}: {{ value }}
{% endfor %}
{% endfor %}
The inner for would show every attribute, and their value, of a document in your MongoDB collection.
If you're using an older version of PyMongo that doesn't allow slicing, you may need to do something like this:
def index(request):
results = settings.vali_collection.find({'index': []}).limit(3)
return render(request, 'db.html', {'results': list(results)})
limit limits the number of results returned by the Cursor.
I believe result in the template isn't what you think it is. Let's examine as follows:
In function index(request):
results = settings.vali_collection.find({'index': [] }) returns a list of dictionary-like objects.
json_docs = [json.dumps(doc, default=json_util.default) for doc in results] returns a list of JSON strings (not dictionaries).
So later on when you iterate through the sublist json_docs[0:3], you are just iterating through a list of strings which is why you cannot reference the .name and .items properties.
It looks like what you actually want is a dict() like object for each result in your template. To do this, avoid dumping the dictionaries into JSON.
So instead of:
# WRONG:
json_docs = [json.dumps(doc, default=json_util.default) for doc in results]
return render(request, 'db.html', {'results': json_docs[0:3]})
# /WRONG
...just pass the result in directly:
results = settings.vali_collection.find({'index': [] })
results = list(results) # This may or may not be necessary, depending on what mongo client you're using
return render(request, 'db.html', {'results' : results[:3])
Then in your template, when you iterate through results, each result should be a dictionary-like object which you can use result.name or result.items on.
BTW, the result.items.name reference in your code looks a bit weird to me (like it would return an error), but it's hard for me to debug without knowing what each record looks like.
Change your view like this
def index(request):
results = settings.vali_collection.find({'index': [] })
return render(request, 'db.html', json.dumps({"results":results[0:3]}))
You can now use your for loop to render
>>> f = ContactForm({'subject': 'Hello', 'message': ''})
>>> f.errors['message']
>>> [u'This field is required.']
>>> f['message'].errors
>>> [u'This field is required.']
What's the difference b/w the 3rd & 5th line ? Do they have different purposes ?
When you access f['message'].errors, you are using the BoundField.errors property.
If you look at the source, you can see that BoundField.errors tries to fetch the error list from the form's errors if it exists.
#property
def errors(self):
"""
Returns an ErrorList for this field. Returns an empty ErrorList
if there are none.
"""
return self.form.errors.get(self.name, self.form.error_class())
In the case that there are no errors for the field, the BoundField.error property returns an empty error list, so the behaviour is slightly different.
>>> f = ContactForm({'subject': 'Hello', 'message': 'world!'})
>>> f.errors
{}
>>> f.errors['message']
KeyError
>>> f['message'].errors
>>> []
Having this errors property on the field makes it easy to loop through a form's fields in the template, and display the errors.
{% for field in form %}
{{ field }}
{{ field.errors }}
{% endfor %}
I have a function in my Django app that has a dictionary containing several long strings. When that function is called, those strings are formatted and the dictionary returned.
For example:
def my_strings(foo, bar, baz):
return = {
'string1': 'a really long string! %s' % foo,
'string2': 'another long one. %s %s' % (foo, bar),
'string3': 'yet another! %s %s %s' % (foo, bar, baz),
}
However, having all these long strings, stored in a Python file is ugly and it seems there should be a cleaner way to do it.
I'd toyed with putting them in a template file and doing some rendering, like so:
mytemplate.txt
{% if string1 %}
a really long string! {{ foo }}
{% endif %}
{% if string2 %}
another long one. {{ foo }} {{ bar }}
{% endif %}
{% if string3 %}
yet another! {{ foo }} {{ bar }} {{ baz }}
{% endif %}
Python
def my_strings(foo, bar, baz):
arg_dict = {
'foo': foo,
'bar': bar,
'baz': baz,
}
my_strings = {}
string_names = ['string1', 'string2', 'string3']
for s in string_names:
arg_dict[s] = True
my_strings[s] = render_to_string('mytemplate.txt', arg_dict).strip()
del arg_dict[s]
return my_strings
But that seems a little too roundabout, and most likely less performant.
Is there a preferred way in Python, or Django specifically, to handle storing and formatting long string assets?
Some extra context: the string assets are either HTML or plaintext. The dictionary is eventually iterated over and all instances of each key in yet another string are replaced with its string value.
I would treat this as something similar to the way many i8n compilation code does it.
Store the long strings in a dictionary in a separate file. Import that dictionary and then format the desired string in your code.
You could place the strings in a module like longstrings.py
STRING1 = ("Bla bla"
" more text"
" even more text")
STRING2 = ("Bla bla"
" more text"
" even more text")
and then
from longstrings import *
def my_strings(foo, bar, baz):
return = {
'string1': STRING1 % foo,
'string2': STRING2 % (foo, bar),
'string3': STRING2 % (foo, bar, baz),
}
You could also create a class for your long strings, and they would be stored in the DB. This would allow you to access them and modify them as any other object. For example, you could use the Django admin tool if you want to modify a specific string.
Let's say I have an string variable called *magic_string* which value is set to "This product name is {{ product.name }}" and it's avaible at django template. Is it ever possible to parse that variable to show me "This product name is Samsung GT-8500" instead (assuming that name of the product is "GT-8500" and variable {{ product.name }} is avaible at the same template) ?
I was trying to do something like this, but it doesn't work (honestly ? Im not surprised):
{{ magic_string|safe }}
Any ideas/suggestions about my problem ?
Write custom template tag and render that variable as a template.
For example look at how "ssi" tag is written.
On the other hand, can you render this string in your view? Anyway here is an untested version of that tag:
#register.tag
def render_string(parser, token):
bits = token.contents.split()
if len(bits) != 2:
raise TemplateSyntaxError("...")
return RenderStringNode(bits[1])
class RenderStringNode(Node):
def __init__(self, varname):
self.varname = varname
def render(self, context):
var = context.get(self.varname, "")
return Template(var).render(context)
Perhaps I dont understand your question but what about,
from django.template import Context, Template
>>> t = Template("This product name is {{ product.name }}")
>>> c = Context({"product.name": " Samsung GT-8500"})
>>> t.render(c)
Regards.
In Jinja2, is it possible to have a Node from the AST render after all include statements have completed?
This is a key piece of a solution to a bigger puzzle.
Example code:
x.py
from jinja2 import nodes, Environment, FileSystemLoader
from jinja2.ext import Extension
class XExtension(Extension):
tags = set(['x', 'get_x'])
def __init__(self, environment):
super(XExtension, self).__init__(environment)
environment.extend(
x = 0,
)
def parse(self, parser):
tag = parser.stream.next()
return getattr(self, "_%s" % str(tag))(parser, tag)
def _get_x(self, parser, tag):
""" Return the output """
return nodes.Const(self.environment.x)
def _x(self, parser, tag):
""" Add an x """
self.environment.x += 1
return nodes.Const('<!-- Adding an X -->')
env = Environment(
loader = FileSystemLoader('.'),
extensions = [XExtension],
)
template = env.get_template('x.html')
print template.render()
x.html
Xx {% x %} Xx {% x %}
{% include "y.html" %}
Xs xes: {% get_x %}
y.html
Yx {% x %}
Ys xes: {% get_x %}
The output is
Xx <!-- Adding an X --> Xx <!-- Adding an X -->
Yx <!-- Adding an X -->
Ys xes:3
Xs xes 2
How may it be possible to have Xs xes count the X's in y.html, which is the behaviour I desire and expect?
In other words, is there a way to delay the parsing or flattening to text returned from the _get_x() in x.html?
Thank you very much for reading.
Kind regards,
Brian
Jinja2 does streaming of template data. The template is evaluated instruction for instruction into a stream of smaller strings that gets concatenated into an actual unicode string by the render() method. However you can also get hold of the stream by calling into generate() instead of render().
With some in-band signalling and postprocessing of the stream one might probably be able to implement something like that, but it's against the way of how Jinja2 was designed so it might break horribly and is totally unsupported.