How do I create this queryset? - django

I have the following three models:
class Category(models.Model):
name = models.CharField(max_length=120)
class Experiment(models.Model):
name = models.CharField(max_length=50, unique=True)
categories = models.ManyToManyField(Category)
class Ad(models.Model):
experiment = models.ForeignKey(Experiment, related_name='ads', on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.PROTECT, blank=True, null=True)
I want to create a queryset which returns all ads where ad.category is in ad.experiment.categories.
Some example data to talk through:
Category: ['Cat1', 'Cat2', 'Cat3', Cat4', 'Cat5']
Experiment: [['Exp1',['Cat2','Cat3]],['Exp2',['Cat5','Cat1']]]
Ad: [['Exp1','Cat4'],['Exp1','Cat2']]
The queryset I'm hoping to create would only return the second ad because the ad's category is in the ad's experiment's category.
Any help would be appreciated!

You need to traverse the reverse relationship for experiment and then use an F object to access the ad's category field in the query. The distinct is used because the experiment will have many categories so one ad may have multiple matches on the comparison.
from django.db.models import F
Ad.objects.filter(experiment__category=F('category')).distinct()

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 Group and Sum Category with ORM-Query

Please note: Similar questions didn't help me as they have the category-foreignkey in the same class.
I have a simple Invoice app with models Invoice, Position, Product and Category. The Product is bound to the Category.
My target is to create a queryset that
filters e. g. a specific date-range
and then group all categories and build their sums
Here is a screenshot of the invoice respectively of its positions:
The expected result of the grouped query should look like this:
Can you help me to create a query that groups and sums the categories within the filtered date-range?
The only solution I was able to create was the filter of a specific date-range:
queryset = Position.objects.filter(invoice__date_of_purchase__range=['2019-01-01', '2019-12-31'])
models.py (which I have simplified):
from django.db import models
from django.urls import reverse
class Category(models.Model):
name = models.CharField(max_length=30, unique=True)
def __str__(self):
return self.name
class Product(models.Model):
name = models.CharField(max_length=120)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products')
def __str__(self):
return self.name
class Invoice(models.Model):
invoice_code = models.CharField(max_length=15)
date_of_purchase = models.DateField()
customer_name = models.CharField(max_length=100)
def __str__(self):
return self.invoice_code
class Position(models.Model):
invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.DecimalField(decimal_places=2, max_digits=6)
price = models.DecimalField(decimal_places=2, max_digits=8)
total = models.DecimalField(
decimal_places=2, max_digits=8, blank=True, null=True) # is calculated in view
def __str__(self):
return self.product.name
The following filter will return all categories that have an invoice in the date range and will also filter the annotation to sum only those positions for those invoices
categories = Category.objects.filter(
products__position__invoice__date_of_purchase__range=['2019-11-17', '2019-12-31']
).annotate(
sum=Sum('products__position__total')
)
Each category will now be annotated with an attribute "sum"
for category in categories:
print(category, category.sum)
I'm going to suggest a tweak based on my experience.
Put position into the invoice model as a many to many fields. This should make it cheaper to filter the date range of invoices. It also may help to add a "sent" bol field depending on your use case.
Either in your view or in a utils.py file. Loop thru the query set's "Position's" field with the category as the conditional to separate by category and += the Position.total field to your awaiting variable.

django, many to many relation get_field name

I had two django models connected with many to many relationship.
First model:
class Category(models.Model):
name = models.CharField(max_length=255)
products = models.ManyToManyField(Product, related_name='categories',
blank=True, null=True,
verbose_name=_('Products'),
)
second model:
class Product(models.Model):
description = models.TextField(verbose_name=_('Description'), default='')
manifactor = models.CharField(verbose_name=_('Manifactor'), default='Blackberry', max_length=255)
ok, so:
product = Product.objects.all()[0]
product.categories - give me a list of categories for this product.
but:
product._meta.many_to_many - return empty list [].
and product._meta.get_field('categories') - return None.
Why ?
How can I get the verbose name of category field from product object ?
You can add
categories = models.ManyToManyField(Category,
through=Category.products.through)
to your Product model

django queryset for many-to-many field

I have the following Django 1.2 models:
class Category(models.Model):
name = models.CharField(max_length=255)
class Article(models.Model):
title = models.CharField(max_length=10, unique=True)
categories = models.ManyToManyField(Category)
class Preference(models.Model):
title = models.CharField(max_length=10, unique=True)
categories = models.ManyToManyField(Category)
How can I perform a query that will give me all Article objects that are associated with any of the same categories that a given Preference object is related with?
e.g. If I have a Preference object that is related to categories "fish", "cats" and "dogs", I want a list of all Articles that are associated with any of "fish", "cats" or "dogs".
Try:
preference = Preference.objects.get(**conditions)
Article.objects.filter(categories__in = preference.categories.all())
Article.objects.filter(categories__in=myPreferenceObject.categories.all())