Django annotated value in template - django

Is it possible to access annotated values on querysets in templates?
For example I have the following queryset that I'm passing to my template:
context[videos] = Videos.objects.annotate(view_count=Count(views)).order_by(view_count)[:100]
In my template I'm trying to get the view count like this:
{% for video in videos %}
{{ video.view_count }}
{% endfor %}
Which displays nothing.
However if I use:
{{ video.views.count }}
It seems fine - but i believe the second option recalculates the view count. I'd like to use the annotated value since it should already be calculated.

It would make sense that the QuerySet aggregation method per-item, annotate(), has such a name because it annotates (sets) the aggregated value to each item (model instance) it yields, like a normal field, for example:
# Build an annotated queryset
>>> q = Book.objects.annotate(Count('authors'))
# Interrogate the first object in the queryset
>>> q[0]
<Book: The Definitive Guide to Django>
>>> q[0].authors__count
2
# Interrogate the second object in the queryset
>>> q[1]
<Book: Practical Django Projects>
>>> q[1].authors__count
1
About the names:
the name for the annotation is automatically derived from the name of the aggregate function and the name of the field being aggregated. You can override this default name by providing an alias when you specify the annotation:
>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q[0].num_authors
2
>>> q[1].num_authors
1
For example with:
context['videos'] = Videos.objects.annotate(view_count=Count('views')).order_by('-view_count')[100:]
You could use:
[video.view_count for video in context['videos']]
Which should be the same as using values_list():
Videos.objects.annotate(view_count=Count('views')).values_list('view_count', flat=True)
And similar to:
{% for video in videos %}
{{ video.view_count }}
{% endfor %}
That said, unlike normal fields, the order in which filters are applied matters, you've been warned B)

Related

Django template: using forloop.counter as an index to a list

