Django - Annotation dictionary does not display correctly in template - python

I got these models in my models.py
class Rating(models.Model):
stars = models.IntegerField()
user_rating = models.ForeignKey(UserProfile, related_name="rater", on_delete=models.CASCADE)
user_rated = models.ForeignKey(UserProfile, related_name="rated", on_delete=models.CASCADE)
ride_number = models.ForeignKey(Ride, related_name="rider", on_delete=models.CASCADE)
class UserProfile(models.Model):
user = models.ForeignKey(User)
And my views
def user_view(request):
avg = user_avg
total = user_count
context = {
'avg': avg,
'total': total
}
return render(request, 'rating/home.html', context)
def user_avg():
avg = (Rating.objects
.values('user_rated').annotate(Avg('stars'))
.distinct())
return avg
and lastly my template looks like this
{% for rating in avg %}
{{ avg }}
{% endfor %}
Right now it renders like this;
[{'user_rated': 1L, 'stars__avg': 3.2222}, {'user_rated': 2L, 'stars__avg': 3.625}] [{'user_rated': 1L, 'stars__avg': 3.2222}, {'user_rated': 2L, 'stars__avg': 3.625}]
I've tried changing the template to {{ avg.user_rated }} but then it renders me nothing, so I'm not sure what im doing wrong here. And how do I get the name of the users avg rating instead of user_rated

This line in your template:
{{ avg }}
should be changed to:
{{ rating.user_rated }}: {{ rating.stars__avg }}
full template:
{% for rating in avg %}
{{ rating.user_rated }}: {{ rating.stars__avg }}
{% endfor %}
Your 2 errors: 1 - loop var is rating - you should use it to access data in loop, 2 - to access data in dict you should use dot notation, check docs.
Docs: https://docs.djangoproject.com/en/2.0/topics/templates/#variables
Dictionary lookup, attribute lookup and list-index lookups are
implemented with a dot notation:
{{ my_dict.key }}
{{ my_object.attribute }}
{{ my_list.0 }}
To get user name in results of annotation you should add it to values in query:
# assuming that your UserProfile model have name field
avg = (Rating.objects.values(
'user_rated', 'user_rated__name').annotate(Avg('stars')).distinct())
But be careful with values and annotations - order is matter and resulting values is depend on values list:
Docs: https://docs.djangoproject.com/en/2.0/topics/db/aggregation/#order-of-annotate-and-values-clauses
As with the filter() clause, the order in which annotate() and
values() clauses are applied to a query is significant. If the
values() clause precedes the annotate(), the annotation will be
computed using the grouping described by the values() clause.

avg is an array of Rating objects, which is exactly what you're seeing in the render. Try {{ rating.user_rated }}.
In answer to your second question, you need to join the Rating table with the UserProfile and User tables, assuming that the User table contains the users' names. See this link for how to do that.

Related

How to get all the fields from my model in Django

Here is my Model.py
class BlogModel(models.Model):
blog_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=1000)
content = FroalaField()
user = models.ForeignKey(User, blank=True , null=True , on_delete=models.CASCADE)
image = models.ImageField(upload_to='public')
created_at = models.DateTimeField(auto_now_add=True)
upload_to = models.DateTimeField(auto_now=True)
Here is my View.py
def my_blogs(request):
d = BlogModel.objects.all().filter(user = request.user)
return render(request,"my_blogs.html",{'message' : d})
But when I try to get the blog_id and created_at fields then it will shows an error that the requested fields are not present in the respective table.
But you can see that the field is n the table itself.
Please help
Here is the error i am getting
enter image description here
Django Queryset d is a list of objects. You have to loop through it:
for each_blog in d:
each_blog.blog_id
This is how you access the data for a filter method query set.
BlogModel.objects.all().filter(user = request.user) returns a query set i.e. a list of blogs satisfying the conditions.
You might want to iterate through all the blogs in order to display it, which can be done as follows:
{% for blog in d %}
{{ blog.blog_id }}
{% endfor %}
Try in views.py:
from .models import BlogModel
def my_blogs(request):
blogs = BlogModel.objects.all()
return render(request, 'my_blogs.html', locals())
In your html template my_blog.html try this:
{% for b in blogs %}
{{ b.id }}: {{ b.created_at }}
{% endfor %}
You can write in views.py:
def my_blogs(request):
# filtered queryset for login user
queryset = BlogModel.objects.filter(user=request.user)
return render(request,"my_blogs.html",{'blogs': queryset})
and in template my_blogs.html:
{% for blog in blogs %}
{{ blog.id }}
{{ blog.created_at }}
{% endfor %}
The BlogModel.objects.filter(user=request.user) returns a queryset which is list of your model objects. You can access object by iterating the queryset in template.

