From 4739e080a08d5efe5532f03c5699fd1cb2912341 Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Mon, 15 Jan 2024 11:12:16 -0300 Subject: [PATCH 01/11] Make footnote model translatable --- .../0003_add_bootstrap_translatable_mixin.py | 39 +++++++++++++++++++ ...strap_translatable_mixin_data_migration.py | 14 +++++++ ...e_locale_alter_footnote_translation_key.py | 32 +++++++++++++++ wagtail_footnotes/models.py | 7 ++-- 4 files changed, 89 insertions(+), 3 deletions(-) create mode 100755 wagtail_footnotes/migrations/0003_add_bootstrap_translatable_mixin.py create mode 100755 wagtail_footnotes/migrations/0004_boostrap_translatable_mixin_data_migration.py create mode 100755 wagtail_footnotes/migrations/0005_alter_footnote_locale_alter_footnote_translation_key.py diff --git a/wagtail_footnotes/migrations/0003_add_bootstrap_translatable_mixin.py b/wagtail_footnotes/migrations/0003_add_bootstrap_translatable_mixin.py new file mode 100755 index 0000000..002dfcb --- /dev/null +++ b/wagtail_footnotes/migrations/0003_add_bootstrap_translatable_mixin.py @@ -0,0 +1,39 @@ +# Generated by Django 4.2.9 on 2024-01-11 12:53 + +import django.db.models.deletion + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("wagtailcore", "0078_referenceindex"), + ("wagtail_footnotes", "0002_alter_footnote_unique_together"), + ] + + operations = [ + migrations.AlterUniqueTogether( + name="footnote", + unique_together={("page", "uuid")}, + ), + migrations.AddField( + model_name="footnote", + name="locale", + field=models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="wagtailcore.locale", + ), + ), + migrations.AddField( + model_name="footnote", + name="translation_key", + field=models.UUIDField(editable=False, null=True), + ), + migrations.AlterUniqueTogether( + name="footnote", + unique_together={("translation_key", "locale"), ("page", "uuid")}, + ), + ] diff --git a/wagtail_footnotes/migrations/0004_boostrap_translatable_mixin_data_migration.py b/wagtail_footnotes/migrations/0004_boostrap_translatable_mixin_data_migration.py new file mode 100755 index 0000000..8e86620 --- /dev/null +++ b/wagtail_footnotes/migrations/0004_boostrap_translatable_mixin_data_migration.py @@ -0,0 +1,14 @@ +# Generated by Django 4.2.9 on 2024-01-11 12:56 + +from django.db import migrations +from wagtail.models import BootstrapTranslatableModel + + +class Migration(migrations.Migration): + dependencies = [ + ("wagtail_footnotes", "0003_add_bootstrap_translatable_mixin"), + ] + + operations = [ + BootstrapTranslatableModel("wagtail_footnotes.Footnote"), + ] diff --git a/wagtail_footnotes/migrations/0005_alter_footnote_locale_alter_footnote_translation_key.py b/wagtail_footnotes/migrations/0005_alter_footnote_locale_alter_footnote_translation_key.py new file mode 100755 index 0000000..62efe8f --- /dev/null +++ b/wagtail_footnotes/migrations/0005_alter_footnote_locale_alter_footnote_translation_key.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.9 on 2024-01-11 12:58 + +import uuid + +import django.db.models.deletion + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("wagtailcore", "0078_referenceindex"), + ("wagtail_footnotes", "0004_boostrap_translatable_mixin_data_migration"), + ] + + operations = [ + migrations.AlterField( + model_name="footnote", + name="locale", + field=models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="wagtailcore.locale", + ), + ), + migrations.AlterField( + model_name="footnote", + name="translation_key", + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + ] diff --git a/wagtail_footnotes/models.py b/wagtail_footnotes/models.py index 4665d3d..5f1ce3e 100644 --- a/wagtail_footnotes/models.py +++ b/wagtail_footnotes/models.py @@ -3,12 +3,13 @@ from modelcluster.fields import ParentalKey from wagtail.admin.panels import FieldPanel from wagtail.fields import RichTextField +from wagtail.models import TranslatableMixin from wagtail_footnotes.fields import CustomUUIDField from wagtail_footnotes.widgets import ReadonlyUUIDInput -class Footnote(models.Model): +class Footnote(TranslatableMixin, models.Model): """ Footnote has a UUID field which is set using JavaScript on object creation so that it is available immediately for hardcoding a reference to the @@ -29,8 +30,8 @@ class Footnote(models.Model): panels = [FieldPanel("text"), FieldPanel("uuid", widget=ReadonlyUUIDInput)] - class Meta: - unique_together = ("page", "uuid") + class Meta(TranslatableMixin.Meta): + unique_together = [("page", "uuid"), ("translation_key", "locale")] def __str__(self): return str(self.uuid) From aea579eeebe343872b4e71809e39bc1bbd852c9e Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Mon, 15 Jan 2024 11:13:42 -0300 Subject: [PATCH 02/11] Update tests to allow for localization --- tests/settings.py | 8 ++++++++ tests/urls.py | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/settings.py b/tests/settings.py index c832642..e166029 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -39,6 +39,8 @@ "wagtail.search", "wagtail.admin", "wagtail_modeladmin", + "wagtail.contrib.simple_translation", + "wagtail.contrib.redirects", "wagtail.sites", "wagtail", "taggit", @@ -53,6 +55,7 @@ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", @@ -110,6 +113,11 @@ USE_TZ = True +USE_I18N = True +WAGTAIL_I18N_ENABLED = True +LANGUAGE_CODE = "en" + + # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/stable/howto/static-files/ diff --git a/tests/urls.py b/tests/urls.py index 2417fa8..357a348 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,3 +1,4 @@ +from django.conf.urls.i18n import i18n_patterns from django.contrib import admin from django.urls import include, path from wagtail import urls as wagtail_urls @@ -11,6 +12,9 @@ path("django-admin/", admin.site.urls), path("admin/", include(wagtailadmin_urls)), path("documents/", include(wagtaildocs_urls)), +] + +urlpatterns += i18n_patterns( path("footnotes/", include(footnotes_urls)), path("", include(wagtail_urls)), -] +) From 82da5c166c11a4acb1e7e0df95ff3311ee9b13bd Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Mon, 15 Jan 2024 11:14:05 -0300 Subject: [PATCH 03/11] Update base test template for localization --- tests/templates/base.html | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/templates/base.html b/tests/templates/base.html index f0a8cf7..570d5f7 100644 --- a/tests/templates/base.html +++ b/tests/templates/base.html @@ -1,6 +1,9 @@ -{% load wagtailuserbar %} +{% load wagtailuserbar i18n %} + +{% get_current_language as LANGUAGE_CODE %} + - + From 2225a9477ed1aa128647e57fe2a7bf3c6aaec3ff Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Mon, 15 Jan 2024 11:14:46 -0300 Subject: [PATCH 04/11] Add tests for translatable Footnote model --- tests/test/test_translation.py | 157 +++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 tests/test/test_translation.py diff --git a/tests/test/test_translation.py b/tests/test/test_translation.py new file mode 100644 index 0000000..5de7a07 --- /dev/null +++ b/tests/test/test_translation.py @@ -0,0 +1,157 @@ +import json + +from bs4 import BeautifulSoup as bs4 +from django.test import override_settings +from django.urls import reverse +from django.utils import translation +from wagtail.models import Locale, Page +from wagtail.test.utils import TestCase, WagtailTestUtils + +from wagtail_footnotes.models import Footnote + +from ..models import TestPageStreamField + + +@override_settings( + LANGUAGES=[ + ("en", "English"), + ("fr", "French"), + ("de", "German"), + ], + WAGTAIL_CONTENT_LANGUAGES=[ + ("en", "English"), + ("fr", "French"), + ("de", "German"), + ], +) +class TestSubmitPageTranslationView(WagtailTestUtils, TestCase): + def setUp(self): + self.en_locale = Locale.objects.first() + self.fr_locale = Locale.objects.create(language_code="fr") + self.de_locale = Locale.objects.create(language_code="de") + + self.en_homepage = Page.objects.get(title="Welcome to your new Wagtail site!") + self.fr_homepage = self.en_homepage.copy_for_translation(self.fr_locale) + self.fr_homepage.save_revision().publish() + self.de_homepage = self.en_homepage.copy_for_translation(self.de_locale) + self.de_homepage.save_revision().publish() + + self.uuid = "f291a4b7-5ac5-4030-b341-b1993efb2ad2" + self.en_test_page = TestPageStreamField( + title="Test Page With Footnote", + slug="test-page-with-footnote", + body=json.dumps( + [ + { + "type": "paragraph", + "value": f'

