Django REST Framework Integration

Django i18n Fields provides first-class Django REST Framework (DRF) support with automatic serialization in the active language.

Quick Setup

Basic Serializer

Use LocalizedModelSerializer for automatic handling of localized fields:

# serializers.py
from i18n_fields.drf import LocalizedModelSerializer
from .models import Article

class ArticleSerializer(LocalizedModelSerializer):
    class Meta:
        model = Article
        fields = ['id', 'title', 'content', 'created_at']

Output (with English active):

{
    "id": 1,
    "title": "Hello World",
    "content": "This is the content.",
    "created_at": "2025-01-28T10:00:00Z"
}

How It Works

Automatic Translation

LocalizedModelSerializer automatically:

  1. Serializes localized fields to simple values (not dicts)

  2. Uses the active language from Django’s translation system

  3. Handles all localized field types correctly

  4. Supports DRF Spectacular for API documentation

# With active language set to Spanish
from django.utils import translation

with translation.override('es'):
    serializer = ArticleSerializer(article)
    print(serializer.data['title'])  # "Hola Mundo"

Field Mapping

The serializer maps localized fields to appropriate DRF fields:

  • LocalizedCharFieldCharField

  • LocalizedTextFieldCharField

  • LocalizedIntegerFieldIntegerField

  • LocalizedFloatFieldFloatField

  • LocalizedBooleanFieldBooleanField

  • LocalizedFileFieldFileField

  • LocalizedUniqueSlugFieldSlugField

ViewSets

Basic ViewSet

Use with any DRF viewset:

# views.py
from rest_framework import viewsets
from .models import Article
from .serializers import ArticleSerializer

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

Language Negotiation

Use Django’s language negotiation with the Accept-Language header:

# views.py
from rest_framework import viewsets
from django.utils import translation

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

    def get_serializer_context(self):
        context = super().get_serializer_context()
        # Language is already set from middleware
        context['language'] = translation.get_language()
        return context

API Request Example:

curl -H "Accept-Language: es" http://api.example.com/articles/1/

Response:

{
    "id": 1,
    "title": "Hola Mundo",
    "content": "Este es el contenido."
}

Creating Objects

Simple Input

Send translations as a dictionary:

# POST /api/articles/
{
    "title": {
        "en": "Hello World",
        "es": "Hola Mundo"
    },
    "content": {
        "en": "Content in English",
        "es": "Contenido en español"
    }
}

The serializer automatically converts dictionaries to LocalizedValue objects.

Single Language Input

Send a string to set only the active language:

# POST /api/articles/ (with Accept-Language: en)
{
    "title": "Hello World",
    "content": "Content in English"
}

This sets values only for English.

Partial Updates

Update specific languages:

# PATCH /api/articles/1/
{
    "title": {
        "es": "Nuevo Título"  # Only update Spanish
    }
}

Custom Serializers

Field-Level Customization

Override specific fields:

from rest_framework import serializers
from i18n_fields.drf import LocalizedModelSerializer

class ArticleSerializer(LocalizedModelSerializer):
    # Custom read-only field
    summary = serializers.SerializerMethodField()

    class Meta:
        model = Article
        fields = ['id', 'title', 'content', 'summary']

    def get_summary(self, obj):
        # Returns translated content
        return obj.content.translate()[:100]

Full Translation Output

Return all translations in the response:

from rest_framework import serializers
from i18n_fields.drf import LocalizedModelSerializer

class ArticleDetailSerializer(LocalizedModelSerializer):
    all_translations = serializers.SerializerMethodField()

    class Meta:
        model = Article
        fields = ['id', 'title', 'content', 'all_translations']

    def get_all_translations(self, obj):
        return {
            'title': dict(obj.title),
            'content': dict(obj.content)
        }

Output:

{
    "id": 1,
    "title": "Hello World",
    "content": "Content in English",
    "all_translations": {
        "title": {
            "en": "Hello World",
            "es": "Hola Mundo",
            "fr": "Bonjour le Monde"
        },
        "content": {
            "en": "Content in English",
            "es": "Contenido en español"
        }
    }
}

Nested Serializers

Localized fields work in nested serializers:

class AuthorSerializer(LocalizedModelSerializer):
    class Meta:
        model = Author
        fields = ['id', 'name', 'bio']

class ArticleSerializer(LocalizedModelSerializer):
    author = AuthorSerializer(read_only=True)

    class Meta:
        model = Article
        fields = ['id', 'title', 'content', 'author']

Output:

{
    "id": 1,
    "title": "Hello World",
    "content": "Content",
    "author": {
        "id": 1,
        "name": "John Doe",
        "bio": "Author bio in English"
    }
}

Filtering

Filter by Language

Use query parameters to filter by specific languages:

from rest_framework import viewsets
from django_filters import rest_framework as filters

class ArticleFilter(filters.FilterSet):
    title_en = filters.CharFilter(field_name='title__en', lookup_expr='icontains')
    title_es = filters.CharFilter(field_name='title__es', lookup_expr='icontains')

    class Meta:
        model = Article
        fields = ['title_en', 'title_es']

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    filterset_class = ArticleFilter

