django-taggit
django-taggit copied to clipboard
Multiple TaggableManager() fields in single model
When trying to create a model like:
class Test(models.Model):
tag1 = TaggableManager()
tag2 = TaggableManager()
It gives the error: ValueError: You can't have two TaggableManagers with the same through model.
How can I allow multiple taggit fields for a single model? Thanks
TaggableManager should allow you to create a list of tags in just one field, why would you want to do two tags field? are they two type of tags?
TaggableManager should allow you to create a list of tags in just one field, why would you want to do two tags field? are they two type of tags?
I want two different types of tags (like interests and skills) in a single model.
@heyitme I'm not sure if you've explored this option, but I was able to achieve this behaviour - having 2 different sets of tags for a single model - by using custom tagging as described here: https://django-taggit.readthedocs.io/en/latest/custom_tagging.html#custom-tag . I created separate TagBase
and GenericTaggedItemBase
classes for each set. I can provide some snippets if that helps.
@yoccodog Thanks for the reply! I would greatly appreciate if you could post some snippets to help me out.
@heyitme Sure. Just taking the example at the linked doc above and expanding on it:
from django.db import models
from django.utils.translation import ugettext_lazy as _
from taggit.managers import TaggableManager
from taggit.models import TagBase, GenericTaggedItemBase
class MyCustomTag(TagBase):
# ... fields here
class Meta:
verbose_name = _("Tag")
verbose_name_plural = _("Tags")
# ... methods (if any) here
class MyOtherCustomTag(TagBase):
# ... fields here
class Meta:
verbose_name = _("Tag")
verbose_name_plural = _("Tags")
# ... methods (if any) here
class TaggedWhatever(GenericTaggedItemBase):
tag = models.ForeignKey(
MyCustomTag,
on_delete=models.CASCADE,
related_name="%(app_label)s_%(class)s_items",
)
class OtherTaggedWhatever(GenericTaggedItemBase):
tag = models.ForeignKey(
MyOtherCustomTag,
on_delete=models.CASCADE,
related_name="%(app_label)s_%(class)s_items",
)
class Food(models.Model):
# ... fields here
tags = TaggableManager(through=TaggedWhatever)
other_tags = TaggableManager(through=OtherTaggedWhatever)
@yoccodog Could you help me out. I used your code snippet and adapted it to my app.
class UserTags(TagBase):
class Meta:
verbose_name = "Tag"
verbose_name_plural = "Tags"
# ... methods (if any) here
class AdminTags(TagBase):
class Meta:
verbose_name = "Tag"
verbose_name_plural = "Tags"
# ... methods (if any) here
class UserTagsAll(GenericTaggedItemBase):
tag = models.ForeignKey(
UserTags,
on_delete=models.CASCADE,
related_name="%(app_label)s_%(class)s_items",
)
class AdminTagsAll(GenericTaggedItemBase):
tag = models.ForeignKey(
AdminTags,
on_delete=models.CASCADE,
related_name="%(app_label)s_%(class)s_items",
)
# Model for all uploaded files
class Uploaded(models.Model):
objects: models.Manager()
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="users")
time_uploaded = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=50)
file = models.FileField(upload_to=MEDIA_ROOT)
admintags = TaggableManager(blank=True, through=AdminTagsAll)
usertags = TaggableManager(blank=True, through=UserTagsAll)
additional_description = models.CharField(max_length=50, blank=True)
def __str__(self):
return f"{self.name} {self.file}"
I've deleted all of my tables and started everything from scratch but I'm getting an error that the table AdminTagsAll does not exist. When I go to admin, there's only one table named Tags.
Hi @MatejMijoski ! I'm not certain but I think you need to remove taggit
from INSTALLED_APPS
. At the top of the page on custom tagging (https://django-taggit.readthedocs.io/en/latest/custom_tagging.html#custom-tag) it says:
"Note: Including ‘taggit’ in settings.py INSTALLED_APPS list will create the default django-taggit and “through model” models. If you would like to use your own models, you will need to remove ‘taggit’ from settings.py’s INSTALLED_APPS list."
@yoccodog I removed taggit but I think that it still doesn't work. I'm still getting a no such table: FileUpload_admintagsall error.
@MatejMijoski Did you remove your old tag-related migrations, remake your migrations after removing taggit
from INSTALLED_APPS
, and re-run the new ones? You should get tables for each tag type and each join table.
I've encountered another problem after solving the previous one (I didn't add the models to admin.py). The new problem is that if I go the Generic Tagged Item Base models i.e. AdminTagsAll and UserTagsAll, I get the following error: no such table: FileUpload_admintagsall. I've deleted all migrations, I've dumped the SQL db and remade everything.
Do those tables exist in your db after running your migrations? What tables are you in your db after running your migrations?
I've got 4 tables: AdminTagsAll, UserTagsAll and 2 tables named Tags. I can access these 2 tables but when I go to AdminTagsAll or UserTagsAll I get the error.
@MatejMijoski When you say there are 2 tables named Tags, it sounds like you're not looking in the database but instead in django admin. Is that correct? If that's the case, the reason there are 2 tables named 'Tags' there is because the verbose_name
and verbose_name_plural
values are the same for both AdminTags
and UserTags
.
The error you're seeing sounds like the migrations have not been generated correctly. Can you post the migration files that were generated after you created your custom tag classes? Can you also post a listing of the tables in your database?
It looks like the tables weren't being created. I installed django-extensions and ran restart_db and then migrate. It now works. Thank you very much.
One more question, why do I need 2 tables for users and 2 for the admin? Doesn't it work the same with just the two TagBase tables?
That's great news!
With django-taggit
, the way that a set of tags (as defined by TagBase
) is associated with a model attribute (as defined via TaggableManager
) is through a specific join table (as defined by GenericTaggedItemBase
). If you were building your own tagging system with a similar structure, you could conceivably have a single join table with multiple tag sets. But you'd have to jump through some hoops with django-taggit
if you wanted to do this.
Consider the queries behind how the tags for a given instance are found: the join table is queried for records that have object_id
(and content_type
) which matches the instance's id (and type) and then the tag table is joined to to get the tag names. If you wanted to have a single join table with multiple tag sets, you would need to include some information in the join table about which tag set a given record is referencing.
In any case, glad you sorted this out!
@yoccodog Is there a way to have one of the tag fields (User Tags) in my model be a multiple select?
I'm using select2 for the user interface and am wondering if I could maybe use it for the admin as well?
@MatejMijoski You should be able to achieve this, but as for the exact implementation, I'm not sure right now. Good luck!
A use case that cannot be solved by multiple tag models is when you want to do something like:
class MyModel(...):
tags = TaggableManager()
class MyFilter(...):
tag = ForeignKey(tagmodel)
parent_tags = TaggableManager()
implied_tags = TaggableManager()
In this situation, parent tags and implied tags must come from the same tag pool. So the workaround proposed above cannot solve this issue.
@thenewguy you can solve this by creating two different TaggedItem
tables, but which both rely on the same Tag
class
class ParentTagItem(TaggedItemBase):
tag = ForeignKey(MyTagModel)
class ImpliedTagItem(TaggedItemBase):
tag = ForeignKey(MyTagModel) # points to the same tags!
class MyFilter(...):
tag = ForeignKey(tagmodel)
parent_tags = TaggableManager(through=ParentTagItem)
implied_tags = TaggableManager(through=ImpliedTagItem)
now of course a lot of your tag helpers will not work "as expected" per se, but you will be able to work off the same base of tags. You just need multiple through tables to track your stuff separately
OK this topic definitely seems worthy of some FAQ-ing
@rtpg I remember thinking that makes sense when reading it. Does this definitely work for you? I seem to get some variation of HINT: Add or change a related_name argument to the definition for 'MyFilter.parent_tags' or 'MyFilter.implied_tags'.
no matter what combination of TagBase
and ItemBase
I use. I've added unique related_name
to the tag ForeignKey and the content_object ForeignKey
For example, how would you make this work?
from taggit.managers import TaggableManager
from taggit.models import TagBase, ItemBase
class ContactSegment(TagBase):
pass
class TaggedAddOperationSegment(ItemBase):
tag = models.ForeignKey(ContactSegment, related_name="tagged_add_operation_segments", on_delete=models.CASCADE)
content_object = models.ForeignKey(
to='Operation',
on_delete=models.CASCADE,
related_name='related_name_for_operation_add'
)
class TaggedRemoveOperationSegment(ItemBase):
tag = models.ForeignKey(ContactSegment, related_name="tagged_remove_operation_segments", on_delete=models.CASCADE)
content_object = models.ForeignKey(
to='Operation',
on_delete=models.CASCADE,
related_name='related_name_for_operation_remove'
)
class Operation(models.Model):
add_segments = TaggableManager(through=TaggedAddOperationSegment, blank=True)
remove_segments = TaggableManager(through=TaggedRemoveOperationSegment, blank=True)
Error:
SystemCheckError: System check identified some issues:
ERRORS:
bulk_contacts.Operation.add_segments: (fields.E304) Reverse accessor for 'Operation.add_segments' clashes with reverse accessor for 'Operation.remove_segments'.
HINT: Add or change a related_name argument to the definition for 'Operation.add_segments' or 'Operation.remove_segments'.
bulk_contacts.Operation.remove_segments: (fields.E304) Reverse accessor for 'Operation.remove_segments' clashes with reverse accessor for 'Operation.add_segments'.
HINT: Add or change a related_name argument to the definition for 'Operation.remove_segments' or 'Operation.add_segments'.
Laughing at myself - read the error message.
class Operation(models.Model):
add_segments = TaggableManager(through=TaggedAddOperationSegment, blank=True, related_name='foo')
remove_segments = TaggableManager(through=TaggedRemoveOperationSegment, blank=True, related_name='bar')
So what's the correct way to achieve this?
class Operation(models.Model):
tags1 = TaggableManager()
tags2 = TaggableManager()
Is this not used anymore in 2024(is there batter methods) ? Why docs doesn't provide FAQ examples for this common problem?
@ChathuraGH This is a good point, we should have an FAQ for this, and some more explanations.
Short version of this is you want to use Custom Foreign Keys here. Basically create a separate through model for each.
Honestly this might be something we could support out of the box, without having to manually create your own through model.