>
> Why shouldn't test_obj be in that query result? test_obj has a m2m with
> int_value 10 and a m2m (although another one) with char_value bar
>
The assertions are just saying what the current behavior of the ORM *is*.
Under the hood, it's because Q(m2ms__int_value=10) &
Q(m2ms__char_value='bar') is equivalent to Q(m2ms__int_value=10,
m2ms__char_value='bar') and a single join to the m2ms table (not including
the through table) is created:
SELECT "testapp_testobj"."id"
FROM "testapp_testobj"
INNER JOIN "testapp_testobj_m2ms"
ON ("testapp_testobj"."id" = "testapp_testobj_m2ms"."testobj_id")
INNER JOIN "testapp_m2mmodel"
ON ("testapp_testobj_m2ms"."m2mmodel_id" = "testapp_m2mmodel"."id")
WHERE ("testapp_m2mmodel"."int_value" = 10
AND "testapp_m2mmodel"."char_value" = bar)
The conditions in the WHERE clause must apply to *the same row* in the
join, so it only matches when the same row in the m2m table matches both
conditions simultaneously.
I think that's a reasonable way to make the ORM behave, espeically since
it's been pretty long standing behavior. You certainly could give Q(foo=1,
bar=2) a different meaning than Q(foo=1) & Q(bar=2), though at this point
that would be a big break with pre-existing behavior. *Given* that
behavior, it seems counterintuitive to not obey logical invariants like De
Morgan's law.
i'll mention that Q object behavior is one of the trickiest bits of the
> ORM. Some of the relevant reading might be:
>
I've definitely seen that in trying to build a library to mimmick its
behavior! Thanks for the links. I'll read through those pull requests and
file a trac ticket if they don't clarify my question. If the determination
from Anssi is that altering the current behavior is desirable/feasibl, I'll
start working on a patch. Thanks very much, Tim!
- Lucas
On Saturday, February 13, 2016 at 6:53:11 PM UTC-8, Vijay Khemlani wrote:
>
> assert test_obj not in TestObj.objects.filter(Q(m2ms__int_value=10) &
> Q(m2ms__char_value='bar'))
>
> Why shouldn't test_obj be in that query result? test_obj has a m2m with
> int_value 10 and a m2m (although another one) with char_value bar
>
> On Sat, Feb 13, 2016 at 7:45 PM, Lucas Wiman > wrote:
>
>> I'm working on the django-predicate library, which defines in-memory
>> evaluation semantics for Q objects. The eventual goal is to precisely
>> match the behavior of the ORM on the subset of supported lookup types.
>> Yesterday, I noticed a bug in that library, where there was a mismatch with
>> the behavior of the ORM.
>>
>> De Morgan's law is a sort of distributive property for Boolean algebras,
>> stating that (A ∧ B) ⇔ ¬(¬A ∨ ¬B). It has an equivalent statement for
>> sets and logical predicates. However, it seems Django's Q object does
>> *not* obey De Morgan's law, which was somewhat surprising.
>>
>> I wanted to verify that this intended behavior. If so, does anyone know
>> where this is documented or Trac tickets that discuss how & and ~ interact
>> when turned into SQL queries?
>>
>> If it is not intended behavior, I'll file a Trac issue about it and we
>> can continue discussion there.
>>
>> Setup of example models.py
>>
>> class Base(models.Model):
>> class Meta:
>> abstract = True
>> char_value = models.CharField(max_length=100, default='')
>> int_value = models.IntegerField(default=0)
>> date_value = models.DateField(default=datetime.date.today)
>> datetime_value = models.DateTimeField(default=datetime.datetime.now)
>>
>>
>> class TestObj(Base):
>> m2ms = models.ManyToManyField(
>> 'testapp.M2MModel', related_name='test_objs')
>>
>> class M2MModel(Base):
>> pass
>>
>> Example
>> test_obj = TestObj.objects.create()
>> test_obj.m2ms.create(int_value=10, char_value='foo')
>> test_obj.m2ms.create(int_value=20, char_value='bar')
>>
>> assert test_obj not in TestObj.objects.filter(Q(m2ms__int_value=10) &
>> Q(m2ms__char_value='bar'))
>> assert test_obj in TestObj.objects.filter(~(~Q(m2ms__int_value=10) |
>> ~Q(m2ms__char_value='bar')))
>>
>> If De Morgan's law were obeyed, the two queries would evaluate to the
>> same result. The generated SQL is recorded in my pull request adding a
>> test cases reproducing the failure in django-predicate:
&