Counting objects within a Foreign Key and ManyToMany in Django - django

I'd like to count the objects I have listed.
I would like to count the number of issues within each title.
This is how my models.py are setup:
# Edited out
Views.py:
def titles (request):
all_titles = Title.objects.all().order_by('title')
num_titles = Title.objects.all().count()
latest_titles = Title.objects.order_by('-date_added')
title_a = Title.objects.filter(title__startswith='A')
title_b = Title.objects.filter(title__startswith='B')
......etc......
I need to do this for creators and characters too, except they are in a Many to Many relationship with Issue and the views essentially look the same as the def titles
Thank you.

To get the related issues for each title you can use the backwards relationship lookup. Since you did not specify a related name in the Issue model where you created the relationship, the lookup is performed using _set appended to the lower cased name of the related model. In your case issue_set.
some_title = Title.object.get(pk=1)
some_title.issue_set.count()
However, this is going to perform a db hit for every title you want to count.
You probably want to annotate the titles qs with the count. docs
from django.db.models import Count
titles_with_counts = Title.objects.all().annotate(issue_count=Count('issue__id'))
now each title in that qs has count accessible by using .issue_count
for title in titles_with_counts:
print title.title, title.issue_count

Related

Checking for many-to-many relation OR a property

How can I check whether a many-to-many relationship exists or another property is fulfilled? When I try the query, it returns some rows twice!
Given a model
from django.db import models
class Plug(models.Model):
name = models.CharField(primary_key=True, max_length=99)
class Widget(models.Model):
name = models.CharField(primary_key=True, max_length=99)
shiny = models.BooleanField()
compatible = models.ManyToManyField(Plug)
I have the following items in my database:
from django.db.models import Q
schuko = Plug.objects.create(name='F')
uk = Plug.objects.create(name='G')
Widget.objects.create(name='microwave', shiny=True).compatible.set([uk])
Widget.objects.create(name='oven', shiny=False).compatible.set([uk])
Widget.objects.create(name='pc', shiny=True).compatible.set([uk, schuko])
Now I want all names of widgets that are shiny and/or compatible with Schuko:
shiny_or_schuko = sorted(
Widget.objects.filter(Q(shiny=True) | Q(compatible=schuko))
.values_list('name', flat=True))
But to my surprise, this does not return ['microwave', 'pc']. Instead, 'pc' is listed twice, i.e. shiny_or_schuko is ['microwave', 'pc', 'pc'].
Is this a Django bug? If not, how can I set up the query that I get 'pc' just once?
Is this a Django bug?
No. You simply perform a LEFT OUTER JOIN with the many-to-many table. If two or more related objects match, it will be included multiple times. This can be wanted behavior, for example if you add extra annotations to the elements that takes values from these related objects.
You can make use of .distinct() [Django-doc] to return only distinct elements:
Widget.objects.filter(
Q(shiny=True) | Q(compatible=schuko)
).values_list('name', flat=True).distinct()

How to sort by the sum of a related field in Django using class based views?

If I have a model of an Agent that looks like this:
class Agent(models.Model):
name = models.CharField(max_length=100)
and a related model that looks like this:
class Deal(models.Model):
agent = models.ForeignKey(Agent, on_delete=models.CASCADE)
price = models.IntegerField()
and a view that looked like this:
from django.views.generic import ListView
class AgentListView(ListView):
model = Agent
I know that I can adjust the sort order of the agents in the queryset and I even know how to sort the agents by the number of deals they have like so:
queryset = Agent.objects.all().annotate(uc_count=Count('deal')).order_by('-uc_count')
However, I cannot figure out how to sort the deals by the sum of the price of the deals for each agent.
Given you already know how to annotate and sort by those annotations, you're 90% of the way there. You just need to use the Sum aggregate and follow the relationship backwards.
The Django docs give this example:
Author.objects.annotate(total_pages=Sum('book__pages'))
You should be able to do something similar:
queryset = Agent.objects.all().annotate(deal_total=Sum('deal__price')).order_by('-deal_total')
My spidy sense is telling me you may need to add a distinct=True to the Sum aggregation, but I'm not sure without testing.
Building off of the answer that Greg Kaleka and the question you asked under his response, this is likely the solution you are looking for:
from django.db.models import Case, IntegerField, When
queryset = Agent.objects.all().annotate(
deal_total=Sum('deal__price'),
o=Case(
When(deal_total__isnull=True, then=0),
default=1,
output_field=IntegerField()
)
).order_by('-o', '-deal_total')
Explanation:
What's happening is that the deal_total field is adding up the price of the deals object but if the Agent has no deals to begin with, the sum of the prices is None. The When object is able to assign a value of 0 to the deal_totals that would have otherwise been given the value of None

django: Models with multiple Foreign Key queries

