Coerce object into list with one element in django template - django

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.

Related

What's the most DRY way to disable a link to the current page?

In my Django web-app, I want to let users sort model objects by different parameters, which I achieve with URL parameters which tell the view which items should be loaded. Here's the Jinja/HTML snippet from the template:
<p><b><span class="text-info">sort by:</span></b>
latest_release |
alphabetically |
soonest release</p>
If the user is already sorting by latest_release (the first link), I want the link from it removed. However, I can't seem to find a way to do this in a DRY way.
You can define dict in your view with argument name - display name mapping:
mapping = {'': 'latest_release', 'name': 'alphabetically', 'next_release': 'soonest release'}
and pass it to context:
context['mapping'] = mapping
Now in template iterate over each pair from dict and show link only if sorted_by value not equal with key:
{% for k, v in mapping.items %}
{% if request.GET.sorted_by|default:"" != k %} {{ v }} |{% endif %}
{% endfor %}
To remove | delimiter after last link you can validate forloop.last status.

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

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

Django Templating: how to access properties of the first item in a list

Pretty simple. I have a Python list that I am passing to a Django template.
I can specifically access the first item in this list using
{{ thelist|first }}
However, I also want to access a property of that item... ideally you'd think it would look like this:
{{ thelist|first.propertyName }}
But alas, it does not.
Is there any template solution to this, or am I just going to find myself passing an extra template variable...
You can access any item in a list via its index number. In a template this works the same as any other property lookup:
{{ thelist.0.propertyName }}
You can combine the with template tag with the first template filter to access the property.
{% with thelist|first as first_object %}
{{ first_object.propertyname }}
{% endwith %}
If you're trying to access a manytomany field, remember to add all, so it will look like object.m2m_field.all.0.item_property
a potentially clearer answer/syntax for accessing a ManyToManyField property in an object list provided to the django template would look like this:
{{ object_list.0.m2m_fieldname.all.0.item_property }}