Opened 5 years ago
Last modified 5 years ago
#30976 closed Bug
ManyToManyField does not create JOIN when target object contains a ForeignKey with the same name as the default reverse_query_name — at Initial Version
Reported by: | Ben Davis | Owned by: | nobody |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 2.2 |
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
Consider the following models. There is an Entity
model which can be one of two types, a USER or a GROUP. There are User
and UserGroup
models that represent the sub-types, each having a OneToOneField
pointing back to Entity
(basically model inheritance w/o subclassing).
An entity (which could be a user or a group) can exist another group (so, a user can belong to a group, and a group can also belong to a group)
class Entity(models.Model): entity_id = models.AutoField(primary_key=True) name = models.CharField(max_length=128) type = models.CharField(max_length=32, choices=( ('USER', 'User'), ('USER_GROUP', 'User group') )) groups = models.ManyToManyField( 'UserGroup', through='UserGroupMember', through_fields=('member_entity', 'user_group') ) class Meta: managed = False db_table = 'entity' unique_together = (('type', 'name'),) class User(models.Model): user_id = models.AutoField(primary_key=True) entity = models.OneToOneField(Entity, models.DO_NOTHING, unique=True) email = models.EmailField() class Meta: managed = False db_table = 'user' class UserGroup(models.Model): user_group_id = models.AutoField(primary_key=True) entity = models.OneToOneField(Entity, models.DO_NOTHING, unique=True, related_name='group') disabled = models.BooleanField() class Meta: managed = False db_table = 'user_group' def __str__(self): return self.entity.name class UserGroupMember(models.Model): user_group = models.ForeignKey(UserGroup, models.DO_NOTHING) member_entity = models.ForeignKey(Entity, models.DO_NOTHING) class Meta: managed = False db_table = 'user_group_member' unique_together = (('user_group', 'member_entity'),) def __str__(self): print(f'{self.user_group}->{self.member_entity}')
When I make the following query, it does not produce the expected join:
>>> entity = Entity.objects.get(pk=2) >>> print(entity.groups.all().query) SELECT "user_group"."user_group_id", "user_group"."entity_id", "user_group"."disabled" FROM "user_group" WHERE "user_group"."entity_id" = 2
This happens because the field accessor creates this implicit filter from the target model: UserGroup.objects.filter(entity__entity_id=2)
. It assumes the default reverse name, which happens to conflict with the entity
field on the target model.
A workaround is to simply supply the reverse_query_name
argument on the ManyToManyField
with something other than "entity"
. However, it took me quite a while to figure out what was happening here.
Django already warns about conflicting reverse names. It seems that there should also be a warning for this case.