Django Tagulous

repository·main·Indexed 18 days ago

https://github.com/radiac/django-tagulous

A tagging library for Django providing SingleTagField and TagField. It allows developers to manage tags using string or list interfaces while utilizing native Django ORM queries via ForeignKey and ManyToManyField relationships. Key features include hierarchical tag trees, built-in autocomplete via Select2, and deep integration with the Django admin for rendering tags and merging tag sets.

Tokens
23.7K
Snippets
72
Records
101
Agent score
62%

What's inside django-tagulous

  1. Compare Tagulous with django-taggit and django-tagging

    main

    Tagulous is designed as a more feature-rich and performant alternative to django-taggit and django-tagging. Key differentiators include:

    • Native Relations: Unlike the generic relations used by other libraries, Tagulous uses ManyToManyField for its TagField, making it a first-class citizen in Django with faster, simpler queries.
    • Tag Model Flexibility: Supports independent or shared tag models, allowing multiple TagField instances on a single model to have distinct tag sets. It also supports custom tag models to store extra metadata.
    • Configuration: Offers granular control over tag behavior, including case sensitivity, forced lowercase, maximum tag counts, and custom delimiters (e.g., space vs. comma).
    • Autocomplete: Provides built-in autocomplete support via Select2 and AJAX views, with integrated Python/JavaScript parsers to ensure consistent tag string handling.
    • Admin Integration: Tag fields are fully integrated into the Django admin, supporting list_display, filtering, and specialized tools for renaming or merging tags.
    • Specialized Field Types: Includes SingleTagField (based on ForeignKey) for single-selection tagging and hierarchical tree support for sub-tags.
  2. Use SingleTagField and TagField in Django models

    main

    Django Tagulous provides two primary field types for adding tagging capabilities to your models:

    • SingleTagField: Acts like a CharField with dynamic choices. It is used when a model should have exactly one tag from a set of options.
    • TagField: Used for conventional tagging (multiple tags) or nested categorization (trees of tags).

    Both fields are built on top of ForeignKey or ManyToManyField relationships, allowing them to behave like native Django ORM fields while providing a string-based interface for easy manipulation.

    from django.db import models
    from tagulous.models import SingleTagField, TagField
    
    class Person(models.Model):
        name = models.CharField(max_length=255)
        # Use SingleTagField for a single choice from a list
        title = SingleTagField(initial="Mr, Mrs, Miss, Ms")
        # Use TagField for multiple tags
        skills = TagField()
  3. Implement hierarchical tag trees

    main

    Tagulous supports hierarchical tagging using a "tree mode." You can create sub-tags by using the / character within a tag name (e.g., parent/child).

    This mode enables:

    • Querying and navigating trees (parents, siblings, children, descendants).
    • Automatic slug and path generation for tree tags.
    • Renaming and merging entire subtrees via code or the Django admin.
  4. Use Tag Trees for hierarchical tags

    main

    By setting tree=True on a TagField, you can use slashes (/) in tag names to denote parent-child relationships (e.g., food/eating).

    Requirements:

    1. The TagField must have tree=True.
    2. The target tag model must subclass tagulous.models.TagTreeModel instead of TagModel.

    Tree Operations:

    • Root nodes: Filter where parent=None.
    • Children: Use the .children.all() property on a tag instance.
    • Descendants: Use the .get_descendants() method on a tag instance to get all nodes in the subtree.
    import tagulous.models
    
    class Person(models.Model):
        name = models.CharField(max_length=255)
        skills = tagulous.models.TagField(
            force_lowercase=True,
            max_count=5,
            tree=True,
        )
    
    class Hobbies(tagulous.models.TagTreeModel):
        class TagMeta:
            initial = "food/eating, food/cooking, gaming/football"
            force_lowercase = True
            autocomplete_view = 'myapp.views.hobbies_autocomplete'
    
    class Person(models.Model):
        name = models.CharField(max_length=255)
        hobbies = tagulous.models.TagField(to=Hobbies)
    
    # Usage
    person.hobbies = "food/eating/mexican, sport/football"
    person.save()
    
    # Querying
    root_nodes = Hobbies.objects.filter(parent=None)
    food_children = Hobbies.objects.get(name="food").children.all()
    descendants = Hobbies.objects.get(name="food").get_descendants()
  5. How Tagged Models work in Django Tagulous

    main

    A tagged model is a Django model that contains tag fields. By default, Tagulous automatically enhances your models to support advanced tagging features.

    Automatic Enhancement

    If TAGULOUS_ENHANCE_MODELS = True (the default), Tagulous listens for the class_prepared signal. When a model with tag fields is constructed, Tagulous dynamically injects the following into your model's hierarchy:

    • tagulous.models.TaggedModel as the base class.
    • tagulous.models.TaggedManager as the base class for your manager.
    • tagulous.models.TaggedQuerySet as the base class for your querysets.

    When this happens, you may notice your manager and queryset classes are prefixed with CastTagged (e.g., CastTaggedMyModelManager), indicating they have been automatically cast to their tagged equivalents.

    Core Abstractions

    • tagulous.models.TaggedModel: The base class for tagged models. It enables passing TagField values as keywords in the model constructor.
    • tagulous.models.TaggedManager: The base class for managers, ensuring querysets are subclasses of TaggedQuerySet.
    • tagulous.models.TaggedQuerySet: The base class for querysets. It enables get(), filter(), and exclude() to accept tag strings, and create()/get_or_create() to accept both strings and TagField values. It also provides similarity searching.
  6. Configure Tag Model Options

    main

    Tag model options define how a tag model behaves. You can set these in two ways:

    1. Custom Tag Models: If you are using a custom model for your tags, you must set options using the TagMeta class.
    2. Auto-generated Tag Models: If you are using auto-generated models (via TagField), you set options in the field arguments. Note that if multiple fields share an auto-generated model, only the first field can set these options.

    Once defined, these options are stored in a TagOptions instance accessible at MyTagModel.tag_options and shared with tag model fields at MyTaggedModel.tags.tag_options.

  7. How Tag Trees work in Django Tagulous

    main

    Tags can be organized into hierarchical structures using Tag Trees. In a tree, tags have parents, children, and siblings.

    Hierarchy Syntax

    • Nesting: Use the forward slash (/) to denote hierarchy. For example, Animal/Mammal/Cat represents a Cat whose parent is Mammal and grandparent is Animal.
    • Escaping Slashes: To include a literal forward slash in a tag name, escape it with a second slash. For example, Animal/Vegetable is written as Animal//Vegetable.

    Implementation

    To use tag trees, your tag model must subclass tagulous.models.TagTreeModel instead of the standard tagulous.models.TagModel. If you are using automatically-generated tag models, set the tree field option to True in your model definition.

    # Example of setting the tree option for auto-generated models
    # (Conceptual, as the specific syntax depends on how you define your model)
    class MyModel(models.Model):
        tags = TagulousField(tree=True)
  8. How protected tags work

    main

    Tagulous tracks usage via a count field. When a tag's count reaches 0, it is automatically deleted unless:

    1. The tag's protected field is set to True.
    2. The protect_all option has been configured.

    Important: Deletion only occurs when the count is updated (e.g., during addition or removal). If you create tags directly in the database with a count of 0, they will not be immediately deleted until a count update is triggered.

  9. Understand the Django Tagulous architecture

    main

    Django Tagulous works by intercepting model field definitions to provide advanced tagging capabilities:

    • Field Initialization: Tag model fields (defined in tagulous/models/fields.py) use the contribute_to_class method to add descriptors (from tagulous/models/descriptors.py) to the model. These descriptors act as getters/setters that interface with managers in tagulous/models/managers.py.
    • Tagged Models: For full support in constructors and querysets, models should use base classes from tagulous/models/tagged.py. This module includes a class_prepared signal listener that dynamically updates base classes for models containing tag fields.
    • Configuration: Field arguments are stored in a TagOptions instance (tagulous/models/options.py). Initial tags can be loaded via tagulous/models/initial.py or the initial_tags management command.
    • Forms & UI: When a ModelForm is created, the field's formfield method generates a tag form field (tagulous/forms.py) using TagOptions. This field utilizes tag widgets to render HTML.
    • Utilities: Tag string parsing and joining are handled by tagulous/utils.py.
    • Admin: The admin interface is enhanced in tagulous/admin.py through two mechanisms: registration (adding tag functionality to standard ModelAdmin) and tag model admin (for managing the tags themselves).
  10. How the Tag String Parser works

    main

    Tagulous uses a parser to convert tag strings into lists of tag names and vice versa.

    Delimiters

    • By default, tags can be separated by spaces or commas.
    • Commas take priority over spaces if both are present.
    • If the space_delimiter (Python) or spaceDelimiter (JS) option is set to False, only commas are used as delimiters.

    Quoting and Escaping

    • If a tag name contains a space or a comma, it must be enclosed in quote marks (e.g., '"shot put"').
    • Tagulous will automatically add these quotes when rendering a list of tags back into a string to ensure clarity.

    Tree Tags

    • For tree-based tag models, the tag name represents a full path separated by / (e.g., path/to/tag).
    • The parser treats the final part of the path as the tag label.
    • To include a literal slash in a tag name, escape it with another slash: slash//escaped.
  11. How TagField behaves on a Model Class (Unbound)

    main

    When accessed via a model class (e.g., MyModel.tags), a TagField behaves similarly to an unbound ManyToManyField. It provides access to the underlying tag configuration:

    • tag_model: The related tag model class.
    • tag_options: A TagOptions instance containing configuration (like force_lowercase or case_sensitive) derived from the tag model's tagmeta or field initialization arguments.
  12. Use SingleTagField for single-selection tagging

    main
    If you need a field that behaves like a CharField with dynamic choices that users can add to at runtime, use SingleTagField. It is implemented using a ForeignKey rather than a ManyToManyField, making it suitable for models that should only have one tag associated with them.