Django template can't loop defaultdict - python

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.

Related

Django: How to Convert QuerySet into String

I'm using Python 3 and trying to convert a QuerySet into human-readable text. I have a line like this:
top_post = Post.objects.filter(category='1')[:1]
That prints like this:
<QuerySet [<Post: Test Post 1>]>
What makes me scratch my head is a similar QuerySet successfully converts when displayed via a template:
latest = Post.objects.order_by('-published_date')[:5]
"Latest" uses a for...loop in the template:
{% for latest_posts in latest %}
<h1>{{ latest_posts }}</h1>
While "top_post" displays only a blank:
<h1>{{ top_post }}</h1>
Anyone see what's missing?
{{top_post}} is a query set (slicing a queryset also produces a queryset), so it should display that. When you loop over the queryset, as you do over latest, you display the objects inside.
{{top_post.0}} would give you the object top_post contains.
Alternatively you can use
top_post = Post.objects.filter(category='1').first()
to get the object directly.
You should implement the __str__ method in the Post model where you define what to print.
Moreover you should .get instead of .filter if you expect one object to be returned.
to solve it you can use nested loops (A loop in other loop)
Convert it into list
def
return(Post.objects.filter(category='1').first)
If you are using filter then I am assuming you want more than one value else use the get method.
You can convert the queryset into a list and then play around the list.
top_post = list(Post.objects.filter(category='1')[:1])[0]
Now top_post will give the first object in the list.

Jinja2 Array Iteration Problems

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"
]
}

Why can't iterate through this list in a Django template

I have a variable from my view that is the output of a Model.objects.all() call on that Model. I'm passing it to my template in my view, and I'm trying to iterate over it in the template. I can access the first element of it simply by this line of code. 'code' is the name of a field in my django model. This line does print the first element's 'code' attribute correctly.
{{ var_name.0.code }}
However, when I try to iterate over var_name in a template for loop, nothing shows up. I tried the following code:
{% for single_var in var_name %}
{{ single_var.code }}
{% endfor %}
This isn't actually what I want to do in the for loop, but getting this to work will let me do what I need in the template. It may be noteworthy to add that at the moment this list has only one element in it.
This is for a work project, so that's why I changed the variable names to something generic.
I found that changing the name of single_var to something without an underscore seemed to fix it. This doesn't make a lot of sense to me because the Django template language documentation states the following:
Variable names consist of any combination of alphanumeric characters and the underscore ("_").
Does anyone know why this seemed to fix the problem?

What is the difference between .get() and .fetch(1)

I have written an app and part of it is uses a URL parser to get certain data in a ReST type manner. So if you put /foo/bar as the path it will find all the bar items and if you put /foo it will return all items below foo
So my app has a query like
data = Paths.all().filter('path =', self.request.path).get()
Which works brilliantly. Now I want to send this to the UI using templates
{% for datum in data %}
{{ datum.title }}
{{ datum.content }}
</div>
{% endfor %}
When I do this I get data is not iterable error. So I updated the Django to {% for datum in data.all %} which now appears to pull more data than I was giving it somehow. It shows all data in the datastore which is not ideal. So I removed the .all from the Django and changed the datastore query to
data = Paths.all().filter('path =', self.request.path).fetch(1)
which now works as I intended. In the documentation it says
The db.get() function fetches an
entity from the datastore for a Key
(or list of Keys).
So my question is why can I iterate over a query when it returns with fetch() but can't with get(). Where has my understanding gone wrong?
You're looking at the docs for the wrong get() - you want the get() method on the Query object. In a nutshell, .fetch() always returns a list, while .get() returns the first result, or None if there are no results.
get() requires (I think) that there be exactly one element, and returns it, while fetch() returns a _list_ of the first _n_ elements, where n happens to be 1 in this case.

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