CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-django-modeltranslation

Translates Django models using a registration approach without modifying original model classes.

Pending
Overview
Eval results
Files

forms-integration.mddocs/

Forms Integration

Form classes and field types specifically designed for handling translations in Django forms, providing automatic translation field inclusion and specialized widgets for multilingual content management.

Capabilities

Translation Model Forms

ModelForm subclass that automatically handles translation fields with proper field exclusion and widget configuration.

class TranslationModelForm(forms.ModelForm):
    """
    ModelForm subclass for translated models.
    
    Automatically:
    - Removes translation fields from form (keeps original field names)
    - Handles language-specific field rendering
    - Manages validation across translation fields
    """
    
    def __init__(self, *args, **kwargs):
        """Initialize form with translation field handling."""

Usage Example:

from modeltranslation.forms import TranslationModelForm

class ArticleForm(TranslationModelForm):
    class Meta:
        model = Article
        fields = ['title', 'content', 'author', 'published']
        
    def clean_title(self):
        """Custom validation for title field."""
        title = self.cleaned_data.get('title')
        if title and len(title) < 5:
            raise forms.ValidationError("Title must be at least 5 characters")
        return title

# Form automatically handles title_en, title_fr, etc. fields
form = ArticleForm(data={
    'title_en': 'English Title',
    'title_fr': 'Titre Français', 
    'content_en': 'English content',
    'content_fr': 'Contenu français',
    'author': 'John Doe',
    'published': True
})

Nullable Field Types

Specialized form fields for handling nullable translation values with proper empty value semantics.

class NullCharField(forms.CharField):
    """
    CharField subclass that returns None for empty values instead of empty string.
    
    Useful for nullable translation fields where distinction between
    None and empty string is important.
    """
    
    def to_python(self, value):
        """
        Convert form value to Python value.
        
        Parameters:
        - value: Form input value
        
        Returns:
        - str | None: Converted value, None for empty inputs
        """

class NullableField(forms.Field):
    """
    Form field mixin that ensures None values are preserved.
    
    Prevents casting None to other types (like empty string in CharField).
    Useful as base class for custom nullable form fields.
    """
    
    def to_python(self, value):
        """
        Convert form value, preserving None values.
        
        Parameters:
        - value: Form input value
        
        Returns:
        - Any | None: Converted value, preserving None
        """
    
    def has_changed(self, initial, data):
        """
        Check if field value has changed, handling None properly.
        
        Parameters:
        - initial: Initial field value
        - data: Current form data value
        
        Returns:
        - bool: True if value has changed
        """

Usage Example:

from modeltranslation.forms import NullCharField, NullableField

class CustomArticleForm(forms.ModelForm):
    # Use NullCharField for nullable string translations
    summary_en = NullCharField(required=False, widget=forms.Textarea)
    summary_fr = NullCharField(required=False, widget=forms.Textarea)
    
    class Meta:
        model = Article
        fields = ['title', 'content', 'summary']

Language-Specific Form Fields

Create forms with explicit language-specific fields for fine-grained control.

class MultilingualArticleForm(forms.Form):
    # English fields
    title_en = forms.CharField(max_length=255, label="Title (English)")
    content_en = forms.TextField(widget=forms.Textarea, label="Content (English)")
    
    # French fields  
    title_fr = forms.CharField(max_length=255, required=False, label="Title (French)")
    content_fr = forms.TextField(widget=forms.Textarea, required=False, label="Content (French)")
    
    # German fields
    title_de = forms.CharField(max_length=255, required=False, label="Title (German)")
    content_de = forms.TextField(widget=forms.Textarea, required=False, label="Content (German)")
    
    # Common fields
    author = forms.CharField(max_length=100)
    published = forms.BooleanField(required=False)
    
    def clean(self):
        """Cross-field validation for translations."""
        cleaned_data = super().clean()
        
        # Ensure at least one language version is provided
        has_en = cleaned_data.get('title_en') and cleaned_data.get('content_en')
        has_fr = cleaned_data.get('title_fr') and cleaned_data.get('content_fr')
        has_de = cleaned_data.get('title_de') and cleaned_data.get('content_de')
        
        if not (has_en or has_fr or has_de):
            raise forms.ValidationError(
                "At least one complete translation (title and content) is required."
            )
        
        return cleaned_data
    
    def save(self, commit=True):
        """Save form data to Article model."""
        article = Article(
            author=self.cleaned_data['author'],
            published=self.cleaned_data['published']
        )
        
        # Set translation fields
        for lang in ['en', 'fr', 'de']:
            title_key = f'title_{lang}'
            content_key = f'content_{lang}'
            
            if self.cleaned_data.get(title_key):
                setattr(article, title_key, self.cleaned_data[title_key])
            if self.cleaned_data.get(content_key):
                setattr(article, content_key, self.cleaned_data[content_key])
        
        if commit:
            article.save()
        
        return article

Form Widget Integration

Integration with translation-specific widgets for enhanced user experience.

from modeltranslation.widgets import ClearableWidgetWrapper

class TranslationForm(forms.ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'content', 'summary']
        widgets = {
            'summary_en': ClearableWidgetWrapper(forms.Textarea()),
            'summary_fr': ClearableWidgetWrapper(forms.Textarea()),
            'summary_de': ClearableWidgetWrapper(forms.Textarea()),
        }
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # Add language labels to fields
        languages = {'en': 'English', 'fr': 'French', 'de': 'German'}
        
        for field_name in ['title', 'content']:
            for lang_code, lang_name in languages.items():
                trans_field = f"{field_name}_{lang_code}"
                if trans_field in self.fields:
                    original_label = self.fields[trans_field].label or field_name
                    self.fields[trans_field].label = f"{original_label} ({lang_name})"

