Manage multiple languages with django-hvad in one formset

555 views Asked by At

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

0

There are 0 answers