Django template: Ordering dictionary items for display - django

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.

Related

Loop over a sorted dictionary a specific number of times in a django template

I have a sorted dictionary that contains sort options:
sort_options = SortedDict([
("importance" , ("Importance" , "warning-sign")),
("effort" , ("Effort" , "wrench" , "effort")),
("time_estimate" , ("Time Estimate" , "time")),
("date_last_completed" , ("Date Last Completed" , "calendar")),
])
I'm displaying these options in my template:
{% for key, icon in sort_options.items %}<!-- Sort Options -->
<a class="btn btn-info" href={{ request.path }}?key={{ key }}&orientation=desc><i class="icon-{{ icon.1 }} icon-large"></i></a>
{% endfor %}
I need to define the 4 sort options, but I only want to display the first 3 (the remaining options are used elsewhere). I also anticipate adding other sort options that I won't need to be displayed. I could write an if statement with a forloop counter to prevent the last option from displaying, but this seems wasteful.
I found this filter but I'm not sure how to combine it with the forloop that needs both the key and the icon data.
How can I write a django template for loop that runs on a dictionary and only loops X number of times?
Similar to Joe's answer, but there's actually a built-in filter slice that'll do this for you:
{% for key, icon in sort_options.items|slice:":3" %}
I think you could do this with a template filter. For example, in:
./mymodules/templatetags/mytags.py
#register.filter
def get_recent(object, token):
"""
Must pass a Option Dictionary
"""
return object.items()[:token]
And then in your template:
{% load mytags %}
{% for option in sort_options|get_recent:3 %}
key: {{ option.0 }}
value: {{ option.1 }}
{% endfor %}
I haven't had a chance to test the above code, but think the logic is sound. Let me know what you think.

Best way to slice a Django queryset without hitting the database more than once

I'm running a query to get the 5 latest News items. In my template, I want to display the first item in one location, then the remaining 4 further down the page.
In my template, I do something like this:
{% for n in news|slice:":1" %}
{{ n.headline }}
{% endfor %}
... more HTML ...
{% for n in news|slice:"1:" %}
{{ n.headline }}
{% endfor %}
When I look in the Debug Toolbar, this results in two queries to the database: one with LIMIT 1 and another with LIMIT 4 OFFSET 1, but otherwise the same. I appreciate this is Django's way of intelligently only requesting the stuff you actually use, but in this case it seems a little excessive. What's the best way to do this kind of thing?
Convert to a sequence in the view, then slice the sequence.
var = list(somequery[:5])
You just need to force the queryset to evaluate itself before the slice. This could be done as simply as calling len() on it in your view before passing it off to the context.
The Django docs have a complete list of everything that causes a queryset to evaluate. Just do something from that list and you're good.

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

What's the best way to allow an infinite amount of foreign keys to the same model?

I'm making a forum with django right now, and I want it so that anyone can comment on anyone else's comment. Right now I have a foreign key in my 'Comment' model that points back to itself so I can get the parent comment easily from any comment.
In my theory this worked great, because from any comment I could get all of its child comments, and then just keep branching down from there to get every single child comment. However I'm having trouble actually implementing this when it comes to getting the data from the view to the template.
I want it to be possible to have an infinite number of child comments, because who knows how long a discussion will last and I don't want to arbitrarily limit it. The problem I'm having is would you get all of those comments from the view to the template without losing their relationship to their parent comment?
Currently this is what the psuedocode for my code looks like:
#the view
def comment_page(request, forum, comment_id):
#this is the main comment that all others will stem from
main_comment = Comment.objects.get(id=comment_id)
children_comments = main_comment.comment_set.all()
#the template
{% for comment in children_comments %}
<p class='comment'>{{comment}}</p>
{% endfor %}
Obviously I'm not even trying to get all the child comments here, it just gets child comments of the very first post. What I don't understand is how can I then go through each of these child comments and then get all of theirs, and keep doing that for each new comment?
It makes the most sense to do it in the view since I am able to use Django's QuerySet API in there, but I don't see how I would be able to pass all of the comments to the template without losing their relationship to their parent. The only idea I can think of is to go through all of the comments in the view and build up a string of html that I just pass and simply display in the template, but that seems like a horrible idea because it'd be dealing with template related stuff in the view.
You might want to look into using a MPTT such as django-mptt
this can be implemented by a custom filter with an inclusion_tag that includes itself but causes a lots of queries to your db:
#register.inclusion_tag('children.html')
def comments_tree(comment):
children = comment.comment_set.all()
return {'children': children}
# children.html
<ul>
{% for child in children %}
<li> {{ child }}</li>
{% if child.comment_set.count > 0 %}
{% comments_tree child %}
{% endif %}
{% endfor %}
</ul>
# your template
{% comments_tree comment %}
This older question is probably of interest:
How can I render a tree structure (recursive) using a django template?
Edit: to future readers, don't do this as the inner for loop's comment variable does not substitute the outer comment variable during the loop execution, leading to infinite recursion. /Edit
If you need a recursive tree structure in your HTML page (i.e. a bunch of nested <div> tags), you can write a recursive "comment" template.
Sample: (untested)
{# comment.html #}
<p class='comment'>{{ comment.text }}</p>
{% if comment.children %}
{% for comment in comment.children %}
{% include "comment.html" %}
{% endfor %}
{% endfor %}
The for loop binds the comment template variable to each child before including itself.
Performance note: Unless your comment sets are often short, this will probably be very slow. I recommend that you make your comments non-editable and cache the result!
Alternate solution: If you don't need the recursive HTML <div> tags, you can write a generator that performs a pre-order traversal of the structure and yields (depth, comment) pairs. This would likely be far more efficient in rendering speed.

Django template access to nested data

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