5. Forms

Although Django’s ModelForm can work with translatable models, they will only know about untranslatable fields. Don’t worry though, django-hvad’s got you covered with the following form types:


5.1. TranslatableModelForm

TranslatableModelForms work like ModelForm, but can display and edit translatable fields as well. There use is very similar, except the form must subclass TranslatableModelForm instead of ModelForm:

class ArticleForm(TranslatableModelForm):
    # language = 'en'       # See below
    class Meta:
        model = Article
        fields = ['pub_date', 'headline', 'content', 'reporter']

Notice the difference from Django's example? There is none but for the parent class. This ArticleForm will allow editing of one Article in one language, correctly introspecting the model to know which fields are translatable.

The language the form uses is computed this way:

  • if the form is given a model instance, it will use the language that instance was loaded with.
  • if this fails, it will look for a language attribute set on the form.
  • if this fails, it will use the current language, as returned by get_language(). If a request is being processed, that will be the language of the request.

In all cases, any language_code field sent with form data will be ignored. It is the reponsibility of calling code to ensure the data matches the language of the form.

All features of Django’s form work as usual. Just be careful while overriding the save() or _post_clean() methods, as they are crucial parts for the form to work.

5.2. TranslatableModelForm factory

Similar to Django’s ModelForm factory, hvad eases the generation of uncustomized forms by providing a factory:

BookForm = translatable_modelform_factory('en', Book, fields=('author', 'title'))

The translation-aware version works exactly the same way as the original one, except it takes the language the form should use as an additional argument.

5.3. TranslatableModel Formset

Similar to Django’s ModelFormset factory, hvad provides a factory to create formsets of translatable models:

AuthorFormSet = translatable_modelformset_factory('en', Author)

It is also possible to override the queryset, the same way you would do it for a regular formset. In fact, it is recommended, as the default will not prefetch translations:

BookForm = translatable_modelformset_factory(
    'en', Book, fields=('author', 'title'),
    queryset=Book.objects.language().filter(name__startswith='O'),
)

Using language() ensures translations will be loaded at once, and allows filtering on translated fields.

Note

To override the form by passing a form= argument to the factory, the custom form must inherit TranslatableModelForm.

5.4. TranslatableModel Inline Formset

Similar to Django’s inline formset factory, hvad provides a factory to create inline formsets of translatable models:

BookFormSet = translatable_inlineformset_factory('en', Author, Book)

Note

To override the form by passing a form= argument to the factory, the custom form must inherit TranslatableModelForm.

5.5. Translations Formset

Basic usage

The translation formset allows one to edit all translations of an instance at once: adding new translations, updating and deleting existing ones. It works mostly like regular BaseInlineFormSet except it automatically sets itself up for working with the Translations Model of given TranslatableModel.

Example:

from django.forms.models import modelform_factory
from hvad.forms import translationformset_factory
from myapp.models import MyTranslatableModel

MyUntranslatableFieldsForm = modelform_factory(MyTranslatableModel)
MyTranslationsFormSet = translationformset_factory(MyTranslatableModel)

Now, MyUntranslatableFieldsForm is a regular, Django, translation-unaware form class, showing only the untranslatable fields of an instance, while MyTranslationsFormSet is a formset class showing only the translatable fields of an instance, with one form for each available translation (plus any additional forms requested with the extra parameter - see modelform_factory()).

Custom Translation Form

As with regular formsets, one may specify a custom form class to use. For instance:

class MyTranslationForm(ModelForm):
    class Meta:
        fields = ['title', 'content', 'slug']

MyTranslationFormSet = translationformset_factory(
    MyTranslatableModel, form=MyTranslationForm, extra=1
)

Note

The translations formset will use a language_code field if defined, or create one automatically if none was defined.

One may also specify a custom formset class to use. It must inherit BaseTranslationFormSet.

Wrapping it up: editing the whole instance

A common requirement, being able to edit the whole instance at once, can be achieved by combining a regular, translation unaware ModelForm with a translation formset in the same view. It works the way one would expect it to. The following code samples highlight a few gotchas.

Creating the form and formset for the object:

FormClass = modelform_factory(MyTranslatableModel)
TranslationsFormSetClass = translationformset_factory(MyTranslatablemodel)

self.object = self.get_object()
form = FormClass(instance=self.object, data=request.POST)
formset = TranslationsFormSetClass(instance=self.object, data=request.POST)

Checking submitted form validity:

if form.is_valid() and formset.is_valid():
    form.save(commit=False)
    formset.save()
    self.object.save_m2m()  # only if our model has m2m relationships
    return HttpResponseRedirect('/confirm_edit_success.html')

Note

When saving the formset, translations will be recombined with the main object, and saved as a whole. This allows custom save() defined on the model to be called properly and signal handlers to be fed a full instance. For this reason, we use commit=False while saving the form, avoiding a useless query.

Warning

You must ensure that form.instance and formset.instance reference the same object, so that saving the formset does not overwrite the values computed by form.

A common way to use this view would be to render the form on top, with the formset below it, using JavaScript to show each translation in a tab.


Next, we will take a look at the administration panel.