Suppose I have following models, which store questions and options for those questions.
P.S: I am just writing the basic code just to give you idea.
class Question(models.Model):
text = models.CharField()
class Option(models.Model):
text = models.CharField()
class QuestionOption(models.Model):
question = models.ForeignKey(Question)
option = models.ForeignKey(Option)
And then I have models which store user Feedback and Options selected for each Question in the Feedback survey.
class Feedback(models.Model):
full_name = models.CharField()
cell_phone = models.CharField()
created_at = models.DateTime()
class FeedbackOption(models.Model):
feedback = models.ForeignKey(Feedback, related_name='feedback_options')
option = models.ForeignKey(QuestionOption)
Every feedback will have lots of feedback option objects. Now I want to filter all the feedback whose feedback options contain specific QuestionOption object and then perform aggregate query where I am checking if that feedback's FeedbackOptions option text is 'boo' add count. Can i do this in one step, Something like this
# lets say i want to filter all feedback with QuestionOption id 1
stats = Feedback.objects.filter(feedback_options__option=1).aggregate(
boo=Count(Case(When(feedback_options__option__option__text='boo', then=1))),
hoo=Count(Case(When(feedback_options__option__option__text='hoo', then=1))))
It looks like it's applying aggregate on the only feedback option where option id is 1 not the rest of the feedback options for each feedback.
.filter() works as a where clause. When you add another method like .aggregate()to the resulting queryset, it is bounded by the same where clause.
In Django 1.11 you can use Subquery
from django.db.models import OuterRef, Subquery, Count
Feedback.objects.filter(feedback_options__option=o1).annotate(
boo=Subquery(
FeedbackOption.objects.filter(option__option__text="boo", feedback=OuterRef('pk')).values('feedback').annotate(count=Count('pk')).values('count'),
output_field=models.IntegerField())
)
Similarly you can add another annotation for hoo.
Doc reference
Related
So I am using Django to construct a Query and I have 3 models as defined:
class Book(models.Model):
...
class Upload(models.Model):
...
book = models.ForeignKey(Book, on_delete=models.CASCADE)
class Region(models.Model):
...
page = models.ForeignKey(Upload, on_delete=models.CASCADE)
Given these 3 models I wanted a query that lists all the books and annotate them with a segmented_pages variable that contains the count of all the Upload that have non-zero number of regions.
Basically, counting the number of uploads per book that have atleast one region.
I am assuming the basic structure of the query would look like this and mainly the logic inside filter needs to be modified as there is no convenient count lookup.
Book.objects.annotate(segmented_pages=Count('upload', filter=Q(upload__region__count__gt=0))
Can someone please help me with the logic of the filter and a simple explanation of how to go about designing these types of queries using django models?
You can rewrite "non-zero number of regions" as "In the join produced by the query, the region for any upload must not be null", hence you can simply use isnull [Django docs]:
from django.db.models import Count, Q
Book.objects.annotate(
segmented_pages=Count(
'upload',
filter=Q(upload__region__isnull=False),
distinct=True
)
)
I am new to django and trying out stuff with it.
How do I display selected fields from the joined table.
For example:
I have two models, X and Y. I am merging these two models based on the foreign key of model Y.
class X(models.Model):
name = models.CharField()
id = models.AutoField(primary_key=True)
class Y(models.Model):
owner_user = models.ForeignKey(X, models.DO_NOTHING,
db_column='id')
detail = models.CharField()
How do I write this query as a django code?
SELECT name, id, Body_details
FROM X, Y
WHERE X.id = Y.OwnerUserId;
You can use select_related
a = Y.objects.select_related('OwnerUserId').all()
for object in a:
print(object.OwneruserId.name, object.OwneruserId.id, object.body)
You can make use of select_related here.
result = Y.objects.select_related('owner_use')
All the work behind joining will automatically be done by this ORM using select_related. You can see previously asked questions similar to this one here.
You need to use the related_name of the ForeignKey field, which is y_set by default, to access the reverse relationship of model :
some_id = 1
instance = X.objects.get(id=some_id)
instance.y_set.all()
I have a couple models
class Order(models.Model):
user = models.ForeignKey(User)
class Lot(models.Model):
order = models.ForeignKey(Order)
buyer = models.ForeignKey(User)
What I'm trying to do is to annotate Lot objects with a number of buys made by a given user to the same seller. (it's not a mistake, Order.user is really a seller). Like “you’ve bought 4 items from this user recently”.
The closest I get was
recent_sold_lots = Lot.objects.filter(
order__user_id=OuterRef('order__user_id'),
status=Lot.STATUS_SOLD,
buyer_id=self.user_id,
date_sold__gte=now() - timedelta(hours=24),
)
qs = Lot.objects.filter(
status=Lot.STATUS_READY,
date_ready__lte=now() - timedelta(seconds=self.lag)
).annotate(same_user_recent_buys=Count(Subquery(recent_sold_lots.values('id'))))
But it fails when recent_sold_lots count is more than one: more than one row returned by a subquery used as an expression.
.annotate(same_user_recent_buys=Subquery(recent_sold_lots.aggregate(Count('id'))) doesn't seem to work also: This queryset contains a reference to an outer query and may only be used in a subquery.
.annotate(same_user_recent_buys=Subquery(recent_sold_lots.annotate(c=Count('id')).values('c')) is giving me Expression contains mixed types. You must set output_field.. If I add output_field=models.IntegerField() to the subquery call, it throws more than one row returned by a subquery used as an expression.
I'm stuck with this one. I feel I'm close to the solution, but what am I missing here?
The models you defined in the question do not correctly reflect the query you are making. In any case i'll use the model as a reference to my query.
from django.db.models import Count
user_id = 123 # my user id and also the buyer
buyer = User.objects.get(pk=user_id)
Lot.objects.filter(buyer=buyer).values('order__user').annotate(unique_seller_order_count=Count('id'))
What the query does is:
Filters the lot objects to the ones you have bought
Groups the Returned lots into the user who created the order
Annotates/Counts the responses for each group
I have a model 'Status' with a ManyToManyField 'groups'. Each group has a ManyToManyField 'users'. I want to get all the users for a certain status. I know I can do a for loop on the groups and add all the users to a list. But the users in the groups can overlap so I have to check to see if the user is already in the group. Is there a more efficient way to do this using queries?
edit: The status has a list of groups. Each group has a list of users. I want to get the list of users from all the groups for one status.
Models
class Status(geomodels.Model):
class Meta:
ordering = ['-date']
def __unicode__(self):
username = self.user.user.username
return "{0} - {1}".format(username, self.text)
user = geomodels.ForeignKey(UserProfile, related_name='statuses')
date = geomodels.DateTimeField(auto_now=True, db_index=True)
groups = geomodels.ManyToManyField(Group, related_name='receivedStatuses', null=True, blank=True)
class Group(models.Model):
def __unicode__(self):
return self.name + " - " + self.user.user.username
name = models.CharField(max_length=64, db_index=True)
members = models.ManyToManyField(UserProfile, related_name='groupsIn')
user = models.ForeignKey(UserProfile, related_name='groups')
I ended up creating a list of the groups I was looking for and then querying all users that were in any of those groups. This should be pretty efficient as I'm only using one query.
statusGroups = []
for group in status.groups.all():
statusGroups.append(group)
users = UserProfile.objects.filter(groupsIn__in=statusGroups)
As you haven't posted your models, its a bit difficult to give you a django queryset answer, but you can solve your overlapping problem by adding your users to a set which doesn't allow duplicates. For example:
from collections import defaultdict
users_by_status = defaultdict(set)
for i in Status.objects.all():
for group in i.group_set.all():
users_by_status[i].add(group.user.pk)
Based on your posted model code, the query for a given status is:
UserProfile.objects.filter(groupsIn__receivedStatuses=some_status).distinct()
I'm not 100% sure that the distinct() call is necessary, but I seem to recall that you'd risk duplicates if a given UserProfile were in multiple groups that share the same status. The main point is that filtering on many-to-many relationships works using the usual underscore notation, if you use the names as defined either by related_name or the default related name.
I'd like to find how to select all objects whose ManyToMany field contains another object. I have the following models (stripped down)
class Category(models.Model):
pass
class Picture(models.Model):
categories = models.ManyToManyField(Category)
visible = models.BooleanField()
I need a function to select all the Pictures in one or more Categories:
def pics_in_cats(cat_ids=()):
pass
BUT it needs to return a QuerySet if possible so that I can do something like:
pics_in_cats((1,2,3)).filter(visible=True)
It could be done by loading all the relevant Category objects and merging their picture_set attributes, but that seems inefficient. I'd also like to avoid falling back to raw SQL if possible.
Thanks in advance
Why write a custom function and not use something like this? (untested)
pics = Picture.objects.filter(categories__in = [1,2,3]).filter(visible=True)