Grouping of search results in a django template - python

I have a search results page in django. The results list a video title, video description and some meta data. It also includes the topic the video is in.
Instead of returning a full list of videos, I would like to try grouping my results by topic. Then, within each group, only show the top 1-3, with a link to "view all x videos in y topic" The determining factor of whether to show 1, 2, or 3 in the group would be a calculation based on the total number. So if there are under 10, only show 1 video in that grouping. If there are between 10-30 in the group, show 2 videos. If 30 or more in that grouping, show 3. (Determining 1-3 might not be necessary, I would be ok doing top 3)
My code is currently pretty simple to just return results. But I can't figure out how to get the groupings set up within django's template language. I guess I could do this in my view but hoping there's some simple filters I'm not aware of to help and do it in the template. Thanks for any advice.. Here's my current code:
<h4>Search Results</h4>
<p>{{ res.hits.total }} matches found for <span id="qresult">{% if q %}'{{ q }}'{% else %}all{% endif %}</span></p>
</div>
<ul class="search-list" id="search-results">
{% if res.hits.total > 0 %}
{% for h in reshh %}
{% with h|get_underscore_value:"_source" as source %}
<li><a href='/video/{{ source.slug }}'>{{ source.title }}</a>
<p>{{ source.description }}</p>
<span class="sr-meta" style="">metadata</span>
<p>(topic: {{ source.topic_name }})</p></li>
{% endwith %}
{% endfor %}
{% endif %}
</ul>
btw it's pulling from elasticsearch. res.hits.hits._source is where my results are contained. The "get_underscore_value" is just a custom filter to get around django complaining that my variable begins with an underscore...

Check out the regroup templatetag: https://docs.djangoproject.com/en/dev/ref/templates/builtins/#regroup

Related

TemplateSyntaxError: 'for' statements should use the format 'for x in y' [duplicate]

With this code:
{% for o in [1,2,3] %}
<div class="{% cycle 'row1' 'row2' %}">
{% cycle 'row1' 'row2' %}
</div>
{% endfor %}
I get a TemplateSyntaxError:
Could not parse the remainder: '[1,2,3]' from '[1,2,3]'
Is there a way of building a list in a template?
We can use split method on str object :
page.html :
{% with '1 2 3' as list %}
{% for i in list.split %}
{{ i }}<br>
{% endfor %}
{% endwith %}
Results :
1
2
3
You can do it via cunning use of the make_list filter, but it's probably a bad idea:
{% for o in "123"|make_list %}
<div class="{% cycle 'row1' 'row2' %}">
{% cycle 'row1' 'row2' %}
</div>
{% endfor %}
p.s. You don't seem to be using o anywhere, so I'm not sure what you're trying to do.
I made this template tag to achieve this goal.
from django import template
register = template.Library()
# use #register.assignment_tag
# only when you're working with django version lower than 1.9
#register.simple_tag
def to_list(*args):
return args
to use it in template:
{% load your_template_tag_file %}
{% to_list 1 2 3 4 5 "yes" as my_list %}
{% for i in my_list %}
{{ i }}
{% endfor %}
Reference here:
Django simple tags
The other answers here look like the ticket (at least for what I wanted), so I'll provide an answer as to WHY you might want to do something like this (and perhaps there's a better answer for my case than what's been provided):
I came across this question looking for a way to build 3 very similar, but not identical buttons using Bootstrap. One button might look like
<div class="btn-group">
<a class="btn btn-primary dropdown-toggle" data-toggle="dropdown" href="#">
Modality
<span class="caret"></span>
</a>
<ul class="dropdown-menu" id="Modality">
<li>Action</li>
</ul>
</div>
where the difference between buttons is limited to the text of the button (Modality, on its own line above) and the contents of the pertaining to the button, which we'll assume is filled dynamically by JS (referencing id="Modality").
If I need to make 10 of these, copy/pasting the HTML seems dumb and tedious, especially if I want to change anything about my button after the fact (like making all of them split-drop-downs) and it goes against DRY.
So, instead, in the template I could do something like
{% with 'Modality Otherbutton Thirdbutton' as list %}
{% for i in list.split %}
<!-- copy/paste above code with Modality replaced by {{ i }} -->
{% endfor %}
{% endwith %}
Now, granted, in this particular case the buttons add functionality to some related data grid, so the button names could be dynamically filled from django model-sourced data as well, but I'm not at that stage in my design right now, and you can see where this sort of functionality is desirable to maintain DRY.
The simplest is to do
{% for x in "123" %}
drodger is correct, you can't do that in the deliberately-crippled Django template lanuage. Either pass in the list as a context variable when you invoke the template or try a template tag like expr. Then you can say {% expr [1,2,3] as my_list %} and then use my_list in your for loop.
This maybe an inspiration. Use the buildin filter add.
{{ first|add:second }}
first is [1, 2, 3] and second is [4, 5, 6], then the output will be [1, 2, 3, 4, 5, 6].
This filter will first try to coerce both values to integers.
If this fails, it'll attempt to add the values together anyway.
This will work on some data types (strings, list, etc.) and fail on others.
If it fails, the result will be an empty string.
The official specification,https://docs.djangoproject.com/zh-hans/2.0/ref/templates/builtins/#built-in-filter-reference

Filter or split a list in Jinja template

