Django template access to nested data - django

This seems silly, but I don't understand how Django Templates access nested data in Contexts. I can access the values of dictionaries nested in the context data structure with the . notation -- {{ aDictionary.i_am_a_key }} works fine. But if I try to iterate over a list of keys and get their value from that same dictionary, I get nothing. So
{% for key in keys_list %}{{ aDictionary.key }}{% endfor}}
just generates blanks.
What am I missing here? Does Django not support key access to context dictionaries on the fly? Do I need to write a custom tag to do this?
EDIT
My examples assume these data structures:
aDictionary = {'i_am_a_key': 'all good', 'i_am_another_key': 'okay'}
keys_list = ['i_am_a_key', 'i_am_another_key']

This is a fundamental limitation of the Django templating language.
Three solutions:
Use {% for key,value in foo.items %} to get key and value.
Use Jinja2 -- an almost Django-like templating system.
User the expr djangosnippet to do the access math.

It's not the same question, but the answer is similar to #844746.
You end up with a filter which you can do...
{% load getattribute %}
{% for key in keys_list %}
{{ aDictionary|attr:key }}
{% endfor %}

This is a different approach, but based on what you want to accomplish this is the angle I'd take.
If you want to keep a subset of some dictionary and you want to iterate around it's values in some ordered fashion, I'd copy the element you're interested in into a SortedDict (django/utils/datastructures.py).
In my mind, stuff like this should live in the view (all of this is untested):
sorted_dict = SortedDict()
for key in orig_dict:
if interested(key):
sorted_dict[key] = orig_dict[val]
and the templates should just be very dumb:
{% for key, val in sorted_dict.items %}{{ val }}{% endfor}}

Related

Was wondering why iterating through a dictionary using .keys in django would not work?

I know that .items would be useful to grab the value, but wanted to see why this would not work?
Data:
...
city_data = {
'city': json_data['name'],
'country': json_data['sys']['country'],
'temp': json_data['main']['temp'],
'feels_like': json_data['main']['feels_like'],
'temp_max': json_data['main']['temp_max'],
'temp_min': json_data['main']['temp_min']
}
return render(request, ..., context={'city_data':city_data})
template:
...
{% for key in city_data.keys %}
<li>{{city_data.key}}</li>
{% endfor %}
...
I think that the reason that it doesn't work that way is because django will look at test.key and try to look up a string "key" as an actual key to the dictionary. There are a couple ways that you could do this. One way is you could define a custom template filter that would allow you to do it. I don't know much about custom filters so I can't say how specifically to do it. Another way though is to use city_data.items in your template instead like this:
{% for key,value in city_data.items %}
<li>{{ value }}</li>
{% endfor %}

Django taggit, listing count for each tag