In a django template, I need to use forloop.counter0 to access an element in a list. For instance:
{% for foo in bar %}
<p>{{data[{{forloop.counter0}}]}}</p>
{%endfor%}
That notation doesn't work - I guet "cannot parse the remainder: [{{forloop.....]"
So... how do I use the variable forloop.counter0 as an index for another variable (data) which is a list in a django template?
EDIT: why I think i need to do that.... Short story is that I have an inlineformset. The formset (let's call it "Discounts") is simple - a description (just a label to show users) and a % discount associated. The user only needs to enter the %. That formset is used within a Customer form. Using inlineformset_factory(Customer, Discounts, ....). There are also 2 foreign keys that must be set in that discount form (to the Customer instance being created, and to the MetaGroup, see below). But those are hidden fields and it's easy to do.
However, the number of forms I need in that formset is determined by the instances in another table (let's call it "MetaGroup"). E.g. metagroupe contains entries for stuff like say Chairs, Tables, Sofa. My "Discounts" inlineformset provides users a way to set the discount % on each of those MetaGroup.
Thus in the template I do:
...
<tbody>
<managmeent forms, error stuff here>
{% for form in formset %}
<td>{{form.field.pourcent}}</td>
That's the forloop in which I need to access the data[fooloop.counter0]. data is provided by the inclusion I use to render that table/snippet.....
EDIT: the accepted answer is solution to the narrow question - which is that I probably shouldn't do that. For the wider issue (e.g. as to why I thought I needed to do this), I ended up using what is detailed here.
Please don't. Django templates are deliberately restricted, not to do this since business logic belongs in the view, not the template.
In the view, you can zip bar and data, so then the view looks like:
def my_view(request):
bar = …
data = …
context = {
'bar_data': zip(bar, data)
}
return render(request, 'some-template.html', context)
and render this with:
{% for foo, datum in bar_data %}
<p>{{ datum }}</p>
{%endfor%}

Django - Getting a list of fields in a queryset

I need help in getting a list of fields in a Queryset.
I am using Django shell to test, but I haven't had any luck.
If I do .values like below
abc = PrescribedMedsSchedule.objects.filter(medication_date=scheduled_date_obj, medication_time__time=scheduled_time_obj) \
.select_related('prescribed_meds_id') \
.select_related('prescribed_meds_id__childno') \
.values(
'prescribed_meds_id',
'medication_date',
'medication_time',
'quantity',
'form',
'prescribed_meds_id__name_of_medication',
'prescribed_meds_id__childno__child_name',
'prescribed_meds_id__childno__group',
'prescribed_meds_id__childno__line_no'
).order_by('prescribed_meds_id__name_of_medication')
I get the exact column names that I can use in the template, when I type abc in shell.
QuerySet [{'prescribed_meds_id': 5731, 'medication_date': datetime.date(2020, 2, 4), ....
But if I use .only instead of .values, when I type abc, I get a
QuerySet [<PrescribedMedsSchedule: PrescribedMedsSchedule object (6117) ....
How can I retrieve/display the fields/columns in this queryset?
The reason I am asking is that the fields in the select_related tables no longer appears in the HTML template when I use .only.
Use QuerySet.values if you want the resulting queryset to contain dictionaries and QuerySet.only if you want the resulting queryset to contain model instances.
You can observe that the result query using values or orders are the same when you print it in terminal.
print(abc.query)
If you like to still use QuerySet.only and retrieve queryset results as an iterable of model instances, then you can access the related fields using the dot operator. For example, to output child name in the template:
{% for a in abc %}
{{ a.prescribed_meds_id.childno.child_name }}
{% endfor %}

Order a django queryset with specific objects first

I have a list of users displaying in templates like the following.
{% for u in users_list %}
{{u.name}}
{% endif %}
Is there a way to rank two or more users at the top If I want to?
For instance with one user, when the current user visits that list, I can rank him in the top without a specific ordering by excluding me before sending the variable to template.
1) me
2) user2
3) user3
If you want to order specific objects by id at the top of a queryset, you can order on a conditional expression - for example, to put the current user at the top, and order other users by name:
from django.db.models import Case, When
from django.contrib.auth.models import User
users = User.objects.order_by(
Case(When(id=request.user.id, then=0), default=1),
'last_name',
'first_name'
)
To put multiple objects at the top, just adjust the When condition:
ids_at_top = [1, 2]
users = User.objects.order_by(
Case(When(id__in=ids_at_top, then=0), default=1))
I would also consider though whether you can achieve what you want via simpler means - for example you could get the current user, exclude them from the main queryset, and then display separately in the template, like this
# in the view
current_user = request.user
users = User.objects.exclude(id=current_user.id)
# in the template
{{ current_user.name }}
{% for u in users %}
{{ u.name }}
{% endif %}
In your view create a list of users that you want to display. In order to display the current user you can use request.user to get the current user and append them to the list followed by the rest of the users. In your template you would do
<ol type="1">
{% for u in users_list %}
<li>{{u.name}}<li>
{% endif %}
</ol>
should out what you are looking for
Update example
user_list = list(Users.objects.filter(name="Test")) #Or how ever you want to get the users.
user_list.insert(0,request.user)
if you wanted to get another individual you would just get the user using
another_person = User.objects.get(id=some_id)
user_list.insert(0,another_person)
Usign the queryset you can order your users_list based on the fields in the User. For this purpose you have the sort method in the queryset. In your case seems to me you need to add a field so you can sort by that field. For example you add a rank field default 0 and than add a value for the hightest rank.
The full example is defined here: https://docs.djangoproject.com/en/2.1/topics/auth/customizing/#a-full-example
In your CustomUserMdel just add a field rank = models.IntegerField(default=0)
Then in your view you need to sort by rank:
from django.contrib.auth import get_user_model
User = get_user_model()
# in your view...
user_list = User.objects.all().sort_by('rank')

django filter and order foreign-foreignkey in a query

I'm looking for a solution to filter and order foreign key of the foreign key of my model.
Let's say I have:
class Song(models.Model):
author = models.CharField(max_length=200,null=False)
class Songbook(models.Model):
songs = models.ManyToManyField(Song)
I'm already able thanks to annotation to sort my songbook query by song count:
context['popular_songbooks_list'] = Songbook.objects.annotate(
favoritesongbook_count=Count('favoritesongbook')).order_by('-favoritesongbook_count')[:10]
Now, I would like to display only the 3 main author contained in each songbook and I didn't find how to do it. Is there a solution using query or do I have to do a post-processing on the query list?
I didn't find an easy way to do it inside the view by annotating the query, but thanks to this post (django regroup and than dictsort by list length) I find a way to rich my goal.
Here is the solution :
From the popular_songbooks_list I showed in my first post, I use the regroup tag in template with custom filter and then slice to 3.
{% regroup songbook.songs.all by author as author_list %}
{% for author in author_list|groups_sort_reversed|slice:":3" %}
{{ author.grouper }}
{% endfor %}
The custom tag :
#register.filter()
def groups_sort_reversed(groups):
groups.sort(key=lambda group: len(group['list']), reverse=True)
return groups
I'm not sure this is the best solution, but it work !

Filter Django Haystack results like QuerySet?

Is it possible to combine a Django Haystack search with "built-in" QuerySet filter operations, specifically filtering with Q() instances and lookup types not supported by SearchQuerySet? In either order:
haystack-searched -> queryset-filtered
or
queryset-filtered -> haystack-searched
Browsing the Django Haystack documentation didn't give any directions how to do this.
You could filter your queryset based on the results of a Haystack search, using the objects' PKs:
def view(request):
if request.GET.get('q'):
from haystack import ModelSearchForm
form = ModelSearchForm(request.GET, searchqueryset=None, load_all=True)
searchqueryset = form.search()
results = [ r.pk for r in searchqueryset ]
docs = Document.objects.filter(pk__in=results)
# do something with your plain old regular queryset
return render_to_response('results.html', {'documents': docs});
Not sure how this scales, but for small resultsets (a few hundred, in my case), this works fine.
From the docs:
SearchQuerySet.load_all(self)
Efficiently populates the objects in the search results. Without using
this method, DB lookups are done on a per-object basis, resulting in
many individual trips to the database. If load_all is used, the
SearchQuerySet will group similar objects into a single query,
resulting in only as many queries as there are different object types
returned.
http://django-haystack.readthedocs.org/en/latest/searchqueryset_api.html#load-all
Therefore, after you have a filtered SQS, you can do a load_all() on it and just access the database objects via SearchResult.object. E.g.
sqs = SearchQuerySet()
# filter as needed, then load_all
sqs = sqs.load_all()
for result in sqs:
my_obj = result.object
# my_obj is a your model object
If you want to keep up with the pertinence, you have to access the object from the database through "object" :
example in your template:
{% for result in results %}
{{ result.object.title }}
{{ result.objects.author }}
{% endfor %}
But this is really bad since haystack will make an extra request like "SELECT * FROM blah WHERE id = 42" on each results.
Seems you're trying to get those object from your database because you didn't put some extra fields in your index ins't it ? If you add the title AND the author in your SearchIndex, then you can just use your results:
{% for result in results %}
{{ result.title }}
{{ result.author }}
{% endfor %}
and avoid some extra queries.