Django Order QuerySet based on ManyToManyField value - django

I have a business model as follows:
class Business(models.Model):
class Meta:
verbose_name_plural = "Businesses"
name = models.CharField(max_length=60, null=False, verbose_name="Title")
about = models.TextField(max_length=5000, null=True, verbose_name="Description", blank=True)
upvote = models.ManyToManyField(Account, verbose_name="Upvote Count", blank=True)
The Account model is as follows:
class Account(models.Model):
CHOICES = [('Male', 'Male'),Female', 'Female'),]
class Meta:
verbose_name_plural = "Accounts"
name = models.CharField(max_length=60, null=False, verbose_name="Title")
gender= models.CharField(max_length=6, null=True, verbose_name="Gender", choices=CHOICES)
I am trying to get a QuerySet that will be sorted by gender of the Account.
How do I achieve this?
So far I have achieved sorting by the upvote count.
Business.objects.all().order_by("upvote")

You can use the Sum function [Django docs] with a Conditional Expression [Django docs] to annotate a value according to which you would order:
from django.db.models import Case, Sum, Value, When
Business.objects.annotate(
order_value=Sum(
Case(
When(upvote__gender='Male', then=Value(1)),
When(upvote__gender='Female', then=Value(-1)),
default=Value(0)
)
)
).order_by('order_value')
The above query would give you Business objects with more upvotes by females first and males later, you can reverse the order by writing .order_by('-order_value') instead.

You can access fiels of related models by double underscore. See documentation here.
Try:
Business.objects.all().order_by("upvote__gender")

Related

Querying using related field names

I've got two models that I'd like to perform a reverse search on. I'm wondering how to do this given the fact that one model has to fields with foreign keys to the same model.
class Review(models.Model):
cart = models.ForeignKey(Cart, on_delete=models.CASCADE, default=None)
class Cart(models.Model):
cost = models.DecimalField(max_digits=50, decimal_places=2, null=True, blank=True)
class Job(models.Model):
cart = models.ForeignKey(Cart, related_name="cart_one", on_delete=models.CASCADE, null=True, blank=True)
unscheduled_job = models.ForeignKey(Cart, related_name="cart_two", on_delete=models.CASCADE, null=True, blank=True)
employee = models.ForeignKey(Employee, on_delete=models.CASCADE, null=True, blank=True)
My query is as follows:
reviews = Review.objects.filter(cart__job__employee=employee)
This query is failing due to the fact that the Job model has two foreign keys that point to the cart model. How would I fix this?
Thanks!
If you specify a related_query_name=… parameter [Django-doc] or a **related_name=… parameter [Django-doc], then that is the name to access the model in reverse, so you can query with:
Review.objects.filter(cart__cart_one__employee=employee)
or if you want to query in reverse with the unscheduled_job, then it is:
Review.objects.filter(cart__cart_two__employee=employee)
You can also combine the two, so bo5th cart anfd unscheduled_job by making use of a Q object:
from django.db.models import Q
Review.objects.filter(Q(cart__cart_one__employee=employee) | Q(cart__cart_two__employee))
You might however want to change the related_name=…s, since this should be the name to access the Job object from the perspective of a Cart model.

Django Sum in Annotate

Good afternoon,
I am really struggling with getting a sum using Annotate in DJango.
I am using User object and the following models:
class Depts(models.Model):
dept_name = models.CharField(max_length=55)
dept_description = models.CharField(max_length=255)
isBranch = models.BooleanField(default=False)
def __str__(self):
return "{}".format(self.dept_name)
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='profile')
title = models.CharField(max_length=75)
dept = models.ForeignKey(Depts, on_delete=models.CASCADE, related_name="dept", null=True)
class ActivityLog(models.Model):
activity_datetime = models.DateTimeField(default=timezone.now)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='activity_user')
activity_category = models.ForeignKey(ActivityCategory, on_delete=models.CASCADE, null=True, related_name='activity_cat')
activity_description = models.CharField(max_length=100, default="Misc Activity")
class ActivityCategory(models.Model):
activity_name = models.CharField(max_length=40)
activity_description = models.CharField(max_length=150)
pts = models.IntegerField()
def __str__(self):
return '%s' % (self.activity_name)
What I need to do is get a group of departments with aggregating the sum of the pts earned by all the users activitylogs.
So a user is part of department, they do activities, each activity is of a type activity_category and has associated points. How can I query using the ORM to get a sum of points for everyone in each department?
Thank you, I cannot seem to wrap my mind around it.
You annotate the departments with the sum:
from django.db.models import Sum
Depts.objects.annotate(
total_pts=Sum('dept__user__activity_user__activity_category__pts')
)
Note: The related_name=… parameter [Django-doc]
is the name of the relation in reverse, so from the Depts model to the UserProfile
model in this case. Therefore it (often) makes not much sense to name it the
same as the forward relation. You thus might want to consider renaming the dept relation to userprofiles.
After setting the related_name='userprofiles', the query is:
from django.db.models import Sum
Depts.objects.annotate(
total_pts=Sum('userprofiles__user__activity_user__activity_category__pts')
)

How to use Count over multiple fields in django?