Let's say I have three tags. I want to show how many objects are linked to it. Like so:
Apple (3)
Orange (0)
Banana (5)
How do I make it work the simplest way possible, preferably without creating new attributes in the database?
I'm guessing I'll need to iterate through all the tags, count them, and put both together inside a dictionary, right? Eg:
something = {'apple': X, 'orange': X, etc...
Then make that available in the context, so that it's accessible in the HTML template? Or is there a simpler way? Thank you!
I did this with an annotated queryset.
queryset = Tag.objects.all()
queryset2 = queryset.annotate(num_times=Count('taggit_taggeditem_items'))
You can then make a dictionary if you want to:
mydict = {}
for tag in querset2:
mydict[tag.name] = tag.num_times
Use django-taggit-templatetags2
Then add in your template:
{% load taggit_templatetags2_tags %}
{% get_taglist as tags for 'yourapp.yourmodel' %}
...
<ul>
{% for tag in tags %}
<li>{{tag}} ({{tag.num_times}})</li>
{% endfor %}
</ul>
...
Usually, the easiest way to do that is by querying the tagged model using aggregate, summing up the tags.

Coerce object into list with one element in django template

First off, I know this is bad practice but I'm constrained to only having access to the template layer of a Django project so here goes.
So say I have list1, and list2 which both contain story objects. I also have featured_story which is a single story object. In my template I can do teh following.
{% with list1|add:list2 as my_list %}
# my_list is now all elements from list1 and list2
{% with my_list|add:featured_story as final_list %}
# my_list has not changed
{% endwith %}
{% endwith %}
Is there a way to coerce featured_story into a list?
Again, I know I shouldn't be doing this and given the ability would take care of this in the view layer but I'm using the Ellington platform and do not have access to the appropriate code to be able to add this. Alternately I also do not have the ability to create a custom tag or filter that would facilitate this.

Django: How do I get the number of elements returned in a database call?

This seems to me like a very simple question, but I can't seem to find the answer.
All I need to do is determine the number of objects returned by a database query.
The specific circumstance is this: I have a model named Student. This model has a ManyToManyField member named courses_current, which relates to a table of Course models. When I pass my Student instance to a template, I want to be able to do something like the following (the syntax may not be exact, but you'll get the basic idea):
<div id="classes">
{% if student.classes_current.all.size == 0 %}
<h1> HEY! YOU AREN'T TAKING ANY CLASSES! REGISTER NOW!
{% else %}
Here are your courses:
<!-- ... -->
{% endif %}
</div>
Now, I'm fairly certain that X_set.all.size is not a real thing. In the manage.py shell I can just use len(student.classes_current.all()), but I don't know of any way to use built-in functions, and "dictionary-like objects" don't have .size() functions, so I'm at a loss. I'm sure there's a very simple solution (or at least I hope there is), but I can't seem to find it.
{{ student.classes_current.all.count }} but be warned that it doesn't fetch the objects so you will need to do a separate query if you want to loop over them.
If you need loop over the classes for tag has way to get what you need.
{% for cl in student.current_classes.all %}
{{ cl }}
{% empty %}
<h1>Hey! ...</h1>
{% endfor %}
Documentation https://docs.djangoproject.com/en/dev/ref/templates/builtins/?from=olddocs#for-empty

Django template: Ordering dictionary items for display

I am making a website that displays a user's chosen youtube videos. A user can enter a comment for each video.
I want to display (in this order):
User comment
video title
I have already made the view and have created the following list of dictionary items. Each one represents one video. I send this to my html page:
[
{"my_own_object": vid_obj1, "youtube_obj": obj1}
{"my_own_object": vid_obj2, "youtube_obj": obj2}
]
"youtube_obj" is the object supplied by youtube, which contains the url, title, rating, etc. "my_own_object" contains the user's comments as well as other information.
I iterate over the list and get one dictionary/video. That's fine. Then I need to display the video's information:
{% for key,value in list.items %}
{% if key = "my_own_object" %}
<div>
<p>{{value.user_comment}}</p>
</div>
{% endif %}
{% if key = "youtube_obj" %}
<div>
<p> {{value.media.title.text}}</p>
</div>
{% endif %}
{% endfor %}
This works, except that, because I cannot determine the dictionary order, I might end up with:
Video title
User comment
I thought I could get around this by assigning variables (and then printing the values in the proper order), and am still reeling from the fact that I cannot assign variables!
So, how can I get around this? Can I pluck the key/value that I need instead of iterating over the dictionary items - I tried looking for ways to do this, but no luck. Any other ideas? (I need to pass both video objects as I may need more information than comment and title, later.)
You can use dictionary keys directly:
{% for item in list %} {# PS: don't use list as a variable name #}
<p>{{item.my_own_object.user_comment}}</p>
<p>{{item.youtube_obj.media.title.text}}</p>
{% endfor %}
Just iterate twice. Once for the videos, and once again for the comments. Or, split them into their own dictionaries that are passed through to the template. That's probably a better option, as you avoid iterating twice over the dict. For very small dicts this will be no problem. For larger ones, it can be a problem.