Jinja2 Array Iteration Problems - python

I am getting some unusual behaviour with Jinja2 and iterating through an array which I can't quite put my finger on. I am passing a dictionary into Jinja2, one of the values of the dictionary is an array. A simple example of this works okay.
mydict = {
'testvar1': 'this is a test var 1',
'testvar2': 'this is a test var 2',
'testvar3': ['test','test','test','test','test'],
}
This is my Jinja template
{{ testvar1 }}
{{ testvar2 }}
{% for var in testvar3 %}
{{ var }}
{% endfor %}
Now this actually works, I get the below output
this is a testvar1
this is a testvar2
test
test
test
test
test
test
However, I am generating a dictionary using the CSV import utility. Now I am aware that this tool imports all values as strings, so I am separating the 'array field' with dashes so I can split them later on.
input_file = csv.DictReader(open("./my_path/import.csv"))
for row in input_file:
for key, value in row.iteritems():
if '-' in value:
value = value.split('-')
When I print both the array value from the dictionary created manually and the dictionary created by the csv import they look identical.
['test','test','test','test','test']
However when I generate the configuration using the dictonary populated by the csv import, the output I get is completely different.
testvar1
testvar2
t
e
s
t
t
e
s
t
And so forth...
Now it seems it is iterating over the values as if is were a simple string value, printing one character at a time for each iteration of the loop.
But it works perfectly when making the dictionary manually.
Any ideas? If there is a cleaner way to do this I'm all ears.
Thanks in advance.

My Problem was I had the wrong JSON syntax in my database.
{
"tags" : "[my, wrong, array]"
}
ended up beeing interpreted as unicode object from python which explains why iterating over it was iterating over every character.
the right syntax is:
{
"tags": [
"tech",
"python",
"flask"
]
}

Related

Can I use a variable as index in for loop in python?

I have this for loop on my python template to fulfill an array of values:
labels: [{% for bftimes in dataBF.buffers.0.times %} "{{ bftimes }}", {% endfor %}]
I would like to know if I can use an int variable as an index instead writing it directly, as seen on the code above.
I need to use the index of the selected value of a dropdown:
//returns the index of the selected value
document.getElementById("buffer").selectedIndex
The question needs more context to help us understand your goal. It seems you are mixing python with javascript data structures.
My general recommendation is that you first prepare the python data structure to what you will need and then convert it to json. Looping within Django template language should be used only on simple cases.
If you use django>=2.1 you can use json-script template tag
{{ data|json_script:"my-data" }}
https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#json-script
If not, you can use
# views.py
import json
def foo(request):
dataBF = {"a": [{"c": [1,2,3,1,1]},{"d": [1,2,3,1,1]}]}
# here you can manipulate the data accordingly to what you need
data = json.dumps(dataBF)
return render(request, "index.html", context={"data": data})
On the template side
<script>
const datajs = {{ data | safe }};
</script>
The datajs is a javascript object that you can work with.
I've made an example that you can check https://repl.it/#IvanPereira/python-to-javascript-django-template
You can do this in 2 ways.
Store all values in a list, which JavaScript will consider as json array, and loop over using JavaScript itself. This way you won't be able to update records from server and all values that has to be looped over should be pre-fetched.
You can use AJAX call to pass the selected index from JavaScript and return the new array and update that in the template using JavaScript itself.

Dynamic HTML content and Python variables with Flask

On the user end, say I have people selecting school records through a form to display in a table below.
For instance, they choose their name from a list of names, and that pulls their records from a database.
Dependent on their class year, they may have 1, 2, 3, or 4 years of records, so the data pulled will always look different.
After they submit the form, their records are stored in a variable containing all their records, and then each record is broken down into subtypes, for instance, all records in the English department. Those subtypes are stored in other lists--so there's a list for all English records. Let's call that variable english_records. I want to use these subtype variables to be able to present only the data users want to see, and to present all data in that particular list.
So using Flask's render_template function, I'm trying to send each of these records to an html template that will create a table cell for each record.
What I've been trying (that hasn't worked so far) is something like:
Python:
i = 0
def index():
for e in english_records:
english_records = [
{
'english': english_records[i]
}
]
i = i + 1
return render_template("index.html",
english_records = english_records)
And in HTML:
...table above...
{% for record in english_records %}
<td>
{{record.english}}
</td>
{% endfor %}
...table continues...
Thus far I've been getting table cells created for each record, but the records not being passed through. Anyone know how to do this?
Is there a reason why you're pre-processing the data? What does english_records look like? If my hunch is correct, then you shouldn't actually need the for loop at all.
There's also the issue that you're overwriting the initial english_records variable with english_records within the for loop, so the assignment, while legal in terms of syntax, is logically nonsensical.
Another issue is that, depending on the actual type of the first english_records, you shouldn't need to use a counter: if english_records is a list, then it'll contain the value you're looking for. If english_records is a dict, then enumerating it might look like for key, val in english_records.iteritems().
for e in english_records:
english_records = [
{
'english': english_records[i]
}
]
i = i + 1
This loop here is creating a list , but the members of those list do not have 'english' key ,
so in the template, the loop is executed, but there is no value for {{record.english}} , hence it is ignored.
All you will get is table rows without any data.

Get translatable text from an external source into catalog

