From bd860f5e31ac5586cd88bdaf81ad47170a49093a Mon Sep 17 00:00:00 2001 From: Miguel Johnson Date: Thu, 25 Jul 2024 00:36:40 -0400 Subject: [PATCH 1/4] create management command to remove orphaned tags --- taggit/management/commands/remove_orphaned_tags.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 taggit/management/commands/remove_orphaned_tags.py diff --git a/taggit/management/commands/remove_orphaned_tags.py b/taggit/management/commands/remove_orphaned_tags.py new file mode 100644 index 00000000..cb84efb3 --- /dev/null +++ b/taggit/management/commands/remove_orphaned_tags.py @@ -0,0 +1,13 @@ +from django.core.management.base import BaseCommand + +from taggit.models import Tag + + +class Command(BaseCommand): + help = "Remove orphaned tags" + + def handle(self, *args, **options): + orphaned_tags = Tag.objects.filter(taggit_taggeditem_items=None) + count = orphaned_tags.count() + orphaned_tags.delete() + self.stdout.write(f"Successfully removed {count} orphaned tags") From 874d2d5aaff50324f6129a00b3e7653633fb8363 Mon Sep 17 00:00:00 2001 From: Miguel Johnson Date: Thu, 25 Jul 2024 00:51:47 -0400 Subject: [PATCH 2/4] adding a note to the CHANGELOG --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2be66e3f..cd7b6cd0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,7 @@ Changelog (Unreleased) ~~~~~~~~~~~~ +* Added a management command (``remove_orphaned_tags``) to remove orphaned tags 6.0.0 (2024-07-27) ~~~~~~~~~~~~~~~~~~ From c668894d13b9220aa3cbd0f8d0600314ebdec3e0 Mon Sep 17 00:00:00 2001 From: Raphael Gaschignard Date: Tue, 30 Jul 2024 21:39:14 +1000 Subject: [PATCH 3/4] Make sure to use the deletion result count --- taggit/management/commands/remove_orphaned_tags.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/taggit/management/commands/remove_orphaned_tags.py b/taggit/management/commands/remove_orphaned_tags.py index cb84efb3..1bd799e7 100644 --- a/taggit/management/commands/remove_orphaned_tags.py +++ b/taggit/management/commands/remove_orphaned_tags.py @@ -8,6 +8,5 @@ class Command(BaseCommand): def handle(self, *args, **options): orphaned_tags = Tag.objects.filter(taggit_taggeditem_items=None) - count = orphaned_tags.count() - orphaned_tags.delete() + count = orphaned_tags.delete() self.stdout.write(f"Successfully removed {count} orphaned tags") From 3b15951745f33b2bac73fd3062385d969ca7d14a Mon Sep 17 00:00:00 2001 From: Raphael Gaschignard Date: Tue, 30 Jul 2024 21:47:03 +1000 Subject: [PATCH 4/4] Include orphaned tags tests --- tests/test_remove_orphaned_tags.py | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/test_remove_orphaned_tags.py diff --git a/tests/test_remove_orphaned_tags.py b/tests/test_remove_orphaned_tags.py new file mode 100644 index 00000000..c66a9c9c --- /dev/null +++ b/tests/test_remove_orphaned_tags.py @@ -0,0 +1,58 @@ +from django.core.management import call_command +from django.test import TestCase + +from taggit.models import Tag +from tests.models import Food, HousePet + + +class RemoveOrphanedTagsTests(TestCase): + def setUp(self): + # Create some tags, some orphaned and some not + self.orphan_tag1 = Tag.objects.create(name="Orphan1") + self.orphan_tag2 = Tag.objects.create(name="Orphan2") + self.used_tag = Tag.objects.create(name="Used") + + # Create instances of Food and HousePet and tag them + self.food_item = Food.objects.create(name="Apple") + self.pet_item = HousePet.objects.create(name="Fido") + + self.food_item.tags.add(self.used_tag) + self.pet_item.tags.add(self.used_tag) + + def test_remove_orphaned_tags(self): + # Ensure the setup is correct + self.assertEqual(Tag.objects.count(), 3) + self.assertEqual(Tag.objects.filter(taggit_taggeditem_items=None).count(), 2) + + # Call the management command to remove orphaned tags + call_command("remove_orphaned_tags") + + # Check the count of tags after running the command + self.assertEqual(Tag.objects.count(), 1) + self.assertEqual(Tag.objects.filter(taggit_taggeditem_items=None).count(), 0) + + # Ensure that the used tag still exists + self.assertTrue(Tag.objects.filter(name="Used").exists()) + self.assertFalse(Tag.objects.filter(name="Orphan1").exists()) + self.assertFalse(Tag.objects.filter(name="Orphan2").exists()) + + def test_no_orphaned_tags(self): + # Ensure the setup is correct + self.assertEqual(Tag.objects.count(), 3) + self.assertEqual(Tag.objects.filter(taggit_taggeditem_items=None).count(), 2) + + # Add used_tag to food_item to make no tags orphaned + self.food_item.tags.add(self.orphan_tag1) + self.food_item.tags.add(self.orphan_tag2) + + # Call the management command to remove orphaned tags + call_command("remove_orphaned_tags") + + # Check the count of tags after running the command + self.assertEqual(Tag.objects.count(), 3) + self.assertEqual(Tag.objects.filter(taggit_taggeditem_items=None).count(), 0) + + # Ensure all tags still exist + self.assertTrue(Tag.objects.filter(name="Used").exists()) + self.assertTrue(Tag.objects.filter(name="Orphan1").exists()) + self.assertTrue(Tag.objects.filter(name="Orphan2").exists())