In many Django Rest Framework (DRF) projects, you may have models with fields that use ChoiceField to restrict the values that can be stored in the database. For example, a status field might have choices like ["Draft", "Published", "Archived"]. When building a frontend application, it’s often necessary to retrieve the available choices for these fields to render dropdowns, radio buttons, or other UI elements.
In this post, I’ll show you how to create a reusable GetFieldChoicesMixin that can be added to any ModelViewSet to provide the available choices for ChoiceField fields in your models.
Why Provide Field Choices?
Providing field choices in your API has several benefits:
- Frontend Flexibility: The frontend can dynamically render UI elements (e.g., dropdowns) based on the available choices.
- Consistency: Ensures that the frontend and backend are always in sync regarding the allowed values for a field.
- Reduced Hardcoding: Avoids hardcoding choices in the frontend, making your application more maintainable.
Implementation
The GetFieldChoicesMixin is a custom mixin that adds a field_choices action to your ModelViewSet. This action returns the available choices for all ChoiceField fields in the model.
Step 1: Create the Mixin
Here’s the implementation of the GetFieldChoicesMixin:
# core/api/mixins.py
from rest_framework import status
from rest_framework.response import Response
from rest_framework.decorators import action
class GetFieldChoicesMixin:
"""
Adds a GET action : `field_choices`
------------------------------------------------------------------------
Returns choices for BaseModel fields with attribute `choices`.
------------------------------------------------------------------------
"""
def get_choices(self, choices_tuple):
keys = ["name", "value"]
choices = [
{key: value for key, value in zip(keys, choice_tuple)}
for choice_tuple in choices_tuple
]
return choices
def get_choice_fields_data(self, choice_fields):
choice_fields_data: list = []
for choice_field in choice_fields:
field_data: dict = {}
field_data["name"] = choice_field.name
field_data["choices"] = self.get_choices(choice_field.choices)
choice_fields_data.append(field_data)
return choice_fields_data
def get_choice_fields(self, model_fields):
choice_fields: list = []
for field in model_fields:
if field.choices is not None:
choice_fields.append(field)
return choice_fields
@action(detail=False, methods=["get"])
def field_choices(self, request, *args, **kwargs):
queryset = self.get_queryset()
model = queryset.model
choice_fields = self.get_choice_fields(model._meta.fields)
response_data: dict = {}
response_data["model"] = model.__name__
response_data["fields"] = self.get_choice_fields_data(choice_fields)
return Response(response_data, status=status.HTTP_200_OK)
Step 2: Add the Mixin to Your ViewSets
You can now add the GetFieldChoicesMixin to your ModelViewSet or ReadOnlyModelViewSet classes. Here’s an example of how to do this:
# core/api/viewsets.py
from rest_framework import mixins, viewsets
from core.api.mixins import GetFieldChoicesMixin
class BaseModelViewSet(viewsets.ModelViewSet, GetFieldChoicesMixin):
pass
class BaseReadOnlyModelViewSet(viewsets.ReadOnlyModelViewSet, GetFieldChoicesMixin):
pass
With this setup, any ModelViewSet that inherits from BaseModelViewSet or BaseReadOnlyModelViewSet will automatically have the field_choices action available.
Example Usage
Let’s say you have a Post model with a status field that has the following choices:
# models.py
class Post(models.Model):
DRAFT = "Draft"
PUBLISHED = "Published"
ARCHIVED = "Archived"
STATUS_CHOICES = [
(DRAFT, "Draft"),
(PUBLISHED, "Published"),
(ARCHIVED, "Archived"),
]
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=DRAFT)
When you make a GET request to the field_choices action, you’ll get a response like this:
{
"model": "Post",
"fields": [
{
"name": "status",
"choices": [
{"name": "Draft", "value": "Draft"},
{"name": "Published", "value": "Published"},
{"name": "Archived", "value": "Archived"}
]
}
]
}
This response can be used by the frontend to dynamically render a dropdown for the status field.
How It Works
get_choices: Converts a tuple of choices (e.g.,[("Draft", "Draft"), ("Published", "Published")]) into a list of dictionaries withnameandvaluekeys.get_choice_fields_data: Iterates over the fields with choices and prepares the data for the response.get_choice_fields: Filters the model fields to find those withchoicesdefined.field_choices: The DRF action that handles theGETrequest and returns the choices for allChoiceFieldfields in the model.
Conclusion
The GetFieldChoicesMixin is a powerful and reusable way to provide field choices in your Django Rest Framework API. By adding this mixin to your ModelViewSet, you can ensure that your frontend always has access to the latest choices for ChoiceField fields, making your application more dynamic and maintainable.
If you found this post helpful, feel free to share it or leave a comment below! Happy coding! 🚀