Django Sum in Annotate - django

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')
)

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 Order QuerySet based on ManyToManyField value

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")

Django tables connection

I have 3 django tables connected like this:
Is there anyway to make a query for table Table that will get id_equip from table equip?
models.py
class Vendor(models.Model):
vendor_name = models.CharField(max_length=50)
def __str__(self):
return self.vendor_name
class Equipment(models.Model):
equipment_name = models.CharField(max_length=50)
id_vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE, default=None)
def __str__(self):
return self.equipment_name
class Table(models.Model):
table_name = models.CharField(max_length=100)
id_vend = models.ForeignKey(Vendor, on_delete=models.CASCADE, default=None)
id_equip = models.ManyToManyField(Equipment)
This part of the django docs is relevant and helpful, I definitely recommend your review at least that section and ideally the whole page.
Your models are already denormalized as evidenced by Table.id_equip which relates to Equipment so you could do:
table = Table.objects.get(SOME_FILTER)
equipment_ids = list(table.id_equip.all().values_list('id', flat=True))
If you wanted to go through the vendor I'd suggest:
table = Table.objects.get(SOME_FILTER)
equipment_ids = list(Equipment.objects.filter(vendor_set__table_set=table).values_list('id', flat=True))
I would recommend that you don't name your relationship fields with id_. With an ORM, these fields should represent the instances of the Model they are mapping to. For example:
class Table(models.Model):
name = models.CharField(max_length=100)
vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE, default=None)
equipment = models.ManyToManyField(Equipment)
If you're trying to create the model on top of an existing table, you can make use of the db_column parameter when defining the field.
vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE, default=None, db_column="id_vend")

How to copy a object data to another object in Django?

I am trying to create an E-Commerce Website and I am at the Final Step i.e. Placing the Order. So, I am trying to add all the Cart Items into my Shipment model. But I am getting this error.
'QuerySet' object has no attribute 'product'
Here are my models
class Product(models.Model):
productId = models.AutoField(primary_key=True)
productName = models.CharField(max_length=200)
productDescription = models.CharField(max_length=500)
productRealPrice = models.IntegerField()
productDiscountedPrice = models.IntegerField()
productImage = models.ImageField()
productInformation = RichTextField()
productTotalQty = models.IntegerField()
alias = models.CharField(max_length=200)
url = models.CharField(max_length=200, blank=True, null=True)
class Customer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=100, null=True, blank=True)
email = models.EmailField(max_length=100)
profileImage = models.ImageField(blank=True, null=True, default='profile.png')
phoneNumber = models.CharField(max_length=10, blank=True, null=True)
address = models.CharField(max_length=500, blank=True, null=True)
class Order(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, blank=True, null=True)
dateOrdered = models.DateTimeField(auto_now_add=True)
orderCompleted = models.BooleanField(default=False)
transactionId = models.AutoField(primary_key=True)
class Cart(models.Model):
product = models.ForeignKey(Product, on_delete=models.SET_NULL, blank=True, null=True)
order = models.ForeignKey(Order, on_delete=models.SET_NULL, blank=True, null=True)
quantity = models.IntegerField(default=0, blank=True, null=True)
dateAdded = models.DateTimeField(auto_now_add=True)
class Shipment(models.Model):
customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, blank=True, null=True)
orderId = models.CharField(max_length=100)
products = models.ManyToManyField(Product)
orderDate = models.CharField(max_length=100)
address = models.CharField(max_length=200)
phoneNumber = models.CharField(max_length=13)
I just removed additional functions i.e. __str__ and others.
Here is the views.py
def orderSuccessful(request):
number = Customer.objects.filter(user=request.user).values('phoneNumber')
fullAddress = Customer.objects.filter(user=request.user).values('address')
timeIn = time.time() * 1000 # convert current time in milliSecond
if request.method == 'POST':
order = Shipment.objects.create(customer=request.user.customer, orderId=timeIn,
orderDate=datetime.datetime.now(), address=fullAddress,
phoneNumber=number)
user = Customer.objects.get(user=request.user)
preOrder = Order.objects.filter(customer=user)
orders = Order.objects.get(customer=request.user.customer, orderCompleted=False)
items = orders.cart_set.all() # Here is all the items of cart
for product in items:
product = Product.objects.filter(productId=items.product.productId) # error is on this line
order.products.add(product)
Cart.objects.filter(order=preOrder).delete()
preOrder.delete()
order.save()
else:
return HttpResponse("Problem in Placing the Order")
context = {
'shipment': Shipment.objects.get(customer=request.user.customer)
}
return render(request, "Amazon/order_success.html", context)
How to resolve this error and all the cart items to field products in Shipment model?
Your model is not really consistent at all. Your Cart object is an m:n (or m2m - ManyToMany) relationship between Product and Order. Usually, you would have a 1:n between Cart and Product (a cart contains one or more products). One Cart might be one Order (unless you would allow more than one carts per order). And a shipment is usually a 1:1 for an order. I do not see any of this relationships in your model.
Draw your model down and illustrate the relations between them first - asking yourself, if it should be a 1:1, 1:n or m:n? The latter can be realized with a "through" model which is necessary if you need attributes like quantities.
In this excample, we have one or more customers placing an order filling a cart with several products in different quantities. The order will also need a shipment fee.
By the way: bear in mind that "filter()" returns a list. If you are filtering on user, which is a one to one to a unique User instance, you would better use "get()" as it returns a single instance.
Putting in into a try - except or using get_object_or_404() makes it more stable.
product = Product.objects.filter(productId=items.product.productId)
should be something like:
product = product.product
not to say, it becomes obsolete.
It looks like you make a cart for a product by multiple instances of Cart, the problem is you try to access the wrong variable, also you don't need to filter again when you already have the instance, make the following changes:
carts = orders.cart_set.all() # Renamed items to carts for clarity
for cart in carts:
product = cart.product
order.products.add(product) # The name order is very misleading makes one think it is an instance of Order, actually it is an instance of Shipment
As mentioned above in my comment your variable names are somewhat misleading, please give names that make sense to any variable.

