Quick Start

This guide will help you quickly implement tagging functionality in your Django application using dj-flexi-tag’s service-only architecture.

Preparing Your Models

First, let’s make your existing models “taggable” by adding the FlexiTagMixin:

from django.db import models
from flexi_tag.utils.models import FlexiTagMixin

class Product(FlexiTagMixin):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    # other fields...

class Article(FlexiTagMixin):
    title = models.CharField(max_length=200)
    content = models.TextField()
    # other fields...

The FlexiTagMixin doesn’t add any fields to your models - it simply marks them for tag model generation.

Generating Tag Models

After adding the FlexiTagMixin to your models, run the management command to generate the corresponding tag models:

# Standard usage
python manage.py generate_tag_models

# For testing what would be generated without creating files
python manage.py generate_tag_models --dry-run

# If models aren't detected after recent changes (development mode)
python manage.py generate_tag_models --force-reload

This command will create files named flexi_generated_model.py in the same directories as your models. It will also automatically run the makemigrations command to create the necessary migration files for the new tag models.

Note

If you’ve just added FlexiTagMixin to a model and the command doesn’t detect it, try using the --force-reload flag or restart your Django development server.

For example, the generated tag model will look like this:

# products/flexi_generated_model.py

# This file is auto-generated. Do not edit manually.
# Generated by the generate_tag_models command.

from django.db import models

from flexi_tag.utils.compat import GinIndex, JSONField


class ProductTag(models.Model):
    instance = models.OneToOneField(
        "products.Product",
        on_delete=models.CASCADE,
        primary_key=True,
    )
    tags = JSONField(default=list)

    class Meta:
        app_label = "products"
        db_table = "products_product_tag"
        indexes = [GinIndex(fields=["tags"])]

    def __str__(self):
        return "Tags for {}".format(self.instance)

The command will also automatically add the necessary import to your original models.py file:

# At the bottom of your imports in models.py
from .flexi_generated_model import ProductTag  # noqa

If you have multiple models with the FlexiTagMixin in the same app, the imports will be combined in a single line:

# At the bottom of your imports in models.py
from .flexi_generated_model import ArticleTag, ProductTag  # noqa

Creating and Applying Migrations

After the tag models have been generated and migrations created automatically, you only need to apply the migrations:

python manage.py migrate

Adding API Support

To expose tagging functionality through a REST API, add the TaggableViewSetMixin to your ViewSets:

from rest_framework import viewsets

from flexi_tag.utils.views import TaggableViewSetMixin

from .models import Product
from .serializers import ProductSerializer

class ProductViewSet(viewsets.ModelViewSet, TaggableViewSetMixin):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

This adds the following endpoints to your ViewSet:

  • POST /products/{id}/add_tag/ - Add a tag to a product

  • POST /products/{id}/bulk_add_tag/ - Add multiple tags to a product

  • POST /products/{id}/remove_tag/ - Remove a tag from a product

  • POST /products/{id}/bulk_remove_tags/ - Remove multiple tags from a product

  • POST /products/bulk_add_tags/ - Add tags to multiple products

  • POST /products/bulk_remove_tags_with_many_instances/ - Remove tags from multiple products

Using the API

Adding a Single Tag

POST /api/products/1/add_tag/
Content-Type: application/json

{
  "key": "featured"
}

Adding Multiple Tags

POST /api/products/1/bulk_add_tag/
Content-Type: application/json

{
  "keys": ["new", "sale", "limited-edition"]
}

Removing a Tag

POST /api/products/1/remove_tag/
Content-Type: application/json

{
  "key": "featured"
}

Removing Multiple Tags

POST /api/products/1/bulk_remove_tags/
Content-Type: application/json

{
  "keys": ["new", "sale"]
}

Bulk Operations

Add tags to multiple products:

POST /api/products/bulk_add_tags/
Content-Type: application/json

{
  "objects": [1, 2, 3, 4],
  "keys": ["clearance", "last-chance"]
}

Remove tags from multiple products:

POST /api/products/bulk_remove_tags_with_many_instances/
Content-Type: application/json

{
  "objects": [1, 2, 3, 4],
  "keys": ["new-arrival"]
}

Programmatic Usage (Service-Only Architecture)

The core power of dj-flexi-tag comes from its service-only architecture that works seamlessly with any QuerySet:

from flexi_tag.utils.service import TaggableService

# Create a service instance
service = TaggableService()

# Get a product instance
product = Product.objects.get(id=1)

# Add tags to instances
service.add_tag(instance=product, key="featured")
service.bulk_add_tags(instance=product, keys=["sale", "new"])

# Remove tags
service.remove_tag(instance=product, key="featured")

# Get all tags for an instance
tags = service.get_tags(product)

QuerySet Filtering (The Power of Service-Only!)

This is where the service-only approach truly shines - you can compose with any existing QuerySet:

from flexi_tag.utils.service import TaggableService

service = TaggableService()

# Start with your existing QuerySet - all filters preserved!
products = (Product.objects
            .filter(is_active=True)
            .select_related('category')
            .prefetch_related('reviews'))

# Add tag filtering - preserves all existing filters!
featured_products = service.filter_by_tag(products, 'featured')
sale_products = service.exclude_by_tag(products, 'out_of_stock')

# Multiple tag filtering
priority_products = service.filter_by_tags(products, ['featured', 'sale'])
any_special = service.filter_by_any_tag(products, ['featured', 'sale', 'new'])

# Performance optimization - prefetch tag data
products_with_tags = service.with_tags(products)

Bulk Operations

# Bulk operations on multiple instances
products = Product.objects.filter(in_stock=True)
service.bulk_add_tags_with_many_instances(instances=products, keys=["available"])
service.bulk_remove_tags_with_many_instances(instances=products, keys=["out_of_stock"])

Custom Exception Integration (Optional)

You can configure dj-flexi-tag to use your project’s base exception class:

# settings.py
FLEXI_TAG_BASE_EXCEPTION_CLASS = 'myproject.exceptions.BaseAPIException'

# Or for DRF projects:
FLEXI_TAG_BASE_EXCEPTION_CLASS = 'rest_framework.exceptions.APIException'

Now all flexi-tag exceptions will inherit from your base class:

from flexi_tag.exceptions import TagValidationException

try:
    service.add_tag(product, 'duplicate_tag')
except TagValidationException as e:
    # Now has your custom base class attributes!
    print(e.status_code)  # From your BaseAPIException

API Integration Example

If you’re using Django REST Framework, you can integrate tag filtering in your views:

from rest_framework.generics import ListAPIView
from flexi_tag.utils.service import TaggableService

class ProductListAPIView(ListAPIView):
    serializer_class = ProductSerializer

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.taggable_service = TaggableService()

    def get_queryset(self):
        queryset = Product.objects.filter(is_active=True)

        # Apply tag filter if provided - preserves existing filters!
        tag = self.request.query_params.get('tag')
        if tag:
            queryset = self.taggable_service.filter_by_tag(queryset, tag)

        return queryset

# Usage: /api/products/?tag=featured

Next Steps

Now that you have basic tagging functionality working, you can explore: