Opened 2 years ago

Closed 2 years ago

Last modified 2 years ago

#19848 closed Bug (invalid)

ANDed Q filters spanning generic relations evaluate unexpectedly to empty querie sets

Reported by: gotoalanlu@… Owned by: nobody
Component: contrib.contenttypes Version: master
Severity: Normal Keywords: generic genericrelations filter q and
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

See this StackOverflow question for some context. There is a answer for a workaround, but this feels a lot less natural than fixing the SQL—in order to do more complex filter queries involving OR and NOT, one would have to programmatically use De Morgan's laws to deconstruct the query into ANDed ANDless subqueries, IIRC.

Change History (6)

comment:1 Changed 2 years ago by anonymous

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Summary changed from ANDed Q filters spanning generic relations evaluate unexpectedly to empty queries to ANDed Q filters spanning generic relations evaluate unexpectedly to empty querie sets

comment:2 Changed 2 years ago by anonymous

  • Summary changed from ANDed Q filters spanning generic relations evaluate unexpectedly to empty querie sets to ANDed Q filters spanning generic relations evaluate unexpectedly to empty query sets

comment:3 Changed 2 years ago by anonymous

  • Needs documentation set
  • Needs tests set

comment:4 Changed 2 years ago by akaariai

  • Needs documentation unset
  • Needs tests unset
  • Resolution set to invalid
  • Status changed from new to closed
  • Summary changed from ANDed Q filters spanning generic relations evaluate unexpectedly to empty query sets to ANDed Q filters spanning generic relations evaluate unexpectedly to empty querie sets

Django is working as designed here. filters inside a single .filter() call target the same join. If you want multiple joins, then you will need to use multiple .filter() calls.

Really, this is not a bug. The API is defined that all the conditions inside single filter() target the same join, with multiple filter()s you get multiple joins. That you can workaround this by negated joins might contain a bug though...

comment:5 Changed 2 years ago by Alan Lu <gotoalanlu@…>

Apologies: my original post contained an error. I meant to refer to the distributive property of AND and OR. Therefore, in my original line of thought on the workaround, queries like

query = (Q(objs__name = "foo") & ~Q(objs__name = "bar")) | Q(objs__name = "baz")
ExampleModel.filter(query)

would instead be written as

query1 = Q(objs__name = "foo") | Q(objs__name = "baz")
query2 = ~Q(objs__name = "bar") | Q(objs__name = "baz")
ExampleModel.filter(query1).filter(query2)

though DeMorgan's laws could certainly still be useful when unwrapping deeply nested logical statements with NOTs on parenthetical groups.

The intent of the code is to query for instances of a model with either a relationship with an object "foo" and no relationship with an object "bar" or a relationship with an object "baz."

Because I was unaware of the way SQL handles these relationships, I misjudged the capacity of the system, so it appears that the workaround is natural. Having read about SQL joins, I am now convinced that Django is working as designed, even though I am a bit surprised.

comment:6 Changed 2 years ago by Alan Lu <gotoalanlu@…>

Just in case somebody in the future misses things like I do, here is another StackOverflow question which addresses why this is right and good for M2M/generic relationships. As it is now, the system is perfectly capable.

Note: See TracTickets for help on using tickets.
Back to Top