Query for top x elements in Django - django

I have two models such that
class JobTitle(models.Model):
name = models.CharField(max_length=1000)
class Employer(models.Model):
jobtitle = models.ForeignKey(JobTitle,unique=False,null=True)
As you see, one employer may have many jobtitles. I try to make a query to get top 5 employers whose number of job titles is maximum
How can I achive this is Django ?
Thanks

Employer.objects.values('id').annotate(jobtitle_count=Count('jobtitle')).order_by('-jobtitle_count')[:5]

from django.db.models import Count
Employer.objects.annotate(Count('jobtitle')).order_by('-jobtitle__count')[:5]

Related

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 Arrayfiled: Annotate queryset with the first item in the arryafield

I have a model that can be represented by something like this.
class BackPack(models.Model):
person = models.ForeignKey(Person, db_index=True,
related_name='back_packs', on_delete=models.CASCADE)
candy = ArrayField(models.CharField(max_length=203, null=True))
I want to build a queryset that is all back packs associated with one person and then annotated with the first item in the candy arrayfield. I tried the following;
first_candy = BackPack.objects.filter(person__id=200)\
.annotate(first_candy=F('candy__0'))
first_candy = BackPack.objects.filter(person__id=200)\
.annotate(first_candy=ExpressionWrapper(F('candy__0'),
output_field=CharField()))
The output for first_candy includes every item in the arrayfield not just the first one.
Any help for the correct way of doing this is much appreciated.
Try this:
from django.db.models.expressions import RawSQL
BackPack.objects.filter(person__id=200).annotate(first_candy=RawSQL('candy[1]', ()))
Postgres arrays are 1-based by default

Getting distinct objects of a queryset from a reverse relation in Django

class Customer(models.Model):
name = models.CharField(max_length=189)
class Message(models.Model):
message = models.TextField()
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="messages")
created_at = models.DateTimeField(auto_now_add=True)
What I want to do here is that I want to get the queryset of distinct Customers ordered by the Message.created_at. My database is mysql.
I have tried the following.
qs = Customers.objects.all().order_by("-messages__created_at").distinct()
m = Messages.objects.all().values("customer").distinct().order_by("-created_at")
m = Messages.objects.all().order_by("-created_at").values("customer").distinct()
In the end , I used a set to accomplish this, but I think I might be missing something. My current solution:
customers = set(Interaction.objects.all().values_list("customer").distinct())
customer_list = list()
for c in customers:
customer_list.append(c[0])
EDIT
Is it possible to get a list of customers ordered by according to their last message time but the queryset will also contain the last message value as another field?
Based on your comment you want to order the customers based on their latest message. We can do so by annotating the Customers and then sort on the annotation:
from dango.db.models import Max
Customer.objects.annotate(
last_message=Max('messages__crated_at')
).order_by("-last_message")
A potential problem is what to do for Customers that have written no message at all. In that case the last_message attribute will be NULL (None) in Python. We can specify this with nulls_first or nulls_last in the .order_by of an F-expression. For example:
from dango.db.models import F, Max
Customer.objects.annotate(
last_message=Max('messages__crated_at')
).order_by(F('last_message').desc(nulls_last=True))
A nice bonus is that the Customer objects of this queryset will have an extra attribute: the .last_message attribute will specify what the last time was when the user has written a message.
You can also decide to filter them out, for example with:
from dango.db.models import F, Max
Customer.objects.filter(
messages__isnull=False,
).annotate(
last_message=Max('messages__crated_at')
).order_by('-last_message')

Django Aggregate Query Values for Multiple Objects

In my project I have a model called Organization that can have multiple Campaign's. Each campaign can then have multiple donor's. Hopefully to clarify here is what my models look like:
class Campaign(models.Model):
name = models.CharField()
organization = models.ForeignKey('org.Organization')
class Donor(models.Model):
lead = models.ForeignKey('org.Lead')
amount = models.DecimalField()
campaign = models.ForeignKey(Campaign)
What I would like to do is show a campaign, then display the sum of all amounts made by donors (donor.amount). So for example, if "Campaign1" has three donors, each of whom donated $5, in my template it will show: "Campaign1: $15."
Any idea on how I can accomplish this? I was thinking about using a backward relationship in my template but you can not create Aggregates this way. Thanks for any help.
You should be able to use annotate to get this information. Try something like:
from django.db.models import Sum
campaigns = Campaign.objects.annotate(total_donations=Sum('donor__amount'))
You can then access the total donations for each campaign:
for campaign in campaigns:
print "%s: $%s" % (campaign.name, campaign.total_donations)
you can try:
from django.db.models import
a = Campaign.objects.get(pk=1)
a.annotate(total_donnation=Sum('donor__amount'))

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):
......