Filtering a reverse lookup in Django template

I have a model that looks like this
class Invoice(models.Model):
inv_number = models.CharField(max_length=10, primary_key=True)
customer = models.ForeignKey(Customer, on_delete=models.PROTECT)
inv_date = models.DateField()
class Txn(models.Model):
invoice = models.ForeignKey(Invoice, on_delete=models.PROTECT)
transaction_date = models.DateField()
reference = models.CharField(max_length=12)
amt = models.IntegerField()
I would like to display a report in my template that lists filtered invoices each with a sub-list of filtered transactions.
In my view I have done the following:
invoice_list = Invoice.objects.filter(customer=customer)
Which I pass into my template. In the template, I do something like the following:
{% for invoice in invoice_list %}
{{ invoice.inv_number }}, {{ invoice.customer}}, {{ invoice.inv_date }}
{% for txn in invoice.txn_set.all %}
{{ txn.transaction_date }}, {{ txn.reference }}, {{ txn.amt }}
{% endfor %}
{% endfor %}
This works great to show the entire transaction list per filtered invoice. The question is, how does one also filter the list of transactions per invoice in the template - what if I wanted just the transactions within a certain date range or that match a specific reference? Is there maybe a way to pass a filter to the txn_set queryset per invoice in the view before putting the main queryset in the context and without converting them to lists?
Thank you for any response!
Suggestion: collect the invoices and transactions in the view, not in the template.
With this view code you can reduce the amount of queries to 1, so it is a lot more optimal than your code (with queries the Txn table for each invoice):
# build basic query
qs = Txn.objects.select_related('invoice')\
.filter(invoice__customer=customer)\
.order_by('transaction_date')
# add filtering; here is an example, but it could be with date filter as well
reference = request.GET.get('reference', '')
if len(reference) > 0:
qs = qs.filter(reference__icontains=reference)
invoice_dict = {}
for txn in qs:
# add the Invoice if it does not exist yet
if txn.invoice_id not in invoice_dict:
invoice_dict[txn.invoice_id] = {
'invoice': txn.invoice,
'txn_list': [],
}
# add the Txn
invoice_dict[txn.invoice_id]['txn_list'].append(txn)
# sort by Invoice date
invoice_data_list = sorted(
invoice_dict.values(),
key=lambda x: x['invoice'].inv_date)
Then in your template:
{% for elem in invoice_data_list %}
{{ elem.invoice.inv_number }}
{{ elem.invoice.customer}}
{{ elem.invoice.inv_date }}
{% for txn in elem.txn_list %}
{{ txn.transaction_date }}
{{ txn.reference }}
{{ txn.amt }}
{% endfor %}
{% endfor %}

Access object value by other object value in template in Django?

This {{ category.id }} returns me 1
This {{ categories_counts.1 }} returns me data
But {{ categories_counts.category.id }} doesn't work?
Is it possible to access value by other object value?
views.py:
categories = Category.objects.all()
categories_counts = {}
for category in categories:
count = Venue.objects.filter(category_id=category.id).count()
categories_counts[category.id] = count
So categories containst:
<QuerySet [<Category: restaurants>, <Category: sportfitness>, <Category: carservices>, <Category: beautysalons>]>
categories_counts contains:
{1: 1, 2: 0, 3: 0}
category Model:
class Category(models.Model):
name = models.CharField(max_length=20)
bg_name = models.CharField(max_length=20, default=None)
category_bg_name = models.CharField(max_length=100, default=None)
In general, you can't access categories_counts[category.id] in the Django template unless you create a custom template tag, as #sayse says in the comments.
In your specific case, you can annotate the queryset with the venue counts.
from django.db.models import Count
categories = Category.objects.annotate(num_venues=Count('venue'))
Then in your template you can do something like:
{% for category in categories %}
{{ category.name }} has {{ category.num_venues }} venue(s)
{% endfor %}

