diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index 19789149b..33f372f47 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -7,6 +7,7 @@ * [#5050](https://github.com/netbox-community/netbox/issues/5050) - Fix potential failure on `0016_replicate_interfaces` schema migration from old release * [#5109](https://github.com/netbox-community/netbox/issues/5109) - Fix representation of custom choice field values for webhook data * [#5111](https://github.com/netbox-community/netbox/issues/5111) - Allow use of tuples when specifying ObjectVar `query_params` +* [#5118](https://github.com/netbox-community/netbox/issues/5118) - Specifying an empty list of tags should clear assigned tags (REST API) --- diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index aa8f6ba69..3cc2d1991 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -101,24 +101,30 @@ class TaggedObjectSerializer(serializers.Serializer): tags = NestedTagSerializer(many=True, required=False) def create(self, validated_data): - tags = validated_data.pop('tags', []) + tags = validated_data.pop('tags', None) instance = super().create(validated_data) - return self._save_tags(instance, tags) + if tags is not None: + return self._save_tags(instance, tags) + return instance def update(self, instance, validated_data): - tags = validated_data.pop('tags', []) + tags = validated_data.pop('tags', None) # Cache tags on instance for change logging - instance._tags = tags + instance._tags = tags or [] instance = super().update(instance, validated_data) - return self._save_tags(instance, tags) + if tags is not None: + return self._save_tags(instance, tags) + return instance def _save_tags(self, instance, tags): if tags: instance.tags.set(*[t.name for t in tags]) + else: + instance.tags.clear() return instance diff --git a/netbox/extras/tests/test_tags.py b/netbox/extras/tests/test_tags.py index 694cd77d9..39aae49dc 100644 --- a/netbox/extras/tests/test_tags.py +++ b/netbox/extras/tests/test_tags.py @@ -59,3 +59,21 @@ class TaggedItemTest(APITestCase): sorted([t.name for t in site.tags.all()]), sorted(["Foo", "Bar", "New Tag"]) ) + + def test_clear_tagged_item(self): + site = Site.objects.create( + name='Test Site', + slug='test-site' + ) + site.tags.add("Foo", "Bar", "Baz") + data = { + 'tags': [] + } + self.add_permissions('dcim.change_site') + url = reverse('dcim-api:site-detail', kwargs={'pk': site.pk}) + + response = self.client.patch(url, data, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_200_OK) + self.assertEqual(len(response.data['tags']), 0) + site = Site.objects.get(pk=response.data['id']) + self.assertEqual(len(site.tags.all()), 0)