This is a paragraph with a footnote. 1

', + }, + ] + ), + ) + self.en_homepage.add_child(instance=self.en_test_page) + self.en_test_page.save_revision().publish() + self.en_footnote = Footnote.objects.create( + page=self.en_test_page, + uuid=self.uuid, + text="This is a footnote", + ) + + def test_translating_page_translates_footnote(self): + url = reverse( + "simple_translation:submit_page_translation", args=(self.en_test_page.id,) + ) + self.login() + + de = Locale.objects.get(language_code="de").id + fr = Locale.objects.get(language_code="fr").id + data = {"locales": [de, fr], "include_subtree": True} + self.client.post(url, data, follow=True) + + de_footnote = self.en_footnote.get_translation(de) + self.assertEqual(de_footnote.text, self.en_footnote.text) + self.assertEqual(de_footnote.uuid, self.en_footnote.uuid) + de_test_page = self.en_test_page.get_translation(de) + self.assertCountEqual(de_test_page.footnotes.all(), [de_footnote]) + + fr_footnote = self.en_footnote.get_translation(fr) + self.assertEqual(fr_footnote.text, self.en_footnote.text) + self.assertEqual(fr_footnote.uuid, self.en_footnote.uuid) + fr_test_page = self.en_test_page.get_translation(fr) + self.assertCountEqual(fr_test_page.footnotes.all(), [fr_footnote]) + + # Can also change the text: + fr_footnote.text = "This is a French translated footnote" + fr_footnote.save() + fr_footnote.refresh_from_db() + en_footnote = self.en_footnote + en_footnote.refresh_from_db() + self.assertEqual(fr_footnote.text, "This is a French translated footnote") + self.assertNotEqual(fr_footnote.text, en_footnote.text) + + def test_translated_page_shows_translated_footnote(self): + url = reverse( + "simple_translation:submit_page_translation", args=(self.en_test_page.id,) + ) + self.login() + + fr = Locale.objects.get(language_code="fr").id + data = {"locales": [fr], "include_subtree": True} + response = self.client.post(url, data, follow=True) + + fr_test_page = self.en_test_page.get_translation(fr) + + self.assertRedirects( + response, reverse("wagtailadmin_pages:edit", args=[fr_test_page.pk]) + ) + + self.assertIn( + "The page 'Test Page With Footnote' was successfully created in French", + [msg.message for msg in response.context["messages"]], + ) + + fr_footnote = self.en_footnote.get_translation(fr) + self.assertEqual(fr_footnote.text, self.en_footnote.text) + self.assertEqual(fr_footnote.uuid, self.en_footnote.uuid) + self.assertCountEqual(fr_test_page.footnotes.all(), [fr_footnote]) + + fr_test_page.title = ("[FR] Test Page With Footnote",) + fr_test_page.body = json.dumps( + [ + { + "type": "paragraph", + "value": f'

