The django-taggit library allows you to add a Tag for any resource in your Django site. The 1.X release of django-taggit includes a breaking change to add a unique constraint.
1.0.0 (2019-03-17)
Backwards incompatible: Remove support for Python 2.
Added has_changed() method to taggit.forms.TagField.
Added multi-column unique constraint to model TaggedItem on fields content_type, object_id, and tag. Databases that contain duplicates will need to add a data migration to resolve these duplicates.
Fixed TaggableManager.most_common() to always evaluate lazily. Allows placing a .most_common() query at the top level of a module.
Fixed setting the related_name on a tags manager that exists on a model named Name.
This means that any Custom Tag that was built under the 0.X version of django-taggit will need to have the duplicates removed from custom TaggedItem model class before the unique constraint can be applied. Here’s an example of how to use annotate to identify duplicate entries and save and exclude the initial row from the delete.
duplicate_tags = MYMODEL.objects.filter(content_type_id=FILTERED_CONTENT_ID)
.values("object_id", "content_type_id", "tag_id")
.annotate(count=Count("object_id"))
.annotate(save_id=Min("id"))
.filter(count__gt=1)
.values_list("save_id", "object_id", "content_type_id", "tag_id")
for save_id, object_id, content_type_id, tag_id in duplicate_tags:
MYMODEL.objects.filter(
object_id=object_id,
content_type_id=content_type_id,
tag_id=tag_id
).exclude(id=save_id).delete()