Skip to main content

Add tags to any model in Django via ModelViewSet

Project description

Django Flexi Tag

Build status Documentation status PyPI PyPI - Django version PyPI - Python version PyPI - License

A flexible and efficient tagging system for Django models that allows you to add, remove, and manage tags on any Django model with minimal configuration. Built with a service-only architecture for maximum compatibility and composability.

Features

  • 🚀 Service-Only Architecture: No manager conflicts, works with any existing QuerySet
  • 🔄 Composable Filtering: Preserves existing QuerySet filters when adding tag filtering
  • ⚡ Easy Integration: Works seamlessly with Django REST Framework ViewSets
  • 📦 Flexible Tag Storage: Uses PostgreSQL JSONField for efficient and flexible tag storage
  • 🤖 Automatic Model Generation: Generates auxiliary Tag models for your existing models
  • 📊 Bulk Operations: Support for bulk tag operations on multiple objects
  • 🔧 Custom Manager Friendly: Preserves your existing custom managers
  • 🌐 Django Compatibility: Works across multiple Django versions (1.11 to 5.0)
  • 🐍 Python Compatibility: Supports Python 3.5+

Installation

Installation using pip:

pip install dj-flexi-tag

For development and testing:

pip install dj-flexi-tag[dev,test]

For documentation development:

pip install dj-flexi-tag[docs]

For all dependencies:

pip install dj-flexi-tag[dev,test,docs]

Add flexi_tag to your INSTALLED_APPS:

INSTALLED_APPS = (
    # other apps here...
    'flexi_tag',
)

Testing

Run the tests with:

python runtests.py

Use the --verbose flag for more detailed output:

python runtests.py --verbose

Use the --interactive flag for interactive mode:

python runtests.py --interactive

Quick Start

1. Define Your Models with FlexiTagMixin

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

class Order(FlexiTagMixin):
    name = models.CharField(max_length=100)
    status = models.CharField(max_length=50)
    created_date = models.DateField()

    class Meta:
        app_label = 'myapp'

# Your custom managers work perfectly!
class CustomManager(models.Manager):
    def active(self):
        return self.filter(status='active')

class Product(FlexiTagMixin):
    name = models.CharField(max_length=100)
    is_active = models.BooleanField(default=True)

    objects = CustomManager()  # This is preserved!

    class Meta:
        app_label = 'myapp'

2. Generate Tag Models

Run the management command:

# Standard usage
python manage.py generate_tag_models

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

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

This will create a flexi_generated_model.py file in the same directory as your model, containing OrderTag and ProductTag models. The command will automatically:

  • Generate the tag models with JSONField for tags
  • Add import statements to your models.py file
  • Run makemigrations to create migration files

3. Apply Migrations

python manage.py migrate

Service-Only Usage 🚀

Instance Operations

from flexi_tag.utils.service import TaggableService

# Create a service instance for instance operations
service = TaggableService()

# Add tags to an instance
order = Order.objects.create(name="Order #123", status="active")
service.add_tag(order, "urgent")
service.add_tag(order, "priority")

# Bulk add tags
service.bulk_add_tags(order, ["important", "customer_vip"])

# Remove tags
service.remove_tag(order, "urgent")

# Get all tags for an instance
tags = service.get_tags(order)  # Returns: ["priority", "important", "customer_vip"]

QuerySet Filtering (The Power of Service-Only!)

# This is where the service-only approach shines!
# You can compose querysets with your existing filters + tag filters

# Create a service instance for filtering operations
service = TaggableService()

# Start with your existing queryset
orders = Order.objects.filter(status='active').filter(created_date__gte='2024-01-01')

# Add tag filtering - preserves all existing filters!
urgent_orders = service.filter_by_tag(orders, 'urgent')

# Chain multiple tag filters
priority_urgent = service.filter_by_tag(urgent_orders, 'priority')

# Or exclude by tag
non_archived = service.exclude_by_tag(orders, 'archived')

# Multiple tags (AND logic)
multi_tagged = service.filter_by_tags(orders, ['urgent', 'priority'])

# Any tag (OR logic)
any_tagged = service.filter_by_any_tag(orders, ['urgent', 'priority', 'vip'])

# Prefetch tag data for performance
orders_with_tags = service.with_tags(orders)