This is a French paragraph with a footnote. 1

', + }, + ] + ) + fr_test_page.save_revision().publish() + fr_test_page.refresh_from_db() + + # Can also change the text: + fr_footnote.text = "This is a French translated footnote" + fr_footnote.save() + + translation.activate("fr") + + response = self.client.get(fr_test_page.get_full_url()) + self.assertEqual(response.status_code, 200) + + soup = bs4(response.content, "html.parser") + + # Test that required html tags are present with correct + # attrs that enable the footnotes to respond to clicks + source_anchor = soup.find("a", {"id": "footnote-source-1"}) + self.assertTrue(source_anchor) + + source_anchor_string = str(source_anchor) + self.assertIn("[1]", source_anchor_string) + self.assertIn('href="#footnote-1"', source_anchor_string) + self.assertIn('id="footnote-source-1"', source_anchor_string) + + footnotes = soup.find("div", {"class": "footnotes"}) + self.assertTrue(footnotes) + + footnotes_string = str(footnotes) + self.assertIn('id="footnote-1"', footnotes_string) + self.assertIn('href="#footnote-source-1"', footnotes_string) + self.assertIn("[1] This is a French translated footnote", footnotes_string) From 10abd968d1db0768d96e0909604ed5cb0a736ab7 Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Mon, 15 Jan 2024 15:11:10 -0300 Subject: [PATCH 05/11] Preserve url in prior tests --- tests/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/urls.py b/tests/urls.py index 357a348..57bd950 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -17,4 +17,5 @@ urlpatterns += i18n_patterns( path("footnotes/", include(footnotes_urls)), path("", include(wagtail_urls)), + prefix_default_language=False, ) From f1440683bd7bf8e706323b780b2a41faa7a474f8 Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Mon, 15 Jan 2024 15:29:37 -0300 Subject: [PATCH 06/11] Address review comments --- tests/test/test_translation.py | 66 +++++++++++++++++----------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/tests/test/test_translation.py b/tests/test/test_translation.py index 5de7a07..c08bef6 100644 --- a/tests/test/test_translation.py +++ b/tests/test/test_translation.py @@ -25,48 +25,51 @@ ], ) class TestSubmitPageTranslationView(WagtailTestUtils, TestCase): - def setUp(self): - self.en_locale = Locale.objects.first() - self.fr_locale = Locale.objects.create(language_code="fr") - self.de_locale = Locale.objects.create(language_code="de") - - self.en_homepage = Page.objects.get(title="Welcome to your new Wagtail site!") - self.fr_homepage = self.en_homepage.copy_for_translation(self.fr_locale) - self.fr_homepage.save_revision().publish() - self.de_homepage = self.en_homepage.copy_for_translation(self.de_locale) - self.de_homepage.save_revision().publish() - - self.uuid = "f291a4b7-5ac5-4030-b341-b1993efb2ad2" - self.en_test_page = TestPageStreamField( + @classmethod + def setUpTestData(cls): + cls.en_locale = Locale.objects.first() + cls.fr_locale = Locale.objects.create(language_code="fr") + cls.de_locale = Locale.objects.create(language_code="de") + + cls.en_homepage = Page.objects.get(title="Welcome to your new Wagtail site!") + cls.fr_homepage = cls.en_homepage.copy_for_translation(cls.fr_locale) + cls.fr_homepage.save_revision().publish() + cls.de_homepage = cls.en_homepage.copy_for_translation(cls.de_locale) + cls.de_homepage.save_revision().publish() + + cls.uuid = "f291a4b7-5ac5-4030-b341-b1993efb2ad2" + cls.en_test_page = TestPageStreamField( title="Test Page With Footnote", slug="test-page-with-footnote", body=json.dumps( [ { "type": "paragraph", - "value": f'

