Django 2.2 ORM Exclude Not Working as expected - django

Im trying to get all customer list form Cust models that not having a record in Stat (ForeignKey), Django 2.2
class Cust(models.Model):
name = models.CharField(max_length=50)
active = models.BooleanField(default=True)
class Stat(models.Model):
cust = models.ForeignKey(Cust, on_delete=models.PROTECT, null=True)
date = models.DateField(null=True, blank=True)
im trying this but doestn work,
month = datetime.now()
month = month.strftime("%Y-%m")
inner_qs = Stat.objects.filter(date__icontains=month)
data = Cust.objects.exclude(id__in=inner_qs)
print(inner_qs)
print(data)
The query above returning:
<QuerySet [<Stat: Ruth>]>
<QuerySet [<Cust: Jhonny>, <Cust: Rony>, <Cust: Sinta>, <Cust: Ruth>]>
As you can see, i need the result [<Stat: Ruth>] excluded from the data queryset/list.
but what i expected is:
<QuerySet [<Stat: Ruth>]>
<QuerySet [<Cust: Jhonny>, <Cust: Rony>, <Cust: Sinta>>

According to django doc 1 about __in, it states:
In a given iterable; often a list, tuple, or queryset. It’s not a common use case, but strings (being iterables) are accepted.
There are several ways to solve this problem.
replace
inner_qs = Stat.objects.filter(date__icontains=month)
data = Cust.objects.exclude(id__in=inner_qs)
1. with
inner_qs = Stat.objects.filter(date__icontains=month)
data = Cust.objects.exclude(id__in=[o.cust_id for o in inner_qs])
2. Another way is to replace with
id_list =Stat.objects.filter(date__icontains=month)\
.values_list('cust_id', flat=True)\
# flat=True will return list rather
# than tuple in the ValueListQueryset
data = Cust.objects.exclude(id__in=id_list)
Here, you first generate an exclude id_list then use it to exclude.
3. a revised way of #2 with distinct()
id_list =Stat.objects.filter(date__icontains=month)\
.values_list('cust_id', flat=True)\
.distinct().order_by()
# distinct() is used to get unique 'cust_id's
# distinct() does not work without order_by()
data = Cust.objects.exclude(id__in=id_list)

Related

QuerySet results being duplicated when chaining queries

So I am attempting to chain queries together. This is what I am doing
queryset_list = modelEmployee.objects.filter(stars__lte=3)
A = len(queryset_list) #A=2
queryset_list = queryset_list.filter(skills__skill_description__in=skill_filter)
A = len(queryset_list) #A=4
So with the above I am suppose to get two results but I am getting four. Seems like the results of first query are being duplicated in the second thus resulting to 4. Any suggestion on why the results are being duplicated and how I can fix this ? I was expecting to get only two items since it passes both the filters.
This is the model
class modelEmployee(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
skills = models.ManyToManyField(modelSkill, blank=True)
location = models.PointField(srid=4326,max_length=40, blank=True,null=True)
If you do a query on a ManyToManyField Django will perform an INNER JOIN which means there will be a row for every item on each side of the join. If you want to have unique results use distinct().
queryset_list = queryset_list.filter(
skills__skill_description__in=skill_filter
).distinct()
See this article for some examples.

How to filter queryset by two lookups of the same field?

I'm building a tinder-like app. Here is a model represents review from one user to another:
class Like(models.Model):
like_from = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='outcome_likes')
like_to = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='income_likes')
is_positive = models.BooleanField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{self.like_from} -> {self.like_to}'
Now I'm trying to filter all matches and all I have is this code:
def get_queryset(self):
return CustomUser.objects.filter(income_likes__like_from=self.request.user, income_likes__is_positive=True)\
.filter(outcome_likes__like_to=self.request.user, outcome_likes__is_positive=True)
# I also tried to go from the opposite side but
# also had no idea how to get correct solution
# Here should be some code to get intersection
# of these two querysets's values lists
# positive_likes_from = self.request.user.income_likes.all().filter(is_positive=True).values('like_from')
# positive_likes_to = self.request.user.outcome_likes.all().filter(is_positive=True).values('like_to')
But uncommented line here will return users that have any positive outcome likes with no guarantee that they will be addressed to the current user.
I want to get a queryset of CustomUser model that have positive income and outcome likes with a current user on another side.
Here is an solution of my problem that requires tons of SQL queries to the database:
def get_queryset(self):
positive_likes_from = self.request.user.income_likes.all().filter(is_positive=True)
positive_likes_to = self.request.user.outcome_likes.all().filter(is_positive=True)
result = []
for like in positive_likes_from:
outcome_like_with_same_user_on_another_side = positive_likes_to.filter(like_to=like.like_from).first()
if outcome_like_with_same_user_on_another_side:
result.append((like, outcome_like_with_same_user_on_another_side))
return result
You can intersect two QuerySets using the intersection function or the & operator (a QuerySet is a python set) as described here.
So if you create two QuerySets for the outgoing likes and incoming likes:
qs1 = self.request.user.income_likes.filter(is_positive=True).values_list('like_from', flat=True)
qs2 = self.request.user.outcome_likes.filter(is_positive=True).values_list('likes_to', flat=True)
you'll have two lists of user_ids of which the intersection are the users that match. The flat=True is required to make two lists otherwise the keys in each list would be different and the intersection empty:
matches = CustomUser.objects.filter(id__in=qs1.intersection(qs2))
or if you just want the list of ids:
match_ids = qs1 & qs2
This gives you all the users that have a match with the request.user.

