I have 3 models, Entry model and Category model, and I have created intermediate model CategoryEntry.
class Entry(models.Model):
entry_text = models.TextField()
class Category(models.Model):
user = models.ForeignKey(User)
category_text = models.CharField(max_length=200)
entries = models.ManyToManyField(Entry, through='CategoryEntry')
class CategoryEntry(models.Model):
category = models.ForeignKey(Category, related_name="related_entry_categories")
entry = models.ForeignKey(Entry)
viewed = models.BooleanField(default=False)
How can I get in template Users total Entry count.
For example I can get total users Category count with
{{ user.category_set.count }}
So I tried many different ways, but don't get how to follow next relation
{{ user.category_set.entries.count}}
{{ user.category_set.categoryentry_set.count}}
{{ user.category_set.all.categoryentry_set.count}}
{{ user.category_set.related_entry_categories.count }}
Is this even possible (good thing to do) to count in template? Or is there better way?
Thanks!
your queries don't make sense because category_set is a collection of objects rather than a single object, so you cannot simply ask for category_set.entries.count
first you have to think about what you want... do you want:
individual count of entries for each category in category_set?
or total count of entries across all categories in category_set?
For the former you need to annotate the queryset. this will have to be done in the view rather than template because the method needs arguments:
from django.db.models import Count
user_categories = user.category_set.annotate(entry_count=Count('entries'))
# then pass the user_categories queryset into your template along with user
you can then iterate over user_categories in the template to display individual counts:
{% for category in user_categories %}
No. of entries: {{ category.entry_count }}
{% endfor %}
For the latter you can use aggregate, again in the view:
from django.db.models import Count
total = user.category_set.aggregate(entry_count=Count('entries'))
# note that aggregate method returns a dict:
print total['entry_count']
# then pass total['entry_count'] as a value into your template along with user
Related
I have 3 Models Product,Company Categories.
class Product(Meta):
categories = models.ManyToManyField(Category)
company = models.ForeignKey(Company, related_name='products', on_delete=models.CASCADE)
updated_at = models.DateTimeField(auto_now_add=False, auto_now=True)
I need:
to get all the products of a company
show the product first category
count the number products per company and show
order products by reverse updated_at
I start from:
1. Company.objects.get(pk=company_pk).prefetch_related('products')
will give me an error, because get returns an object:
class CompanyProductListView(ListView):
model = Company
template_name_suffix = '_company_list'
def get_queryset(self):
company_pk = self.kwargs.get('pk')
return Company.objects.get(pk=company_pk).prefetch_related('products')
get without prefetch works.
return Company.objects.filter(pk=company_pk).prefetch_related('products')
there is no error, but in template:
{% for company in company_list %}
{{ company.name}}
{% endfor %}
I loop even is one, but doesn't show me anything.
Besides that I need to attach first category to each product, and count the number of products
I'm thinking on access something like this:
{{company.name}}
{% for product in company.products %}
{{ product.name }}
{{ product.category }}
This query will get a little complicated, but should help you solve your issue.
PS: I haven't tested this but should mostly work. Will take a deeper look once I get some more time.
First we get the company we want:
company = Company.objects.get(pk=company_pk)
Then we fetch all the first categories for all products, it can be done by using this question as a guide:
first_categories = Category.objects.order_by('product__id', '-id').distinct('product__id')
Now we use the first_categories to use to limit the amount of data we prefetch (giving this a different perspective, we will query the Product model instead of the Company model)
product_list = Products.objects.filter(company=company).prefetch_related(
Prefetch('categories', queryset=first_categories)
)
def get_queryset():
company_pk = ...
company = ...
first_categories = ...
product_list = ...
return product_list
I want to perform row level math on a model and display the results in the template. The database has a row for each company per day.
class MyModel(models.Model):
company = model.CharField(...
daily_target = model.PositiveSmallIntger(...
actual = model.PositiveSmallIntger(...
date = model.DateField(...
In the template I'd want to display the result of 100 * actual / daily_target for the logged-in company. I have no problem doing this in the python interpreter but am confused about how to do this with views & templates.
You could add a property to the model:
class MyModel(models.Model):
company = model.CharField(...
daily_target = model.PositiveSmallIntger(...
actual = model.PositiveSmallIntger(...
...
#property
def pct_target(self):
return 100. * self.actual / self.daily_target
Then in your template:
{% for item in queryset %}
{{ item.pct_target }}
{% endfor %}
The disadvantage of this is that you cannot filter or order the queryset by the property. Another option would be to annotate your queryset with the calculated field.
I will give my models first and then write description.
class Entry(models.Model):
entry_text = models.TextField()
class Category(models.Model):
user = models.ForeignKey(User)
category_text = models.CharField(max_length=200)
entries = models.ManyToManyField(Entry, through='CategoryEntry')
class CategoryEntry(models.Model):
category = models.ForeignKey(Category)
entry = models.ForeignKey(Entry)
viewed = models.BooleanField(default=False)
So I have Entry model and Category model, and I have created intermediate model CategoryEntry as descriebed here https://docs.djangoproject.com/en/1.7/topics/db/models/#extra-fields-on-many-to-many-relationships because I need one extra field "viewed" (marked as True when user for the first time opens specific Entry link).
So I have created generic.ListView view, where I show all these categories that user has created for himself. What I want, is to show next to every category name, how many entries there are and how many entries he hasn't viewed yet.
Like:
Category Total Not_viewed
AAA 126 5
BBB 17 15
I have managed to show total entries in template by
{% for category in categories %}
{{ category.text }}
{{ category.entries.count }}
{% endfor %}
In my view I have get_queryset like
def get_queryset(self):
categories = Category.objects.filter(user=self.request.user.id)[:]
return categories
As I understand, then the best way would somehow add this extra info about every categories entries viewed count in get_queryset. I have searched around but didn't found anything what works. Have tried some things with select_related, prefetch_related, annotate but don't get whats the right way to do this.
Know that it's not right, but tried something like that and some other things.
categories = Category.objects.filter(user=self.request.user.id).select_related('categoryentry').filter(categoryentry__viewed=False).count()
categories = Category.objects.filter(user=self.request.user.id).annotate(not_viewed_count=Count('categoryentry')).filter(not_viewed_count__viewed=False)
Hope you get my idea what I wan't to achieve.
In your CategoryEntry model, use related_name in the category field like so:
category = models.ForeignKey(Category, related_name="related_entry_categories")
Now you can use this related name when querying the Category model. For example:
from itertools import chain
categories_not_viewed = Category.objects.filter(user=self.request.user.id, related_entry_categories__viewed=False).annotate(num_not_viewed=Count('related_entry_categories'))
categories_viewed = Category.objects.filter(user=self.request.user.id, related_entry_categories__viewed=True).extra(select={'num_not_viewed': 0})
categories = chain(list(categories_not_viewed), list(categories_viewed))
At end I came up with this solution:
categories = Category.objects.filter(user=self.request.user.id).extra(select = {
"num_not_viewed" : """
SELECT COUNT(*)
FROM app_categoryentry
WHERE app_categoryentry.category_id = app_category.id
AND app_categoryentry.viewed = %d """ % 0,
})
Based on the solution from this resource http://timmyomahony.com/blog/filtering-annotations-django/
If anyone have other solution how the get the same result with only Django ORM, I would like to know.
When a ManyToMany relationship has extra data via a through table, how can you get to the data in a template? From a view I can get the data if I supply parameters:
class Category(models.Model):
title = models.CharField(max_length=1024,null=True,blank=True)
entry = models.ManyToManyField(Entry,null=True,blank=True,
related_name='category_entry',
through='CategoryEntry',
)
class CategoryEntry(models.Model):
category = models.ForeignKey(Category)
entry = models.ForeignKey(Entry)
votes = models.IntegerField(null=False, default=0)
def category_detail(request, pk):
category = models.Category.objects.select_related().get(pk=pk)
entries = category.entry.order_by('-temp_sort_order').filter(temp_sort_order__gte=0)
for entry in entries:
assert isinstance(entry, models.Entry)
ce = models.CategoryEntry.objects.get(entry=entry, category=category)
pprint('Show votes as a test: ' + ce.votes) #OK
pprint('entry title: ' + entry.title) #OK
pprint('entry votes: ' + str(entry.category_entry.votes)) #BAD
pprint('entry votes: ' + str(entry.entry.votes)) #BAD
....
But templates can't supply parameters to methods.
The documentation at https://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships is silent on templates. Using using for entry in category.category_entry_set.all gives 'Category' object has no attribute 'category_entry_set'. category.category_entry.all does not work either.
Ultimately I want to display the extra data in a template:
{% for entry in entries %}
<ul>
<li>Title: {{ entry.title }} Votes: {{ entry.category_entry.votes }} {{ entry.entry.votes }}</li>
</ul>
{% endfor %}
If you have a category instance in template:
category.entry.all -> list of entries
If you have an entry instance in template:
entry.category_entry.all -> list of categories
You should call M2M fields in plural form,
then you will have a more readable code
category.entries.all
%model%_set syntax (or related name, if you've specified it) is using to access to model trough a backward relationship.
https://docs.djangoproject.com/en/1.4/topics/db/queries/#following-relationships-backward
But how do I get the 'votes' associated with the m2m instance? – Bryce
I suggest you the following way:
class Category(models.Model):
title = models.CharField(max_length=1024,null=True,blank=True)
entries = models.ManyToManyField(Entry,null=True,blank=True,
related_name='categories',
through='CategoryEntry',
)
class CategoryEntry(models.Model):
category = models.ForeignKey(Category, related_name='category_entries')
entry = models.ForeignKey(Entry)
votes = models.IntegerField(null=False, default=0)
def category_detail(request, pk):
category = models.Category.objects.select_related().get(pk=pk)
category_entries = category.category_entries.filter(entry__temp_sort_order__gte=0).order_by('-entry__temp_sort_order')
for category_entry in category_entries:
# category_entry is an instance of the model CategoryEntry
pprint('category entry votes: ' + str(category_entry.votes))
pprint('entry title: ' + category_entry.entry.title)
....
HOW TO
entry = Entry.objects.get(pk=1)
entry.categories.all() # list of categories (here we work through related name of the field entries)
category = Category.objects.get(pk=1)
category.entries.all() # list of entries (here we work through m2m field entries)
category.category_entries.all() # list of CategoryEntry objects (through related name category_entries of the field category in model CategoryEntry)
Updating my answer, i mistakenly put related manager on wrong model, in your case, like Andrey said, the correct way to get entries from category is:
category.entry.all()
Now, to address your iteration and ordering question. In python it will look like this:
for ce in category.categoryentry_set.order_by('-votes'):
print ce.entry, ce.votes
This will give you entries in each category ordered by votes. To get this to template you can just save a queryset category.categoryentry_set.order_by('-votes') into variable and iterate over it.
Here's an ugly ugly hack that works. After the filter and sort, process the list and append the extra model fields. The templates now have easy access:
entries = category.entry.order_by('-temp_sort_order').filter(temp_sort_order__gte=0)
for entry in entries:
assert isinstance(entry, models.Entry)
ce = models.CategoryEntry.objects.get(entry=entry, category=category)
entry.xxx_votes = mark_safe(ce.votes) # use {{ entry.xxx_votes to access }}
entry.xxx_ce = ce # Use {{ entry.ce.votes to access }}
return render_to_response('category.html')
Hopefully someone can provide a better answer, or suggest an improvement to django itself. This solution does not allow me to sort: category.entry.order_by('-category_entry.votes')
Here's my Model:
class User(models.Model):
pass
class Item(models.Model):
pass
class ItemVote(models.Model):
user = models.ForeignKey(User)
item = models.ForeignKey(Item)
vote = models.BooleanField()
I want to retrieve a list of Items, and I want to know if the current user has voted for each Item. How do I alter my query object so that it will generate sql similar to:
SELECT ...
FROM items
LEFT OUTER JOIN item_votes ON (item_votes.user_id = ? AND
item_votes.item_id = items.id)
You cannot in plain django queries. This is a many to many relation ship, you can even specify it more clearly by doing something like this:
class Item(models.Model):
votes = models.ManyToManyField(User, through='ItemVote', related='votedItems')
In a Many To Many relationship, we can speak of related sets (because there are multiple objects). While django can filter on related sets, something like:
Item.objects.filter(votes=request.user)
this will return you all Items that a user has voted for. However when you will list the .votes attribute in those objects, you will get all users that have voted for that item. the filtering is for the original object, never for the related set.
In a purely django way, you can expand my previous Item class to:
class Item(models.Model):
votes = models.ManyToManyField(User, through='ItemVote', related='votedItems')
def markVoted(self, user):
self.voted = user in self.votes
And then call this method for every object. This will however create an additional query to the votes set for every object (and no, select_related does not work for many to many relationships).
The only way to solve this is to add some SQL to your queryset using the extra method, like this:
Item.objects.extra('voted' : 'SELECT COUNT(*) FROM app_itemvote WHERE app_itemvote.item_id = app_item.id AND app_itemvote.user_id=%d'%request.user.pk)
Return ItemVote items:
items = ItemVote.objects.filter(user=user)
use in Django template:
{% for i in items %}
item: {{ i.item }}
voted: {{ i.voted }}
{% endfor %}