From c6c7bb219d81a4c9a191505486dc5241c03b7357 Mon Sep 17 00:00:00 2001 From: Raphael Gaschignard Date: Fri, 26 Jul 2024 10:59:37 +1000 Subject: [PATCH] Fix issue when merging tags when the item has both the old and new tag --- taggit/admin.py | 17 ++++++--- .../admin/taggit/merge_tags_form.html | 2 +- tests/test_admin.py | 36 +++++++++++++++++++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/taggit/admin.py b/taggit/admin.py index f55e5624..408a3d42 100644 --- a/taggit/admin.py +++ b/taggit/admin.py @@ -27,7 +27,7 @@ def get_urls(self): path( "merge-tags/", self.admin_site.admin_view(self.merge_tags_view), - name="merge_tags", + name="taggit_tag_merge_tags", ), ] return custom_urls + urls @@ -58,9 +58,18 @@ def merge_tags_view(self, request): tag = Tag.objects.get(id=tag_id) tagged_items = TaggedItem.objects.filter(tag=tag) for tagged_item in tagged_items: - tagged_item.tag = new_tag - TaggedItem.objects.filter(tag=tag).update(tag=new_tag) - # tag.delete() #this will delete the selected tags after merge...leaving out for now + if TaggedItem.objects.filter( + tag=new_tag, + content_type=tagged_item.content_type, + object_id=tagged_item.object_id, + ).exists(): + # we have the new tag as well, so we can just + # remove the tag association + tagged_item.delete() + else: + # point this taggedItem to the new one + tagged_item.tag = new_tag + tagged_item.save() self.message_user(request, "Tags have been merged", level="success") # clear the selected_tag_ids from session after merge is complete diff --git a/taggit/templates/admin/taggit/merge_tags_form.html b/taggit/templates/admin/taggit/merge_tags_form.html index 224cfc95..3a57d4be 100644 --- a/taggit/templates/admin/taggit/merge_tags_form.html +++ b/taggit/templates/admin/taggit/merge_tags_form.html @@ -6,7 +6,7 @@
{% csrf_token %} {% for field in form %}
diff --git a/tests/test_admin.py b/tests/test_admin.py index 3b33c829..be835623 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -1,6 +1,7 @@ from django.contrib.auth.models import User from django.test import TestCase from django.urls import reverse +from taggit.models import Tag from .models import Food @@ -10,6 +11,11 @@ def setUp(self): super().setUp() self.apple = Food.objects.create(name="apple") self.apple.tags.add("Red", "red") + self.pear = Food.objects.create(name="pear") + self.pear.tags.add("red", "RED") + self.peach = Food.objects.create(name="peach") + self.peach.tags.add("red", "Yellow") + user = User.objects.create_superuser( username="admin", email="admin@mailinator.com", password="password" ) @@ -40,3 +46,33 @@ def test_get_change(self): reverse("admin:tests_food_change", args=(self.apple.pk,)) ) self.assertEqual(response.status_code, 200) + + def test_tag_merging(self): + response = self.client.get(reverse("admin:taggit_tag_changelist")) + + # merging red and RED into Red + pks_to_select = [Tag.objects.get(name="red").pk, Tag.objects.get(name="RED").pk] + response = self.client.post( + reverse("admin:taggit_tag_changelist"), + data={"action": "render_tag_form", "_selected_action": pks_to_select}, + ) + # we're redirecting + self.assertEqual(response.status_code, 302) + # make sure what we expected got into the session keys + assert "selected_tag_ids" in self.client.session.keys() + self.assertEqual( + self.client.session["selected_tag_ids"], ",".join(map(str, pks_to_select)) + ) + + # let's do the actual merge operation + response = self.client.post( + reverse("admin:taggit_tag_merge_tags"), {"new_tag_name": "Red"} + ) + self.assertEqual(response.status_code, 302) + + # time to check the result of the merges + self.assertSetEqual({tag.name for tag in self.apple.tags.all()}, {"Red"}) + self.assertSetEqual({tag.name for tag in self.pear.tags.all()}, {"Red"}) + self.assertSetEqual( + {tag.name for tag in self.peach.tags.all()}, {"Yellow", "Red"} + )