Required Language Validation

Enforce required languages in form validation.

class RequiredLanguageForm(TranslationModelForm):
    class Meta:
        model = Article
        fields = ['title', 'content']
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # Mark required language fields
        required_languages = ['en', 'fr']  # From translation options
        
        for field_name in ['title', 'content']:
            for lang in required_languages:
                trans_field = f"{field_name}_{lang}"
                if trans_field in self.fields:
                    self.fields[trans_field].required = True
    
    def clean(self):
        cleaned_data = super().clean()
        required_languages = ['en', 'fr']
        
        # Validate required languages have values
        for field_name in ['title', 'content']:
            for lang in required_languages:
                trans_field = f"{field_name}_{lang}"
                if not cleaned_data.get(trans_field):
                    self.add_error(
                        trans_field,
                        f"{field_name.title()} in {lang.upper()} is required"
                    )
        
        return cleaned_data

Formset Integration

Handle translation fields in Django formsets.

from django.forms import modelformset_factory

# Create formset for translation forms
ArticleFormSet = modelformset_factory(
    Article,
    form=TranslationModelForm,
    fields=['title', 'content'],
    extra=1
)

# Usage in views
def edit_articles(request):
    if request.method == 'POST':
        formset = ArticleFormSet(request.POST)
        if formset.is_valid():
            formset.save()
            return redirect('article_list')
    else:
        formset = ArticleFormSet(queryset=Article.objects.all())
    
    return render(request, 'articles/edit.html', {'formset': formset})

Dynamic Field Generation

Generate form fields dynamically based on available languages.

from modeltranslation.settings import AVAILABLE_LANGUAGES

class DynamicTranslationForm(forms.Form):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # Dynamically add fields for each language
        for lang in AVAILABLE_LANGUAGES:
            self.fields[f'title_{lang}'] = forms.CharField(
                max_length=255,
                required=(lang == 'en'),  # English required, others optional
                label=f"Title ({lang.upper()})"
            )
            
            self.fields[f'content_{lang}'] = forms.CharField(
                widget=forms.Textarea,
                required=(lang == 'en'),
                label=f"Content ({lang.upper()})"
            )

Advanced Usage

Custom Form Field Validation

Create custom validation rules for translation fields.

class TranslationValidationMixin:
    def validate_translation_completeness(self, field_base_name):
        """Validate that if one language is provided, related languages are too."""
        primary_lang = 'en'
        primary_field = f"{field_base_name}_{primary_lang}"
        
        if self.cleaned_data.get(primary_field):
            # If primary language is provided, check for required translations
            required_translations = ['fr']  # Configurable
            
            for lang in required_translations:
                trans_field = f"{field_base_name}_{lang}"
                if not self.cleaned_data.get(trans_field):
                    self.add_error(
                        trans_field,
                        f"Translation required when {primary_lang.upper()} is provided"
                    )

class ArticleForm(TranslationValidationMixin, TranslationModelForm):
    class Meta:
        model = Article
        fields = ['title', 'content']
    
    def clean(self):
        cleaned_data = super().clean()
        
        # Apply translation validation
        self.validate_translation_completeness('title')
        self.validate_translation_completeness('content')
        
        return cleaned_data

AJAX Form Updates

Handle AJAX form submissions with translation fields.

import json
from django.http import JsonResponse

def update_translation(request):
    if request.method == 'POST':
        form = TranslationModelForm(request.POST)
        
        if form.is_valid():
            article = form.save()
            
            # Return success with updated translation data
            return JsonResponse({
                'success': True,
                'article_id': article.id,
                'translations': {
                    'title_en': article.title_en,
                    'title_fr': article.title_fr,
                    'content_en': article.content_en,
                    'content_fr': article.content_fr,
                }
            })
        else:
            # Return validation errors
            return JsonResponse({
                'success': False,
                'errors': form.errors
            })
    
    return JsonResponse({'success': False, 'error': 'Invalid request'})

Form Rendering Helpers

Custom template tags and filters for rendering translation forms.

# In templatetags/translation_tags.py
from django import template
from modeltranslation.settings import AVAILABLE_LANGUAGES

register = template.Library()

@register.filter
def translation_fields(form, field_name):
    """Get all translation fields for a base field name."""
    fields = []
    for lang in AVAILABLE_LANGUAGES:
        trans_field_name = f"{field_name}_{lang}"
        if trans_field_name in form.fields:
            fields.append(form[trans_field_name])
    return fields

@register.inclusion_tag('translation/field_tabs.html')
def translation_field_tabs(form, field_name):
    """Render translation fields as language tabs."""
    return {
        'form': form,
        'field_name': field_name,
        'languages': AVAILABLE_LANGUAGES,
    }

Template Usage:

<!-- Load custom tags -->
{% load translation_tags %}

<!-- Render translation fields as tabs -->
{% translation_field_tabs form 'title' %}

<!-- Or iterate over translation fields -->
{% for field in form|translation_fields:'content' %}
    <div class="field-{{ field.name }}">
        {{ field.label_tag }}
        {{ field }}
        {{ field.errors }}
    </div>
{% endfor %}

Install with Tessl CLI

npx tessl i tessl/pypi-django-modeltranslation

docs

admin-integration.md

core-registration.md

field-management.md

forms-integration.md

index.md

query-interface.md

utils-configuration.md

tile.json