#36954 closed Bug (invalid)
Filtering an ManyToManyField causes the `.values` call to be filtered
| Reported by: | Ryan "RB" Barnes | Owned by: | Mahi Singhal |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | 5.1 |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
Given two following tables:
class Technician(models.Model):
...
class Appointment(models.Model):
technicians = models.ManyToManyField(Technician)
If a have an appointment with two technicians assigned to it and create the follow queries I get results I would not expect
tech_1 = Technician()
tech_1.save()
tech_2 = Technician()
tech_2.save()
appointment = Appointment(technicians=[tech_1, tech_2])
appointment.save()
# No issues here
r1 = Appointment.objects.filter(id=appointment.id).values_list('technicians', flat=True)
assert tech_1.id in r1
assert tech_2.id in r1
# Now filter for a single technician
r2 = Appointment.objects.filter(id=appointment.id, technicians__in=[tech_1]).values_list('technicians', flat=True)
assert tech_1.id in r2
# Fails here as tech_2 has been filtered out
assert tech_2.id in r2
This is certainly because the SQL produced joins against the through model, but then filters it directly while also selecting from it.
I know I can get around this but using subqueries and ArrayAggs in various ways but it feels like a footgun that most users would miss.
Change History (3)
comment:1 by , 3 weeks ago
| Owner: | set to |
|---|---|
| Status: | new → assigned |
comment:2 by , 3 weeks ago
| Resolution: | → invalid |
|---|---|
| Status: | assigned → closed |
comment:3 by , 3 weeks ago
Hi Simon,
Thanks for the reply.
As stated at the end of my ticket I am aware of the possible workarounds but the requirement for a workaround is my problem with the SQL being generated.
My issue with the following querysets is that they produce different results because of the generated SQL and that is unexpected.
Appointment.objects.filter(id=appointment.id).values_list('technicians', flat=True)
# [1, 2]
Appointment.objects.filter(id=appointment.id, technicians__in=[tech_1]).values_list('technicians', flat=True)
# [1]
Accessing technicians from a filtered queryset produces different results
qs = Appointment.objects.filter(id=appointment.id, technicians__in=[tech_1])
qs.values_list('technicians', flat=True)
# [1]
qs.get().technicians.all()
# [1, 2]
The engineers I've talked to were not aware of this interaction as it feels unintuitive.
Either way I will keep this in mind as another thing that the ORM abstraction does not address.
Thank you for your time on this matter.
There's no bug here please refer to TicketClosingReasons/UseSupportChannels
Your second query literally generate
If you're interested in all technicians for a particular appointment where a particular technician is you effectively need to use
Exists