Possibly i am overlooking an obvious solution or thinking the wrong way...
I have a limited amount of text, words in a database, that I want to display translated to users in a flask/jinja/babel webapp. eg. "running" is a possible value of an "activity" column and that should be "laufen" for my german users.
Words in templates and code are extracted and put into the catalog, but how do i get additional words into the catalog? Is there a simple text file extractor?
The only thing i could think of is, just create a .py file and put lots of _('...') lines in them, but that feels just wrong... is it?
I created a messages.txt with my "words" like gettext function calls:
_('cycling')
_('running')
and added it to my babel.cfg as python source:
[python: messages.txt]
plain, simple, stupid, but works.
First, start with http://flask.pocoo.org/snippets/4/.
Secondly, you need to store these 'limited' values as integers or enums in database and then create the lookup table for all these enums in code (so Babel knows about them):
i18n_val = {0: _('running'), ...}
# Or multi-level dict with different categories:
i18n_all = {
'activity': {
0: _('running'), ...
'foo': {
0: _('bar..'), ...
}
}
And accessing the translated string from template is now as simple as:
{{ i18n_val[obj.activity] }}
{{ i18n_all['activity'][obj.activity] }}
In order to make the i18n_val and i18n_all variables available for all the templates, just register them with context processors.

Django template can't loop defaultdict

import collections
data = [
{'firstname': 'John', 'lastname': 'Smith'},
{'firstname': 'Samantha', 'lastname': 'Smith'},
{'firstname': 'shawn', 'lastname': 'Spencer'},
]
new_data = collections.defaultdict(list)
for d in data:
new_data[d['lastname']].append(d['firstname'])
print new_data
Here's the output:
defaultdict(<type 'list'>, {'Smith': ['John', 'Samantha'], 'Spencer': ['shawn']})
and here's the template:
{% for lastname, firstname in data.items %}
<h1> {{ lastname }} </h1>
<p> {{ firstname|join:", " }} </p>
{% endfor %}
But the loop in my template doesn't work. Nothing shows up. It doesn't even give me an error. How can i fix this? It's supposed to show the lastname along with the firstname, something like this:
<h1> Smith </h1>
<p> John, Samantha </p>
<h1> Spencer </h1>
<p> shawn </p>
You can avoid the copy to a new dict by disabling the defaulting feature of defaultdict once you are done inserting new values:
new_data.default_factory = None
Explanation
The template variable resolution algorithm in Django will attempt to resolve new_data.items as new_data['items'] first, which resolves to an empty list when using defaultdict(list).
To disable the defaulting to an empty list and have Django fail on new_data['items'] then continue the resolution attempts until calling new_data.items(), the default_factory attribute of defaultdict can be set to None.
try:
dict(new_data)
and in Python 2 it is better to use iteritems instead of items :)
Since the "problem" still exist years later and is inherint to the way Django templates work, I prefer writing a new answer giving the full details of why this behaviour is kept as-is.
How-to fix the bug
First, the solution is to cast the defaultdict into a dict before passing it to the template context:
context = {
'data': dict(new_data)
}
You should not use defaultdict objects in template context in Django.
But why?
The reason behind this "bug" is detailed in the following Django issue #16335:
Indeed, it boils down to the fact that the template language uses the same syntax for dictionary and attribute lookups.
... and from the docs:
Dictionary lookup, attribute lookup and list-index lookups are implemented with a dot notation. [...] If a variable resolves to a callable, the template system will call it with no arguments and use its result instead of the callable.
When Django resolve your template expression it will try first data['items']. BUT, this is a valid expression, which will automatically creates a new entry items in your defaultdict data, initialized with an empty list (in the original author case) and returns the list created (empty).
The intented action would be to call the method items with no arguments of the instance data (in short: data.items()), but since data['items'] was a valid expression, Django stop there and gets the empty list just created.
If you try the same code but with data = defaultdict(int), you would get a TypeError: 'int' object is not iterable, because Django won't be able to iterate over the "0" value returned by the creation of the new entry of the defaultdict.

Displaying dictionary value in django template

All,
I have the following in my views.py
def getgradeform(request):
id1=request.user.get_pf().id
sc=Sc.objects.filter(id=id1)
logging.debug(sc)
logging.debug("++++")
dict={}
dict.update({'sc': sc})
return render_to_response('content/add.html',dict)
Logging.debug gives an output as [<sc: Robert>]
My question is that how do i display Robert in the template .
I have tried the following in the template:<input type ="text" value={{sc}}/> //This gives me the dictionary itself
<input type ="text" value={{dict.sc}}/> //This also doesnt work.
Thanks......
If you want any value in a dictionary, you have to do it on the way
dict.key
(On python you'll write it as dict['key'])
So, to present the value stored with key 'name'
{{ sc.name }}
Anyway, I think this is not you're case. I think you're not seeing a dictionary, but an object defined from models (as is a entry on the database).
You're storing in dict (don't call that value as dict , you're masking a keyword) a key 'sc' with value variable sc, which is returned from a model. I'm having to guess, because I don't know how this model is. Maybe 'Robert' is stored in the attribute name, id or something similar?
You need to show then the proper attribute, something like
{{ sc.name }}
{{ sc.id }}

Categories

Resources