Cannot render model fields in forloop with annotate() and values() (Django)

Question:

I am using .values() and .annotate()to sum up 2 models fields based on matching criteria.

I wrapped this in a forloop in my template to iterate.

Problem: I cannot call the model fields anymore. The forloop returns the venue_id instead of the name and the usual approach to call the logo does not work anymore.

(these were rendering fine before I used .values() and .annotate(). Makes me think I am missing something in the logic here. Any ideas?

Models

class Venue(models.Model, HitCountMixin):
    id = models.AutoField(primary_key=True)
    name = models.CharField(verbose_name="Name",max_length=100, blank=True)
    logo = models.URLField('Logo', null=True, blank=True)

class Itemised_Loyatly_Card(models.Model):
    user = models.ForeignKey(UserProfile, blank=True, null=True, on_delete=models.CASCADE)
    venue = models.ForeignKey(Venue, blank=True, null=True, on_delete=models.CASCADE)
    add_points = models.IntegerField(name = 'add_points', null = True, blank=True, default=0)
    use_points = models.IntegerField(name= 'use_points', null = True, blank=True, default=0)

  

Views

from django.db.models import Sum, F
def user_loyalty_card(request):

    itemised_loyalty_cards = Itemised_Loyatly_Card.objects.filter(user=request.user.id).values('venue').annotate(add_points=Sum('add_points')).annotate(use_points=Sum('use_points')).annotate(total=F('add_points')-F('use_points'))
    
    return render(request,"main/account/user_loyalty_card.html", {'itemised_loyalty_cards':itemised_loyalty_cards})

Templates

{%for itemised_loyatly_card in itemised_loyalty_cards %}
<img"src="{{itemised_loyatly_card.venue.logo}}">

{{itemised_loyatly_card.venue}}
{{itemised_loyatly_card.total}}
{%endfor%}

Renders

enter image description here

Asked By: PhilM

||

Answers:

It returns only the value ID because that is the only field you passed in as an argument to .values(‘venue’). Whereas if you want another attribute you have to use field lookups, like so:

.values('venue__name'. 'venue__logo')

Also, .values() returns a QuerySet that returns dictionaries and not object instances, so if you pass that data into the template you can not access related data, meaning
{{itemised_loyatly_card.venue.logo}} will not work, but taking the first codeblock as an example {{itemised_loyatly_card.venue__logo}} works.

Another "mistake" is at these two annotations, annotations are a per object aggregation, these two lines are redundant, your model already has these fields.

.annotate(add_points=Sum('add_points'))
.annotate(use_points=Sum('use_points'))

That being said, if you want objects instead of values, that leaves you with:

views.py

itemised_loyalty_cards = (
    Itemised_Loyatly_Card.objects.filter(user=request.user.id)
                                .annotate(total=F('add_points')-F('use_points'))
    )

template.html

{%for itemised_loyatly_card in itemised_loyalty_cards %}
<p>
    {{itemised_loyatly_card.venue.name}}
    {{itemised_loyatly_card.add_points}}-
    {{itemised_loyatly_card.use_points}}=
    {{itemised_loyatly_card.total}} 
</p>
<img src="{{itemised_loyatly_card.venue.logo}}" alt="" >
{%endfor%}
Answered By: Niko