Getting Started

Installation

pip install drf-choices-mixin

Or with uv:

uv add drf-choices-mixin

No changes to INSTALLED_APPS are needed — the mixin works purely at the viewset level.

Basic usage

1. Define a model with choices

from django.db import models


class Status(models.TextChoices):
    ACTIVE = "active", "Active"
    INACTIVE = "inactive", "Inactive"


class Priority(models.IntegerChoices):
    LOW = 1, "Low"
    MEDIUM = 2, "Medium"
    HIGH = 3, "High"


class Task(models.Model):
    title = models.CharField(max_length=100)
    status = models.CharField(max_length=20, choices=Status.choices)
    priority = models.IntegerField(choices=Priority.choices)
    description = models.TextField()

2. Add the mixin to your viewset

Place ChoicesMixin before the base viewset class in the inheritance list so its methods take priority:

from drf_choices_mixin import ChoicesMixin
from rest_framework.viewsets import ModelViewSet


class TaskViewSet(ChoicesMixin, ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer

3. Register the viewset with a router

from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register("tasks", TaskViewSet)

That’s it. Two new endpoints are now available.

Trying it out

All choicesGET /tasks/choices/

{
  "status": [
    {"value": "active", "display": "Active"},
    {"value": "inactive", "display": "Inactive"}
  ],
  "priority": [
    {"value": 1, "display": "Low"},
    {"value": 2, "display": "Medium"},
    {"value": 3, "display": "High"}
  ]
}

Only status and priority appear — title and description have no choices and are excluded automatically.

Single fieldGET /tasks/choices/status/

[
  {"value": "active", "display": "Active"},
  {"value": "inactive", "display": "Inactive"}
]

Field without choicesGET /tasks/choices/title/

{"detail": "Field 'title' does not have choices."}

Returns HTTP 404.

Supported choice formats

The mixin works with every way Django lets you define choices.

TextChoices / IntegerChoices enums

class Status(models.TextChoices):
    ACTIVE = "active", "Active"
    INACTIVE = "inactive", "Inactive"

status = models.CharField(choices=Status.choices)

Plain tuples

status = models.CharField(
    choices=[("active", "Active"), ("inactive", "Inactive")]
)

Grouped choices

Groups are flattened automatically in the response:

COLOR_CHOICES = [
    ("Warm", [("red", "Red"), ("orange", "Orange")]),
    ("Cool", [("blue", "Blue"), ("green", "Green")]),
]
color = models.CharField(choices=COLOR_CHOICES)
[
  {"value": "red", "display": "Red"},
  {"value": "orange", "display": "Orange"},
  {"value": "blue", "display": "Blue"},
  {"value": "green", "display": "Green"}
]

Next steps

See Configuration to customize endpoint names, filter fields, or change the response format.