wagtail / wagtail-localize

Translation plugin for Wagtail CMS
https://wagtail-localize.org/
Other
222 stars 84 forks source link

Support for translatable foreign keys on inline models (MultipleChooserPanel) #710

Open alexalligator opened 1 year ago

alexalligator commented 1 year ago

Creating issue after posting to slack #multi-language and discussing with @zerolab

I have a translatable snippet which I add to a page model via an inline model:

@register_snippet
class Material(TranslatableMixin, models.Model):
    name = CharField(...)

class ProductPageMaterial(Orderable):

    page = ParentalKey(
        "app.ProductPage", on_delete=models.CASCADE, related_name="materials"
    )
    material = models.ForeignKey(
        "app.Material", on_delete=models.CASCADE, related_name="+"
    )

    panels = [
        FieldPanel("material"),
    ]

class ProductPage(Page):
    content_panels = Page.content_panels + [
        MultipleChooserPanel(
            "materials", chooser_field_name="material"
        ),
    ]

What happens:

  1. I publish a new default language (norwegian) ProductPage choosing a couple of materials
  2. I translate to english using wagtail-localize. The materials field is hidden from the UI (auto-sync behaviour)
  3. I inspect the new page's materials (instances of ProductPageMaterial) via shell

I see that a new ProductPageMaterial has been created by wagtail/wagtail-localize. It's page foreign key field is pointing the new english version of the page as expected. The material foreign key field, however, is still pointing to a norweigan material. I would expect to see the local (english) material in the same way I see the local page.

I have made this temporary workaround but don't image it will be relevant for any long term solution:

class InlineModelTranslationHelper:
    page: Page

    target_field_name: str

    def save(self, *args, **kwargs) -> None:
        # Force the target field locale to match the locale of the page.
        # This is because wagtail localize seems to translate the page foreign key
        # but leaves the other foreign key in the original language.
        target_obj: TranslatableMixin = getattr(self, self.target_field_name)
        if not self.page.locale == target_obj.locale:
            logger.info(
                f"Page {self.target_field_name} '{target_obj}' has locale "
                f"{target_obj.locale} but page has locale {self.page.locale}"
            )

            if local_obj := target_obj.get_translation_or_none(locale=self.page.locale):
                logger.info(f"... using an existing translation of {target_obj}.")
            else:
                logger.info(
                    f"... no existing translation for {target_obj} "
                    f"with locale {self.page.locale} so creating a new one."
                )
                # This will crash if the targer object is a page whose parent has not yet been translated.
                local_obj = target_obj.copy_for_translation(locale=self.page.locale)
                local_obj.save()
            setattr(self, self.target_field_name, local_obj)
        return super().save(*args, **kwargs)
underdoeg commented 9 months ago

I think if you also make ProductPageMaterial translatable it should show up?

enzedonline commented 9 months ago

The key doesn't get translated and will always point to the key in the locale the page was created in. You should be able to get the translated child object by localising it.

page.localized.material.localized

alexalligator commented 8 months ago

Thanks for your comments, I'll look into them and report back.