I am using django-hvad to edit objects in different languages. I got a form (for the object) and a formset (for the objects attributes in different languages) as described in the documentation. The formset is shown in a tab per language which are build with django-crispy-forms.
Everything works fine as long as I do not use initials. My target is to preselect the language and hide the language_code-field so you may enter more than one language but you do not have to:
forms.py:
languages = [x[0] for x in settings.LANGUAGES]
class MyTitleTranslationForm(forms.ModelForm):
# language_code = forms.CharField()
class Meta:
fields = ['title'] # , 'language_code'
def __init__(self, *args, **kwargs):
super(MyTitleTranslationForm, self).__init__(*args, **kwargs)
# self.fields['language_code'].widget = forms.HiddenInput()
# Crispy
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.label_class = 'col-md-3'
self.helper.field_class = 'col-md-9'
self.helper.layout = Layout(
Div('id', 'title', 'language_code', 'DELETE',
role="tabpanel", css_class="tab-pane", css_id=self.initial.get('language_code'))
)
class MyBaseTranslationFormSet(BaseTranslationFormSet):
def __init__(self, *args, **kwargs):
super(MyBaseTranslationFormSet, self).__init__(*args, **kwargs)
self.used_language_codes = []
self.languages = []
actual_language = False
counter = 0
for form in self.forms:
if form.instance.id:
actual_language = form.initial.get('language_code', 'de')
else:
for language in languages:
if language not in self.used_language_codes:
actual_language = language
break
self.used_language_codes.append(actual_language)
self.languages.append({
'language': actual_language,
'error': bool(form.errors)
})
# DANGEROUS LINE
form.fields['language_code'].initial = actual_language
form.helper.layout.fields[0].css_id = actual_language
if counter == 0:
form.helper.layout.fields[0].css_class += " active"
counter += 1
Somewhere in django-hvad the initials are set again or overriden so the form is bound. This leads to invalid data because the content-field per language is not filled. It works as long as I set the initials in the same order as defined in settings.LANGUAGES but this may produce duplicate languages if you only fill the second tab/language: If you reload the view, both first tabs are filled with the second language. The first one because of the prefilled form in formset and the second one because of the initial.
views.py:
class CategoryEditView(TranslatableUpdateViewMixin, UpdateView):
model = Category
success_url = reverse_lazy('category:list-view')
form_class = modelform_factory(Category, form=CategoryForm)
translationformset_class = translationformset_factory(Category, form=MyTitleTranslationForm,
formset=MyBaseTranslationFormSet,
extra=len(settings.LANGUAGES),
max_num=len(settings.LANGUAGES))
def get_context_data(self, **kwargs):
context = super(_TranslatableViewMixin, self).get_context_data(**kwargs)
context['translationformset'] = self.translationformset_class(instance=self.object)
return context
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST, request.FILES, instance=self.object)
translationformset = self.translationformset_class(request.POST, request.FILES, instance=self.object)
if form.is_valid() and translationformset.is_valid():
self.object = form.save(commit=False)
translationformset.instance = self.object
translationformset.save()
self.object.save()
form.save_m2m()
return redirect(self.get_success_url())
else:
return render(request, self.get_template_names(), {
'form': form,
'translationformset': translationformset
})
The Category-model and Category form are pretty much standard so I don't attach them. I'm really curious if anyone ran into a similar problem or may even offer a solution for the one key question: How can I set initials in the formset dynamically?
Cheers, Marius