I have finished a proof-of-concept django project and now want to redesign the models to be more robust.
The basic model is called a PhraseRequest:
class PhraseRequest(models.Model):
user = models.ForeignKey(User)
timestamp = models.DateTimeField()
phrase = models.TextField()
Now the complication comes in that one PhraseRequest has a bunch of associated models, PhraseRequestVote, Phrase (a response), PhraseRequestComment&c.
Now when I list say, the top ten Phrase Requests in order of votes, my template has a for-each loop which is fed the ten PhraseRequest objects. It then populates the HTML with the request, and all it's associated data.
So far I have been adding to each PhraseRequest's dictionary to achieve this:
for r in phrase_requests:
r.votes = PhraseRequestVote.objects.filter(request=r)
r.n_votes = sum([v.weight for v in r.votes])
r.comments = PhraseRequestComment.objects.filter(request=r)
#and so on
Intuitively, this doesn't seem right - There must be a "correct" way to do this. Do I need to redesign the models? The query?
You can make function in your model and order it in your view. Like this
models.py
class model_name(models.Model)
........
def votes(self):
return Vote_Name.objects.filter(phrase_id=self).count()

Django Filter Return Many Values

I'm new to django and I think this is a simple question -
I have an intermediate class which is coded as follows -
class Link_Book_Course(models.Model):
book = models.ForeignKey(Book)
course = models.ForeignKey(Course)
image = models.CharField(max_length = 200, null=True)
rating = models.CharField(max_length = 200,null=True)
def __unicode__(self):
return self.title
def save(self):
self.date_created = datetime.now()
super(Link_Book_Course,self).save()
I'm making this call as I'd like to have to have all of the authors of the books (Book is another model with author as a CharField)
storeOfAuthorNames = Link_Book_Course.objects.filter(book__author)
However, it doesn't return a querySet of all of the authors, in fact, it throws an error.
I think it's because book__author has multiple values- how can I get all of them?
Thanks!
I don't think you're using the right queryset method. filter() filters by its arguments - so the expected usage is:
poe = Author.objects.get(name='Edgar Allen Poe')
course_books_by_poe = Link_Book_Course.objects.filter(book__author=poe)
It looks like you're trying to pull a list of the names all the authors of books used in a particular course (or maybe all courses?). Maybe you're looking for .values() or values_list()?
all_authors_in_courses = Link_Book_Course.objects.values_list(
'book__author', flat=True
).distinct()
(Edit: Updated per #ftartaggia's suggestion)
As others already explained, the use of filter method is to get a subset of the whole set of objects and does not return instances of other models (no matter if related objects or so)
If you want to have Author models instances back from django ORM and you can use aggregation APIs then you might want to do something like this:
from django.db.models import Count
Author.objects.annotate(num_books=Count('book')).filter(num_books__gt=1)
the filter method you are trying to use translates more or less into SQL like this:
SELECT * FROM Link_Book_Course INNER JOIN Book ON (...) WHERE Book.author = ;
So as you see your query has an incomplete where clause.
Anyway, it's not the query you are looking for.
What about something like (assuming author is a simple text field of Book and you want only authors of books referred from Link_Book_Course instances):
Book.objects.filter(pk__in=Link_Book_Course.objects.all().values_list("book", flat=True)).values_list("author", flat=True)
To start with, a filter statement filters on a field matching some pattern. So if Book has a simple ForeignKey to Author, you could have
storeOfAuthorNames = Link_Book_Course.objects.filter(book__author="Stephen King"), but not just
storeOfAuthorNames = Link_Book_Course.objects.filter(book__author).
Once you get past that, I am guessing Book has Author as a ManyToManyField, not a ForeignKey (because a book can have multiple authors, and an author can publish multiple books?) In that case, just filter(book__author="Stephen King") will still not be enough. Try Link_Book_Course.objects.filter(book_author__in=myBookObject.author.all())

Django. Confusing query. Count items

I have table cold Mark.
class Mark(models.Model):
media = models.ForeignKey('media.Media')
mark = models.PositiveIntegerField()
user = models.ForeignKey(User)
class Meta:
unique_together = ('media_object','user')
How can i get query set of media instances (or just count the media) which has at least one vote?
Can i make it whith out using extra?
UPDATED:
I other words: I'm running through all table and counting all unique media. If i found it second time I'm not counting it.
Other words: I need count unique media fields.
I'm assuming the "Mark" model is how users vote. To get all media models with their mark counts, you need aggregation:
from django.db.models import Count
media_with_vote_count = Media.objects.annotate(vote_count=Count('mark_set'))
You can then use filter() that refers to that annotation:
voted_media = media_with_vote_count.filter(vote_count__gt=0)
There are also other useful aggregates. For example, you could calculate an average mark for every media:
from django.db.models import Avg
media_with_markavg = Media.objects.annotate(average_mark=Avg('mark_set__mark'))
mk = Mark.objects.all()
mk.media.count()
U can use the count function but not sure of ur question what u want to do from it or what is vote..
EDIT:
One row of media
if( mk.media.count() > 0):
......