Usage:

# Filter by English title
GET /api/articles/?title_en=hello

# Filter by Spanish title
GET /api/articles/?title_es=hola

Ordering

Order by Localized Fields

from rest_framework import viewsets
from i18n_fields import L

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

    def get_queryset(self):
        queryset = super().get_queryset()
        # Order by title in current language
        return queryset.order_by(L('title'))

DRF Spectacular Integration

Automatic Schema Generation

Django i18n Fields works seamlessly with DRF Spectacular:

# settings.py
INSTALLED_APPS = [
    # ...
    'drf_spectacular',
    'i18n_fields',
]

REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}

SPECTACULAR_SETTINGS = {
    'TITLE': 'Your API',
    'VERSION': '1.0.0',
}

The schema will show localized fields as simple types (string, integer, etc.) in the active language.

Custom Schema

Customize the schema for localized fields:

from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers

class ArticleSerializer(LocalizedModelSerializer):
    @extend_schema_field(serializers.CharField)
    title = serializers.CharField()

    class Meta:
        model = Article
        fields = ['id', 'title', 'content']

Pagination

Localized fields work with pagination:

from rest_framework.pagination import PageNumberPagination

class ArticlePagination(PageNumberPagination):
    page_size = 10

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    pagination_class = ArticlePagination

Permissions

Permissions work normally with localized fields:

from rest_framework import permissions

class IsAuthorOrReadOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True
        return obj.author == request.user

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [IsAuthorOrReadOnly]

Complete Example

Here’s a full example with all features:

# serializers.py
from rest_framework import serializers
from i18n_fields.drf import LocalizedModelSerializer
from .models import Article, Category

class CategorySerializer(LocalizedModelSerializer):
    class Meta:
        model = Category
        fields = ['id', 'name']

class ArticleListSerializer(LocalizedModelSerializer):
    category = CategorySerializer(read_only=True)
    author_name = serializers.CharField(source='author.name', read_only=True)

    class Meta:
        model = Article
        fields = ['id', 'title', 'category', 'author_name', 'created_at']

class ArticleDetailSerializer(LocalizedModelSerializer):
    category = CategorySerializer(read_only=True)
    all_translations = serializers.SerializerMethodField()

    class Meta:
        model = Article
        fields = [
            'id', 'title', 'content', 'category',
            'author', 'created_at', 'all_translations'
        ]

    def get_all_translations(self, obj):
        return {
            'title': dict(obj.title),
            'content': dict(obj.content),
        }

# views.py
from rest_framework import viewsets, filters
from django_filters import rest_framework as django_filters
from i18n_fields import L
from .models import Article
from .serializers import ArticleListSerializer, ArticleDetailSerializer

class ArticleFilter(django_filters.FilterSet):
    title = django_filters.CharFilter(field_name='title__en', lookup_expr='icontains')

    class Meta:
        model = Article
        fields = ['category', 'author']

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.select_related('author', 'category')
    filter_backends = [django_filters.DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_class = ArticleFilter
    search_fields = ['title__en', 'title__es', 'content__en']
    ordering_fields = ['created_at']

    def get_serializer_class(self):
        if self.action == 'list':
            return ArticleListSerializer
        return ArticleDetailSerializer

    def get_queryset(self):
        queryset = super().get_queryset()
        return queryset.order_by(L('title'))

# urls.py
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet

router = DefaultRouter()
router.register(r'articles', ArticleViewSet)

urlpatterns = router.urls

Best Practices

  1. Use LocalizedModelSerializer

    Always use the specialized serializer for models with localized fields:

    # Good
    class ArticleSerializer(LocalizedModelSerializer):
        pass
    
    # Missing automatic translation
    class ArticleSerializer(serializers.ModelSerializer):
        pass
    
  2. Language Negotiation

    Use Django’s middleware for automatic language detection:

    MIDDLEWARE = [
        # ...
        'django.middleware.locale.LocaleMiddleware',
    ]
    
  3. Return All Translations for Admin

    Provide endpoints that return all translations for admin interfaces:

    class ArticleAdminSerializer(LocalizedModelSerializer):
        translations = serializers.SerializerMethodField()
    
        def get_translations(self, obj):
            return {'title': dict(obj.title)}
    
  4. Validate Required Languages

    Add custom validation for required languages:

    def validate_title(self, value):
        if not value.get('en'):
            raise serializers.ValidationError('English title required')
        return value
    
  5. Use Filtering and Search

    Enable filtering by specific languages for better search:

    search_fields = ['title__en', 'title__es', 'content__en']
    

Troubleshooting

Serializer Not Translating

Ensure you’re using LocalizedModelSerializer:

from i18n_fields.drf import LocalizedModelSerializer

Wrong Language Returned

Check the active language:

from django.utils import translation
print(translation.get_language())

DRF Not Installed

Install Django REST Framework:

pip install djangorestframework

Next Steps