Skip to content

Commit

Permalink
Fix similar_objects() in multi inheritance contexts (KeyError) (#712)
Browse files Browse the repository at this point in the history
* Rename Django's dev branch to main.

More information: https://groups.google.com/g/django-developers/c/tctDuKUGosc/
Refs: django/django#14048

* Add tests demonstrating similar_objects exceptions due to multi inheritance.

* Fix similar_objects to resolve field_names for multi inheritance.

* Moved hand-crafted migrations to django-automated migration.

Co-authored-by: Jannis Leidel <jannis@leidel.info>
  • Loading branch information
czr137 and jezdez committed Mar 18, 2021
1 parent 564fe78 commit 7f0e159
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 1 deletion.
3 changes: 2 additions & 1 deletion taggit/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,9 @@ def similar_objects(self):
% remote_field.field_name: [r["content_object"] for r in qs]
}
)
actual_remote_field_name = f.target_field.get_attname()
for obj in objs:
items[(getattr(obj, remote_field.field_name),)] = obj
items[(getattr(obj, actual_remote_field_name),)] = obj
else:
preload = {}
for result in qs:
Expand Down
93 changes: 93 additions & 0 deletions tests/migrations/0003_auto_20210310_0918.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Generated by Django 3.1.7 on 2021-03-10 09:18

import django.db.models.deletion
from django.db import migrations, models

import taggit.managers


class Migration(migrations.Migration):

dependencies = [
("taggit", "0003_taggeditem_add_unique_index"),
("contenttypes", "0002_remove_content_type_name"),
("tests", "0002_auto_20200214_1129"),
]

operations = [
migrations.CreateModel(
name="BaseFood",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=50)),
],
),
migrations.CreateModel(
name="MultiInheritanceFood",
fields=[
(
"basefood_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="tests.basefood",
),
),
],
bases=("tests.basefood",),
),
migrations.CreateModel(
name="MultiInheritanceLazyResolutionFoodTag",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"tag",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="tests_multiinheritancelazyresolutionfoodtag_items",
to="taggit.tag",
),
),
(
"content_object",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="tagged_items",
to="tests.multiinheritancefood",
),
),
],
options={
"unique_together": {("content_object", "tag")},
},
),
migrations.AddField(
model_name="multiinheritancefood",
name="tags",
field=taggit.managers.TaggableManager(
help_text="A comma-separated list of tags.",
through="tests.MultiInheritanceLazyResolutionFoodTag",
to="taggit.Tag",
verbose_name="Tags",
),
),
]
23 changes: 23 additions & 0 deletions tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,29 @@ def __str__(self):
return self.name


class BaseFood(models.Model):
name = models.CharField(max_length=50)

def __str__(self):
return self.name


class MultiInheritanceLazyResolutionFoodTag(TaggedItemBase):
content_object = models.ForeignKey(
"MultiInheritanceFood", related_name="tagged_items", on_delete=models.CASCADE
)

class Meta:
unique_together = [["content_object", "tag"]]


class MultiInheritanceFood(BaseFood):
tags = TaggableManager(through=MultiInheritanceLazyResolutionFoodTag)

def __str__(self):
return self.name


class Pet(models.Model):
name = models.CharField(max_length=50)

Expand Down
17 changes: 17 additions & 0 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
Food,
HousePet,
Movie,
MultiInheritanceFood,
Name,
OfficialFood,
OfficialHousePet,
Expand Down Expand Up @@ -164,6 +165,7 @@ class TagUUIDModelTestCase(TagModelTestCase):

class TaggableManagerTestCase(BaseTaggingTestCase):
food_model = Food
multi_inheritance_food_model = MultiInheritanceFood
pet_model = Pet
housepet_model = HousePet
taggeditem_model = TaggedItem
Expand Down Expand Up @@ -606,6 +608,21 @@ def test_exclude(self):
ordered=False,
)

def test_multi_inheritance_similarity_by_tag(self):
"""Test that pears are more similar to apples than watermelons using multi_inheritance"""
apple = self.multi_inheritance_food_model.objects.create(name="apple")
apple.tags.add("green", "juicy", "small", "sour")

pear = self.multi_inheritance_food_model.objects.create(name="pear")
pear.tags.add("green", "juicy", "small", "sweet")

watermelon = self.multi_inheritance_food_model.objects.create(name="watermelon")
watermelon.tags.add("green", "juicy", "large", "sweet")

similar_objs = apple.tags.similar_objects()
self.assertEqual(similar_objs, [pear, watermelon])
self.assertEqual([obj.similar_tags for obj in similar_objs], [3, 2])

def test_similarity_by_tag(self):
"""Test that pears are more similar to apples than watermelons"""
apple = self.food_model.objects.create(name="apple")
Expand Down

0 comments on commit 7f0e159

Please sign in to comment.