From 252c63da8319c1256a952ab7a1f71b4193c9e77a Mon Sep 17 00:00:00 2001 From: Pomax Date: Tue, 27 Jul 2021 15:04:09 -0700 Subject: [PATCH] rebase fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix the language picker by making sure 'next' is the right URL (#6780) allow authenticated users to hit the wagtail localize endpoints (#6778) * allow authenticated users to hit the wagtail localize endpoints Updated migrations (#6795) Enable pontoon integration for wagtail-localize (#6794) * add wagtail_localize_git * heroku ssh generation script rebase fixes Update settings.py Update settings.py Better 500 logging (#6836) * allow forced logging of stack traces to console for 500 errors * Update urls.py * Update urls.py blog fixes for the localize branch (#6840) * blog fixes for the localize branch * import * oh flake8, you joker Show locale in admin title (until a proper solution is released in Wagtail Core) (#6843) rebase fixes Redirect /pt to /pt-BR (#6854) only create the locale, rather than cloning all pages, when creating a synced locale (#6853) Campaign page translation fix (#6855) Permanent pt-BR redirect (#6856) Added localized URLs for every page through the FoundationMetadataPageMixin (#6849) * Added localized URLs for every page through the FoundationMetadataPageMixin * Cache sitemap * Localized sitemaps Make tests pass (#6872) * Flake8 * Sitemap.xml test fix Localization synching and creating management commands (#6866) * Localization synching and creating management commands * Don't lower strings; requires exact string matching now Allow banner pages to be further localized (#6876) Marked fields are sync or translatable (#6875) * Marked fields are sync or translatable * Oop * Flake8 * Add petition synchronized fields Co-authored-by: Théo Chevalier Fix makemessages command for Django >= 3.1 (#6907) Use localized wagtail-footnotes (#6913) Localize missing fields for YT pages (#6923) Translate article page titles (#6924) Translate What You Can Do title (#6925) address all acceptance criteria (#6930) rebase fixes update all homepage fragments to pull from localized content (#7067) * update all homepage fragments to pull from localized content * Update network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/news_you_can_use.html Co-authored-by: Kalob Taulien <4743971+KalobTaulien@users.noreply.github.com> * Update network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/spotlight_posts.html Co-authored-by: Kalob Taulien <4743971+KalobTaulien@users.noreply.github.com> Co-authored-by: Kalob Taulien <4743971+KalobTaulien@users.noreply.github.com> Fix all the localized properties (#7068) * update all homepage fragments to pull from localized content * Update network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/news_you_can_use.html Co-authored-by: Kalob Taulien <4743971+KalobTaulien@users.noreply.github.com> * Update network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/spotlight_posts.html Co-authored-by: Kalob Taulien <4743971+KalobTaulien@users.noreply.github.com> * rest of the localized updates... I think * forgot to save a file Co-authored-by: Kalob Taulien <4743971+KalobTaulien@users.noreply.github.com> Translatable PNI fields (#7075) Translate 3 more PNI fields (#7081) Localized limks in Related products section (#7125) Localize PNI categories & products (#7124) Translate Host controls field (#7123) regenerating requirements.txt . migration renames migration fixes Override translatable fields for PNI home (#7146) Localize title field on Default pages (#7147) Bump wagtail-localize to 1.0rc3 (#7176) Category snippet localization (#7183) * category-snippet-localization * well then * Update network-api/networkapi/wagtailpages/templatetags/localization.py * update based on Karl's advice * some WIP removals * show correct categories on product pages, and relocalize urls to point to use the correct locale * let's not cache-clear Restore product filtering per language (#7208) Fix category fallthrough (#7212) * make missing categories fall through to the EN version * small docs update * ??? * oops * data-name should stay English Moved consts inside of functions to prevent runtime/migration conflicts (#7215) * Moved consts inside of functions to prevent runtime/migration conflicts * That's better --- .profile | 23 ++ dev-requirements.txt | 23 +- network-api/networkapi/campaign/views.py | 12 +- .../migrations/0005_auto_20210531_1735.py | 25 ++ .../migrations/0006_bootstrap_migration.py | 15 + .../migrations/0007_localize_migration.py | 31 ++ network-api/networkapi/highlights/models.py | 14 +- .../management/commands/create_locales.py | 13 + .../management/commands/sync_locale.py | 38 ++ ...525_1516.py => 0014_auto_20210525_1516.py} | 2 +- network-api/networkapi/mozfest/models.py | 26 +- .../migrations/0005_auto_20210531_1735.py | 25 ++ .../migrations/0006_bootstrap_migration.py | 15 + .../migrations/0007_localize_migration.py | 31 ++ network-api/networkapi/news/models.py | 5 +- network-api/networkapi/settings.py | 26 +- network-api/networkapi/sitemaps.py | 23 ++ .../templates/fragments/canonical_url.html | 2 +- .../fragments/language_switcher.html | 15 +- network-api/networkapi/urls.py | 13 +- .../networkapi/utility/custom_url_handlers.py | 15 + .../networkapi/wagtailpages/donation_modal.py | 18 +- .../wagtailpages/factory/buyersguide.py | 16 +- network-api/networkapi/wagtailpages/fields.py | 33 ++ ...525_1516.py => 0015_auto_20210525_1516.py} | 290 +++++++++++++- ...525_1716.py => 0016_auto_20210525_1716.py} | 2 +- .../migrations/0017_bootstrap_models.py | 266 +++++++++++++ .../migrations/0018_bootstrap_migrations.py | 38 ++ .../0019_translation_mixin_migration.py | 374 ++++++++++++++++++ ...0020_remove_localization_from_cta_model.py | 25 ++ .../0021_add_localization_to_subclasses.py | 35 ++ .../0022_bootstrap_subclassed_cta_models.py | 16 + ...nslation_mixin_migration_for_cta_models.py | 46 +++ .../0024_fix_software_fields_part_1.py | 55 +++ .../0025_fix_software_fields_part_2.py | 24 ++ .../wagtailpages/pagemodels/base.py | 166 +++++++- .../wagtailpages/pagemodels/blog/blog.py | 23 +- .../pagemodels/blog/blog_category.py | 5 +- .../pagemodels/blog/blog_index.py | 5 +- .../wagtailpages/pagemodels/campaign_index.py | 16 + .../wagtailpages/pagemodels/campaigns.py | 80 +++- .../wagtailpages/pagemodels/content_author.py | 3 +- .../wagtailpages/pagemodels/dear_internet.py | 19 + .../wagtailpages/pagemodels/index.py | 16 + .../pagemodels/mixin/foundation_metadata.py | 7 + .../wagtailpages/pagemodels/mixin/snippets.py | 21 + .../wagtailpages/pagemodels/modular.py | 14 + .../wagtailpages/pagemodels/primary.py | 19 + .../wagtailpages/pagemodels/products.py | 263 ++++++++++-- .../pagemodels/publications/article.py | 19 + .../pagemodels/publications/publication.py | 23 ++ .../wagtailpages/pagemodels/youtube.py | 32 ++ .../templates/buyersguide/bg_base.html | 15 +- .../templates/buyersguide/catalog.html | 4 +- .../fragments/category_nav_links.html | 8 +- .../templates/buyersguide/product_page.html | 8 +- .../templates/fragments/buyersguide_item.html | 7 +- .../fragments/chapter_table_of_contents.html | 2 +- .../publication_table_of_contents.html | 10 +- .../fragments/software_product_privacy.html | 4 +- .../fragments/cause_statement.html | 2 +- .../wagtailpages/fragments/focus_area.html | 2 +- .../fragments/news_you_can_use.html | 12 +- .../wagtailpages/fragments/partner.html | 2 +- .../fragments/publication_hero.html | 4 +- .../fragments/spotlight_posts.html | 6 +- .../wagtailpages/fragments/take_action.html | 6 +- .../wagtailpages/templatetags/bg_nav_tags.py | 7 +- .../templatetags/bg_selector_tags.py | 13 +- .../wagtailpages/templatetags/debug_tags.py | 12 + .../wagtailpages/templatetags/localization.py | 31 +- network-api/networkapi/wagtailpages/tests.py | 2 +- .../networkapi/wagtailpages/translation.py | 2 + .../networkapi/wagtailpages/wagtail_hooks.py | 31 +- requirements.in | 6 +- requirements.txt | 136 ++++--- source/js/components/join/language-select.jsx | 2 +- .../js/components/petition/locale-strings.jsx | 2 +- .../pages/youtube-regrets/carousel.js | 6 +- .../youtube-regrets/categories-bar-chart.js | 19 +- source/sass/buyers-guide/views/product.scss | 2 +- .../youtube-regrets-2021-carousel-hero.scss | 1 - .../views/youtube-regrets-2021-carousel.scss | 4 - source/sass/views/youtube-regrets-2021.scss | 4 +- source/sass/wagtail.scss | 1 - source/sass/wagtail/blocks/profiles.scss | 27 +- source/sass/wagtail/blocks/rich-text.scss | 19 +- tasks.py | 2 +- 88 files changed, 2522 insertions(+), 260 deletions(-) create mode 100644 .profile create mode 100644 network-api/networkapi/highlights/migrations/0005_auto_20210531_1735.py create mode 100644 network-api/networkapi/highlights/migrations/0006_bootstrap_migration.py create mode 100644 network-api/networkapi/highlights/migrations/0007_localize_migration.py create mode 100644 network-api/networkapi/management/commands/create_locales.py create mode 100644 network-api/networkapi/management/commands/sync_locale.py rename network-api/networkapi/mozfest/migrations/{0012_auto_20210525_1516.py => 0014_auto_20210525_1516.py} (99%) create mode 100644 network-api/networkapi/news/migrations/0005_auto_20210531_1735.py create mode 100644 network-api/networkapi/news/migrations/0006_bootstrap_migration.py create mode 100644 network-api/networkapi/news/migrations/0007_localize_migration.py create mode 100644 network-api/networkapi/sitemaps.py create mode 100644 network-api/networkapi/utility/custom_url_handlers.py rename network-api/networkapi/wagtailpages/migrations/{0009_auto_20210525_1516.py => 0015_auto_20210525_1516.py} (89%) rename network-api/networkapi/wagtailpages/migrations/{0010_auto_20210525_1716.py => 0016_auto_20210525_1716.py} (92%) create mode 100644 network-api/networkapi/wagtailpages/migrations/0017_bootstrap_models.py create mode 100644 network-api/networkapi/wagtailpages/migrations/0018_bootstrap_migrations.py create mode 100644 network-api/networkapi/wagtailpages/migrations/0019_translation_mixin_migration.py create mode 100644 network-api/networkapi/wagtailpages/migrations/0020_remove_localization_from_cta_model.py create mode 100644 network-api/networkapi/wagtailpages/migrations/0021_add_localization_to_subclasses.py create mode 100644 network-api/networkapi/wagtailpages/migrations/0022_bootstrap_subclassed_cta_models.py create mode 100644 network-api/networkapi/wagtailpages/migrations/0023_translation_mixin_migration_for_cta_models.py create mode 100644 network-api/networkapi/wagtailpages/migrations/0024_fix_software_fields_part_1.py create mode 100644 network-api/networkapi/wagtailpages/migrations/0025_fix_software_fields_part_2.py create mode 100644 network-api/networkapi/wagtailpages/pagemodels/mixin/snippets.py create mode 100644 network-api/networkapi/wagtailpages/templatetags/debug_tags.py diff --git a/.profile b/.profile new file mode 100644 index 00000000000..f591ca7f872 --- /dev/null +++ b/.profile @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# Copy SSH private key to file, if set +# This is used for talking to GitHub over an SSH connection +if [ $WAGTAIL_LOCALIZE_PRIVATE_KEY ]; then + echo "Generating SSH config" + SSH_DIR=/app/.ssh + + mkdir -p $SSH_DIR + chmod 700 $SSH_DIR + + echo $WAGTAIL_LOCALIZE_PRIVATE_KEY | base64 --decode > $SSH_DIR/id_rsa + + chmod 400 $SSH_DIR/id_rsa + + cat << EOF > $SSH_DIR/config +StrictHostKeyChecking no +EOF + + chmod 600 $SSH_DIR/config + + echo "Done!" +fi diff --git a/dev-requirements.txt b/dev-requirements.txt index 3221abefec9..cebcd4db766 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -4,16 +4,16 @@ # # pip-compile dev-requirements.in # -certifi==2021.5.30 +certifi==2020.4.5.1 # via # -c requirements.txt # requests # urllib3 -cffi==1.14.6 +cffi==1.14.0 # via # -c requirements.txt # cryptography -charset-normalizer==2.0.3 +chardet==3.0.4 # via # -c requirements.txt # requests @@ -21,7 +21,7 @@ coverage==5.1 # via coveralls coveralls==3.0.1 # via -r dev-requirements.in -cryptography==3.4.7 +cryptography==3.2 # via # -c requirements.txt # pyopenssl @@ -30,12 +30,12 @@ docopt==0.6.2 # via coveralls flake8==3.8.4 # via -r dev-requirements.in -idna==3.2 +idna==2.9 # via # -c requirements.txt # requests # urllib3 -importlib-metadata==4.6.1 +importlib-metadata==3.10.1 # via # -c requirements.txt # flake8 @@ -55,23 +55,24 @@ pyopenssl==20.0.1 # via # -c requirements.txt # urllib3 -requests==2.26.0 +requests==2.25.1 # via # -c requirements.txt # coveralls -six==1.16.0 +six==1.14.0 # via # -c requirements.txt + # cryptography # pyopenssl -typing-extensions==3.10.0.0 +typing-extensions==3.7.4.3 # via # -c requirements.txt # importlib-metadata -urllib3[secure]==1.26.6 +urllib3[secure]==1.25.9 # via # -c requirements.txt # requests -zipp==3.5.0 +zipp==3.4.1 # via # -c requirements.txt # importlib-metadata diff --git a/network-api/networkapi/campaign/views.py b/network-api/networkapi/campaign/views.py index 071f39b31f4..f722d091393 100644 --- a/network-api/networkapi/campaign/views.py +++ b/network-api/networkapi/campaign/views.py @@ -13,6 +13,14 @@ from networkapi.wagtailpages.models import Petition, Signup +def process_lang_code(lang): + # Salesforce expects "pt" instead of "pt-BR". + # See https://github.com/mozilla/foundation.mozilla.org/issues/5993 + if lang == 'pt-BR': + return 'pt' + return lang + + class SQSProxy: """ We use a proxy class to make sure that code that @@ -128,7 +136,7 @@ def signup_submission(request, signup): "format": "html", "source_url": source, "newsletters": signup.newsletter, - "lang": rq.get('lang', 'en'), + "lang": process_lang_code(rq.get('lang', 'en')), "country": rq.get('country', ''), # Empty string instead of None due to Basket issues "first_name": rq.get('givenNames', ''), @@ -178,7 +186,7 @@ def petition_submission(request, petition): "email": request.data['email'], "email_subscription": request.data['newsletterSignup'], "source_url": request.data['source'], - "lang": request.data['lang'], + "lang": process_lang_code(request.data['lang']), } if petition: diff --git a/network-api/networkapi/highlights/migrations/0005_auto_20210531_1735.py b/network-api/networkapi/highlights/migrations/0005_auto_20210531_1735.py new file mode 100644 index 00000000000..6324c50e00a --- /dev/null +++ b/network-api/networkapi/highlights/migrations/0005_auto_20210531_1735.py @@ -0,0 +1,25 @@ +# Generated by Django 3.1.11 on 2021-05-31 17:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0062_comment_models_and_pagesubscription'), + ('highlights', '0004_remove_highlight_image'), + ] + + operations = [ + migrations.AddField( + model_name='highlight', + 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='highlight', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + ] diff --git a/network-api/networkapi/highlights/migrations/0006_bootstrap_migration.py b/network-api/networkapi/highlights/migrations/0006_bootstrap_migration.py new file mode 100644 index 00000000000..d318026ef75 --- /dev/null +++ b/network-api/networkapi/highlights/migrations/0006_bootstrap_migration.py @@ -0,0 +1,15 @@ +# Generated by Django 3.1.11 on 2021-05-31 17:18 + +from django.db import migrations +from wagtail.core.models import BootstrapTranslatableModel + + +class Migration(migrations.Migration): + + dependencies = [ + ('highlights', '0005_auto_20210531_1735'), + ] + + operations = [ + BootstrapTranslatableModel('highlights.Highlight'), + ] diff --git a/network-api/networkapi/highlights/migrations/0007_localize_migration.py b/network-api/networkapi/highlights/migrations/0007_localize_migration.py new file mode 100644 index 00000000000..b524774f1c5 --- /dev/null +++ b/network-api/networkapi/highlights/migrations/0007_localize_migration.py @@ -0,0 +1,31 @@ +# Generated by Django 3.1.11 on 2021-05-31 18:02 + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0062_comment_models_and_pagesubscription'), + ('highlights', '0006_bootstrap_migration'), + ] + + operations = [ + migrations.AlterField( + model_name='highlight', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='highlight', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterUniqueTogether( + name='highlight', + unique_together={('translation_key', 'locale')}, + ), + ] diff --git a/network-api/networkapi/highlights/models.py b/network-api/networkapi/highlights/models.py index ca7b56785aa..5e48b0a11e5 100644 --- a/network-api/networkapi/highlights/models.py +++ b/network-api/networkapi/highlights/models.py @@ -5,9 +5,12 @@ from adminsortable.models import SortableMixin from wagtail.admin.edit_handlers import FieldPanel from wagtail.core.fields import RichTextField +from wagtail.core.models import TranslatableMixin from wagtail.images.edit_handlers import ImageChooserPanel from wagtail.snippets.models import register_snippet +from wagtail_localize.fields import TranslatableField + from networkapi.utility.images import get_image_upload_path @@ -33,7 +36,7 @@ def published(self): @register_snippet -class Highlight(SortableMixin): +class Highlight(TranslatableMixin, SortableMixin): """ An data type to highlight things like pulse projects, custom pages, etc @@ -97,9 +100,16 @@ class Highlight(SortableMixin): FieldPanel("expires"), ] + translatable_fields = [ + TranslatableField('title'), + TranslatableField('description'), + TranslatableField('link_label'), + TranslatableField('footer'), + ] + objects = HighlightQuerySet.as_manager() - class Meta: + class Meta(TranslatableMixin.Meta): verbose_name_plural = 'highlights' ordering = ('order',) diff --git a/network-api/networkapi/management/commands/create_locales.py b/network-api/networkapi/management/commands/create_locales.py new file mode 100644 index 00000000000..ed7b0fce01e --- /dev/null +++ b/network-api/networkapi/management/commands/create_locales.py @@ -0,0 +1,13 @@ +from django.conf import settings +from django.core.management.base import BaseCommand +from wagtail.core.models import Locale + + +class Command(BaseCommand): + help = 'Look for and create locales if they do not exist. This can be run multiple times if needed.' + + def handle(self, *args, **options): + for language_code, name in settings.WAGTAIL_CONTENT_LANGUAGES: + locale, created = Locale.objects.get_or_create(language_code=language_code) + if created: + print(f"Create new locale: {name}") diff --git a/network-api/networkapi/management/commands/sync_locale.py b/network-api/networkapi/management/commands/sync_locale.py new file mode 100644 index 00000000000..18f67347dd7 --- /dev/null +++ b/network-api/networkapi/management/commands/sync_locale.py @@ -0,0 +1,38 @@ +from django.conf import settings +from django.core.management.base import BaseCommand +from wagtail.core.models import Locale +from wagtail_localize.models import LocaleSynchronization + + +class Command(BaseCommand): + help = 'Sync pages with original English pages' + + def handle(self, *args, **options): + print("Select a language code to sync with English. ie: de") + + for language_code, name in settings.WAGTAIL_CONTENT_LANGUAGES: + if language_code != 'en': + print(f"{language_code} ({name})") + + language_code = input("Language code: ") + + # Confirm the language code is in the WAGTAIL_CONTENT_LANGUAGES + language_codes_dict = dict(settings.WAGTAIL_CONTENT_LANGUAGES) + if language_code not in language_codes_dict: + print("Invalid language code") + return + + print("Getting both locales...") + english_locale, _ = Locale.objects.get_or_create(language_code='en') + locale, _ = Locale.objects.get_or_create(language_code=language_code) + + print("Getting LocaleSynchronization object") + sync, created = LocaleSynchronization.objects.get_or_create( + locale=locale, + sync_from=english_locale, + ) + if created: + print("\tNew LocaleSynchronization object created") + + print(f"Syncing {locale} from {english_locale}") + sync.sync_trees() diff --git a/network-api/networkapi/mozfest/migrations/0012_auto_20210525_1516.py b/network-api/networkapi/mozfest/migrations/0014_auto_20210525_1516.py similarity index 99% rename from network-api/networkapi/mozfest/migrations/0012_auto_20210525_1516.py rename to network-api/networkapi/mozfest/migrations/0014_auto_20210525_1516.py index 043af27a1cd..ef94043f1ee 100644 --- a/network-api/networkapi/mozfest/migrations/0012_auto_20210525_1516.py +++ b/network-api/networkapi/mozfest/migrations/0014_auto_20210525_1516.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('mozfest', '0011_auto_20210519_1654'), + ('mozfest', '0013_auto_20210721_1921'), ] operations = [ diff --git a/network-api/networkapi/mozfest/models.py b/network-api/networkapi/mozfest/models.py index 9b266ee625c..32db42c3d84 100644 --- a/network-api/networkapi/mozfest/models.py +++ b/network-api/networkapi/mozfest/models.py @@ -4,6 +4,8 @@ from wagtail.core.models import Page from wagtail.images.edit_handlers import ImageChooserPanel from wagtail.snippets.edit_handlers import SnippetChooserPanel +from wagtail_localize.fields import SynchronizedField, TranslatableField + from networkapi.wagtailpages.utils import ( set_main_site_nav_information, @@ -167,10 +169,32 @@ class MozfestHomepage(MozfestPrimaryPage): else: content_panels = all_panels - # Because we inherit from PrimaryPage, but the "use_wide_templatae" property does nothing + # Because we inherit from PrimaryPage, but the "use_wide_template" property does nothing # we should hide it and make sure we use the right template settings_panels = Page.settings_panels + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('title'), + TranslatableField('cta_button_label'), + SynchronizedField('cta_button_destination'), + TranslatableField('banner_heading'), + TranslatableField('banner_guide_text'), + SynchronizedField('banner_video_url'), + TranslatableField('title'), + TranslatableField('search_description'), + TranslatableField('search_image'), + TranslatableField('signup'), + TranslatableField('body'), + TranslatableField('footnotes'), + ] + def get_context(self, request): context = super().get_context(request) context['banner_video_type'] = self.specific.banner_video_type diff --git a/network-api/networkapi/news/migrations/0005_auto_20210531_1735.py b/network-api/networkapi/news/migrations/0005_auto_20210531_1735.py new file mode 100644 index 00000000000..a1ac79bd0d3 --- /dev/null +++ b/network-api/networkapi/news/migrations/0005_auto_20210531_1735.py @@ -0,0 +1,25 @@ +# Generated by Django 3.1.11 on 2021-05-31 17:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0062_comment_models_and_pagesubscription'), + ('news', '0004_remove_news_featured'), + ] + + operations = [ + migrations.AddField( + model_name='news', + 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='news', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + ] diff --git a/network-api/networkapi/news/migrations/0006_bootstrap_migration.py b/network-api/networkapi/news/migrations/0006_bootstrap_migration.py new file mode 100644 index 00000000000..ead586276cd --- /dev/null +++ b/network-api/networkapi/news/migrations/0006_bootstrap_migration.py @@ -0,0 +1,15 @@ +# Generated by Django 3.1.11 on 2021-05-31 17:37 + +from django.db import migrations +from wagtail.core.models import BootstrapTranslatableModel + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0005_auto_20210531_1735'), + ] + + operations = [ + BootstrapTranslatableModel('news.News') + ] diff --git a/network-api/networkapi/news/migrations/0007_localize_migration.py b/network-api/networkapi/news/migrations/0007_localize_migration.py new file mode 100644 index 00000000000..b89dd06a17a --- /dev/null +++ b/network-api/networkapi/news/migrations/0007_localize_migration.py @@ -0,0 +1,31 @@ +# Generated by Django 3.1.11 on 2021-05-31 18:02 + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0062_comment_models_and_pagesubscription'), + ('news', '0006_bootstrap_migration'), + ] + + operations = [ + migrations.AlterField( + model_name='news', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='news', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterUniqueTogether( + name='news', + unique_together={('translation_key', 'locale')}, + ), + ] diff --git a/network-api/networkapi/news/models.py b/network-api/networkapi/news/models.py index 7faafd5e67a..8c93dae2ce5 100644 --- a/network-api/networkapi/news/models.py +++ b/network-api/networkapi/news/models.py @@ -4,6 +4,7 @@ from networkapi.utility.images import get_image_upload_path from wagtail.snippets.models import register_snippet +from wagtail.core.models import TranslatableMixin def get_thumbnail_upload_path(instance, filename): @@ -29,7 +30,7 @@ def published(self): @register_snippet -class News(models.Model): +class News(TranslatableMixin, models.Model): """ Medium blog posts, articles and other media """ @@ -88,7 +89,7 @@ class News(models.Model): objects = NewsQuerySet.as_manager() - class Meta: + class Meta(TranslatableMixin.Meta): """Meta settings for news model""" verbose_name = 'news article' diff --git a/network-api/networkapi/settings.py b/network-api/networkapi/settings.py index 393bfc117f6..1596d99081d 100644 --- a/network-api/networkapi/settings.py +++ b/network-api/networkapi/settings.py @@ -50,6 +50,7 @@ FEED_LIMIT=(int, 10), FILEBROWSER_DEBUG=(bool, False), FILEBROWSER_DIRECTORY=(str, ''), + FORCE_500_STACK_TRACES=(bool, False), FRONTEND_CACHE_CLOUDFLARE_BEARER_TOKEN=(str, ''), FRONTEND_CACHE_CLOUDFLARE_ZONEID=(str, ''), GITHUB_TOKEN=(str, ''), @@ -85,6 +86,9 @@ USE_S3=(bool, True), USE_X_FORWARDED_HOST=(bool, False), WAGTAILIMAGES_INDEX_PAGE_SIZE=(int, 60), + WAGTAILLOCALIZE_GIT_URL=(str, ''), + WAGTAILLOCALIZE_GIT_CLONE_DIR=(str, ''), + WAGTAIL_LOCALIZE_PRIVATE_KEY=(str, ''), WEB_MONETIZATION_POINTER=(str, ''), XROBOTSTAG_ENABLED=(bool, False), XSS_PROTECTION=bool, @@ -138,6 +142,9 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = FILEBROWSER_DEBUG = env('DEBUG') +# SECURITY WARNING: same as above! +FORCE_500_STACK_TRACES = env('FORCE_500_STACK_TRACES') + # whether or not to send the X-Robots-Tag header XROBOTSTAG_ENABLED = env('XROBOTSTAG_ENABLED') @@ -221,6 +228,13 @@ 'modelcluster', 'taggit', + # Base wagtail localization + 'wagtail_localize', + 'wagtail_localize.locales', + + # git integration for localization + 'wagtail_localize_git', + 'rest_framework', 'django_filters', 'gunicorn', @@ -325,6 +339,7 @@ 'blog_tags': 'networkapi.wagtailpages.templatetags.blog_tags', 'card_tags': 'networkapi.wagtailpages.templatetags.card_tags', 'class_tags': 'networkapi.wagtailpages.templatetags.class_tags', + 'debug_tags': 'networkapi.wagtailpages.templatetags.debug_tags', 'homepage_tags': 'networkapi.wagtailpages.templatetags.homepage_tags', 'localization': 'networkapi.wagtailpages.templatetags.localization', 'mini_site_tags': 'networkapi.wagtailpages.templatetags.mini_site_tags', @@ -423,10 +438,10 @@ # https://docs.djangoproject.com/en/1.10/topics/i18n/ LANGUAGE_CODE = 'en' -LANGUAGES = ( +WAGTAIL_CONTENT_LANGUAGES = LANGUAGES = ( ('en', gettext_lazy('English')), ('de', gettext_lazy('German')), - ('pt', gettext_lazy('Portuguese')), + ('pt-BR', gettext_lazy('Portuguese (Brazil)')), ('es', gettext_lazy('Spanish')), ('fr', gettext_lazy('French')), ('fy-NL', gettext_lazy('Frisian')), @@ -434,6 +449,10 @@ ('pl', gettext_lazy('Polish')), ) +WAGTAILLOCALIZE_GIT_URL = env('WAGTAILLOCALIZE_GIT_URL') +WAGTAILLOCALIZE_GIT_CLONE_DIR = env('WAGTAILLOCALIZE_GIT_CLONE_DIR') +WAGTAIL_LOCALIZE_PRIVATE_KEY = env('WAGTAIL_LOCALIZE_PRIVATE_KEY') + TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True @@ -467,6 +486,7 @@ WAGTAIL_SITE_NAME = 'Mozilla Foundation' WAGTAILIMAGES_INDEX_PAGE_SIZE = env('WAGTAILIMAGES_INDEX_PAGE_SIZE') WAGTAIL_USAGE_COUNT_ENABLED = True +WAGTAIL_I18N_ENABLED = True # Wagtail Frontend Cache Invalidator Settings @@ -482,7 +502,7 @@ # Rest Framework Settings REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly', + 'rest_framework.permissions.IsAuthenticatedOrReadOnly', ] } diff --git a/network-api/networkapi/sitemaps.py b/network-api/networkapi/sitemaps.py new file mode 100644 index 00000000000..eee00f7ea6d --- /dev/null +++ b/network-api/networkapi/sitemaps.py @@ -0,0 +1,23 @@ +# Solution came from Aleksi44 on Github: +# https://github.com/wagtail/wagtail/issues/6583#issuecomment-798960446 +from django.contrib.sitemaps import views as sitemap_views +from wagtail.contrib.sitemaps.sitemap_generator import Sitemap + + +class CustomSitemap(Sitemap): + + def items(self): + return ( + self.get_wagtail_site() + .root_page + .localized # This is missing from sitemap_generator + .get_descendants(inclusive=True) + .live() + .public() + .order_by('path') + .specific()) + + +def sitemap(request, **kwargs): + sitemaps = {'wagtail': CustomSitemap(request)} + return sitemap_views.sitemap(request, sitemaps, **kwargs) diff --git a/network-api/networkapi/templates/fragments/canonical_url.html b/network-api/networkapi/templates/fragments/canonical_url.html index 8aa9be44f9a..bc1c206fea6 100644 --- a/network-api/networkapi/templates/fragments/canonical_url.html +++ b/network-api/networkapi/templates/fragments/canonical_url.html @@ -28,7 +28,7 @@ {% elif CODE == 'pa-IN' %} - {% elif CODE == 'pt' %} + {% elif CODE == 'pt-BR' %} diff --git a/network-api/networkapi/templates/fragments/language_switcher.html b/network-api/networkapi/templates/fragments/language_switcher.html index 6bbe1fb130d..0aed1e0b841 100644 --- a/network-api/networkapi/templates/fragments/language_switcher.html +++ b/network-api/networkapi/templates/fragments/language_switcher.html @@ -1,15 +1,16 @@ -{% load i18n localization %} +{% load i18n localization wagtailcore_tags %} + +{% get_current_language as current_language %} +{% get_local_language_names as languages %}
- +
diff --git a/network-api/networkapi/urls.py b/network-api/networkapi/urls.py index 413cf730a27..73369ed5c01 100644 --- a/network-api/networkapi/urls.py +++ b/network-api/networkapi/urls.py @@ -21,8 +21,8 @@ # from wagtail.core import urls as wagtail_urls from .utility import watail_core_url_override as wagtail_urls +from .sitemaps import sitemap -from wagtail.contrib.sitemaps.views import sitemap from wagtail_footnotes import urls as footnotes_urls from networkapi.wagtailcustomization.image_url_tag_urls import urlpatterns as image_url_tag_urls from networkapi.views import EnvVariablesView, review_app_help_view @@ -69,7 +69,6 @@ re_path(r'^cms/', include(wagtailadmin_urls)), re_path(r'^en/cms/', RedirectView.as_view(url='/cms/')), re_path(r'^documents/', include(wagtaildocs_urls)), - re_path(r'^sitemap.xml$', sitemap), # Sentry test url path('sentry-debug', lambda r: 1 / 0) if settings.SENTRY_DSN and settings.DEBUG else None, @@ -80,6 +79,9 @@ # Wagtail Footnotes package path("footnotes/", include(footnotes_urls)), + + # redirect /pt to /pt-BR. See https://github.com/mozilla/foundation.mozilla.org/issues/5993 + re_path(r'^pt/(?P.*)', RedirectView.as_view(url='/pt-BR/%(rest)s', query_string=True, permanent=True)), ])) # Anything that needs to respect the localised @@ -95,6 +97,8 @@ # wagtail-managed data re_path(r'', include(wagtail_urls)), + + path('sitemap.xml', cache_page(86400)(sitemap)), ) if settings.USE_S3 is not True: @@ -111,3 +115,8 @@ # Use a custom 404 handler so that we can serve distinct 404 # pages for each "site" that wagtail services. handler404 = 'networkapi.wagtailpages.views.custom404_view' + +# Use a custom 500 handler if and only if Django refuses to give any stack +# traces for server error 500... And even then, do not use this on prod. +if settings.FORCE_500_STACK_TRACES is True: + handler500 = 'networkapi.utility.custom_url_handlers.server_error_500_handler' diff --git a/network-api/networkapi/utility/custom_url_handlers.py b/network-api/networkapi/utility/custom_url_handlers.py new file mode 100644 index 00000000000..675d7a32064 --- /dev/null +++ b/network-api/networkapi/utility/custom_url_handlers.py @@ -0,0 +1,15 @@ +import sys +import traceback +from django.http import HttpResponse + + +def server_error_500_handler(request): + type, value, tb = sys.exc_info() + + print('\n----intercepted 500 error stack trace----') + print(value) + print(type) + print(traceback.format_exception(type, value, tb)) + print('----\n') + + return HttpResponse(status=404) diff --git a/network-api/networkapi/wagtailpages/donation_modal.py b/network-api/networkapi/wagtailpages/donation_modal.py index 42283321475..1e01e1908df 100644 --- a/network-api/networkapi/wagtailpages/donation_modal.py +++ b/network-api/networkapi/wagtailpages/donation_modal.py @@ -1,12 +1,15 @@ from django.db import models +from wagtail.core.models import TranslatableMixin from wagtail.snippets.edit_handlers import SnippetChooserPanel from wagtail.snippets.models import register_snippet from modelcluster.fields import ParentalKey +from wagtail_localize.fields import TranslatableField + @register_snippet -class DonationModal(models.Model): +class DonationModal(TranslatableMixin, models.Model): name = models.CharField( default='', max_length=100, @@ -40,6 +43,13 @@ class DonationModal(models.Model): default="No thanks", ) + translatable_fields = [ + TranslatableField('header'), + TranslatableField('body'), + TranslatableField('donate_text'), + TranslatableField('dismiss_text'), + ] + def to_simple_dict(self): keys = ['name', 'header', 'body', 'donate_text', 'dismiss_text'] values = map(lambda k: getattr(self, k), keys) @@ -48,11 +58,11 @@ def to_simple_dict(self): def __str__(self): return self.name - class Meta: + class Meta(TranslatableMixin.Meta): verbose_name_plural = 'Donation CTA' -class DonationModals(models.Model): +class DonationModals(TranslatableMixin, models.Model): page = ParentalKey( 'wagtailpages.CampaignPage', related_name='donation_modals', @@ -75,6 +85,6 @@ def to_simple_dict(self): SnippetChooserPanel('donation_modal'), ] - class Meta: + class Meta(TranslatableMixin.Meta): verbose_name = 'Donation Modals' verbose_name_plural = 'Donation Modals' diff --git a/network-api/networkapi/wagtailpages/factory/buyersguide.py b/network-api/networkapi/wagtailpages/factory/buyersguide.py index e9be981ba49..39c1ce00c2c 100644 --- a/network-api/networkapi/wagtailpages/factory/buyersguide.py +++ b/network-api/networkapi/wagtailpages/factory/buyersguide.py @@ -38,6 +38,10 @@ def get_random_option(options=[]): return choice(options) +def get_extended_boolean_value(): + return get_random_option(['Yes', 'No', 'U']) + + def get_extended_yes_no_value(): return get_random_option(['Yes', 'No', 'NA', 'CD']) @@ -173,18 +177,10 @@ class Meta: handles_recordings_how = Faker('sentence') recording_alert = LazyFunction(get_extended_yes_no_value) recording_alert_helptext = Faker('sentence') - medical_privacy_compliant = Faker('boolean') - medical_privacy_compliant_helptext = Faker('sentence') - host_controls = Faker('sentence') - easy_to_learn_and_use = Faker('boolean') - easy_to_learn_and_use_helptext = Faker('sentence') - handles_recordings_how = Faker('sentence') - recording_alert = LazyFunction(get_extended_yes_no_value) - recording_alert_helptext = Faker('sentence') - medical_privacy_compliant = Faker('boolean') + medical_privacy_compliant = LazyFunction(get_extended_boolean_value) medical_privacy_compliant_helptext = Faker('sentence') host_controls = Faker('sentence') - easy_to_learn_and_use = Faker('boolean') + easy_to_learn_and_use = LazyFunction(get_extended_boolean_value) easy_to_learn_and_use_helptext = Faker('sentence') diff --git a/network-api/networkapi/wagtailpages/fields.py b/network-api/networkapi/wagtailpages/fields.py index 6360be8cbf8..23455a9b15f 100644 --- a/network-api/networkapi/wagtailpages/fields.py +++ b/network-api/networkapi/wagtailpages/fields.py @@ -1,6 +1,39 @@ from django.db import models +class ExtendedBoolean(models.CharField): + """ + TODO: unify this with the ExtendedYesNoField below. Because this would + introduce a superclass hierarchy change, and Django is notoriously + bad at those, this is a separate task. + + See https://github.com/mozilla/foundation.mozilla.org/issues/6929 + """ + + description = "Yes, No, Unknown" + + choice_list = [ + ('Yes', 'Yes'), + ('No', 'No'), + ('U', 'Unknown'), + ] + + default_choice = 'U' + + def __init__(self, *args, **kwargs): + kwargs['choices'] = self.choice_list + kwargs['default'] = self.default_choice + kwargs['max_length'] = 3 + super().__init__(*args, **kwargs) + + def deconstruct(self): + name, path, args, kwargs = super().deconstruct() + del kwargs['choices'] + del kwargs['default'] + del kwargs['max_length'] + return name, path, args, kwargs + + class ExtendedYesNoField(models.CharField): description = "Yes, No, Not Applicable, or Can’t Determine" diff --git a/network-api/networkapi/wagtailpages/migrations/0009_auto_20210525_1516.py b/network-api/networkapi/wagtailpages/migrations/0015_auto_20210525_1516.py similarity index 89% rename from network-api/networkapi/wagtailpages/migrations/0009_auto_20210525_1516.py rename to network-api/networkapi/wagtailpages/migrations/0015_auto_20210525_1516.py index 3f03f963448..44e638b90e9 100644 --- a/network-api/networkapi/wagtailpages/migrations/0009_auto_20210525_1516.py +++ b/network-api/networkapi/wagtailpages/migrations/0015_auto_20210525_1516.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('wagtailpages', '0008_auto_20210519_1654'), + ('wagtailpages', '0014_auto_20210722_2145'), ] operations = [ @@ -2538,4 +2538,292 @@ class Migration(migrations.Migration): model_name='youtuberegretsreporterpage', name='intro_text_pt', ), + migrations.RemoveField( + model_name='articlepage', + name='body_de', + ), + migrations.RemoveField( + model_name='articlepage', + name='body_en', + ), + migrations.RemoveField( + model_name='articlepage', + name='body_es', + ), + migrations.RemoveField( + model_name='articlepage', + name='body_fr', + ), + migrations.RemoveField( + model_name='articlepage', + name='body_fy_NL', + ), + migrations.RemoveField( + model_name='articlepage', + name='body_nl', + ), + migrations.RemoveField( + model_name='articlepage', + name='body_pl', + ), + migrations.RemoveField( + model_name='articlepage', + name='body_pt', + ), + migrations.RemoveField( + model_name='articlepage', + name='secondary_subtitle_de', + ), + migrations.RemoveField( + model_name='articlepage', + name='secondary_subtitle_en', + ), + migrations.RemoveField( + model_name='articlepage', + name='secondary_subtitle_es', + ), + migrations.RemoveField( + model_name='articlepage', + name='secondary_subtitle_fr', + ), + migrations.RemoveField( + model_name='articlepage', + name='secondary_subtitle_fy_NL', + ), + migrations.RemoveField( + model_name='articlepage', + name='secondary_subtitle_nl', + ), + migrations.RemoveField( + model_name='articlepage', + name='secondary_subtitle_pl', + ), + migrations.RemoveField( + model_name='articlepage', + name='secondary_subtitle_pt', + ), + migrations.RemoveField( + model_name='articlepage', + name='subtitle_de', + ), + migrations.RemoveField( + model_name='articlepage', + name='subtitle_en', + ), + migrations.RemoveField( + model_name='articlepage', + name='subtitle_es', + ), + migrations.RemoveField( + model_name='articlepage', + name='subtitle_fr', + ), + migrations.RemoveField( + model_name='articlepage', + name='subtitle_fy_NL', + ), + migrations.RemoveField( + model_name='articlepage', + name='subtitle_nl', + ), + migrations.RemoveField( + model_name='articlepage', + name='subtitle_pl', + ), + migrations.RemoveField( + model_name='articlepage', + name='subtitle_pt', + ), + migrations.RemoveField( + model_name='publicationpage', + name='additional_author_copy_de', + ), + migrations.RemoveField( + model_name='publicationpage', + name='additional_author_copy_en', + ), + migrations.RemoveField( + model_name='publicationpage', + name='additional_author_copy_es', + ), + migrations.RemoveField( + model_name='publicationpage', + name='additional_author_copy_fr', + ), + migrations.RemoveField( + model_name='publicationpage', + name='additional_author_copy_fy_NL', + ), + migrations.RemoveField( + model_name='publicationpage', + name='additional_author_copy_nl', + ), + migrations.RemoveField( + model_name='publicationpage', + name='additional_author_copy_pl', + ), + migrations.RemoveField( + model_name='publicationpage', + name='additional_author_copy_pt', + ), + migrations.RemoveField( + model_name='publicationpage', + name='contents_title_de', + ), + migrations.RemoveField( + model_name='publicationpage', + name='contents_title_en', + ), + migrations.RemoveField( + model_name='publicationpage', + name='contents_title_es', + ), + migrations.RemoveField( + model_name='publicationpage', + name='contents_title_fr', + ), + migrations.RemoveField( + model_name='publicationpage', + name='contents_title_fy_NL', + ), + migrations.RemoveField( + model_name='publicationpage', + name='contents_title_nl', + ), + migrations.RemoveField( + model_name='publicationpage', + name='contents_title_pl', + ), + migrations.RemoveField( + model_name='publicationpage', + name='contents_title_pt', + ), + migrations.RemoveField( + model_name='publicationpage', + name='intro_notes_de', + ), + migrations.RemoveField( + model_name='publicationpage', + name='intro_notes_en', + ), + migrations.RemoveField( + model_name='publicationpage', + name='intro_notes_es', + ), + migrations.RemoveField( + model_name='publicationpage', + name='intro_notes_fr', + ), + migrations.RemoveField( + model_name='publicationpage', + name='intro_notes_fy_NL', + ), + migrations.RemoveField( + model_name='publicationpage', + name='intro_notes_nl', + ), + migrations.RemoveField( + model_name='publicationpage', + name='intro_notes_pl', + ), + migrations.RemoveField( + model_name='publicationpage', + name='intro_notes_pt', + ), + migrations.RemoveField( + model_name='publicationpage', + name='notes_de', + ), + migrations.RemoveField( + model_name='publicationpage', + name='notes_en', + ), + migrations.RemoveField( + model_name='publicationpage', + name='notes_es', + ), + migrations.RemoveField( + model_name='publicationpage', + name='notes_fr', + ), + migrations.RemoveField( + model_name='publicationpage', + name='notes_fy_NL', + ), + migrations.RemoveField( + model_name='publicationpage', + name='notes_nl', + ), + migrations.RemoveField( + model_name='publicationpage', + name='notes_pl', + ), + migrations.RemoveField( + model_name='publicationpage', + name='notes_pt', + ), + migrations.RemoveField( + model_name='publicationpage', + name='secondary_subtitle_de', + ), + migrations.RemoveField( + model_name='publicationpage', + name='secondary_subtitle_en', + ), + migrations.RemoveField( + model_name='publicationpage', + name='secondary_subtitle_es', + ), + migrations.RemoveField( + model_name='publicationpage', + name='secondary_subtitle_fr', + ), + migrations.RemoveField( + model_name='publicationpage', + name='secondary_subtitle_fy_NL', + ), + migrations.RemoveField( + model_name='publicationpage', + name='secondary_subtitle_nl', + ), + migrations.RemoveField( + model_name='publicationpage', + name='secondary_subtitle_pl', + ), + migrations.RemoveField( + model_name='publicationpage', + name='secondary_subtitle_pt', + ), + migrations.RemoveField( + model_name='publicationpage', + name='subtitle_de', + ), + migrations.RemoveField( + model_name='publicationpage', + name='subtitle_en', + ), + migrations.RemoveField( + model_name='publicationpage', + name='subtitle_es', + ), + migrations.RemoveField( + model_name='publicationpage', + name='subtitle_fr', + ), + migrations.RemoveField( + model_name='publicationpage', + name='subtitle_fy_NL', + ), + migrations.RemoveField( + model_name='publicationpage', + name='subtitle_nl', + ), + migrations.RemoveField( + model_name='publicationpage', + name='subtitle_pl', + ), + migrations.RemoveField( + model_name='publicationpage', + name='subtitle_pt', + ), ] diff --git a/network-api/networkapi/wagtailpages/migrations/0010_auto_20210525_1716.py b/network-api/networkapi/wagtailpages/migrations/0016_auto_20210525_1716.py similarity index 92% rename from network-api/networkapi/wagtailpages/migrations/0010_auto_20210525_1716.py rename to network-api/networkapi/wagtailpages/migrations/0016_auto_20210525_1716.py index e835a16aaaa..72e10a05a11 100644 --- a/network-api/networkapi/wagtailpages/migrations/0010_auto_20210525_1716.py +++ b/network-api/networkapi/wagtailpages/migrations/0016_auto_20210525_1716.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('wagtailpages', '0009_auto_20210525_1516'), + ('wagtailpages', '0015_auto_20210525_1516'), ] operations = [ diff --git a/network-api/networkapi/wagtailpages/migrations/0017_bootstrap_models.py b/network-api/networkapi/wagtailpages/migrations/0017_bootstrap_models.py new file mode 100644 index 00000000000..51e5dbb2762 --- /dev/null +++ b/network-api/networkapi/wagtailpages/migrations/0017_bootstrap_models.py @@ -0,0 +1,266 @@ +# Generated by Django 3.1.11 on 2021-06-04 16:01 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0062_comment_models_and_pagesubscription'), + ('wagtailpages', '0016_auto_20210525_1716'), + ] + + operations = [ + migrations.AddField( + model_name='blogpagecategory', + 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='blogpagecategory', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='buyersguideproductcategory', + 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='buyersguideproductcategory', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='contentauthor', + 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='contentauthor', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='cta', + 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='cta', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='donationmodal', + 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='donationmodal', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='focusarea', + 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='focusarea', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='update', + 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='update', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AlterModelOptions( + name='homepagefocusareas', + options={'verbose_name': 'Homepage Focus Area'}, + ), + migrations.AddField( + model_name='homepagefocusareas', + 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='homepagefocusareas', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='homepagetakeactioncards', + 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='homepagetakeactioncards', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='partnerlogos', + 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='partnerlogos', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='donationmodals', + 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='donationmodals', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AlterModelOptions( + name='excludedcategories', + options={'verbose_name': 'Excluded Category'}, + ), + migrations.AlterModelOptions( + name='productpageprivacypolicylink', + options={'verbose_name': 'Privacy Link'}, + ), + migrations.AlterModelOptions( + name='productupdates', + options={'verbose_name': 'Product Update'}, + ), + migrations.AlterModelOptions( + name='relatedproducts', + options={'verbose_name': 'Related Product'}, + ), + migrations.AddField( + model_name='excludedcategories', + 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='excludedcategories', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='productpagecategory', + 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='productpagecategory', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='productpageprivacypolicylink', + 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='productpageprivacypolicylink', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='productupdates', + 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='productupdates', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='relatedproducts', + 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='relatedproducts', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='cta4', + 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='cta4', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='homepagenewsyoucanuse', + 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='homepagenewsyoucanuse', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='homepagespotlightposts', + 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='homepagespotlightposts', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='initiativesection', + 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='initiativesection', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='initiativeshighlights', + 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='initiativeshighlights', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='participatehighlights', + 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='participatehighlights', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='participatehighlights2', + 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='participatehighlights2', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + + ] diff --git a/network-api/networkapi/wagtailpages/migrations/0018_bootstrap_migrations.py b/network-api/networkapi/wagtailpages/migrations/0018_bootstrap_migrations.py new file mode 100644 index 00000000000..76d8a65b8d4 --- /dev/null +++ b/network-api/networkapi/wagtailpages/migrations/0018_bootstrap_migrations.py @@ -0,0 +1,38 @@ +# Generated by Django 3.1.11 on 2021-06-04 16:02 + +from django.db import migrations +from wagtail.core.models import BootstrapTranslatableModel + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailpages', '0017_bootstrap_models'), + ] + + operations = [ + BootstrapTranslatableModel('wagtailpages.PartnerLogos'), + BootstrapTranslatableModel('wagtailpages.HomepageTakeActionCards'), + BootstrapTranslatableModel('wagtailpages.HomepageFocusAreas'), + BootstrapTranslatableModel('wagtailpages.DonationModals'), + BootstrapTranslatableModel('wagtailpages.DonationModal'), + BootstrapTranslatableModel('wagtailpages.ProductPageCategory'), + BootstrapTranslatableModel('wagtailpages.RelatedProducts'), + BootstrapTranslatableModel('wagtailpages.ProductPagePrivacyPolicyLink'), + BootstrapTranslatableModel('wagtailpages.ProductUpdates'), + BootstrapTranslatableModel('wagtailpages.ExcludedCategories'), + BootstrapTranslatableModel('wagtailpages.FocusArea'), + BootstrapTranslatableModel('wagtailpages.Petition'), + BootstrapTranslatableModel('wagtailpages.Signup'), + BootstrapTranslatableModel('wagtailpages.ContentAuthor'), + BootstrapTranslatableModel('wagtailpages.BuyersGuideProductCategory'), + BootstrapTranslatableModel('wagtailpages.Update'), + BootstrapTranslatableModel('wagtailpages.BlogPageCategory'), + BootstrapTranslatableModel('wagtailpages.InitiativeSection'), + BootstrapTranslatableModel('wagtailpages.HomepageSpotlightPosts'), + BootstrapTranslatableModel('wagtailpages.HomepageNewsYouCanUse'), + BootstrapTranslatableModel('wagtailpages.InitiativesHighlights'), + BootstrapTranslatableModel('wagtailpages.CTA4'), + BootstrapTranslatableModel('wagtailpages.ParticipateHighlights'), + BootstrapTranslatableModel('wagtailpages.ParticipateHighlights2'), + + ] diff --git a/network-api/networkapi/wagtailpages/migrations/0019_translation_mixin_migration.py b/network-api/networkapi/wagtailpages/migrations/0019_translation_mixin_migration.py new file mode 100644 index 00000000000..21afa255eb3 --- /dev/null +++ b/network-api/networkapi/wagtailpages/migrations/0019_translation_mixin_migration.py @@ -0,0 +1,374 @@ +# Generated by Django 3.1.11 on 2021-06-04 16:17 + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0062_comment_models_and_pagesubscription'), + ('wagtailpages', '0018_bootstrap_migrations'), + ] + + operations = [ + migrations.AlterField( + model_name='homepagefocusareas', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='homepagefocusareas', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='homepagetakeactioncards', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='homepagetakeactioncards', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='partnerlogos', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='partnerlogos', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterUniqueTogether( + name='homepagefocusareas', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='homepagetakeactioncards', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='partnerlogos', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterField( + model_name='donationmodals', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='donationmodals', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterUniqueTogether( + name='donationmodals', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterField( + model_name='excludedcategories', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='excludedcategories', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='productpagecategory', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='productpagecategory', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='productpageprivacypolicylink', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='productpageprivacypolicylink', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='productupdates', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='productupdates', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='relatedproducts', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='relatedproducts', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterUniqueTogether( + name='excludedcategories', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='productpagecategory', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='productpageprivacypolicylink', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='productupdates', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='relatedproducts', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterField( + model_name='blogpagecategory', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='blogpagecategory', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='buyersguideproductcategory', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='buyersguideproductcategory', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='donationmodal', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='donationmodal', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterUniqueTogether( + name='donationmodal', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterField( + model_name='focusarea', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='focusarea', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='update', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='update', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterUniqueTogether( + name='blogpagecategory', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='buyersguideproductcategory', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='focusarea', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='update', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterField( + model_name='contentauthor', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='contentauthor', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='cta', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='cta', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterUniqueTogether( + name='contentauthor', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='cta', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterField( + model_name='cta4', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='cta4', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='homepagenewsyoucanuse', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='homepagenewsyoucanuse', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='homepagespotlightposts', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='homepagespotlightposts', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='initiativesection', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='initiativesection', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='initiativeshighlights', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='initiativeshighlights', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='participatehighlights', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='participatehighlights', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='participatehighlights2', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='participatehighlights2', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterUniqueTogether( + name='cta4', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='homepagenewsyoucanuse', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='homepagespotlightposts', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='initiativesection', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='initiativeshighlights', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='participatehighlights', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='participatehighlights2', + unique_together={('translation_key', 'locale')}, + ), + # Meta classes from TranslatableMixin's + migrations.AlterModelOptions( + name='cta4', + options={}, + ), + migrations.AlterModelOptions( + name='participatehighlights', + options={}, + ), + migrations.AlterModelOptions( + name='participatehighlights2', + options={}, + ), + ] diff --git a/network-api/networkapi/wagtailpages/migrations/0020_remove_localization_from_cta_model.py b/network-api/networkapi/wagtailpages/migrations/0020_remove_localization_from_cta_model.py new file mode 100644 index 00000000000..7987bd8a219 --- /dev/null +++ b/network-api/networkapi/wagtailpages/migrations/0020_remove_localization_from_cta_model.py @@ -0,0 +1,25 @@ +# Generated by Django 3.1.11 on 2021-06-10 16:53 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailpages', '0019_translation_mixin_migration'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='cta', + unique_together=set(), + ), + migrations.RemoveField( + model_name='cta', + name='locale', + ), + migrations.RemoveField( + model_name='cta', + name='translation_key', + ), + ] diff --git a/network-api/networkapi/wagtailpages/migrations/0021_add_localization_to_subclasses.py b/network-api/networkapi/wagtailpages/migrations/0021_add_localization_to_subclasses.py new file mode 100644 index 00000000000..468e2dbb751 --- /dev/null +++ b/network-api/networkapi/wagtailpages/migrations/0021_add_localization_to_subclasses.py @@ -0,0 +1,35 @@ +# Generated by Django 3.1.11 on 2021-06-10 16:55 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0062_comment_models_and_pagesubscription'), + ('wagtailpages', '0020_remove_localization_from_cta_model'), + ] + + operations = [ + migrations.AddField( + model_name='petition', + 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='petition', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + migrations.AddField( + model_name='signup', + 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='signup', + name='translation_key', + field=models.UUIDField(editable=False, null=True), + ), + ] diff --git a/network-api/networkapi/wagtailpages/migrations/0022_bootstrap_subclassed_cta_models.py b/network-api/networkapi/wagtailpages/migrations/0022_bootstrap_subclassed_cta_models.py new file mode 100644 index 00000000000..7d4dc8cffe5 --- /dev/null +++ b/network-api/networkapi/wagtailpages/migrations/0022_bootstrap_subclassed_cta_models.py @@ -0,0 +1,16 @@ +# Generated by Django 3.1.11 on 2021-06-10 16:55 + +from django.db import migrations + +from wagtail.core.models import BootstrapTranslatableModel + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailpages', '0021_add_localization_to_subclasses'), + ] + + operations = [ + BootstrapTranslatableModel('wagtailpages.Petition'), + BootstrapTranslatableModel('wagtailpages.Signup'), + ] diff --git a/network-api/networkapi/wagtailpages/migrations/0023_translation_mixin_migration_for_cta_models.py b/network-api/networkapi/wagtailpages/migrations/0023_translation_mixin_migration_for_cta_models.py new file mode 100644 index 00000000000..2154e86a8ff --- /dev/null +++ b/network-api/networkapi/wagtailpages/migrations/0023_translation_mixin_migration_for_cta_models.py @@ -0,0 +1,46 @@ +# Generated by Django 3.1.11 on 2021-06-10 18:23 + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0062_comment_models_and_pagesubscription'), + ('wagtailpages', '0022_bootstrap_subclassed_cta_models'), + ] + + operations = [ + migrations.AlterField( + model_name='petition', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='petition', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterField( + model_name='signup', + name='locale', + field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale'), + preserve_default=False, + ), + migrations.AlterField( + model_name='signup', + name='translation_key', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + migrations.AlterUniqueTogether( + name='petition', + unique_together={('translation_key', 'locale')}, + ), + migrations.AlterUniqueTogether( + name='signup', + unique_together={('translation_key', 'locale')}, + ), + ] diff --git a/network-api/networkapi/wagtailpages/migrations/0024_fix_software_fields_part_1.py b/network-api/networkapi/wagtailpages/migrations/0024_fix_software_fields_part_1.py new file mode 100644 index 00000000000..2a6cf15314d --- /dev/null +++ b/network-api/networkapi/wagtailpages/migrations/0024_fix_software_fields_part_1.py @@ -0,0 +1,55 @@ +# Generated by Django 3.1.11 on 2021-06-23 21:40 + +from django.db import migrations +import networkapi.wagtailpages.fields + + +def update_software_product_fields(apps, schema_editor): + SoftwareProductPage = apps.get_model("wagtailpages", "SoftwareProductPage") + + # ExtendedBoolean is char(3), so the original boolean + # values True/False become the string values 'tru'/'fal'. + + for product in SoftwareProductPage.objects.all(): + if product.easy_to_learn_and_use == 'tru': + product.easy_to_learn_and_use = 'Yes' + + if product.easy_to_learn_and_use == 'fal': + product.easy_to_learn_and_use = 'No' + + if product.easy_to_learn_and_use == None: + product.easy_to_learn_and_use = 'U' + + if product.medical_privacy_compliant == 'tru': + product.medical_privacy_compliant = 'Yes' + + if product.medical_privacy_compliant == 'fal': + product.medical_privacy_compliant = 'No' + + if product.medical_privacy_compliant == None: + product.medical_privacy_compliant = 'U' + + product.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailpages', '0023_translation_mixin_migration_for_cta_models'), + ] + + operations = [ + migrations.AlterField( + model_name='softwareproductpage', + name='easy_to_learn_and_use', + field=networkapi.wagtailpages.fields.ExtendedBoolean(help_text='Is it easy to learn & use the features?', null=True, default='U'), + ), + migrations.AlterField( + model_name='softwareproductpage', + name='medical_privacy_compliant', + field=networkapi.wagtailpages.fields.ExtendedBoolean(help_text='Compliant with US medical privacy laws?', null=True, default='U'), + ), + migrations.RunPython( + code=update_software_product_fields + ), + ] diff --git a/network-api/networkapi/wagtailpages/migrations/0025_fix_software_fields_part_2.py b/network-api/networkapi/wagtailpages/migrations/0025_fix_software_fields_part_2.py new file mode 100644 index 00000000000..ae3b3d9b252 --- /dev/null +++ b/network-api/networkapi/wagtailpages/migrations/0025_fix_software_fields_part_2.py @@ -0,0 +1,24 @@ +# Generated by Django 3.1.11 on 2021-06-23 22:05 + +from django.db import migrations +import networkapi.wagtailpages.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailpages', '0024_fix_software_fields_part_1'), + ] + + operations = [ + migrations.AlterField( + model_name='softwareproductpage', + name='easy_to_learn_and_use', + field=networkapi.wagtailpages.fields.ExtendedBoolean(help_text='Is it easy to learn & use the features?'), + ), + migrations.AlterField( + model_name='softwareproductpage', + name='medical_privacy_compliant', + field=networkapi.wagtailpages.fields.ExtendedBoolean(help_text='Compliant with US medical privacy laws?'), + ), + ] diff --git a/network-api/networkapi/wagtailpages/pagemodels/base.py b/network-api/networkapi/wagtailpages/pagemodels/base.py index 3a4949de6a5..f3974cf42af 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/base.py +++ b/network-api/networkapi/wagtailpages/pagemodels/base.py @@ -2,13 +2,16 @@ from django.db import models from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel -from wagtail.core.models import Page, Orderable as WagtailOrderable +from wagtail.core.models import TranslatableMixin, Page, Orderable as WagtailOrderable + from wagtail.core.fields import RichTextField from wagtail.images.edit_handlers import ImageChooserPanel from wagtail.snippets.models import register_snippet from wagtail.snippets.edit_handlers import SnippetChooserPanel from wagtail.admin.edit_handlers import PageChooserPanel +from wagtail_localize.fields import SynchronizedField, TranslatableField + from modelcluster.fields import ParentalKey from .primary import PrimaryPage @@ -22,7 +25,7 @@ class NewsPage(PrimaryPage): template = 'wagtailpages/static/news_page.html' -class InitiativeSection(models.Model): +class InitiativeSection(TranslatableMixin, models.Model): page = ParentalKey( 'wagtailpages.InitiativesPage', related_name='initiative_sections', @@ -76,6 +79,16 @@ class InitiativeSection(models.Model): FieldPanel('sectionButtonURL2'), ] + translatable_fields = [ + SynchronizedField('sectionImage'), + TranslatableField('sectionHeader'), + TranslatableField('sectionCopy'), + TranslatableField('sectionButtonTitle'), + SynchronizedField('sectionButtonURL'), + TranslatableField('sectionButtonTitle2'), + SynchronizedField('sectionButtonURL2'), + ] + class InitiativesPage(PrimaryPage): template = 'wagtailpages/static/initiatives_page.html' @@ -125,6 +138,24 @@ class InitiativesPage(PrimaryPage): InlinePanel('featured_highlights', label='Highlights', max_num=9), ] + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('title'), + SynchronizedField('primaryHero'), + TranslatableField('header'), + TranslatableField('subheader'), + TranslatableField('h3'), + TranslatableField('sub_h3'), + TranslatableField('featured_highlights'), + TranslatableField('initiative_sections'), + ] + class ParticipatePage2(PrimaryPage): template = 'wagtailpages/static/participate_page2.html' @@ -236,6 +267,39 @@ class ParticipatePage2(PrimaryPage): verbose_name='H2 Subheader', ) + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('title'), + SynchronizedField('ctaHero'), + TranslatableField('ctaHeroHeader'), + TranslatableField('ctaHeroSubhead'), + TranslatableField('ctaButtonTitle'), + TranslatableField('ctaButtonURL'), + SynchronizedField('ctaHero2'), + TranslatableField('ctaHeroHeader2'), + TranslatableField('ctaHeroSubhead2'), + TranslatableField('ctaButtonTitle2'), + TranslatableField('ctaButtonURL2'), + SynchronizedField('ctaHero3'), + TranslatableField('ctaHeroHeader3'), + TranslatableField('ctaHeroSubhead3'), + TranslatableField('ctaFacebook3'), + TranslatableField('ctaTwitter3'), + TranslatableField('ctaEmailShareBody3'), + TranslatableField('ctaEmailShareSubject3'), + TranslatableField('h2'), + TranslatableField('h2Subheader'), + TranslatableField('featured_highlights'), + TranslatableField('featured_highlights2'), + TranslatableField('cta4'), + ] + content_panels = Page.content_panels + [ MultiFieldPanel( [ @@ -284,7 +348,7 @@ class Styleguide(PrimaryPage): template = 'wagtailpages/static/styleguide.html' -class HomepageSpotlightPosts(WagtailOrderable): +class HomepageSpotlightPosts(TranslatableMixin, WagtailOrderable): page = ParentalKey( 'wagtailpages.Homepage', related_name='spotlight_posts', @@ -294,7 +358,7 @@ class HomepageSpotlightPosts(WagtailOrderable): PageChooserPanel('blog'), ] - class Meta: + class Meta(TranslatableMixin.Meta): verbose_name = 'blog' verbose_name_plural = 'blogs' ordering = ['sort_order'] # not automatically inherited! @@ -303,7 +367,7 @@ def __str__(self): return self.page.title + '->' + self.blog.title -class HomepageNewsYouCanUse(WagtailOrderable): +class HomepageNewsYouCanUse(TranslatableMixin, WagtailOrderable): page = ParentalKey( 'wagtailpages.Homepage', related_name='news_you_can_use', @@ -313,7 +377,7 @@ class HomepageNewsYouCanUse(WagtailOrderable): PageChooserPanel('blog'), ] - class Meta: + class Meta(TranslatableMixin.Meta): verbose_name = 'blog' verbose_name_plural = 'blogs' ordering = ['sort_order'] # not automatically inherited! @@ -322,7 +386,7 @@ def __str__(self): return self.page.title + '->' + self.blog.title -class InitiativesHighlights(WagtailOrderable, models.Model): +class InitiativesHighlights(TranslatableMixin, WagtailOrderable, models.Model): page = ParentalKey( 'wagtailpages.InitiativesPage', related_name='featured_highlights', @@ -332,7 +396,7 @@ class InitiativesHighlights(WagtailOrderable, models.Model): SnippetChooserPanel('highlight'), ] - class Meta: + class Meta(TranslatableMixin.Meta): verbose_name = 'highlight' verbose_name_plural = 'highlights' ordering = ['sort_order'] # not automatically inherited! @@ -381,7 +445,7 @@ class CTABase(WagtailOrderable, models.Model): FieldPanel('buttonURL'), ] - class Meta: + class Meta(TranslatableMixin.Meta): abstract = True verbose_name = 'cta' verbose_name_plural = 'ctas' @@ -391,14 +455,17 @@ def __str__(self): return self.page.title + '->' + self.highlight.title -class CTA4(CTABase): +class CTA4(TranslatableMixin, CTABase): page = ParentalKey( 'wagtailpages.ParticipatePage2', related_name='cta4', ) + class Meta(TranslatableMixin.Meta): + pass + -class ParticipateHighlightsBase(WagtailOrderable, models.Model): +class ParticipateHighlightsBase(TranslatableMixin, WagtailOrderable, models.Model): page = ParentalKey( 'wagtailpages.ParticipatePage2', related_name='featured_highlights', @@ -424,6 +491,9 @@ class ParticipateHighlights(ParticipateHighlightsBase): related_name='featured_highlights', ) + class Meta(TranslatableMixin.Meta): + pass + class ParticipateHighlights2(ParticipateHighlightsBase): page = ParentalKey( @@ -431,9 +501,12 @@ class ParticipateHighlights2(ParticipateHighlightsBase): related_name='featured_highlights2', ) + class Meta(TranslatableMixin.Meta): + pass + @register_snippet -class FocusArea(models.Model): +class FocusArea(TranslatableMixin, models.Model): interest_icon = models.ForeignKey( 'wagtailimages.Image', null=True, @@ -466,15 +539,21 @@ class FocusArea(models.Model): PageChooserPanel('page'), ] + translatable_fields = [ + SynchronizedField('interest_icon'), + TranslatableField('name'), + TranslatableField('description'), + ] + def __str__(self): return self.name - class Meta: + class Meta(TranslatableMixin.Meta): verbose_name = 'Area of focus' verbose_name_plural = 'Areas of focus' -class HomepageFocusAreas(WagtailOrderable): +class HomepageFocusAreas(TranslatableMixin, WagtailOrderable): page = ParentalKey( 'wagtailpages.Homepage', related_name='focus_areas', @@ -486,8 +565,11 @@ class HomepageFocusAreas(WagtailOrderable): SnippetChooserPanel('area'), ] + class Meta(TranslatableMixin.Meta): + verbose_name = 'Homepage Focus Area' + -class HomepageTakeActionCards(WagtailOrderable): +class HomepageTakeActionCards(TranslatableMixin, WagtailOrderable): page = ParentalKey( 'wagtailpages.Homepage', related_name='take_action_cards', @@ -512,15 +594,21 @@ class HomepageTakeActionCards(WagtailOrderable): PageChooserPanel('internal_link'), ] + # translatable_fields = [ + # SynchronizedField('image'), + # TranslatableField('text'), + # SynchronizedField('internal_link'), + # ] + def __str__(self): return self.name - class Meta: + class Meta(TranslatableMixin.Meta): verbose_name = "Take Action Card" ordering = ['sort_order'] # not automatically inherited! -class PartnerLogos(WagtailOrderable): +class PartnerLogos(TranslatableMixin, WagtailOrderable): page = ParentalKey( 'wagtailpages.Homepage', related_name='partner_logos', @@ -549,12 +637,19 @@ class PartnerLogos(WagtailOrderable): FieldPanel('width'), ] + translatable_fields = [ + SynchronizedField('logo'), + TranslatableField('name'), + SynchronizedField('link'), + SynchronizedField('width'), + ] + @property def image_rendition(self): width = self.width * 2 return self.logo.get_rendition(f'width-{width}') - class Meta: + class Meta(TranslatableMixin.Meta): verbose_name = 'Partner Logo' ordering = ['sort_order'] # not automatically inherited! @@ -736,6 +831,41 @@ def get_banner(self): ), ] + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('title'), + TranslatableField('hero_headline'), + SynchronizedField('hero_image'), + TranslatableField('hero_button_text'), + SynchronizedField('hero_button_url'), + SynchronizedField('spotlight_image'), + TranslatableField('spotlight_headline'), + TranslatableField('cause_statement'), + TranslatableField('cause_statement_link_text'), + TranslatableField('cause_statement_link_page'), + SynchronizedField('quote_image'), + TranslatableField('quote_text'), + TranslatableField('quote_source_name'), + TranslatableField('quote_source_job_title'), + TranslatableField('partner_heading'), + TranslatableField('partner_intro_text'), + TranslatableField('partner_page_text'), + SynchronizedField('partner_page'), + SynchronizedField('partner_background_image'), + TranslatableField('take_action_title'), + TranslatableField('focus_areas'), + TranslatableField('take_action_cards'), + TranslatableField('partner_logos'), + TranslatableField('spotlight_posts'), + TranslatableField('news_you_can_use'), + ] + subpage_types = [ 'BanneredCampaignPage', 'BlogIndexPage', diff --git a/network-api/networkapi/wagtailpages/pagemodels/blog/blog.py b/network-api/networkapi/wagtailpages/pagemodels/blog/blog.py index 6debdb78c12..8dc447dd0bb 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/blog/blog.py +++ b/network-api/networkapi/wagtailpages/pagemodels/blog/blog.py @@ -11,11 +11,13 @@ StreamFieldPanel, ) from wagtail.core import blocks -from wagtail.core.models import Orderable, Page +from wagtail.core.models import Orderable, Locale, Page from wagtail.core.fields import StreamField from wagtail.snippets.edit_handlers import SnippetChooserPanel from wagtail.images.edit_handlers import ImageChooserPanel +from wagtail_localize.fields import TranslatableField, SynchronizedField + from taggit.models import TaggedItemBase from modelcluster.fields import ParentalKey, ParentalManyToManyField from modelcluster.contrib.taggit import ClusterTaggableManager @@ -141,6 +143,18 @@ class BlogPage(FoundationMetadataPageMixin, Page): PrivacyModalPanel(), ] + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('body'), + TranslatableField('title'), + ] + subpage_types = [ 'ArticlePage' ] @@ -151,11 +165,8 @@ def get_context(self, request): context['show_comments'] = settings.USE_COMMENTO and self.feature_comments # Pull this object specifically using the English page title - blog_page = BlogIndexPage.objects.get(title__iexact='Blog') - - # If that doesn't yield the blog page, pull using the universal title - if blog_page is None: - blog_page = BlogIndexPage.objects.get(title__iexact='Blog') + default_locale = Locale.objects.get(language_code=settings.LANGUAGE_CODE) + blog_page = BlogIndexPage.objects.get(title__iexact='Blog', locale=default_locale) if blog_page: context['blog_index'] = blog_page diff --git a/network-api/networkapi/wagtailpages/pagemodels/blog/blog_category.py b/network-api/networkapi/wagtailpages/pagemodels/blog/blog_category.py index 1835e939d89..97775e2780c 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/blog/blog_category.py +++ b/network-api/networkapi/wagtailpages/pagemodels/blog/blog_category.py @@ -1,11 +1,12 @@ from django.db import models from django.template.defaultfilters import slugify from wagtail.core.fields import RichTextField +from wagtail.core.models import TranslatableMixin from wagtail.snippets.models import register_snippet @register_snippet -class BlogPageCategory(models.Model): +class BlogPageCategory(TranslatableMixin, models.Model): name = models.CharField( max_length=50 ) @@ -41,6 +42,6 @@ def slug(self): def __str__(self): return self.name - class Meta: + class Meta(TranslatableMixin.Meta): verbose_name = "Blog Page Category" verbose_name_plural = "Blog Page Categories" diff --git a/network-api/networkapi/wagtailpages/pagemodels/blog/blog_index.py b/network-api/networkapi/wagtailpages/pagemodels/blog/blog_index.py index 5683ea10fa1..4f6187bf038 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/blog/blog_index.py +++ b/network-api/networkapi/wagtailpages/pagemodels/blog/blog_index.py @@ -54,11 +54,14 @@ class BlogIndexPage(IndexPage): 'featured_pages', label='Featured', help_text='Choose two blog pages to feature', - min_num=2, + min_num=0, max_num=2, ) ] + # Empty translatable fields + translatable_fields = IndexPage.translatable_fields + template = 'wagtailpages/blog_index_page.html' def get_all_entries(self): diff --git a/network-api/networkapi/wagtailpages/pagemodels/campaign_index.py b/network-api/networkapi/wagtailpages/pagemodels/campaign_index.py index 7faf5c204da..e1001a26f41 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/campaign_index.py +++ b/network-api/networkapi/wagtailpages/pagemodels/campaign_index.py @@ -1,5 +1,7 @@ from .index import IndexPage +from wagtail_localize.fields import SynchronizedField, TranslatableField + from networkapi.wagtailpages.pagemodels.publications.publication import PublicationPage @@ -19,6 +21,20 @@ class CampaignIndexPage(IndexPage): 'ArticlePage' ] + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields from IndexPage + TranslatableField('title'), + TranslatableField('intro'), + TranslatableField('header'), + SynchronizedField('page_size'), + ] + template = 'wagtailpages/index_page.html' def get_context(self, request): diff --git a/network-api/networkapi/wagtailpages/pagemodels/campaigns.py b/network-api/networkapi/wagtailpages/pagemodels/campaigns.py index f10790d31ea..75555ee3ad5 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/campaigns.py +++ b/network-api/networkapi/wagtailpages/pagemodels/campaigns.py @@ -3,11 +3,13 @@ from django.db import models from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, StreamFieldPanel -from wagtail.core.models import Page +from wagtail.core.models import TranslatableMixin, Page from wagtail.core.fields import RichTextField from wagtail.snippets.edit_handlers import SnippetChooserPanel from wagtail.snippets.models import register_snippet +from wagtail_localize.fields import SynchronizedField, TranslatableField + from taggit.models import TaggedItemBase from modelcluster.fields import ParentalKey from modelcluster.contrib.taggit import ClusterTaggableManager @@ -53,7 +55,7 @@ class Meta: @register_snippet -class Signup(CTA): +class Signup(TranslatableMixin, CTA): campaign_id = models.CharField( max_length=20, help_text='Which campaign identifier should this petition be tied to?', @@ -66,7 +68,13 @@ class Signup(CTA): default=False, ) - class Meta: + translatable_fields = [ + # Fields from the CTA model + TranslatableField('header'), + TranslatableField('description'), + ] + + class Meta(TranslatableMixin.Meta): verbose_name = 'signup snippet' @@ -82,7 +90,17 @@ class OpportunityPage(MiniSiteNameSpace): 'RedirectingPage', 'PublicationPage', 'ArticlePage' + ] + translatable_fields = [ + # Promote tab fields + TranslatableField('seo_title'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('title'), + TranslatableField('header'), + TranslatableField('body'), ] class Meta: @@ -91,7 +109,7 @@ class Meta: @register_snippet -class Petition(CTA): +class Petition(TranslatableMixin, CTA): campaign_id = models.CharField( max_length=20, help_text='Which campaign identifier should this petition be tied to?', @@ -175,7 +193,23 @@ class Petition(CTA): default='Thank you for signing too!', ) - class Meta: + translatable_fields = [ + # This models fields + SynchronizedField('requires_country_code'), + SynchronizedField('requires_postal_code'), + TranslatableField('comment_requirements'), + TranslatableField('checkbox_1'), + TranslatableField('checkbox_2'), + SynchronizedField('share_twitter'), + SynchronizedField('share_facebook'), + SynchronizedField('share_email'), + TranslatableField('thank_you'), + # Fields from the CTA model + TranslatableField('header'), + TranslatableField('description'), + ] + + class Meta(TranslatableMixin.Meta): verbose_name = 'petition snippet' @@ -208,6 +242,23 @@ def get_donation_modal_json(self): StreamFieldPanel('body'), ] + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('cta'), + TranslatableField('title'), + TranslatableField('header'), + SynchronizedField('narrowed_page_content'), + SynchronizedField('zen_nav'), + TranslatableField('body'), + TranslatableField('donation_modals'), + ] + subpage_types = [ 'CampaignPage', 'RedirectingPage', @@ -264,6 +315,25 @@ class BanneredCampaignPage(PrimaryPage): FieldPanel('tags'), ] + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('header'), + TranslatableField('intro'), + TranslatableField('body'), + TranslatableField("title"), + SynchronizedField("banner"), + SynchronizedField("narrowed_page_content"), + SynchronizedField("zen_nav"), + TranslatableField("cta"), + TranslatableField("signup"), + ] + subpage_types = [ 'BanneredCampaignPage', 'RedirectingPage', diff --git a/network-api/networkapi/wagtailpages/pagemodels/content_author.py b/network-api/networkapi/wagtailpages/pagemodels/content_author.py index d4b041a4c53..41847b17e99 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/content_author.py +++ b/network-api/networkapi/wagtailpages/pagemodels/content_author.py @@ -1,12 +1,13 @@ from django.db import models from wagtail.admin.edit_handlers import FieldPanel +from wagtail.core.models import TranslatableMixin from wagtail.images.edit_handlers import ImageChooserPanel from wagtail.snippets.models import register_snippet @register_snippet -class ContentAuthor(models.Model): +class ContentAuthor(TranslatableMixin, models.Model): name = models.CharField(max_length=70, blank=False) image = models.ForeignKey( diff --git a/network-api/networkapi/wagtailpages/pagemodels/dear_internet.py b/network-api/networkapi/wagtailpages/pagemodels/dear_internet.py index edd7d6db1fc..d2bcf89ee65 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/dear_internet.py +++ b/network-api/networkapi/wagtailpages/pagemodels/dear_internet.py @@ -4,6 +4,8 @@ from wagtail.core.models import Page from wagtail.core.fields import StreamField +from wagtail_localize.fields import SynchronizedField, TranslatableField + from wagtail.core import blocks from . import customblocks from .mixin.foundation_metadata import FoundationMetadataPageMixin @@ -53,6 +55,23 @@ class DearInternetPage(FoundationMetadataPageMixin, Page): ), ] + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('title'), + TranslatableField('intro_texts'), + TranslatableField('letters_section_heading'), + TranslatableField('letters'), + TranslatableField('cta'), + TranslatableField('cta_button_text'), + SynchronizedField('cta_button_link'), + ] + zen_nav = True def get_context(self, request): diff --git a/network-api/networkapi/wagtailpages/pagemodels/index.py b/network-api/networkapi/wagtailpages/pagemodels/index.py index 1d623224fb9..5571846f877 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/index.py +++ b/network-api/networkapi/wagtailpages/pagemodels/index.py @@ -13,6 +13,8 @@ from wagtail.core.models import Page from wagtail.contrib.routable_page.models import RoutablePageMixin, route +from wagtail_localize.fields import SynchronizedField, TranslatableField + from .mixin.foundation_metadata import FoundationMetadataPageMixin from networkapi.wagtailpages.utils import ( @@ -61,6 +63,20 @@ class IndexPage(FoundationMetadataPageMixin, RoutablePageMixin, Page): FieldPanel('page_size'), ] + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('title'), + TranslatableField('intro'), + TranslatableField('header'), + SynchronizedField('page_size'), + ] + def get_context(self, request): # bootstrap the render context context = super().get_context(request) diff --git a/network-api/networkapi/wagtailpages/pagemodels/mixin/foundation_metadata.py b/network-api/networkapi/wagtailpages/pagemodels/mixin/foundation_metadata.py index 735dfbca86d..abc6e7f1313 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/mixin/foundation_metadata.py +++ b/network-api/networkapi/wagtailpages/pagemodels/mixin/foundation_metadata.py @@ -1,4 +1,5 @@ from taggit.models import Tag + from wagtailmetadata.models import MetadataPageMixin from wagtail.images.models import Image @@ -69,5 +70,11 @@ def get_meta_image(self): # whatever is the default social share image. Which could be `None`! return default_social_share_image + def get_admin_display_title(self): + title = self.draft_title or self.title + if self.locale: + return f"({self.locale.language_code}) {title}" + return title + class Meta: abstract = True diff --git a/network-api/networkapi/wagtailpages/pagemodels/mixin/snippets.py b/network-api/networkapi/wagtailpages/pagemodels/mixin/snippets.py new file mode 100644 index 00000000000..9c5093155ce --- /dev/null +++ b/network-api/networkapi/wagtailpages/pagemodels/mixin/snippets.py @@ -0,0 +1,21 @@ +from django.conf import settings +from wagtail.core.models import Locale + + +class LocalizedSnippet(): + + DEFAULT_LOCALE = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.DEFAULT_LOCALE is None: + self.DEFAULT_LOCALE = Locale.objects.get(language_code=settings.LANGUAGE_CODE) + + @property + def original(self): + DEFAULT_LOCALE = Locale.objects.get(language_code=self.DEFAULT_LOCALE) + + try: + return self.get_translation(DEFAULT_LOCALE) + except AttributeError: + return self diff --git a/network-api/networkapi/wagtailpages/pagemodels/modular.py b/network-api/networkapi/wagtailpages/pagemodels/modular.py index 67aff335520..bc205569ae1 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/modular.py +++ b/network-api/networkapi/wagtailpages/pagemodels/modular.py @@ -3,6 +3,7 @@ from wagtail.admin.edit_handlers import FieldPanel, MultiFieldPanel, StreamFieldPanel from wagtail.core.models import Page from wagtail.core.fields import StreamField +from wagtail_localize.fields import SynchronizedField, TranslatableField from .base_fields import base_fields from .mixin.foundation_metadata import FoundationMetadataPageMixin @@ -56,6 +57,19 @@ class ModularPage(FoundationMetadataPageMixin, Page): StreamFieldPanel('body'), ] + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('header'), + SynchronizedField('narrowed_page_content'), + SynchronizedField('zen_nav'), + ] + show_in_menus_default = True def get_context(self, request): diff --git a/network-api/networkapi/wagtailpages/pagemodels/primary.py b/network-api/networkapi/wagtailpages/pagemodels/primary.py index 8142473a21c..cbb4f7806b9 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/primary.py +++ b/network-api/networkapi/wagtailpages/pagemodels/primary.py @@ -5,6 +5,8 @@ from wagtail.core.fields import StreamField from wagtail.images.edit_handlers import ImageChooserPanel +from wagtail_localize.fields import SynchronizedField, TranslatableField + from .base_fields import base_fields from .mixin.foundation_metadata import FoundationMetadataPageMixin from .mixin.foundation_banner_inheritance import FoundationBannerInheritanceMixin @@ -78,6 +80,23 @@ class PrimaryPage(FoundationMetadataPageMixin, FoundationBannerInheritanceMixin, StreamFieldPanel('body'), ] + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('title'), + TranslatableField('header'), + SynchronizedField('banner'), + TranslatableField('intro'), + TranslatableField('body'), + SynchronizedField('narrowed_page_content'), + SynchronizedField('zen_nav'), + ] + subpage_types = [ 'PrimaryPage', 'RedirectingPage', diff --git a/network-api/networkapi/wagtailpages/pagemodels/products.py b/network-api/networkapi/wagtailpages/pagemodels/products.py index 72c7fa3ea0d..4dbd132e842 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/products.py +++ b/network-api/networkapi/wagtailpages/pagemodels/products.py @@ -17,15 +17,17 @@ from wagtail.admin.edit_handlers import InlinePanel, FieldPanel, MultiFieldPanel, PageChooserPanel from wagtail.contrib.routable_page.models import RoutablePageMixin, route -from wagtail.core.models import Orderable, Page +from wagtail.core.models import Locale, Orderable, Page, TranslatableMixin + from wagtail.images.edit_handlers import ImageChooserPanel from wagtail.search import index from wagtail.snippets.edit_handlers import SnippetChooserPanel from wagtail.snippets.models import register_snippet +from wagtail_localize.fields import SynchronizedField, TranslatableField from wagtail_airtable.mixins import AirtableMixin -from networkapi.wagtailpages.fields import ExtendedYesNoField +from networkapi.wagtailpages.fields import ExtendedBoolean, ExtendedYesNoField from networkapi.wagtailpages.pagemodels.mixin.foundation_metadata import ( FoundationMetadataPageMixin ) @@ -33,7 +35,7 @@ # TODO: Move this util function from networkapi.buyersguide.utils import get_category_og_image_upload_path - +from .mixin.snippets import LocalizedSnippet TRACK_RECORD_CHOICES = [ ('Great', 'Great'), @@ -43,14 +45,53 @@ ] -def get_product_subset(cutoff_date, authenticated, key, products): +def get_language_code_from_request(request): + """ + Accepts a request. Returns a language code (string) if there is one. Falls back to English. + """ + language_code = settings.LANGUAGE_CODE + if hasattr(request, 'LANGUAGE_CODE'): + language_code = request.LANGUAGE_CODE + return language_code + + +def get_categories_for_locale(language_code): + """ + Start with the English list of categories, and replace any of them + with their localized counterpart, where possible, so that we don't + end up with an incomplete category list due to missing locale records. + """ + DEFAULT_LANGUAGE_CODE = settings.LANGUAGE_CODE + DEFAULT_LOCALE = Locale.objects.get(language_code=DEFAULT_LANGUAGE_CODE) + + default_locale_list = BuyersGuideProductCategory.objects.filter( + hidden=False, + locale=DEFAULT_LOCALE, + ) + + if language_code == DEFAULT_LANGUAGE_CODE: + return default_locale_list + + actual_locale = Locale.objects.get(language_code=language_code) + return [ + BuyersGuideProductCategory.objects.filter( + translation_key=cat.translation_key, + locale=actual_locale, + ).first() or cat for cat in default_locale_list + ] + + +def get_product_subset(cutoff_date, authenticated, key, products, language_code='en'): """ filter a queryset based on our current cutoff date, as well as based on whether a user is authenticated to the system or not (authenticated users get to see all products, including draft products) """ - products = products.filter(review_date__gte=cutoff_date) + products = products.filter( + review_date__gte=cutoff_date, + locale=Locale.objects.get(language_code=language_code) + ) if not authenticated: products = products.live() products = sort_average(products) @@ -65,7 +106,7 @@ def sort_average(products): @register_snippet -class BuyersGuideProductCategory(models.Model): +class BuyersGuideProductCategory(TranslatableMixin, LocalizedSnippet, models.Model): """ A simple category class for use with Buyers Guide products, registered as snippet so that we can moderate them if and @@ -105,15 +146,16 @@ class BuyersGuideProductCategory(models.Model): blank=True, ) + translatable_fields = [ + TranslatableField('name'), + TranslatableField('description'), + SynchronizedField('slug'), + ] + @property def published_product_page_count(self): return ProductPage.objects.filter(product_categories__category=self).live().count() - @property - def published_product_count(self): - # TODO: REMOVE: LEGACY FUNCTION - return ProductPage.objects.filter(product_category=self, draft=False).count() - def __str__(self): return self.name @@ -121,13 +163,16 @@ def save(self, *args, **kwargs): self.slug = slugify(self.name) super().save(*args, **kwargs) - class Meta: + class Meta(TranslatableMixin.Meta): verbose_name = "Buyers Guide Product Category" verbose_name_plural = "Buyers Guide Product Categories" ordering = ['sort_order', 'name', ] class ProductPageVotes(models.Model): + """ + PNI product voting bins. This does not need translating. + """ vote_bins = models.CharField(default="0,0,0,0,0", max_length=50, validators=[int_list_validator]) def set_votes(self, bin_list): @@ -145,7 +190,7 @@ def get_votes(self): return votes -class ProductPageCategory(Orderable): +class ProductPageCategory(TranslatableMixin, Orderable): product = ParentalKey( 'wagtailpages.ProductPage', related_name='product_categories', @@ -165,11 +210,11 @@ class ProductPageCategory(Orderable): def __str__(self): return self.category.name - class Meta: + class Meta(TranslatableMixin.Meta): verbose_name = "Product Category" -class RelatedProducts(Orderable): +class RelatedProducts(TranslatableMixin, Orderable): page = ParentalKey( 'wagtailpages.ProductPage', related_name='related_product_pages', @@ -187,8 +232,11 @@ class RelatedProducts(Orderable): PageChooserPanel('related_product') ] + class Meta(TranslatableMixin.Meta): + verbose_name = 'Related Product' + -class ProductPagePrivacyPolicyLink(Orderable): +class ProductPagePrivacyPolicyLink(TranslatableMixin, Orderable): page = ParentalKey( 'wagtailpages.ProductPage', related_name='privacy_policy_links', @@ -211,12 +259,20 @@ class ProductPagePrivacyPolicyLink(Orderable): FieldPanel('url'), ] + translatable_fields = [ + TranslatableField('label'), + SynchronizedField('url'), + ] + def __str__(self): return f'{self.page.title}: {self.label} ({self.url})' + class Meta(TranslatableMixin.Meta): + verbose_name = 'Privacy Link' + @register_snippet -class Update(index.Indexed, models.Model): +class Update(TranslatableMixin, index.Indexed, models.Model): source = models.URLField( max_length=2048, help_text='Link to source', @@ -258,15 +314,22 @@ class Update(index.Indexed, models.Model): index.SearchField('title', partial_match=True), ] + translatable_fields = [ + SynchronizedField('source'), + SynchronizedField('title'), + SynchronizedField('author'), + SynchronizedField('snippet'), + ] + def __str__(self): return self.title - class Meta: + class Meta(TranslatableMixin.Meta): verbose_name = "Buyers Guide Product Update" verbose_name_plural = "Buyers Guide Product Updates" -class ProductUpdates(Orderable): +class ProductUpdates(TranslatableMixin, Orderable): page = ParentalKey( 'wagtailpages.ProductPage', related_name='updates', @@ -281,10 +344,17 @@ class ProductUpdates(Orderable): null=True ) + translatable_fields = [ + TranslatableField("update"), + ] + panels = [ SnippetChooserPanel('update'), ] + class Meta(TranslatableMixin.Meta): + verbose_name = 'Product Update' + class ProductPage(AirtableMixin, FoundationMetadataPageMixin, Page): """ @@ -698,6 +768,54 @@ def get_voting_json(self): ), ] + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('title'), + TranslatableField('search_description'), + SynchronizedField('privacy_ding'), + SynchronizedField('adult_content'), + SynchronizedField('uses_wifi'), + SynchronizedField('uses_bluetooth'), + SynchronizedField('review_date'), + SynchronizedField('company'), + TranslatableField('blurb'), + SynchronizedField('product_url'), + TranslatableField('price'), + SynchronizedField('image'), + TranslatableField('worst_case'), + SynchronizedField('signup_requires_email'), + SynchronizedField('signup_requires_phone'), + SynchronizedField('signup_requires_third_party_account'), + TranslatableField('signup_requirement_explanation'), + SynchronizedField('signup_requires_third_party_account'), + TranslatableField('how_does_it_use_data_collected'), + SynchronizedField('data_collection_policy_is_bad'), + SynchronizedField('user_friendly_privacy_policy'), + TranslatableField('user_friendly_privacy_policy_helptext'), + SynchronizedField('show_ding_for_minimum_security_standards'), + SynchronizedField('meets_minimum_security_standards'), + SynchronizedField('uses_encryption'), + TranslatableField('uses_encryption_helptext'), + SynchronizedField('security_updates'), + TranslatableField('security_updates_helptext'), + SynchronizedField('strong_password'), + TranslatableField('strong_password_helptext'), + SynchronizedField('manage_vulnerabilities'), + TranslatableField('manage_vulnerabilities_helptext'), + SynchronizedField('privacy_policy'), + TranslatableField('privacy_policy_helptext'), + SynchronizedField('phone_number'), + SynchronizedField('live_chat'), + SynchronizedField('email'), + SynchronizedField('twitter'), + ] + @property def product_type(self): return "unknown" @@ -717,7 +835,8 @@ def get_or_create_votes(self): def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) context['product'] = self - context['categories'] = BuyersGuideProductCategory.objects.filter(hidden=False) + language_code = get_language_code_from_request(request) + context['categories'] = get_categories_for_locale(language_code) context['mediaUrl'] = settings.MEDIA_URL context['use_commento'] = settings.USE_COMMENTO context['pageTitle'] = f'{self.title} | ' + gettext("Privacy & security guide") + ' | Mozilla Foundation' @@ -813,11 +932,8 @@ class SoftwareProductPage(ProductPage): max_length=5000, blank=True ) - # NullBooleanField is deprecated as of Django 3.1. - # We're using it here primarily for a data migration, but we should - # move to BooleanField as soon as it's safe to do so with the content we have - medical_privacy_compliant = models.BooleanField( - null=True, + + medical_privacy_compliant = ExtendedBoolean( help_text='Compliant with US medical privacy laws?' ) @@ -832,11 +948,8 @@ class SoftwareProductPage(ProductPage): max_length=5000, blank=True ) - # NullBooleanField is deprecated as of Django 3.1. - # We're using it here primarily for a data migration, but we should - # move to BooleanField as soon as it's safe to do so with the content we have - easy_to_learn_and_use = models.BooleanField( - null=True, + + easy_to_learn_and_use = ExtendedBoolean( help_text='Is it easy to learn & use the features?', ) @@ -909,10 +1022,22 @@ def get_export_fields(self): ], ) + translatable_fields = ProductPage.translatable_fields + [ + TranslatableField('handles_recordings_how'), + SynchronizedField('recording_alert'), + TranslatableField('recording_alert_helptext'), + SynchronizedField('medical_privacy_compliant'), + TranslatableField('medical_privacy_compliant_helptext'), + TranslatableField('host_controls'), + SynchronizedField('easy_to_learn_and_use'), + TranslatableField('easy_to_learn_and_use_helptext'), + ] + @property def product_type(self): return "software" + # TODO: Needs translatable_fields class Meta: verbose_name = "Software Product Page" @@ -1171,6 +1296,23 @@ def get_export_fields(self): ], ) + translatable_fields = ProductPage.translatable_fields + [ + TranslatableField('personal_data_collected'), + TranslatableField('biometric_data_collected'), + TranslatableField('social_data_collected'), + TranslatableField('how_can_you_control_your_data'), + SynchronizedField('data_control_policy_is_bad'), + SynchronizedField('company_track_record'), + SynchronizedField('track_record_is_bad'), + TranslatableField('track_record_details'), + SynchronizedField('offline_capable'), + TranslatableField('offline_use_description'), + SynchronizedField('uses_ai'), + SynchronizedField('ai_uses_personal_data'), + SynchronizedField('ai_is_transparent'), + TranslatableField('ai_helptext'), + ] + @property def product_type(self): return "general" @@ -1179,8 +1321,10 @@ class Meta: verbose_name = "General Product Page" -class ExcludedCategories(Orderable): - """This allows us to select one or more blog authors from Snippets.""" +class ExcludedCategories(TranslatableMixin, Orderable): + """ + This allows us to filter categories from showing up on the PNI site + """ page = ParentalKey("wagtailpages.BuyersGuidePage", related_name="excluded_categories") category = models.ForeignKey( @@ -1195,6 +1339,9 @@ class ExcludedCategories(Orderable): def __str__(self): return self.category.name + class Meta(TranslatableMixin.Meta): + verbose_name = 'Excluded Category' + class BuyersGuidePage(RoutablePageMixin, FoundationMetadataPageMixin, Page): """ @@ -1254,6 +1401,18 @@ def get_banner(self): ), ] + translatable_fields = [ + TranslatableField('title'), + SynchronizedField('hero_image'), + TranslatableField('header'), + TranslatableField('intro_text'), + SynchronizedField('dark_theme'), + # Promote tab fields + TranslatableField('seo_title'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + ] + @route(r'^about/$', name='how-to-use-view') def about_page(self, request): context = self.get_context(request) @@ -1327,16 +1486,35 @@ def product_view(self, request, slug): @route(r'^categories/(?P[\w\W]+)/', name='category-view') def categories_page(self, request, slug): context = self.get_context(request, bypass_products=True) + language_code = get_language_code_from_request(request) + locale_id = Locale.objects.get(language_code=language_code).id slug = slugify(slug) - # If getting by slug fails, also try to get it by name. + DEFAULT_LOCALE = Locale.objects.get(language_code=settings.LANGUAGE_CODE) + DEFAULT_LOCALE_ID = DEFAULT_LOCALE.id + + # because we may be working with localized content, and the slug + # will always be our english slug, we need to find the english + # category first, and then find its corresponding localized version try: - category = BuyersGuideProductCategory.objects.get(slug=slug) + original_category = BuyersGuideProductCategory.objects.get(slug=slug, locale_id=DEFAULT_LOCALE_ID) except BuyersGuideProductCategory.DoesNotExist: - category = get_object_or_404(BuyersGuideProductCategory, name__iexact=slug) + original_category = get_object_or_404(BuyersGuideProductCategory, name__iexact=slug) + + if locale_id != DEFAULT_LOCALE_ID: + try: + category = BuyersGuideProductCategory.objects.get( + translation_key=original_category.translation_key, + locale_id=DEFAULT_LOCALE_ID, + ) + except BuyersGuideProductCategory.DoesNotExist: + category = original_category + else: + category = original_category authenticated = request.user.is_authenticated key = f'cat_product_dicts_{slug}_auth' if authenticated else f'cat_product_dicts_{slug}_live' + key = f'{language_code}_{key}' products = cache.get(key) exclude_cat_ids = [excats.category.id for excats in self.excluded_categories.all()] @@ -1345,11 +1523,13 @@ def categories_page(self, request, slug): self.cutoff_date, authenticated, key, - ProductPage.objects.filter(product_categories__category__in=[category]) - .exclude(product_categories__category__id__in=exclude_cat_ids) + ProductPage.objects.filter(product_categories__category__in=[original_category]) + .exclude(product_categories__category__id__in=exclude_cat_ids), + language_code=language_code ) - context['category'] = category.slug + context['category'] = slug + context['current_category'] = category context['products'] = products context['pageTitle'] = f'{category} | ' + gettext("Privacy & security guide") + ' | Mozilla Foundation' context['template_cache_key_fragment'] = f'{category.slug}_{request.LANGUAGE_CODE}' @@ -1389,9 +1569,11 @@ def get_sitemap_urls(self, request): def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) + language_code = get_language_code_from_request(request) authenticated = request.user.is_authenticated key = 'home_product_dicts_authed' if authenticated else 'home_product_dicts_live' + key = f'{key}_{language_code}' products = cache.get(key) exclude_cat_ids = [excats.category.id for excats in self.excluded_categories.all()] @@ -1400,10 +1582,11 @@ def get_context(self, request, *args, **kwargs): self.cutoff_date, authenticated, key, - ProductPage.objects.exclude(product_categories__category__id__in=exclude_cat_ids) + ProductPage.objects.exclude(product_categories__category__id__in=exclude_cat_ids), + language_code=language_code ) - context['categories'] = BuyersGuideProductCategory.objects.filter(hidden=False) + context['categories'] = get_categories_for_locale(language_code) context['products'] = products context['web_monetization_pointer'] = settings.WEB_MONETIZATION_POINTER pni_home_page = BuyersGuidePage.objects.first() diff --git a/network-api/networkapi/wagtailpages/pagemodels/publications/article.py b/network-api/networkapi/wagtailpages/pagemodels/publications/article.py index 15ffb69bb8f..8603a2a298d 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/publications/article.py +++ b/network-api/networkapi/wagtailpages/pagemodels/publications/article.py @@ -8,6 +8,8 @@ from wagtail.images.edit_handlers import ImageChooserPanel from wagtail.snippets.edit_handlers import SnippetChooserPanel +from wagtail_localize.fields import SynchronizedField, TranslatableField + from networkapi.wagtailpages.models import ContentAuthor, PublicationPage from networkapi.wagtailpages.utils import get_plaintext_titles, set_main_site_nav_information, TitleWidget @@ -115,6 +117,23 @@ class ArticlePage(FoundationMetadataPageMixin, Page): InlinePanel("footnotes", label="Footnotes"), ] + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('title'), + SynchronizedField('toc_thumbnail_image'), + SynchronizedField('hero_image'), + TranslatableField('subtitle'), + SynchronizedField('article_file'), + TranslatableField('body'), + TranslatableField('footnotes'), + ] + @property def is_publication_article(self): parent = self.get_parent().specific diff --git a/network-api/networkapi/wagtailpages/pagemodels/publications/publication.py b/network-api/networkapi/wagtailpages/pagemodels/publications/publication.py index 7592bed3eaf..067e424be80 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/publications/publication.py +++ b/network-api/networkapi/wagtailpages/pagemodels/publications/publication.py @@ -9,6 +9,8 @@ from wagtail.images.edit_handlers import ImageChooserPanel from wagtail.snippets.edit_handlers import SnippetChooserPanel +from wagtail_localize.fields import SynchronizedField, TranslatableField + from networkapi.wagtailpages.models import ContentAuthor from networkapi.wagtailpages.utils import set_main_site_nav_information from ..mixin.foundation_metadata import FoundationMetadataPageMixin @@ -117,6 +119,27 @@ class PublicationPage(FoundationMetadataPageMixin, Page): FieldPanel('notes'), ] + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField("title"), + TranslatableField("subtitle"), + TranslatableField('secondary_subtitle'), + SynchronizedField('toc_thumbnail_image'), + SynchronizedField('hero_image'), + SynchronizedField('publication_date'), + SynchronizedField('publication_file'), + TranslatableField('additional_author_copy'), + TranslatableField('intro_notes'), + TranslatableField('contents_title'), + TranslatableField('notes'), + ] + @property def is_publication_page(self): """ diff --git a/network-api/networkapi/wagtailpages/pagemodels/youtube.py b/network-api/networkapi/wagtailpages/pagemodels/youtube.py index 4ae974d4f56..3b0f7a6599e 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/youtube.py +++ b/network-api/networkapi/wagtailpages/pagemodels/youtube.py @@ -5,6 +5,8 @@ from wagtail.core.models import Page from wagtail.core.fields import StreamField +from wagtail_localize.fields import TranslatableField, SynchronizedField + from . import customblocks from .mixin.foundation_metadata import FoundationMetadataPageMixin from ..utils import set_main_site_nav_information @@ -51,6 +53,22 @@ class YoutubeRegretsPage(FoundationMetadataPageMixin, Page): StreamFieldPanel('regret_stories'), ] + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('title'), + TranslatableField('headline'), + TranslatableField('intro_text'), + TranslatableField('intro_images'), + TranslatableField('faq'), + TranslatableField('regret_stories'), + ] + zen_nav = True def get_context(self, request): @@ -81,6 +99,20 @@ class YoutubeRegretsReporterPage(FoundationMetadataPageMixin, Page): StreamFieldPanel('intro_images'), ] + translatable_fields = [ + # Promote tab fields + SynchronizedField('slug'), + TranslatableField('seo_title'), + SynchronizedField('show_in_menus'), + TranslatableField('search_description'), + SynchronizedField('search_image'), + # Content tab fields + TranslatableField('title'), + TranslatableField('headline'), + TranslatableField('intro_text'), + TranslatableField('intro_images'), + ] + zen_nav = True def get_context(self, request): diff --git a/network-api/networkapi/wagtailpages/templates/buyersguide/bg_base.html b/network-api/networkapi/wagtailpages/templates/buyersguide/bg_base.html index 9ec63f1262a..312c6376a0a 100644 --- a/network-api/networkapi/wagtailpages/templates/buyersguide/bg_base.html +++ b/network-api/networkapi/wagtailpages/templates/buyersguide/bg_base.html @@ -1,6 +1,6 @@ {% extends "pages/base.html" %} -{% load bg_nav_tags localization i18n static wagtailroutablepage_tags wagtailmetadata_tags %} +{% load bg_nav_tags localization i18n static wagtailroutablepage_tags wagtailmetadata_tags debug_tags %} {% get_current_language as lang_code %} @@ -85,15 +85,18 @@
{% if pagetype == "product" or pagetype == "about" %} - {% trans "All" %} + {% trans "All" %} {% else %} - {% trans "All" %} + {% trans "All" %} {% endif %} + {% for cat in categories %} - {% if cat.published_product_page_count > 0 %} - {% routablepageurl home_page 'category-view' cat.slug as cat_url %} - {{ cat.name }} + {% with original=cat.original %} + {% if original.published_product_page_count > 0 %} + {% localizedroutablepageurl home_page 'category-view' lang_code original.slug as cat_url %} + {{ cat.name }} {% endif %} + {% endwith %} {% endfor %}
diff --git a/network-api/networkapi/wagtailpages/templates/buyersguide/catalog.html b/network-api/networkapi/wagtailpages/templates/buyersguide/catalog.html index 264466041a7..615bfe0556f 100644 --- a/network-api/networkapi/wagtailpages/templates/buyersguide/catalog.html +++ b/network-api/networkapi/wagtailpages/templates/buyersguide/catalog.html @@ -68,13 +68,13 @@

{{ page.header }}

{# User is not logged in. Return cached results. 24 hour caching applied. #} {% cache 86400 pni_home_page template_cache_key_fragment %} {% for product in products %} - {% include "../fragments/buyersguide_item.html" with product=product %} + {% include "../fragments/buyersguide_item.html" with product=product.localized %} {% endfor %} {% endcache %} {% else %} {# User is logged in. Don't cache their results so they can see live and draft products here. #} {% for product in products %} - {% include "../fragments/buyersguide_item.html" with product=product %} + {% include "../fragments/buyersguide_item.html" with product=product.localized %} {% endfor %} {% endif %}
diff --git a/network-api/networkapi/wagtailpages/templates/buyersguide/fragments/category_nav_links.html b/network-api/networkapi/wagtailpages/templates/buyersguide/fragments/category_nav_links.html index 998d0fc80a0..031dcb34d87 100644 --- a/network-api/networkapi/wagtailpages/templates/buyersguide/fragments/category_nav_links.html +++ b/network-api/networkapi/wagtailpages/templates/buyersguide/fragments/category_nav_links.html @@ -5,13 +5,15 @@ {% endcomment %} {% for cat in sorted_categories %} + {% with original=cat.original %} {% if cat.hidden == False and cat.published_product_page_count > 0 %} - {% url 'category-view' cat.slug as cat_url %} + {% localizedroutablepageurl home_page 'category-view' lang_code original.slug as cat_url %} + data-name="{{ original.name }}"> {{ cat.name }} {% endif %} + {% endwith %} {% endfor %} diff --git a/network-api/networkapi/wagtailpages/templates/buyersguide/product_page.html b/network-api/networkapi/wagtailpages/templates/buyersguide/product_page.html index 5f37ce77377..5f300e30cac 100644 --- a/network-api/networkapi/wagtailpages/templates/buyersguide/product_page.html +++ b/network-api/networkapi/wagtailpages/templates/buyersguide/product_page.html @@ -1,6 +1,8 @@ {% extends "buyersguide/bg_base.html" %} -{% load bg_selector_tags env l10n i18n static wagtailimages_tags %} +{% load bg_selector_tags env l10n i18n localization static wagtailimages_tags %} + +{% get_current_language as lang_code %} {% block head_extra %} @@ -295,9 +297,9 @@

{% trans "Comments" %}

{% trans "Related products" %}

{% for related_product_page in product.related_product_pages.all %} - {% with related_product=related_product_page.related_product %} + {% with related_product=related_product_page.related_product.localized %}