Support for translatable foreign keys on inline models (MultipleChooserPanel)
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:
- I publish a new default language (norwegian) ProductPage choosing a couple of materials
- I translate to english using wagtail-localize. The materials field is hidden from the UI (auto-sync behaviour)
- 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)
I think if you also make ProductPageMaterial translatable it should show up?
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
Thanks for your comments, I'll look into them and report back.
Do you have an update of this issue? Currently I am facing the same case as you. It could be nice if you share the report.
Hi, really sorry for never posting a response as promised.
I've created a vanilla wagtal + wagtail-localize project and added my code as posted above and can confirm that there is no change in behaviour. That is to say that wagtail-localize doesn't scan foreign keys for further foreign keys in a recursive way which is what I would need it to do.
Can also confirm that using chained .localized calls as suggested by @enzedonline works so you can do something like this:
<ul>
{% for page_material in page.materials.all %}
{% with material=page_material.material.localized %}
<li>{{ material.name }} ({{ material.locale }}) </li>
{% endwith %}
{% endfor %}
</ul>
However, in my case I wanted the foreign keys to point to localized values already at the db level because I was using wagtail in a headless setup. Using .localized at the instance level isn't idea when working with filters and listing querysets.