=============
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:

.. code-block:: python

    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:

.. code-block:: bash

    # 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:

.. code-block:: python

    # 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:

.. code-block:: python

    # 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:

.. code-block:: python

    # 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:

.. code-block:: bash

    python manage.py migrate

Adding API Support
===============

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

.. code-block:: python

    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
-----------------

.. code-block:: http

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

    {
      "key": "featured"
    }

Adding Multiple Tags
-----------------

.. code-block:: http

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

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

Removing a Tag
------------

.. code-block:: http

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

    {
      "key": "featured"
    }

Removing Multiple Tags
-------------------

.. code-block:: http

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

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

Bulk Operations
-------------

Add tags to multiple products:

.. code-block:: http

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

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

Remove tags from multiple products:

.. code-block:: http

    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:

.. code-block:: python

    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:

.. code-block:: python

    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
=============

.. code-block:: python

    # 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:

.. code-block:: python

    # 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:

.. code-block:: python

    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:

.. code-block:: python

    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:

* :doc:`advanced` - For custom tag validation, advanced queries, and more
* :doc:`api` - For a complete API reference
