0

I'm attempting to dynamically add translatable fields for my forms.ModelForm, depending on whether the customer has enabled the language or not. However, the translated value isn't saved to the model.

from copy import deepcopy
from django import forms
from modeltranslation.fields import TranslationField

class DefaultModelForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        if user := self.request and self.request.user:
            company = user.company
            app_setting = company.settings
            default_lang = settings.MODELTRANSLATION_DEFAULT_LANGUAGE  # "en"
            default_search = f"_{default_lang}"
            to_add = []
            """Set field_order to the same order as set in Meta.fields, unless already set"""
            if self.field_order is None:
                self.field_order = list(self.Meta.fields)

            for modelfield in self._meta.model._meta.fields:
                """If we found a default translation field, add it to the list"""
                if (
                    (formfield := self.fields.get(modelfield.name, None))
                    and modelfield.name.endswith(default_search)
                    and isinstance(modelfield, TranslationField)
                ):
                    to_add.append(
                        {
                            "position": self.field_order.index(modelfield.name),
                            "formfield": deepcopy(formfield),
                            "name": modelfield.name.removesuffix(default_search),  # "description"
                            "languages": app_setting.get_additional_language_codes,  # ["es"]
                        }
                    )

            for addable in to_add:
                for lang in addable.get("languages"):
                    field_name = f"{addable.get('name')}_{lang}"  # "description_es"
                    formfield = addable.get("formfield")
                    formfield.label = formfield.label.replace(f"[{default_lang}]", f"[{lang}]")
                    formfield.required = False
                    formfield.initial = getattr(self.instance, field_name, "")

                    self.fields[field_name] = formfield
                    self.field_order.insert(addable.get("position", 0) + 1, field_name)

            self.order_fields(self.field_order)

This code allows me to render the fields accordingly. If the customer has selected to show e.g. "es" (Spanish), the translatable field ("description_en") will be copied and I create a new field ("description_es") in the right position inside the field_order. So far, all is well.

But when I POST the form, this is what happens inside my view:

    def form_valid(self, form):
        is_updating = True if form.instance.pk else False
        self.object = form.save()
        if is_updating:
            # Todo: print message
            pass
        breakpoint()
        """
        (Pdb++) form.is_valid()
        True
        (Pdb++) form.cleaned_data
        {'company': <Company: Test>, 'description_en': 'I have a library!', 'description_es': 'Tengo una biblioteca!'}
        (Pdb++) self.object = form.save()
        (Pdb++) self.object
        <Venue: TestVenue>
        (Pdb++) self.object.description_en
        'I have a library!'
        (Pdb++) self.object.description_es
        ''
        """
        return super().form_valid(form)

Here is what I don't understand: why isn't the description_es value saved to the object?

kunambi
  • 756
  • 1
  • 10
  • 25
  • tried using `create_translation_field` from `from modeltranslation.fields import create_translation_field` and then `formfield = translated_field.formfield()` with the same erroneous result. – kunambi Jul 30 '23 at 15:50

1 Answers1

0

I managed to find a solution.

The problem was within django.forms.models.BaseModelForm which removes any fields which aren't defined in Meta.fields, through the use of object_data = model_to_dict(instance, opts.fields, opts.exclude) in its __init__() method.

Solution became:

            # ...
            for addable in to_add:
                for lang in addable.get("languages"):
                    field_name = f"{addable.get('name')}_{lang}"  # "description_es"
                    formfield = addable.get("formfield")
                    formfield.label = formfield.label.replace(f"[{default_lang}]", f"[{lang}]")
                    formfield.required = False
                    formfield.initial = getattr(self.instance, field_name, "")

                    self.fields[field_name] = formfield
                    self.field_order.insert(addable.get("position", 0) + 1, field_name)
                    # these lines made the difference
                    if field_name not in self._meta.fields:
                        self._meta.fields = self._meta.fields + (field_name,)

            self.order_fields(self.field_order)
kunambi
  • 756
  • 1
  • 10
  • 25