Calculating score by counting and subtracting foreign key instances in Django

I have a class Summary:
class Summary(models.Model):
title = models.CharField(max_length=128)
category = models.ForeignKey(Category)
subcategory = models.ForeignKey(Subcategory)
content = RichTextField(null=True, blank=True)
users_rated_positive = models.ManyToManyField(
User, blank=True, related_name='summaries_rated_positive')
users_rated_negative = models.ManyToManyField(
User, blank=True, related_name='summaries_rated_negative')
author = models.ForeignKey(User, related_name='summaries_authored')
and a class UserProfile:
class UserProfile(models.Model):
user = models.OneToOneField(User, primary_key=True, related_name='profile')
karma = models.IntegerField(default=0)
rank = models.IntegerField(null=True,blank=True)
I want the karma to be calculated as the positive ratings on all the users summaries minus the negative ratings on all the users summaries
I figured I could add a property like this instead of a field:
#property
def karma(self):
summaries_list = self.user.summaries_authored.all()
positive_karma = sum(
[summary.users_rated_positive.count() for summary in summaries_list])
negative_karma = sum(
[summary.users_rated_negative.count() for summary in summaries_list])
return positive_karma - negative_karma
Is this the right way to do this? I feel like I should be using aggregate, or annotate but to be honest I'm new to Django and not entirely sure how they work for complex situations.
from django.db.models import Count
positive_karma = Summary.objects.filter(author=self.user).aggregate(pos_count=Count('users_rated_positive'))['pos_count']
negative_karma = Summary.objects.filter(author=self.user).aggregate(neg_count=Count('users_rated_negative'))['neg_count']
aggregate returns a dict so the actual value must be retrieved by the key
You would use annotate if you wanted to get the count of pos/neg ratings for every user.