DJANGO: Sorting sets of sets

I have this models (simplified):
#models.py
class Expression(models.Model):
text = models.CharField(max_length=254)
class Country(models.Model):
name = models.CharField(max_length=100)
class Definition(models.Model):
expression = models.ForeignKey(Expression)
country = models.ForeignKey(Country)
text = models.CharField(max_length=254)
class Vote(models.Model):
definition = models.ForeignKey(Definition)
And this view
#views.py
def index(request):
expressions = Expression.objects.all()
return render(request, 'expression_index.html', { 'expressions':expressions)
So it will show the last 10 created expressions.
Then in the template I have this:
#index.html
{% for expression in expressions %}
{{ expression }}
{% for definition in expression.definition_set.all %}
<ul>
<li>{{ definition }}</li>
</ul>
{% endfor %}
{% endfor %}
Every definition has several votes.
Every vote is a single row so we can do:
definition.votes_set.count()
How can I achieve to display them like this:
The top definition of every country alphabetically. Each country appears only with one definition.
Lets say Germany has two definitions for expression "A" and Denmark has three definitions for the same expression it will show only two definitions: the one with the most votes.
I hope I'm making sense.
Thanks
I think something like this should work (untested)
from django.db.models import Count
{{ expression.definition_set.annotate(Count('votes_set')) }}
{% for definition in expression.definition_set.order_by('country','votes_set__count') %}
This queryset will sort alphabetically by country, then each country from it's top voted definition to least.
expression = Expression.objects.get(text__exact="A")
definitions = Definition.objects.filter(expression=expression).annotate(num_votes=Count('vote')).order_by("country__name", "-num_votes")
If i try to keep only the top definition of each country as you wanted, and set .distinct("country") at the end of the queryset, it will throw this error:
NotImplementedError at annotate() + distinct(fields) is not implemented.
So another solution would be:
import operator
expression = Expression.objects.get(text__exact="A")
# Get top definitions sorted by num of votes and convert the values to list.
top_definitions = list(Definition.objects.filter(expression=expression).annotate(num_votes=Count('vote')).order_by("num_votes").values("id", "country", "country__name", "expression", "text", "num_votes"))
# Remove dublicate countries and leave the only the top voted definition.
definitions = list({v['country']: v for v in top_definitions}.values())
# Sort alphabetically by country.
definitions.sort(key=operator.itemgetter('country__name'))
return render(request, 'expression_index.html', {'definitions': definitions, 'expression': expression})
template:
<h1>{{ expression.text }}</h1>
<ul>
{% for definition in definitions %}
<li>{{ definition.country__name }} - {{ definition.text }} - {{ definition.num_votes }}</li>
{% endfor %}
</ul>

Divide option in django model query

This is my model:
class DailyObj(models.Model):
RedDate = models.DateField('RecDate')
Name= models.CharField(Name,max_length=100,default=None)
Total_Rec = models.DecimalField('Total Rec',max_digits=18,decimal_places=2,default=None)
top10records = DailyObj.objects.filter(RedDate__gte = fromdate,RedDate__lte = todate,Total_Rec__gt=0).order_by('-Total_Rec')[:10]
Here fromdate and todate are variables having proper date values.
The above query works fine and returns the records which satisfy the given criteria.
However I want each value of Total_Rec to be divided by 10,000,000 (i.e. I would like to convert whole amount in crores). Just to pass this modified values to template. I don't want to update actual table values.
You could use a method like this:
class DailyObj(models.Model):
...
def total_crores(self):
return self.Total_Rec / 10000000
It won't be stored in the database, but will be accessible in the template with something like this:
{{ daily_obj.total_crores }}
Or, in keeping with your query, something like this (assuming that top10records gets added to the view's context):
<ul>
{% for record in top10records %}
<li>{{ forloop.counter }}: {{ record.total_crores }}</li>
{% endfor %}
</ul>

Categories