How to use Count over many fields in Django? How to count row only if given multiple columns are unique?
For example for a model below.
class ProductViewed(models.Model):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE,
blank=True, null=True, related_name="viewed")
ip_address = models.CharField(max_length=255, blank=True, null=True)
product = models.ForeignKey(Product, on_delete=models.CASCADE,
related_name="views")
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{str(self.product)} viewed on {self.created_at}'
class Meta:
ordering = ('-created_at', )
verbose_name_plural = "ProductViewed"
I want to achieve followings. Count based on user_id, ip_address, created_at__day. Is there any way to do so?
Currently I could achieve the following
Product.objects.annotate(vcount=Count('views__ip_address'))
One way to do this is to join all the fields that you want to perform the unique count on together using Concat and then counting on the result. The Trunc function is good for extracting part of a datetime
from django.db.models.functions import Trunc, Concat
Product.objects.annotate(
view_day=Trunc('views__created_at', 'day')
).annotate(
user_ip_and_day=Concat('views__user_id', 'views__ip_address', 'view_day', output_field=models.CharField())
).annotate(
vcount=models.Count('user_ip_and_day')
)
Can't you just do this?
Product.objects.annotate(vcount=Count('views__ip_address')).annotate(ucount=Count('views__user_id')).annotate(ccount=Count('views__created_at__day'))

how to order a queryset in Django model by relational foreign key

I have the following models
models.py
class Category(models.Model):
label = models.CharField(max_length=100, unique=True)
description = models.CharField(max_length=255, blank=True)
class Expense(models.Model):
description = models.CharField(max_length=255, blank=True)
amount = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
null=True,
related_name='category'
)
class CategoryViewSet(viewsets.ModelViewSet):
queryset = Category.objects.order_by("-expense__expense")
I'm struggling to how to return a Category list ordered by the most picked category in the Expense model like:
<Expense category=3>
<Expense category=3>
<Expense category=2>
<Expense category=3>
<Expense category=1>
<Expense category=1>
I want to return in this order:
<Category id=3>
<Category id=1>
<Category id=2>
Solution is using annotate:
queryset = Category.objects.annotate(
expense_count=Count('category')).order_by('-expense_count')
from django.db.models import Count
Category.objects.annotate(expense_count=Count('category')).order_by('-expense_count')
This is based on your model definition yet I suggest that the related_name should be modified to a term that related to Expense, such as expenses. Since the related_name indicate the name you are using for reverse querying.
Use Django's lovely aggregation features.
queryset = Category.objects.annotate(expense_count=Count('expense')).order_by('-expense_count')
We can use annotate to achieve this:
from django.db.models import Count
...
queryset = Category.objects.annotate(expense_count=Count('expense')).order_by('-expense_count')
Just add .distinct('field-name') at end of the query. like this
queryset = Category.objects.order_by("-expense__expense").distinct('category')

How to use two unique constraints in Django model?

I have a Django model for a player of a game
class Player(models.Model):
name = models.CharField(max_length=50)
team = models.ForeignKey('Team', on_delete=models.CASCADE, blank=True, null=True)
game = models.ForeignKey('Game', on_delete=models.CASCADE)
objects = GameManager()
class Meta:
unique_together = ('name', 'game',)
I have only one unique constraint, that the name and the game are unique together.
Now, I would like to extend our page by adding registered users. So, I would add this to the model.
user = models.ForeignKey('auth.User', on_delete=models.CASCADE, blank=True, null=True)
So, an registered user can subscribe to a game by adding a name, team, game, and his/her user. However, the user should only be able to add his account once to an game, which would be a second unique constrain
unique_together = ('user', 'game',)
Is it possible to give in Django two unique constraints to the model? Or do I have to search in the table manually prior to saving the new entries? Or is there a better way?
Yes, in fact by default unique_together is a collection of collections of fields that are unique together, so something like:
class Player(models.Model):
name = models.CharField(max_length=50)
team = models.ForeignKey('Team', on_delete=models.CASCADE, blank=True, null=True)
game = models.ForeignKey('Game', on_delete=models.CASCADE)
objects = GameManager()
class Meta:
unique_together = (('name', 'game',), ('user', 'game',))
Here we thus specify that every name, game pair is unique, and every user, game pair is unique. So it is impossible to create two Player objects for the same user and game, or for the same game and name.
It is only because a single unique_together constraint is quite common, that one can also pass a single collection of field names that should be unique together, as is written in the documentation on Options.unique_together [Django-doc]:
Sets of field names that, taken together, must be unique:
unique_together = (("driver", "restaurant"),)
This is a tuple of tuples that must be unique when considered
together. It's used in the Django admin and is enforced at the
database level (i.e., the appropriate UNIQUE statements are included
in the CREATE TABLE statement).
For convenience, unique_together can be a single tuple when dealing with a single set of fields:
unique_together = ("driver", "restaurant")
You should use models.UniqueConstraint (reference).
As noted in the reference:
UniqueConstraint provides more functionality than unique_together. unique_together may be deprecated in the future.
Do this:
class Meta:
constraints = [
models.UniqueConstraint(fields=['name', 'game'], name="unique_name_game"),
models.UniqueConstraint(fields=['user', 'game'], name="unique_user_game"),
]
For example please refer to this :-
class Stores(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=50)
lat = models.FloatField()
lng = models.FloatField()
merchant = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name="stores")
def __str__(self):
return "{}: {}".format(self.name, self.address)
class Meta:
verbose_name_plural = 'Stores'
class Items(models.Model):
name = models.CharField(max_length=50, unique=False)
price = models.IntegerField()
description = models.TextField()
stores = models.ForeignKey(Stores, on_delete=models.CASCADE, related_name="items")
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Items"
unique_together = ('name', 'stores',)