Django combine queries with and (&) in m2m field - django

I have such models:
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
tags = models.ManyToManyField(to="tag", related_name="tags", blank=True)
def __str__(self):
return self.title
class Tag(models.Model):
value = models.CharField(max_length=50)
parent = models.ManyToManyField("Tag", related_name="children", blank=True)
image = models.ImageField(upload_to="tags", null=True, blank=True)
def __str__(self):
return self.value
And I have an object, for example, post C which has tag 2 and tag 3
Currently I cannot create a combined ( q1 & q2 ) query, where I will be able to filter by that condition.
I am using combined queries because the case is more complex(so double filter won't do), I want to be able to filter by such queries as " 2 or ( 3 and 4 ) ", "(1 or 2) and (3 or 4)"
>>> q1
<Q: (AND: ('tags__in', '2'))>
>>> q2
<Q: (AND: ('tags__in', '3'))>
>>> Post.objects.filter(q1)
<QuerySet [<Post: post_B>, <Post: post C>, <Post: post D>]>
>>> Post.objects.filter(q2)
<QuerySet [<Post: post C>, <Post: post D>]>
>>> Post.objects.filter(q1 & q2)
<QuerySet []>
>>> str(Post.objects.filter(q1).query)
'SELECT "posts_post"."id", "posts_post"."title", "posts_post"."content" FROM "posts_post" INNER JOIN "posts_post_tags" ON ("posts_post"."id" = "posts_post_tags"."post_id") WHERE "posts_post_tags"."tag_id" IN (2)'
>>> str(Post.objects.filter(q2).query)
'SELECT "posts_post"."id", "posts_post"."title", "posts_post"."content" FROM "posts_post" INNER JOIN "posts_post_tags" ON ("posts_post"."id" = "posts_post_tags"."post_id") WHERE "posts_post_tags"."tag_id" IN (3)'
>>> str(Post.objects.filter(q1 & q2).query)
'SELECT "posts_post"."id", "posts_post"."title", "posts_post"."content" FROM "posts_post" INNER JOIN "posts_post_tags" ON ("posts_post"."id" = "posts_post_tags"."post_id") WHERE ("posts_post_tags"."tag_id" IN (2) AND "posts_post_tags"."tag_id" IN (3))'
What I've found is https://stackoverflow.com/a/8637972/20685072
It says " ANDed Q objects would not work "

from django.db.models import Q
query = Q()
for the or condition //
query.add(Q(id__in=[1,2] | Q(id__in=[3,4], query.connector)
as you asked for 2 and 3 //
query.add(Q(id=2) & Q(id=3), query.connector)
data = Model.objects.filter(query)
depends what is the requirement you can modify it as per need

I resolved it using raw Sql queries, UNION and INTERSECT

Related

Filter a Django queryset based on specific manytomany relationships

I have models similar to the Django documentation's pizza example:
class Pizza(models.Model):
name = models.CharField()
toppings = models.ManyToManyField('Topping')
def __str__(self):
return self.name
class Topping(models.Model):
name = models.CharField()
def __str__(self):
return self.name
And a few toppings with the expected pizzas:
>>> pepperoni = Topping.objects.create(name='pepperoni')
>>> sausage = Topping.objects.create(name='sausage')
>>> pineapple = Topping.objects.create(name='pineapple')
>>> olives = Topping.objects.create(name='olives')
>>> p1 = Pizza.objects.create(name='Pepperoni')
>>> p1.toppings.add(pepperoni)
>>> p2 = Pizza.objects.create(name='Sausage')
>>> p2.toppings.add(sausage)
>>> p3 = Pizza.objects.create(name='Pepperoni and Sausage')
>>> p3.toppings.add(pepperoni)
>>> p3.toppings.add(sausage)
>>> p4 = Pizza.objects.create(name='Pepperoni and Olives')
>>> p4.toppings.add(pepperoni)
>>> p4.toppings.add(olives)
>>> p5 = Pizza.objects.create(name='Pepperoni and Sausage and Olives')
>>> p5.toppings.add(pepperoni)
>>> p5.toppings.add(sausage)
>>> p5.toppings.add(olives)
>>> ...
How can I create a query that will return only return pizzas that have a topping of pepperoni (p1), or sausage (p2), or both pepperoni or sausage (p3)? I do not want pizzas that include pepperoni, sausage, and something else (p5).
Something like this will include a pizza that has pepperoni and olives (p4), which I don't want:
>>> Pizza.objects.filter(toppings__in=[pepperoni, sausage])
I can create a list of all toppings except the two I want and use that as an exclusion:
>>> toppings_i_do_not_want = Topping.objects.exclude(name__in=['Pepperoni', ['Sausage'])
>>> toppings_i_want = Topping.objects.filter(name__in=['Pepperoni', ['Sausage'])
>>> Pizza.objects.filter(toppings__in=toppings_i_want).exclude(toppings_i_do_not_want)
That will result in what I want, but it seems like the performance of such a query would suffer greatly if I'm only interested in two toppings but I have to pass ~100,000 other toppings into the exclude filter.
Is there a better way?
We can count the number of toppings that are pepperoni or sausage and compare that with the total number of related toppings, if the two match, and the number is larger than 0, then we can return such pizza:
from django.db.models import Count, Q
Pizza.objects.annotate(
ntopping=Count('toppings')
).filter(
ntopping__gte=1,
ntopping=Count('toppings', filter=Q(toppings__in=[pepperoni, sausage]))
)
will exactly do what you want. It will return Pizza records for which "there exists a related topping that is in the list [pepperoni, sausage]". So for pizza's that have a pepperoni topping, a sausage topping, or both toppings.

How do I filter by occurrences count in another table in Django?

Here are my models:
class Zoo(TimeStampedModel):
id = models.AutoField(primary_key=True)
class Animal(models.Model):
id = models.AutoField(primary_key=True)
zoo = models.ForeignKey(Zoo, on_delete=models.PROTECT, related_name='diffbot_results')
I would like to run a query like this:
Zoo.objects.filter("WHERE zoo.id IN (select zoo_id from animal_table having count(*) > 10 group by zoo_id)")
One way is to use a raw queryset:
>>> from testapp.models import Zoo, Animal
>>> z1, z2 = Zoo(), Zoo()
>>> z1.save(), z2.save()
(None, None)
>>> z1_animals = [Animal(zoo=z1) for ii in range(5)]
>>> z2_animals = [Animal(zoo=z2) for ii in range(15)]
>>> x = [a.save() for a in z1_animals+z2_animals]
>>> qs = Zoo.objects.raw("select * from testapp_zoo zoo WHERE zoo.id IN (select zoo_id from testapp_animal group by zoo_id having count(1) > 10)")
>>> list(qs)
[<Zoo: Zoo object (2)>]
In theory, per these docs, it should be possible to pass in a queryset to a regular .filter(id__in=<queryset>), but the queryset must only select one column, and I can't find a way of adding the HAVING clause without also causing the queryset to select a num_animals column, preventing it from being used with an __in filter expression.

how to get a related value from a ForeignKey model django admin

I've a model with a fk, but when save method I need get related value, e.g:
class Pedidos(models.Model):
ped_cliente = models.ForeignKey(Clientes, verbose_name='Cliente')
ped_remetente = models.ForeignKey(Remetentes, verbose_name='Remetente')
ped_produto = models.ForeignKey(Produtos, verbose_name='Produto')
ped_data_pedido = models.DateField(verbose_name='Data Pedido')
ped_quantidade = models.DecimalField(verbose_name='Peso/Volume', max_digits=10, decimal_places=0)
ped_key = models.IntegerField(unique=True, editable=False, verbose_name='Cod. Pedido')
class Pagamentos(models.Model):
pag_cliente = models.ForeignKey(Clientes, verbose_name='Cliente')
pag_key_ped = models.ForeignKey(Pedidos, verbose_name='Cód. Pedido')
pag_vencimento = models.DateField(verbose_name='Data Vencimento')
pag_vlr_total = models.DecimalField(verbose_name='Valor Total', max_digits=10, decimal_places=2)
I need when I save model Pagamentos the value field: pag_key_ped receive Pedidos.ped_key value
How I do to access this value?
You can access it via pag_key_ped.ped_key.
So:
>>> p = Pedidos(ped_key=1)
>>> p.save()
>>> pagamentos = Pagamentos()
>>> pagamentos.pag_key_ped = p
>>> pagamentos.save()
>>> print(pagamentos.pag_key_ped.ped_key)
[out] 1
>>> pagamentos.pag_key_ped = pagamentos.pag_key_ped.ped_key
>>> pagamentos.save()
HOWEVER!: This is a strange thing you want to do. pag_key_ped is already a ForeignKey and you want to override it with another ID. If there is no object with that ID, it will throw an DoesNotExist error.

Django query using filters

I have 3 models in django:
class Movie(models.Model):
mid = models.IntegerField(primary_key = True)
name = models.CharField(max_length = 100)
class User(models.Model):
username = models.CharField(max_length=128, null=True)
uid = models.CharField(max_length=100)
movie = models.ManyToManyField(Movie, through = "Vote")
class Vote(models.Model):
movie = models.ForeignKey(Movie)
user = models.ForeignKey(User)
rating = models.IntegerField()
here rating = 0/1, 0 means dislike, 1 means like
i want to make some queries using filters:
find out all movies that a current user likes. For that i use this following 2 queries, but none of them work. In both cases it gives erroneous results
ans = Movie.objects.filter(vote__user = self).filter(vote__rating = 1)
ans = Movie.objects.filter(user__uid = self.uid).filter(vote__rating = 1)
I have a list of users in an array ids. I want to find out how many users from this list like a particular movie?
i tried this, but this is also incorrect:
ret = User.objects.filter(uid__in = ids).filter(vote__movie = mov).filter(vote__rating = 1)
can somebody please help me with these 2 queries?
I'd also suggest letting django assign the model's id's but if you are using a legacy database or for some other reason need to assign the id's you can query like so:
# uid is some uid
user = User.objects.get(uid=uid)
likes = Movie.objects.filter(vote__user=user, vote__rating=1)
or
likes = Movie.objects.filter(vote__user__uid=some_uid, vote__rating=1)
count of people in the list of users who like a specific movie:
>>> uids = ['1','2','3']
>>> # if mov is a Movie instance
>>> votes = Vote.objects.filter(user__uid__in=uids, movie=mov, rating=1)
>>> print votes.query
SELECT "so1_vote"."id", "so1_vote"."movie_id", "so1_vote"."user_id", "so1_vote"."rating" FROM "so1_vote" INNER JOIN "so1_user" ON ("so1_vote"."user_id" = "so1_user"."id") WHERE ("so1_user"."uid" IN (1, 2, 3) AND "so1_vote"."movie_id" = 1 AND "so1_vote"."rating" = 1 )
>>> # if mov is a mid for a movie
>>> # get movie instance by using Movie.objects.get(mid=mov)
>>> # or query:
>>> # votes = Vote.objects.filter(user__uid__in=uids, movie__mid=mov, rating=1)
>>> likes_count = votes.count()
>>> print likes_count
0
combined:
likes_count = Votes.objects.filter(user__uid__in=uids, movie=mov, rating=1).count()

Django ORM equivalent for this SQL..calculated field derived from related table

I have the following model structure below:
class Master(models.Model):
name = models.CharField(max_length=50)
mounting_height = models.DecimalField(max_digits=10,decimal_places=2)
class MLog(models.Model):
date = models.DateField(db_index=True)
time = models.TimeField(db_index=True)
sensor_reading = models.IntegerField()
m_master = models.ForeignKey(Master)
The goal is to produce a queryset that returns all the fields from MLog plus a calculated field (item_height) based on the related data in Master
using Django's raw sql:
querySet = MLog.objects.raw('''
SELECT a.id,
date,
time,
sensor_reading,
mounting_height,
(sensor_reading - mounting_height) as item_height
FROM db_mlog a JOIN db_master b
ON a.m_master_id = b.id
''')
How do I code this using Django's ORM?
I can think of two ways to go about this without relying on raw(). The first is pretty much the same as what #tylerl suggested. Something like this:
class Master(models.Model):
name = models.CharField(max_length=50)
mounting_height = models.DecimalField(max_digits=10,decimal_places=2)
class MLog(models.Model):
date = models.DateField(db_index=True)
time = models.TimeField(db_index=True)
sensor_reading = models.IntegerField()
m_master = models.ForeignKey(Master)
def _get_item_height(self):
return self.sensor_reading - self.m_master.mounting_height
item_height = property(_get_item_height)
In this case I am defining a custom (derived) property for MLog called item_height. This property is calculated as the difference of the sensor_reading of an instance and the mounting_height of its related master instance. More on property here.
You can then do something like this:
In [4]: q = MLog.objects.all()
In [5]: q[0]
Out[5]: <MLog: 2010-09-11 8>
In [6]: q[0].item_height
Out[6]: Decimal('-2.00')
The second way to do this is to use the extra() method and have the database do the calculation for you.
In [14]: q = MLog.objects.select_related().extra(select =
{'item_height': 'sensor_reading - mounting_height'})
In [16]: q[0]
Out[16]: <MLog: 2010-09-11 8>
In [17]: q[0].item_height
Out[17]: Decimal('-2.00')
You'll note the use of select_related(). Without this the Master table will not be joined with the query and you will get an error.
I always do the calculations in the app rather than in the DB.
class Thing(models.Model):
foo = models.IntegerField()
bar = models.IntegerField()
#Property
def diff():
def fget(self):
return self.foo - self.bar
def fset(self,value):
self.bar = self.foo - value
Then you can manipulate it just as you would any other field, and it does whatever you defined with the underlying data. For example:
obj = Thing.objects.all()[0]
print(obj.diff) # prints .foo - .bar
obj.diff = 4 # sets .bar to .foo - 4
Property, by the way, is just a standard property decorator, in this case coded as follows (I don't remember where it came from):
def Property(function):
keys = 'fget', 'fset', 'fdel'
func_locals = {'doc':function.__doc__}
def probeFunc(frame, event, arg):
if event == 'return':
locals = frame.f_locals
func_locals.update(dict((k,locals.get(k)) for k in keys))
sys.settrace(None)
return probeFunc
sys.settrace(probeFunc)
function()
return property(**func_locals)