Django How To Query ManyToMany Relationship Where All Objects Match

I have the following models:
## Tags for issues
class issueTags(models.Model):
name = models.CharField(max_length=400)
class issues(models.Model):
tags = models.ManyToManyField(issueTags,blank = True)
In my view I get an array from some client side JavaScript i.e.
(Pdb) array_data = request.POST['arr']
(Pdb) array_data
'["2","3"]'
How should I filter my issues object to find all issues which match all tags in the array? (the 2,3 are the ID values for tag__id.
If there is a better way to arrange the objects that would also work so I can search in this fashion.
At the time of writing this, the existing answers are either incorrect (e.g. filtering matching all Issues that have any of the specified tags and the correct tag count) or inefficient (e.g. attaching filters in a loop).
For the following models:
class IssueTag(models.Model):
name = models.CharField(max_length=400, blank=True)
class Issue(models.Model):
label = models.CharField(max_length=50, blank=True)
tags = models.ManyToManyField(IssueTag, related_name='issues')
I suggest using Django Annotation in conjunction with a filter like so:
from django.db.models import Count, Q
tags_to_match = ['tag1', 'tag2']
issues_containing_all_tags = Issue.objects \
.annotate(num_correct_tags=Count('tags',
filter=Q(tags__name__in=tags_to_match))) \
.filter(num_correct_tags=2)
to get all Issues that have all required tags (but may have additional tags, as is required in the question).
This will produce the following SQL query, that resolves all tag matching in a single IN clause:
SELECT "my_app_issue"."id", "my_app_issue"."label",
COUNT("my_app_issue_tags"."issuetag_id")
FILTER (WHERE "my_app_issuetag"."name" IN ('tag1', 'tag2'))
AS "num_correct_tags"
FROM "my_app_issue"
LEFT OUTER JOIN "my_app_issue_tags" ON ("my_app_issue"."id" = "my_app_issue_tags"."issue_id")
LEFT OUTER JOIN "my_app_issuetag" ON ("my_app_issue_tags"."issuetag_id" = "my_app_issuetag"."id")
GROUP BY "my_app_issue"."id", "my_app_issue"."label"
HAVING COUNT("my_app_issue_tags"."issuetag_id")
FILTER (WHERE ("my_app_issuetag"."name" IN ('tag1', 'tag2'))) = 2;
args=('tag1', 'tag2', 'tag1', 'tag2', 2)
I haven't tested this, but I think you could do the following:
from django.db.models import Q
array_data = array_data.split(',')
issues.objects.filter(
tags__in=array_data,
).exclude(
# Exclude any that aren't in array_data
~Q(tags__in=array_data)
).annotate(
matches=Count(tags, distinct=True)
).filter(
# Make sure the number found is right.
matches=len(array_data)
)
FYI, you should be using Issue, IssueTag for your model names to follow Django's naming pattern.
It isn't most elegant solution or pythonic but I ended up just looping around the resulting filter.
def filter_on_category(issue_object,array_of_tags):
#keep filtering to make an and
i = 0
current_filter = issue_object
while (i < (len(array_of_tags))):
#lets filter again
current_filter=current_filter.filter(tags__id__in=array_of_tags[i])
i=i+1
return current_filter
Django field lookups argument (__) for many-to-many fields needs list argument. I have created a dummy list for each array element of IssueTags and pass it to lookups argument and it works as expected.
Let you have this models:
class IssueTags(models.Model):
name = models.CharField(max_length=400)
class Issues(models.Model):
tags = models.ManyToManyField(IssueTags,blank = True)
You want to get Issues which contains all of these IssueTags = ["1","2","3"]
issue_tags_array = ["1","2","3"]
#First initialize queryset
queryset = Issues.objects.all()
i = 0
while i < len(issue_tags_array):
#dummy issue_tag list
issue_tag = [issue_tags_array[i]]
#lets filter again
queryset = queryset.filter(tags__id__in=issue_tag)
i=i+1
return queryset

Django - Do different queries depending on value of field

I'm stuck on a seemingly simple issue. I want to do a different queryset if sold_data is empty. Is there an effective way to do this? Or do I need to use a for loop and loop over all the listing objects and check each one?
class Listing(models.Model):
list_price = models.IntegerField()
sold_price = models.IntegerField(null=True, blank=True)
... other fields
data = Listing.objects.filter(...) # Note: I had already made other queries
if sold_price == None:
data = data.filter(list_price__gte=1)
else:
data = data.filter(sold_price__gte=1)
You can do it using Q object.
from django.db.models import Q
# your filtered queryset is in 'data' varibale
data.filter(Q(sold_price__isnull=False, sold_price__gte=1) | Q(sold_price__isnull=True, list_price__gte=1))
if you wanna check if an object is None use the is operator
I'm not sure if I did understand your question here is what I get : you wanna filter list_price if the data contains an object with empty value else filter sold_price
You can try this
if data.filter(sold_price__isnull=True).exists():
data = data.filter(list_price__gte=1)
else:
data = data.filter(sold_price__gte=1)

Reducing queries for manytomany models in django

EDIT:
It turns out the real question is - how do I get select_related to follow the m2m relationships I have defined? Those are the ones that are taxing my system. Any ideas?
I have two classes for my django app. The first (Item class) describes an item along with some functions that return information about the item. The second class (Itemlist class) takes a list of these items and then does some processing on them to return different values. The problem I'm having is that returning a list of items from Itemlist is taking a ton of queries, and I'm not sure where they're coming from.
class Item(models.Model):
# for archiving purposes
archive_id = models.IntegerField()
users = models.ManyToManyField(User, through='User_item_rel',
related_name='users_set')
# for many to one relationship (tags)
tag = models.ForeignKey(Tag)
sub_tag = models.CharField(default='',max_length=40)
name = models.CharField(max_length=40)
purch_date = models.DateField(default=datetime.datetime.now())
date_edited = models.DateTimeField(auto_now_add=True)
price = models.DecimalField(max_digits=6, decimal_places=2)
buyer = models.ManyToManyField(User, through='Buyer_item_rel',
related_name='buyers_set')
comments = models.CharField(default='',max_length=400)
house_id = models.IntegerField()
class Meta:
ordering = ['-purch_date']
def shortDisplayBuyers(self):
if len(self.buyer_item_rel_set.all()) != 1:
return "multiple buyers"
else:
return self.buyer_item_rel_set.all()[0].buyer.name
def listBuyers(self):
return self.buyer_item_rel_set.all()
def listUsers(self):
return self.user_item_rel_set.all()
def tag_name(self):
return self.tag
def sub_tag_name(self):
return self.sub_tag
def __unicode__(self):
return self.name
and the second class:
class Item_list:
def __init__(self, list = None, house_id = None, user_id = None,
archive_id = None, houseMode = 0):
self.list = list
self.house_id = house_id
self.uid = int(user_id)
self.archive_id = archive_id
self.gen_balancing_transactions()
self.houseMode = houseMode
def ret_list(self):
return self.list
So after I construct Itemlist with a large list of items, Itemlist.ret_list() takes up to 800 queries for 25 items. What can I do to fix this?
Try using select_related
As per a question I asked here
Dan is right in telling you to use select_related.
select_related can be read about here.
What it does is return in the same query data for the main object in your queryset and the model or fields specified in the select_related clause.
So, instead of a query like:
select * from item
followed by several queries like this every time you access one of the item_list objects:
select * from item_list where item_id = <one of the items for the query above>
the ORM will generate a query like:
select item.*, item_list.*
from item a join item_list b
where item a.id = b.item_id
In other words: it will hit the database once for all the data.
You probably want to use prefetch_related
Works similarly to select_related, but can deal with relations selected_related cannot. The join happens in python, but I've found it to be more efficient for this kind of work than the large # of queries.
Related reading on the subject