I'm working with Jinja templates in Flask on Python and trying to achieve the following.
I have a list of items ('trainings') that have a certain start date. I'm looping through them succesfully and got the order sorted on date. I'd love to add sub headers that divide the long list of trainings with sub headers, e.g. "this week", "next week", "later". How would you go about this? I was thinking either
Add something in Jinja template that would look at the date and group by these dates
Add a method in the python class for trainings that tells the template which grouping it belongs (e.g. "this week") to, but how do i then use that to sort and add the sub headers?
Many thanks!
Scott
{% for training in trainings|sort(attribute="starts_at") %}
<div class="card">
<div class="card-body">
<h5 class="card-title">{{training.starts_at.strftime('%A, %d %b')}} - {{training.title}}</h5>
<p class="card-text">Short description about the training</p>
See information
</div>
</div>
<br />
{% endfor %}
Think I found it, you can use groupby in Jinja, see below for an example. Could also use this more customized if I add a class method that returns custom grouping (for example: "this week", "next 4 weeks", "later")
{% for year, year_group in trainings|groupby('starts_at.year') %}
{% for month, list in year_group|groupby('starts_at.month') %}
<b>{{ month }} {{ year }}</b><br />
{% for training in list %}
{% endfor %}
{% endfor %}
{% endfor %}
source: Jinja2 Group by Month/year

slice requires 2 arguments, 1 provided

I'm trying to add some values from another list synchronously in the for loop of django ’s template. When I use a given number everything works fine, but when I replace it to {{forloop.counter}} it reports an error slice requires 2 arguments, 1 provided
I Googled it and the suggestion is that there should be no spaces in the code, but there are no spaces in my code, here the code in question.
{% for i in invtypes %}
<li>
...
<p>{{data|slice:":{{forloop.counter}}"}}</p>
</li>
{% endfor %}
The {{data}} is a list of extra data for invtypes so they have same length and sort。
I added a colon after slice to fix the error.
Before:
{{ post.body|slice":200" }}
After:
{{ post.body|slice:":200" }}
The problem is that in the line {{data|slice:":{{forloop.counter}}"}} the ":{{forloop.counter}}" would not work as you expect as if it were performing string interpolation. What can be done to get around this is to use the with template tag along with the add and stringformat template filters to get the required value as a template variable before you try to render that:
{% for i in invtypes %}
<li>
...
{% with str_counter=forloop.counter|stringformat:"i" %}
{% with slice_var=":"|add:str_counter %}
<p>{{ data|slice:slice_var }}</p>
{% endwith %}
{% endwith %}
</li>
{% endfor %}
One should also note that a need to do this implies that the view is not supplying the data to the template in an efficient manner, ideally the template should not be doing all of this manipulation with data. It can be seen that invtypes and data have some type of relationship but the context given by the view does not convey that at all.
I had the same problem and the solutions seems to be spaces indeed.
Try:
{% for i in invtypes %}
<li>
...
<p>{{ data|slice:":{{ forloop.counter }}" }}</p>
</li>
{% endfor %}
and see how that goes.
I found the correct solution.
(Wrong) =>
{{data|slice: ":{{forloop.counter}}"}}
[don't add a space after slice's colon.
(Right) =>
{{data|slice:":{{forloop.counter}}"}}
If you add a space after slice's colon, it takes only the part which comes before that space and considered it as one argument and second one will be considered as missing.

Issues linking data in Flask / MongoDB

New to the whole coding thing, currently doing a course in Python/Flask and giving it my best shot.
I have a MongoDB Atlas DB, with 2 collections in it for drinks.
The structure looks like the below. The values in the array are the _id values of the ingredients in another collection.
_id:5e0f54d31c9d44000015483f
drinkName:"Margarita"
Ingredients:["5e13274e1c9d440000490ed8","5e1327571c9d440000490ed9","5e1327621c9d440000490eda","5e13276b1c9d440000490edb"]
And another collection with the below
_id:5e1327261c9d440000490ed7
ingredientName:"Slice Lemon"
My problem is I'm trying to render the results of the array in Flask on a page. But I can't seem to look up the object id's in the ingredients table, and display the ingredientName of the object.
I can display the array fine, but can't then go to the ingredients collection to get the name.
This is what I have in the html
{% for ingredient in drink.ingredientList %}
{% for ing in ingredients %}
{% if ing._id == ingredient %}
<li>
{{ ing.ingredientName }}
</li>
{% endif %}
{% endfor %}
{% endfor %}
Any help on this would be greatly appreciated, because I feel like I'm banging my head against the wall at this point.

Filtering a django news post

I'm new to Django and I have a news post and on that same template I have a section on the right hand side displaying all of the latest posts. However when you are on one of the main news posts it also shows up in the 'latest news' tab on the right.
I'm pretty sure i need to use .exclude to filter out the one that is being displayed. However i don't know how django know which post is being displayed.
If you need to look at my code please ask. I'm only using basic models / views to output the data.
The line that shows the latest 3 posts:
other_news = NewsPost.objects.filter(live=True, categories__in=post.categories.all).distinct().order_by("-posted")[:3]
Code for the template:
<div class='related_article_wrapper'>
{% if other_news %}
{% for news in other_news %}
<div class="article_snipppet_wrap">
<img class="article_icon" src="/media/images/article_icon.png" alt="" />
<p>{{news.title}}</p>
<span>{{news.posted|date:"d/m/y"}} »</span>
</div>
{% endfor %}
<span><a style="text-decoration: none; href="/news-hub/news/">View all news »</a></span>
{% endif %}
</div>
Thanks,
Josh
Just add .exclude(id=post.id) to your filter chain:
other_news = NewsPost.objects.exclude(id=post.id).filter(live=True,
categories__in=post.categories.all).distinct().order_by("-posted")[:3]
exclude() takes arguments in the same format as filter(), it just does the opposite!

Categories