I have a book model and a rating model,
class Book(models.Model):
title = models.CharField(max_length=255)
slug = AutoSlugField(unique=True, populate_from='title')
description = models.TextField()
# more fields
class Rating(models.Model):
book = models.ForeignKey('library.Book')
score = models.DecimalField(max_digits=2, decimal_places=1)
the Query,
books = {'books': Book.objects.filter(pk__in=Rating.objects.all().order_by('-score'
).values_list('book__id', flat=True))[:10] }
template,
{% for i in books %}
{{ i.title }}, {{ i.rating_set.all.first.score }} <br/>
{% endfor %}
renders the model to the template, but the django debug toolbar shows as Duplicated n times where n is the number of objects in the list. when I use queryset caching, its normal.
whats going on behind, how can I fix this?
thanks.
Didn't test but you should definitely prefetch rating_set to not make additional database hit for each book to find their highest score:
rated_books = Rating.objects.all().order_by('-score').values_list('book', flat=True)
books = Book.objects.prefetch_related('rating_set').filter(pk__in=rated_books)[:10]
In the template, I also suspect .first and .all as they may cause an additional db hit. Besides, you don't need to call .first because we already know these rated books have at least one rating object.
{% for book in books %}
{{ book.title }}, {{ book.rating_set.all.0.score }} <br/>
{% endfor %}
Update: You need to use rating_set.all.0 instead of rating_set.0 to selec first rate
Read about select_related and prefetch_related.
Book.objects.filter(pk__in=Rating.objects.all().order_by('-score').values_list('book__id', flat=True)).preferch_related('rating_set')[:10]
In template You want to access to book rating {{ i.rating_set.all.0.score }}. Without select_related/prefetch_related Django in each row make new query. With prefetch_related Django made 1 query and fetch all ratings.
In Your case the problem may be in .first..
Related
In my listview I want to display several fields from two models containing a many-to-many field.
I can display the correct values from one table, but then I cannot access the other table with the many-to-manyfield.
Models.py
class Books(models.Model):
title = models.CharField(max_length=100)
class Author(models.Model):
book = models.ManyToManyField(Books)
first_name = models.CharField(max_length=150)
last_name = models.CharField(max_length=200)
Views.py
class BooksListView(ListView):
context_object_name = 'booklist'
model = Author
template_name = 'Books/books_list.html'
Book_list.html
{% for books in booklist %}
<h5><li class="list-group-item list-group-item-light"">
{{books.book.all}}:{{books.first_name}} {{books.last_name}}</li></h5>
{% endfor %}
The first and lastname are displayed properly, but the books.book.all ()i know this is the wrong query) returns a queryset containing the title (which is what I need, but this is in format <QuerySet [<Books: Book Title>]>. But books.book.title doesnt seem to work. What I want to display is "booktitle - firstname lastname", and because I am using two tables, I need a many-to-many relationship. Any thoughts?
The many-to-many relationship goes both ways, so you don't need to base everything around authors.
class BooksListView(ListView):
context_object_name = 'booklist'
model = Book
template_name = 'Books/books_list.html'
And the template:
{% for books in booklist %}
<li class="list-group-item list-group-item-light">
<h5>{{ book.title }}</h5>
<p>
{% for author in book.author_set.all %}
{{ author.first_name }} {{ author.last_name }}{% if not forloop.last %}, {% endif %}
{% endfor %}
</p>
</li>
{% endfor %}
Notes:
By default, the related_name is {{ModelName}}_set (author_set in this case). If you want something more natural you can set related_name='authors'.
There will be a query made to the databse for every book in the list and this can be quite slow with a lot of data, so take a look at prefetch_related().
I am trying to return a list of categories for a business, and for each category I would like to list all the items related to the category.
I was returning all of my items, not by category, but I have decided I want them sorted by category. This is what I have tried (among other attempts as well) I simply am having trouble getting the items into there categories. This is my latest attempt
In my models.py I have
Business(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
logo = models.CharField(max_length=300)
ItemCategory(models.Model):
name = models.CharField(max_length=50)
Item(models.Model):
name = models.CharField(max_length=100)
business = models.ForeignKey(Business)
category = models.ForeignKey(ItemCategory)
short_description = models.CharField(max_length=250)
in my views.py
def business_profile(request, business_id):
items = Item.objects.filter(business_id = business_id).select_related('itemcategory')
return render(request, 'business_profile.html', {"items": items})
in my template I am trying
{% for itemcategory in items.itemcategory_set.all %}
{{ itemcategory.name }}
{% for item in itemcategory %}
{{ item.name }} <br>
{{ item.short_description }}
{% endfor %}
{% endfor %}
From my research into other questions and reading the documents, i felt this would be right.. However from my results I can not get the correct output in my template.
Would this be the correct practice? Or should I try getting the categories first with
categories = ItemCategory.objects.filter(business = business_id)
and then possibly add a select_related('item') to process all the items for the category?
When I refer to data in the template but my model has 2 words (ItemCategory) - for instance when I move backward for a foreign key or use the model in select_related('') - would I use item_category or do you simply use itemcategory with no underscore?
UPDATE: I answered below in comment with explanation.
To list the items by category, I used #yusuf.oguntola's idea and initially got the business with .get(), then I created a queryset to get all the items. My function in views.py included
business = Business.objects.get(id = business_id)
items = business.item_set.all().order_by('category')
note: the business_id was passed in url pattern
However, the key change was in my template. I found this awesome built in functionality for templates in the django documents here
I implemented it in template like so...
{% regroup items by category as category_list %}
<ul>
{% for category in category_list %}
<li>{{ category.grouper }}
<ul>
{% for item in category.list %}
<li>{{ item.name }}<br> {{ item.short_description }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
One of the most important parts is you have to have order_by() and put the parameter of the field you are ordering by - in my case, category. If this is not included, it will simply put the items listed in chronological order and will repeat your field instance.
items = Item.objects.filter(business_id = business_id) is wrong.
You should rather say:
items = Item.objects.filter(business__id = business_id) #Notice the double underscore.
You may also rather get the business and simply say:
business.item_set.all() #This would retrieve all Item for the business.
select_related() expects a fieldname as a paramter. So use a field name that exists on the Item model. Like:
select_related('category')
Where's the meal? May be you can proceed from there though.
I show comments in the topic detail view. Exactly two queries happens
in this view: one to get topic and one to get comments list. I
understand that select_related technique can't be used here since
there are two different querysets created in the view. Is there any
way to decrease number of queries to one?
Application code follows below.
app/models.py
class Topic(models.Model):
headline = models.CharField(max_length=400)
description = models.TextField()
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, related_name='topics')
class Comment(models.Model):
headline = models.CharField(blank=True, max_length=400)
description = models.TextField()
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, related_name='comments')
topic = models.ForeignKey(Topic, related_name='comments')
app/views.py
class TopicDetail(DetailView):
queryset = Topic.objects.select_related('author').all()
context_object_name = 'topic'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
topic = self.object
context['comments'] = topic.comments.select_related('author').all()
return context
app/templates/app/topic_detail.html
{{ topic.headline }}
{{ topic.description }}
{{ topic.created }}
{{ topic.modified }}
{{ topic.author }}
{% for comment in comments %}
{{ comment.headline }}
{{ comment.description }}
{{ comment.author }}
{% endfor %}
Have a look at the prefetch_related method for reversed foreignKey lookups. This won't actually reduce the number of SQL queries, but django will merge in python related comments and Topics.
In the docs : https://docs.djangoproject.com/en/1.8/ref/models/querysets/#prefetch-related
Also, you should make sure to give different related_name to your fields, otherwise you'll have issues calling them :
author = models.ForeignKey(User, related_name='user_comments')
topic = models.ForeignKey(Topic, related_name='topic_comments')
This way you can write a single line query (but 2 SQL hits) :
topics = Topic.objects.prefetch_related('topic_comments').select_related('author').all()
Then for example :
{% for topic in topics %}
{% for comment in topic.topic_comments.all %}
{{ ... }}
{% endfor %}
{% endfor %}
Even in pure SQL such "single" query would be hard.
Basically You would need to repeat all the topic data in each comment! Like in this pseudo code:
Select comment.*, topic.*
From comments
Right Join topic
That is massive data processing/transfer overhead. Whole operation should take more time, resources then separate queries.
If You really need it, then just write custom function in comment topic model, with pure SQL.
I'm trying to create a structure to show the opening times from food companies, ordered by "open" status. But I don't know how can I get the informations on my template. For example:
#models.py
class Company(Model):
#... fields ...
def __unicode__(self):
return u'%s'%self.title
Here I'll store all times and days.
class OpeningHours(Model):
class Meta:
verbose_name = _(u"Horário de Abertura")
verbose_name_plural = _(u"Horários de Abertura")
unique_together = ('company', 'weekday')
company = ForeignKey(Company, related_name="opening_times", verbose_name=_(u"Empresa"))
weekday = IntegerField(choices=WEEKDAYS, verbose_name=_(u"Dia da Semana"))
fromHour = TimeField(verbose_name=_(u"Abre ás:"), null=True, blank=True)
toHour = TimeField(verbose_name=_(u"Fecha ás:"), null=True, blank=True)
def __unicode__(self):
return "%s %s (%s - %s)" % (self.company, self.weekday, self.fromHour, self.toHour)
And then I'm catching all the companies just like that on my views:
#views.py - This is how I'm processing the views
companies = sorted(Company.objects.filter(category__food=True).order_by('?')[0:4], key=lambda c: c.show_open(), reverse=True)
So, now the problem are in template, I need someway to catch this informations:
template.html - This is what I need to do on template
{% for company in companies %}
{% if company.open %}
OPEN
{% else %}
CLOSED
{% endif %}
<!-- I need to know when it's today, when it's tomorrow or when it's another day -->
{% ifequal company.open today %}
Next day will open is today at {{ company.next_time_open }}
{% ifequal company.open another_day %}
Next day will open is Sunday at {{ company.next_time_open }}
{% else %}
Wait, it's open right now from 22h till 00h
{% endif %}
{% endfor %}
First off, doing .order_by('?') is randomizing your queryset, which you are then sorting in Python. Ordering randomly causes the query to take more process time on the database side, and then sorting it in Python is its own additional process time. I would consider ordering your queryset using Django's methods, instead, by specifying the field(s) you want to order by in .order_by().
Secondly, one way to reduce database hits is to use .select_related() on the queryset. This will include related models in the queryset under a single SQL statement, so that later calls to the related models in the template don't cause new database hits.
Third, there is a lot of code in your different sections that reference fields and methods that I assume you've defined, but without seeing them directly, I can't make a judgment on what exactly you're doing. It is not possible at this time to give you a more direct answer.
I have two models:
class User(ndb.Model):
email = ndb.StringProperty(required=True)
name = ndb.StringProperty(required=True)
class Post(ndb.Model):
created = ndb.DateTimeProperty(auto_now_add=True)
message = ndb.TextProperty(required=True)
user = ndb.KeyProperty(kind=User)
If I want to loop through some posts in the Post model. How I would access the 'name' field in the User model? For example:
for post in posts:
print post.message
print post.user.name
So I'm guessing the last line isn't correct. I tried searching and couldn't figure it out. And to add to that. Is there a way to access that info in a Jinja2 template?
And then finally, am I going about this wrong? Let's say there are 50 posts. And I have 1 query to get those 50 posts. Is accessing the 'name' field on the User model going to be an additional query for each time it loops? So 51 queries?? If so, is it better to not use KeyProperty and just store the User data with each post (even if I have more data like avatar, user_id, etc)?
If I recall correctly, a KeyProperty will return you a ndb.Key. Once you have the key, it is easy to get the model instance (key.get()). So, in your example, you would:
print post.user.get().name
As far as accessing it in a jinja template -- Sure, it'd just be something like:
{% for post in posts %}
{{ post.message }}
{{ post.user.get().name }}
{% endfor %}
And yes, this will interact with the datastore once for each key you have. If you'd rather, you can factor it out into one datastore interaction:
keys = [p.user for p in posts]
users = ndb.get_multi(keys)
user_posts = zip(users, posts)
And then in your jinja template:
{% for user, post in user_posts %}
{{ post.message }}
{{ user.name }}
{% endfor %}