This is a paragraph with a footnote. 1

', + "value": f'

This is a paragraph with a footnote. 1

', }, ] ), ) - self.en_homepage.add_child(instance=self.en_test_page) - self.en_test_page.save_revision().publish() - self.en_footnote = Footnote.objects.create( - page=self.en_test_page, - uuid=self.uuid, + cls.en_homepage.add_child(instance=cls.en_test_page) + cls.en_test_page.save_revision().publish() + cls.en_footnote = Footnote.objects.create( + page=cls.en_test_page, + uuid=cls.uuid, text="This is a footnote", ) - - def test_translating_page_translates_footnote(self): - url = reverse( - "simple_translation:submit_page_translation", args=(self.en_test_page.id,) + cls.url = reverse( + "simple_translation:submit_page_translation", args=(cls.en_test_page.id,) ) + + def setUp(self): + super().setUp() self.login() - de = Locale.objects.get(language_code="de").id - fr = Locale.objects.get(language_code="fr").id + def test_translating_page_translates_footnote(self): + de = self.de_locale.id + fr = self.fr_locale.id data = {"locales": [de, fr], "include_subtree": True} - self.client.post(url, data, follow=True) + self.client.post(self.url, data, follow=True) de_footnote = self.en_footnote.get_translation(de) self.assertEqual(de_footnote.text, self.en_footnote.text) @@ -82,7 +85,7 @@ def test_translating_page_translates_footnote(self): # Can also change the text: fr_footnote.text = "This is a French translated footnote" - fr_footnote.save() + fr_footnote.save(update_fields=["text"]) fr_footnote.refresh_from_db() en_footnote = self.en_footnote en_footnote.refresh_from_db() @@ -90,14 +93,9 @@ def test_translating_page_translates_footnote(self): self.assertNotEqual(fr_footnote.text, en_footnote.text) def test_translated_page_shows_translated_footnote(self): - url = reverse( - "simple_translation:submit_page_translation", args=(self.en_test_page.id,) - ) - self.login() - - fr = Locale.objects.get(language_code="fr").id + fr = self.fr_locale.id data = {"locales": [fr], "include_subtree": True} - response = self.client.post(url, data, follow=True) + response = self.client.post(self.url, data, follow=True) fr_test_page = self.en_test_page.get_translation(fr) From d70c5498f1216fd0682108d57e420e1e97c9086e Mon Sep 17 00:00:00 2001 From: zerolab Date: Fri, 17 May 2024 15:06:22 +0100 Subject: [PATCH 07/11] Make strings translatable --- .../locale/en/LC_MESSAGES/django.po | 55 +++++++++++++++++++ .../static/footnotes/js/footnotes.js | 5 +- .../admin/footnotes_modal.html | 32 ++++++----- .../wagtail_footnotes/includes/footnotes.html | 6 +- 4 files changed, 79 insertions(+), 19 deletions(-) create mode 100644 wagtail_footnotes/locale/en/LC_MESSAGES/django.po diff --git a/wagtail_footnotes/locale/en/LC_MESSAGES/django.po b/wagtail_footnotes/locale/en/LC_MESSAGES/django.po new file mode 100644 index 0000000..301de16 --- /dev/null +++ b/wagtail_footnotes/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,55 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024 Torchbox +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-05-17 09:04-0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +#: templates/wagtail_footnotes/admin/footnotes_modal.html:14 +msgid "Choose a footnote" +msgstr "" + +#: templates/wagtail_footnotes/admin/footnotes_modal.html:26 +msgid "Footnotes" +msgstr "" + +#: templates/wagtail_footnotes/admin/footnotes_modal.html:28 +msgid "" +"To add a new footnote, close this window and scroll to the \"Footnotes\" " +"section at bottom of the page." +msgstr "" + +#: templates/wagtail_footnotes/admin/footnotes_modal.html:36 +msgid "Text" +msgstr "" + +#: templates/wagtail_footnotes/admin/footnotes_modal.html:37 +msgid "ID" +msgstr "" + +#: templates/wagtail_footnotes/admin/footnotes_modal.html:45 +msgid "" +"Footnotes will appear with arbitrary numbers here in the editor. On the " +"published page, and in previews, their numbering will be numerical and " +"ordered." +msgstr "" + +#: templates/wagtail_footnotes/includes/footnotes.html:6 +msgid "Foonotes" +msgstr "" + +#: templates/wagtail_footnotes/includes/footnotes.html:12 +msgid "Back to content" +msgstr "" diff --git a/wagtail_footnotes/static/footnotes/js/footnotes.js b/wagtail_footnotes/static/footnotes/js/footnotes.js index ae1ff2d..54257ea 100644 --- a/wagtail_footnotes/static/footnotes/js/footnotes.js +++ b/wagtail_footnotes/static/footnotes/js/footnotes.js @@ -50,8 +50,8 @@ class FootnoteSource extends React.Component { "#id_footnotes-FORMS .w-panel" ); Array.prototype.forEach.call(live_footnotes, function (value) { - var text = $(".public-DraftEditor-content", value).text(); - var uuid = $('input[id*="-uuid"]', value)[0].value; + const text = $(".public-DraftEditor-content", value).text(); + const uuid = $('input[id*="-uuid"]', value)[0].value; var row = $( "