API Integration

from rest_framework.generics import ListAPIView
from flexi_tag.utils.service import TaggableService
from .models import Order
from .serializers import OrderSerializer

class OrderListAPIView(ListAPIView):
    serializer_class = OrderSerializer

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

    def get_queryset(self):
        queryset = Order.objects.all()

        # Apply regular filters
        status = self.request.query_params.get('status')
        if status:
            queryset = queryset.filter(status=status)

        created_date = self.request.query_params.get('created_date')
        if created_date:
            queryset = queryset.filter(created_date=created_date)

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

        return queryset

# Usage: /api/v1/orders/?status=active&created_date=2024-01-01&tag=urgent

ViewSet Integration

from rest_framework import viewsets
from flexi_tag.utils.views import TaggableViewSetMixin
from .models import Order

class OrderViewSet(TaggableViewSetMixin, viewsets.ModelViewSet):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer

# This gives you endpoints like:
# POST /api/orders/1/add_tag/ - {"key": "urgent"}
# POST /api/orders/1/bulk_add_tag/ - {"keys": ["urgent", "priority"]}
# POST /api/orders/1/remove_tag/ - {"key": "urgent"}
# GET  /api/orders/1/get_tags/
# GET  /api/orders/filter_by_tag/?key=urgent
# GET  /api/orders/exclude_by_tag/?key=archived

Why Service-Only Architecture? 🤔

Preserves Existing QuerySets

# Your complex querysets work perfectly
orders = Order.objects.select_related('customer').prefetch_related('items')
filtered = TaggableService.filter_by_tag(orders, 'urgent')  # All relations preserved!

No Manager Conflicts

# Your custom managers are never touched
products = Product.objects.active()  # Your custom method
tagged = TaggableService.filter_by_tag(products, 'featured')  # Service layer

Composable and Chainable

# Chain multiple operations seamlessly
result = (Order.objects
          .filter(status='active')
          .pipe(lambda qs: TaggableService.filter_by_tag(qs, 'urgent'))
          .filter(created_date__gte=today))

Explicit and Clear

# It's always clear what you're doing
TaggableService.filter_by_tag(queryset, 'tag')  # Obvious service call
# vs old approach with mysterious managers

Testing

Run the tests with:

python runtests.py

Use the --verbose flag for more detailed output:

python runtests.py --verbose

Use the --interactive flag for interactive mode:

python runtests.py --interactive

Documentation

For comprehensive documentation including advanced usage, custom validation, and detailed API reference, please visit: https://dj-flexi-tag.readthedocs.io

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Development Setup

  1. Clone the repository
  2. Create a virtual environment
  3. Install dependencies: pip install -e .[dev,test,docs]
  4. Run tests: python runtests.py

License

This project is licensed under the MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dj-flexi-tag-2.0.0.tar.gz (41.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dj_flexi_tag-2.0.0-py3-none-any.whl (14.5 kB view details)

Uploaded Python 3

File details

Details for the file dj-flexi-tag-2.0.0.tar.gz.

File metadata

  • Download URL: dj-flexi-tag-2.0.0.tar.gz
  • Upload date:
  • Size: 41.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.7.17

File hashes

Hashes for dj-flexi-tag-2.0.0.tar.gz
Algorithm Hash digest
SHA256 55434ad8e01beebaa82f83f42b254b4f720d12ed32323ff047f3793b602963ea
MD5 9757e4c1f2e8785be3dec6b7a99039a0
BLAKE2b-256 005f74f9223ec4646bd5475755046fd8e29b32b50d272a0dc8aabefcffa62894

See more details on using hashes here.

File details

Details for the file dj_flexi_tag-2.0.0-py3-none-any.whl.

File metadata

  • Download URL: dj_flexi_tag-2.0.0-py3-none-any.whl
  • Upload date:
  • Size: 14.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.7.17

File hashes

Hashes for dj_flexi_tag-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9d5307d2c09df931272a8fd41401b957d5d720fc6b1ad2ba3b33a4c2a4706465
MD5 557bf2bdd049207bd3b9b51be64797b8
BLAKE2b-256 41bed57551d6f83154c9da99cf0541d82186d3fe61a7a496a61432051977edd2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page