Switch from wagtail-modeltranslation to wagtail-localize (#7228)

This commit is contained in:
Pomax 2021-08-17 08:07:50 -07:00 коммит произвёл GitHub
Родитель a04bb196e3
Коммит bd8214ea28
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
112 изменённых файлов: 5407 добавлений и 511 удалений

3
.github/workflows/ci-image-diff-sync.yml поставляемый
Просмотреть файл

@ -81,10 +81,7 @@ jobs:
python network-api/manage.py collectstatic --no-input --verbosity 0
python network-api/manage.py migrate --no-input
python network-api/manage.py block_inventory
python network-api/manage.py sync_page_translation_fields
python network-api/manage.py update_translation_fields
python network-api/manage.py load_fake_data
python network-api/manage.py compilemessages
- name: Configure AWS Credentials for visual diffing
uses: aws-actions/configure-aws-credentials@v1

3
.github/workflows/ci-image-diff.yml поставляемый
Просмотреть файл

@ -98,10 +98,7 @@ jobs:
python network-api/manage.py collectstatic --no-input --verbosity 0
python network-api/manage.py migrate --no-input
python network-api/manage.py block_inventory
python network-api/manage.py sync_page_translation_fields
python network-api/manage.py update_translation_fields
python network-api/manage.py load_fake_data
python network-api/manage.py compilemessages
- name: Retrieving visual baseline
run: |

5
.github/workflows/continous-integration.yml поставляемый
Просмотреть файл

@ -86,8 +86,6 @@ jobs:
python network-api/manage.py collectstatic --no-input --verbosity 0
python network-api/manage.py migrate --no-input
python network-api/manage.py block_inventory
python network-api/manage.py sync_page_translation_fields
python network-api/manage.py update_translation_fields
python network-api/manage.py compilemessages
- name: Run Tests
run: |
@ -151,10 +149,7 @@ jobs:
python network-api/manage.py collectstatic --no-input --verbosity 0
python network-api/manage.py migrate --no-input
python network-api/manage.py block_inventory
python network-api/manage.py sync_page_translation_fields
python network-api/manage.py update_translation_fields
python network-api/manage.py load_fake_data
python network-api/manage.py compilemessages
- name: Percy Test
uses: ./.github/actions/percy-exec
with:

2
.github/workflows/prod-like-ci.yml поставляемый
Просмотреть файл

@ -68,8 +68,6 @@ jobs:
python network-api/manage.py collectstatic --no-input --verbosity 0
python network-api/manage.py migrate --no-input
python network-api/manage.py block_inventory
python network-api/manage.py sync_page_translation_fields
python network-api/manage.py update_translation_fields
python network-api/manage.py load_fake_data
- name: Run URL tests
run: |

23
.profile Normal file
Просмотреть файл

@ -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

Просмотреть файл

@ -4,38 +4,38 @@
#
# pip-compile dev-requirements.in
#
certifi==2020.4.5.1
certifi==2021.5.30
# via
# -c requirements.txt
# requests
# urllib3
cffi==1.14.0
cffi==1.14.6
# via
# -c requirements.txt
# cryptography
chardet==3.0.4
charset-normalizer==2.0.4
# via
# -c requirements.txt
# requests
coverage==5.1
coverage==5.5
# via coveralls
coveralls==3.0.1
coveralls==3.2.0
# via -r dev-requirements.in
cryptography==3.2
cryptography==3.4.7
# via
# -c requirements.txt
# pyopenssl
# urllib3
docopt==0.6.2
# via coveralls
flake8==3.8.4
flake8==3.9.2
# via -r dev-requirements.in
idna==2.9
idna==3.2
# via
# -c requirements.txt
# requests
# urllib3
importlib-metadata==3.10.1
importlib-metadata==4.6.3
# via
# -c requirements.txt
# flake8
@ -43,36 +43,35 @@ mccabe==0.6.1
# via flake8
ptvsd==4.3.2
# via -r dev-requirements.in
pycodestyle==2.6.0
pycodestyle==2.7.0
# via flake8
pycparser==2.20
# via
# -c requirements.txt
# cffi
pyflakes==2.2.0
pyflakes==2.3.1
# via flake8
pyopenssl==20.0.1
# via
# -c requirements.txt
# urllib3
requests==2.25.1
requests==2.26.0
# via
# -c requirements.txt
# coveralls
six==1.14.0
six==1.16.0
# via
# -c requirements.txt
# cryptography
# pyopenssl
typing-extensions==3.7.4.3
typing-extensions==3.10.0.0
# via
# -c requirements.txt
# importlib-metadata
urllib3[secure]==1.25.9
urllib3[secure]==1.26.6
# via
# -c requirements.txt
# requests
zipp==3.4.1
zipp==3.5.0
# via
# -c requirements.txt
# importlib-metadata

Просмотреть файл

@ -26,9 +26,6 @@ To get a list of invoke commands available, run `invoke -l`:
```
catch-up (catchup, docker-catchup) Rebuild images, install dependencies, and apply migrations
compilemessages (docker-compilemessages) Compile the latest translations
l10n-sync (docker-l10n-sync) Sync localizable fields in the database
l10n-update (docker-l10n-update) Update localizable field data (copies from original unlocalized to
default localized field)
makemessages (docker-makemessages) Extract all template messages in .po files for localization
makemigrations (docker-makemigrations) Creates new migration(s) for apps
manage (docker-manage) Shorthand to manage.py. inv docker-manage "[COMMAND] [ARG]"

Просмотреть файл

@ -26,7 +26,7 @@ If you are defining a new page class for the site, make sure it inherits both th
#### localization
We use [wagtail-modeltranslations](https://github.com/infoportugal/wagtail-modeltranslation) for CMS content localization. Please see its documentation for more information.
We use [wagtail-localize](https://wagtail-localize.org/) for CMS content localization. Please see its documentation for more information.
#### A/B testing

Просмотреть файл

@ -13,6 +13,14 @@ import json
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:

Просмотреть файл

@ -1,12 +1,12 @@
from datetime import timezone
from factory import (
DjangoModelFactory,
Faker,
Trait,
LazyAttribute,
post_generation,
)
from factory.django import DjangoModelFactory
from wagtail_factories import ImageFactory
from networkapi.utility.faker import generate_fake_data

Просмотреть файл

@ -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),
),
]

Просмотреть файл

@ -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'),
]

Просмотреть файл

@ -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')},
),
]

Просмотреть файл

@ -5,9 +5,12 @@ from django.db.models import Q
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 @@ class HighlightQuerySet(models.query.QuerySet):
@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',)

Просмотреть файл

@ -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}")

Просмотреть файл

@ -26,7 +26,7 @@ class Command(BaseCommand):
digits=True,
upper_case=True,
lower_case=True
).generate({})
)
User.objects.create_superuser('admin', 'admin@example.com', password)
reviewapp_name = settings.HEROKU_APP_NAME

Просмотреть файл

@ -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()

Просмотреть файл

@ -0,0 +1,237 @@
# Generated by Django 3.0.14 on 2021-05-25 15:16
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('mozfest', '0015_auto_20210805_1702'),
]
operations = [
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_guide_text_de',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_guide_text_en',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_guide_text_es',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_guide_text_fr',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_guide_text_fy_NL',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_guide_text_nl',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_guide_text_pl',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_guide_text_pt',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_heading_de',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_heading_en',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_heading_es',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_heading_fr',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_heading_fy_NL',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_heading_nl',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_heading_pl',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_heading_pt',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_video_url_de',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_video_url_en',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_video_url_es',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_video_url_fr',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_video_url_fy_NL',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_video_url_nl',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_video_url_pl',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='banner_video_url_pt',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='cta_button_label_de',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='cta_button_label_en',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='cta_button_label_es',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='cta_button_label_fr',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='cta_button_label_fy_NL',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='cta_button_label_nl',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='cta_button_label_pl',
),
migrations.RemoveField(
model_name='mozfesthomepage',
name='cta_button_label_pt',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='body_de',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='body_en',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='body_es',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='body_fr',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='body_fy_NL',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='body_nl',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='body_pl',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='body_pt',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='header_de',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='header_en',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='header_es',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='header_fr',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='header_fy_NL',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='header_nl',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='header_pl',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='header_pt',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='intro_de',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='intro_en',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='intro_es',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='intro_fr',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='intro_fy_NL',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='intro_nl',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='intro_pl',
),
migrations.RemoveField(
model_name='mozfestprimarypage',
name='intro_pt',
),
]

Просмотреть файл

@ -4,6 +4,8 @@ from wagtail.core.fields import StreamField, RichTextField
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,
@ -89,7 +91,7 @@ class MozfestPrimaryPage(FoundationMetadataPageMixin, FoundationBannerInheritanc
context['menu_items'] = self.get_children().live().in_menu()
# Also make sure that these pages always tap into the mozfest newsletter for the footer!
mozfest_footer = Signup.objects.filter(name_en__iexact='mozfest').first()
mozfest_footer = Signup.objects.filter(name__iexact='mozfest').first()
context['mozfest_footer'] = mozfest_footer
if not bypass_menu_buildstep:
@ -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

Просмотреть файл

@ -1,26 +0,0 @@
from .models import (
MozfestHomepage,
MozfestPrimaryPage,
)
from modeltranslation.translator import TranslationOptions
from modeltranslation.decorators import register
@register(MozfestPrimaryPage)
class MozfestPrimaryPageTR(TranslationOptions):
fields = (
'header',
'intro',
'body',
)
@register(MozfestHomepage)
class MozfestHomepageTR(TranslationOptions):
fields = (
'cta_button_label',
'banner_heading',
'banner_guide_text',
'banner_video_url',
)

Просмотреть файл

@ -1,14 +1,15 @@
from datetime import timezone
from random import shuffle
from factory import (
DjangoModelFactory,
Faker,
post_generation,
Trait,
LazyAttribute,
)
from factory.django import DjangoModelFactory
from networkapi.utility.faker import ImageProvider, generate_fake_data
from networkapi.utility.faker import generate_fake_data
from networkapi.utility.faker.helpers import reseed
from networkapi.news.models import News
@ -17,7 +18,18 @@ from django.conf import settings
RANDOM_SEED = settings.RANDOM_SEED
TESTING = settings.TESTING
Faker.add_provider(ImageProvider)
GENERIC_IMAGES = [
'tigerparrot.jpg',
'photographer.jpg',
'windfarm.jpg',
'hotair.jpg',
'computerandcoffee.jpg',
]
def get_random_image():
shuffle(GENERIC_IMAGES)
return f'images/placeholders/generic/{GENERIC_IMAGES[0]}'
class NewsFactory(DjangoModelFactory):
@ -59,7 +71,7 @@ class NewsFactory(DjangoModelFactory):
@post_generation
def set_thumbnail(self, create, extracted, **kwargs):
self.thumbnail.name = Faker('generic_image').generate({})
self.thumbnail.name = get_random_image()
def generate(seed):

Просмотреть файл

@ -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),
),
]

Просмотреть файл

@ -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')
]

Просмотреть файл

@ -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')},
),
]

Просмотреть файл

@ -4,6 +4,7 @@ from django.db.models import Q
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 @@ class NewsQuerySet(models.query.QuerySet):
@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'

Просмотреть файл

@ -50,6 +50,7 @@ env = environ.Env(
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 @@ env = environ.Env(
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 @@ SECRET_KEY = env('DJANGO_SECRET_KEY')
# 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')
@ -222,6 +229,13 @@ INSTALLED_APPS = list(filter(None, [
'modelcluster',
'taggit',
# Base wagtail localization
'wagtail_localize',
'wagtail_localize.locales',
# git integration for localization
'wagtail_localize_git',
'rest_framework',
'django_filters',
'gunicorn',
@ -240,11 +254,6 @@ INSTALLED_APPS = list(filter(None, [
# possibly still used?
'networkapi.highlights',
# wagtail localisation app
'wagtail_modeltranslation',
'wagtail_modeltranslation.makemigrations',
'wagtail_modeltranslation.migrate',
# wagtail to airtable integration
'wagtail_airtable',
@ -332,6 +341,7 @@ TEMPLATES = [
'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',
@ -430,10 +440,10 @@ AUTH_PASSWORD_VALIDATORS = [
# 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')),
@ -441,6 +451,10 @@ LANGUAGES = (
('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
@ -474,6 +488,7 @@ STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
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
@ -489,7 +504,7 @@ if env("FRONTEND_CACHE_CLOUDFLARE_BEARER_TOKEN"):
# Rest Framework Settings
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
]
}

Просмотреть файл

@ -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)

Просмотреть файл

@ -28,7 +28,7 @@
{% elif CODE == 'pa-IN' %}
<link rel="alternate" hreflang="pa" href="{{ CANONICAL_SITE_URL }}/{{ CODE }}{{ CANONICAL_PATH }}" title="{{ lang.name_local|safe }}">
<link rel="alternate" hreflang="pa-IN" href="{{ CANONICAL_SITE_URL }}/{{ CODE }}{{ CANONICAL_PATH }}" title="{{ lang.name_local|safe }}">
{% elif CODE == 'pt' %}
{% elif CODE == 'pt-BR' %}
<link rel="alternate" hreflang="pt" href="{{ CANONICAL_SITE_URL }}/{{ CODE }}{{ CANONICAL_PATH }}" title="{{ lang.name_local|safe }}">
<link rel="alternate" hreflang="pt-PT" href="{{ CANONICAL_SITE_URL }}/{{ CODE }}{{ CANONICAL_PATH }}" title="{{ lang.name_local|safe }}">
<link rel="alternate" hreflang="pt-BR" href="{{ CANONICAL_SITE_URL }}/{{ CODE }}{{ CANONICAL_PATH }}" title="{{ lang.name_local|safe }}">

Просмотреть файл

@ -1,15 +1,16 @@
{% load i18n localization %}
{% load i18n localization wagtailcore_tags %}
{% get_current_language as current_language %}
{% get_local_language_names as languages %}
<form action="{% url 'set_language' %}" method="post">
<input name="next" type="hidden" value="{{ request.path }}">
<input name="next" type="hidden" value="{% get_unlocalized_url page current_language %}" />
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-center mb-md-0">
<label class="h5-heading mb-0 d-flex align-items-center globe-glyph medium" for="language-switcher">{% trans "Language" %}</label>
<select name="language" id="language-switcher" class="mt-3 mt-md-0 ml-md-3 w-100 form-control" onchange="this.form.submit()">
{% get_current_language as LANGUAGE_CODE %}
{% get_local_language_names as LANGUAGES %}
{% for CODE, NAME in LANGUAGES %}
<option value="{{ CODE }}"{% if CODE == LANGUAGE_CODE %} selected{% endif %}>
{{ NAME | capfirst }}
{% for language_code, language_name in languages %}
<option value="{{ language_code }}"{% if language_code == current_language %} selected{% endif %}>
{{ language_name | capfirst }}
</option>
{% endfor %}
</select>

Просмотреть файл

@ -21,8 +21,8 @@ from wagtail.documents import urls as wagtaildocs_urls
# 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 @@ urlpatterns = list(filter(None, [
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 @@ urlpatterns = list(filter(None, [
# 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<rest>.*)', RedirectView.as_view(url='/pt-BR/%(rest)s', query_string=True, permanent=True)),
]))
# Anything that needs to respect the localised
@ -95,6 +97,8 @@ urlpatterns += i18n_patterns(
# 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 @@ if settings.DEBUG:
# 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'

Просмотреть файл

@ -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)

Просмотреть файл

@ -1,6 +1,8 @@
from faker.providers import BaseProvider
# FIXME: this code doesn't work at all, probably due to major version updates,
# and this ImageProvider is basically dead code by now.
class ImageProvider(BaseProvider):
"""
A custom Faker Provider for relative image urls, for use with factory_boy

Просмотреть файл

@ -1,42 +0,0 @@
{% extends "modeltranslation_copy.html" %}
{% load static i18n %}
{% block content %}
{% trans "Copy" as copy_str %}
{% include "wagtailadmin/shared/header.html" with title=copy_str subtitle=page.get_admin_display_title icon="doc-empty-inverse" %}
<div class="nice-padding">
<form action="{% url 'wagtailadmin_pages:copy' page.id %}" method="POST" novalidate>
{% csrf_token %}
<input type="hidden" name="next" value="{{ next }}" />
{% comment %}
There is unfortunately no block that we can use to inject content in the right place here,
so instead we've copied the entirety of https://github.com/infoportugal/wagtail-modeltranslation's
templates/modeltranslation_copy.html so that we can add these buttons for staff to click:
{% endcomment %}
<div>
<p><h2>{% trans "Locale synchronization options:" %}</h2></p>
<p>
<button type="button" class="locale helper button">{% trans "Show all locale fields" %}</button>
<button type="button" class="synchronize helper button">{% trans "Synchronize" %}</button>
</p>
</div>
<ul class="fields">
{% for field in form.visible_fields %}
{% include "wagtailadmin/shared/field_as_li.html" with field=field %}
{% endfor %}
</ul>
<input type="submit" value="{% trans 'Copy this page' %}" class="button">
</form>
</div>
{% endblock %}
{% block extra_js %}
{{ block.super }}
<script src="{% static "wagtailadmin/js/copy-helper.js" %}" async defer></script>
{% endblock %}

Просмотреть файл

@ -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 @@ class DonationModal(models.Model):
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 @@ class DonationModals(models.Model):
SnippetChooserPanel('donation_modal'),
]
class Meta:
class Meta(TranslatableMixin.Meta):
verbose_name = 'Donation Modals'
verbose_name_plural = 'Donation Modals'

Просмотреть файл

@ -4,12 +4,13 @@ from random import randint, random, choice, randrange, shuffle
from datetime import date, datetime, timezone, timedelta
from factory import (
DjangoModelFactory,
Faker,
post_generation,
LazyAttribute,
LazyFunction,
)
from factory.django import DjangoModelFactory
from wagtail.images.models import Image
from wagtail_factories import PageFactory
@ -38,6 +39,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 +178,10 @@ class SoftwareProductPageFactory(ProductPageFactory):
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_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_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')

Просмотреть файл

@ -1,5 +1,5 @@
from factory import DjangoModelFactory
from factory import Faker
from factory.django import DjangoModelFactory
from networkapi.wagtailpages.models import ContentAuthor
from networkapi.utility.faker import generate_fake_data

Просмотреть файл

@ -2,8 +2,8 @@ from networkapi.wagtailpages.donation_modal import (
DonationModal,
DonationModals
)
from factory.django import DjangoModelFactory
from factory import Faker, SubFactory
from factory.django import DjangoModelFactory
class DonationModalFactory(DjangoModelFactory):

Просмотреть файл

@ -10,7 +10,7 @@ def generate(seed):
reseed(seed)
home_page.cause_statement_link_text = Faker('text', max_nb_chars=80).generate()
home_page.cause_statement_link_text = Faker('text', max_nb_chars=80)
all_children = list(home_page.get_descendants())

Просмотреть файл

@ -27,8 +27,8 @@ def generate(seed):
partner_logo_orderable = PartnerLogos.objects.create(
page=home_page,
logo=choice(all_images),
link=Faker('url').generate(),
name=Faker('text', max_nb_chars=30).generate(),
link=Faker('url'),
name=Faker('text', max_nb_chars=30),
)
home_page.partner_logos.add(partner_logo_orderable)

Просмотреть файл

@ -21,7 +21,7 @@ def generate(seed):
take_action_orderable = HomepageTakeActionCards.objects.create(
page=home_page,
image=choice(all_images),
text=Faker('text', max_nb_chars=255).generate(),
text=Faker('text', max_nb_chars=255),
internal_link=choice(all_pages),
)
home_page.take_action_cards.add(take_action_orderable)

Просмотреть файл

@ -1,4 +1,4 @@
import factory
from factory.django import DjangoModelFactory, ImageField
from wagtail.images import get_image_model
from wagtail.core.models import Collection
@ -6,7 +6,7 @@ from wagtail.core.models import Collection
# Slightly modify the wagtail_factories ImageFactory so that it
# always generates images in the Root collection:
class CollectionMemberFactory(factory.DjangoModelFactory):
class CollectionMemberFactory(DjangoModelFactory):
collection = Collection.objects.get(name='Root')
@ -21,4 +21,4 @@ class ImageFactory(CollectionMemberFactory):
model = get_image_model()
title = "An image"
file = factory.django.ImageField()
file = ImageField()

Просмотреть файл

@ -12,8 +12,8 @@ from factory import (
Faker,
SubFactory,
django,
DjangoModelFactory,
)
from factory.django import DjangoModelFactory
from networkapi.wagtailpages.pagemodels.publications.article import ArticleAuthors

Просмотреть файл

@ -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 Cant Determine"

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,23 @@
# Generated by Django 3.1.11 on 2021-05-25 17:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wagtailpages', '0020_auto_20210525_1516'),
]
operations = [
migrations.AlterField(
model_name='softwareproductpage',
name='easy_to_learn_and_use',
field=models.BooleanField(help_text='Is it easy to learn & use the features?', null=True),
),
migrations.AlterField(
model_name='softwareproductpage',
name='medical_privacy_compliant',
field=models.BooleanField(help_text='Compliant with US medical privacy laws?', null=True),
),
]

Просмотреть файл

@ -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', '0021_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),
),
]

Просмотреть файл

@ -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', '0022_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'),
]

Просмотреть файл

@ -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', '0023_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={},
),
]

Просмотреть файл

@ -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', '0024_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',
),
]

Просмотреть файл

@ -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', '0025_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),
),
]

Просмотреть файл

@ -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', '0026_add_localization_to_subclasses'),
]
operations = [
BootstrapTranslatableModel('wagtailpages.Petition'),
BootstrapTranslatableModel('wagtailpages.Signup'),
]

Просмотреть файл

@ -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', '0027_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')},
),
]

Просмотреть файл

@ -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', '0028_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
),
]

Просмотреть файл

@ -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', '0029_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?'),
),
]

Просмотреть файл

@ -2,13 +2,16 @@ from django.conf import settings
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 @@ class HomepageSpotlightPosts(WagtailOrderable):
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 @@ class HomepageNewsYouCanUse(WagtailOrderable):
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 @@ class CTABase(WagtailOrderable, models.Model):
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 @@ class Homepage(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('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',

Просмотреть файл

@ -11,11 +11,13 @@ from wagtail.admin.edit_handlers import (
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
@ -177,6 +179,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'
]
@ -194,11 +208,8 @@ class BlogPage(FoundationMetadataPageMixin, Page):
context['related_posts'] = related_posts
# Pull this object specifically using the English page title
blog_page = BlogIndexPage.objects.get(title_en__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

Просмотреть файл

@ -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 @@ class BlogPageCategory(models.Model):
def __str__(self):
return self.name
class Meta:
class Meta(TranslatableMixin.Meta):
verbose_name = "Blog Page Category"
verbose_name_plural = "Blog Page Categories"

Просмотреть файл

@ -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):

Просмотреть файл

@ -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):

Просмотреть файл

@ -3,11 +3,13 @@ import json
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 CTA(models.Model):
@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 OpportunityPage(MiniSiteNameSpace):
@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 @@ class CampaignPage(MiniSiteNameSpace):
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',

Просмотреть файл

@ -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(

Просмотреть файл

@ -40,7 +40,7 @@ class RecentBlogEntries(blocks.StructBlock):
def get_context(self, value, parent_context=None):
context = super().get_context(value, parent_context=parent_context)
BlogIndexPage = apps.get_model('wagtailpages.BlogIndexPage')
blogpage = BlogIndexPage.objects.get(title_en__iexact="blog")
blogpage = BlogIndexPage.objects.get(title__iexact="blog")
tag = value.get("tag_filter", False)
category = value.get("category_filter", False)

Просмотреть файл

@ -4,6 +4,8 @@ from wagtail.admin.edit_handlers import StreamFieldPanel, MultiFieldPanel, Field
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):

Просмотреть файл

@ -13,6 +13,8 @@ from wagtail.admin.edit_handlers import FieldPanel
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)

Просмотреть файл

@ -1,4 +1,5 @@
from taggit.models import Tag
from wagtailmetadata.models import MetadataPageMixin
from wagtail.images.models import Image
@ -69,5 +70,11 @@ class FoundationMetadataPageMixin(MetadataPageMixin):
# 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

Просмотреть файл

@ -0,0 +1,19 @@
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):
try:
return self.get_translation(self.DEFAULT_LOCALE)
except AttributeError:
return self

Просмотреть файл

@ -3,6 +3,7 @@ from django.db import models
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):

Просмотреть файл

@ -5,6 +5,8 @@ from wagtail.core.models import Page
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',

Просмотреть файл

@ -17,15 +17,17 @@ from modelcluster.fields import ParentalKey
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 @@ from networkapi.wagtailpages.utils import insert_panels_after
# 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,16 +45,63 @@ TRACK_RECORD_CHOICES = [
]
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
try:
actual_locale = Locale.objects.get(language_code=language_code)
except Locale.DoesNotExist:
actual_locale = Locale.objects.get(language_code=settings.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)
try:
locale = Locale.objects.get(language_code=language_code)
except Locale.DoesNotExist:
locale = Locale.objects.get(language_code=settings.LANGUAGE_CODE)
products = products.filter(review_date__gte=cutoff_date, locale=locale)
if not authenticated:
products = products.live()
products = sort_average(products)
return cache.get_or_set(key, products, 86400)
@ -65,7 +114,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,29 +154,33 @@ 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
def save(self, *args, **kwargs):
self.slug = slugify(self.name_en if self.name_en else self.name)
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 +198,7 @@ class ProductPageVotes(models.Model):
return votes
class ProductPageCategory(Orderable):
class ProductPageCategory(TranslatableMixin, Orderable):
product = ParentalKey(
'wagtailpages.ProductPage',
related_name='product_categories',
@ -165,11 +218,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 +240,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 +267,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 +322,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 +352,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 +776,54 @@ class ProductPage(AirtableMixin, 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('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 +843,8 @@ class ProductPage(AirtableMixin, FoundationMetadataPageMixin, Page):
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 +940,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.NullBooleanField(
null=True,
medical_privacy_compliant = ExtendedBoolean(
help_text='Compliant with US medical privacy laws?'
)
@ -832,11 +956,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.NullBooleanField(
null=True,
easy_to_learn_and_use = ExtendedBoolean(
help_text='Is it easy to learn & use the features?',
)
@ -909,10 +1030,22 @@ class SoftwareProductPage(ProductPage):
],
)
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 +1304,23 @@ class GeneralProductPage(ProductPage):
],
)
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 +1329,10 @@ class GeneralProductPage(ProductPage):
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 +1347,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 +1409,18 @@ class BuyersGuidePage(RoutablePageMixin, FoundationMetadataPageMixin, Page):
),
]
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 +1494,35 @@ class BuyersGuidePage(RoutablePageMixin, FoundationMetadataPageMixin, Page):
@route(r'^categories/(?P<slug>[\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 +1531,13 @@ class BuyersGuidePage(RoutablePageMixin, FoundationMetadataPageMixin, Page):
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 +1577,11 @@ class BuyersGuidePage(RoutablePageMixin, FoundationMetadataPageMixin, Page):
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 +1590,11 @@ class BuyersGuidePage(RoutablePageMixin, FoundationMetadataPageMixin, Page):
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()

Просмотреть файл

@ -8,6 +8,8 @@ from wagtail.documents.edit_handlers import DocumentChooserPanel
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

Просмотреть файл

@ -9,6 +9,8 @@ from wagtail.documents.edit_handlers import DocumentChooserPanel
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):
"""

Просмотреть файл

@ -5,6 +5,8 @@ from wagtail.core import blocks
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):

Просмотреть файл

@ -26,7 +26,7 @@ class RSSFeed(Feed):
# as a BlogIndexPage, to make sure we're not filtering out all the
# "featured" posts (which we need to do for site content purposes).
try:
index = IndexPage.objects.get(title_en__iexact='Blog')
index = IndexPage.objects.get(title__iexact='Blog')
except IndexPage.DoesNotExist:
# If that doesn't yield the blog page, pull using the universal title

Просмотреть файл

@ -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 @@
<div class="row">
<div class="col">
{% if pagetype == "product" or pagetype == "about" %}
<a class="multipage-link active" href="{{ home_page.url }}">{% trans "All" %}</a>
<a class="multipage-link active" href="{% relocalized_url home_page.localized.url lang_code %}">{% trans "All" %}</a>
{% else %}
<a class="multipage-link {% if not category %} active{% endif %}" href="{{ home_page.url }}">{% trans "All" %}</a>
<a class="multipage-link {% if not category %} active{% endif %}" href="{% relocalized_url home_page.localized.url lang_code %}">{% trans "All" %}</a>
{% endif %}
{% for cat in categories %}
{% if cat.published_product_page_count > 0 %}
{% routablepageurl home_page 'category-view' cat.slug as cat_url %}
<a class="multipage-link {% check_active_category current_category cat %}{% if cat.featured is True %} featured{% endif %}{% if category == cat.slug %} active{% endif %}" href="{{ cat_url }}" data-name="{{ cat.name }}">{{ cat.name }}</a>
{% with original=cat.original %}
{% if original.published_product_page_count > 0 %}
{% localizedroutablepageurl home_page 'category-view' lang_code original.slug as cat_url %}
<a class="multipage-link {% check_active_category current_category cat %}{% if original.featured is True %} featured{% endif %}" href="{{ cat_url }}" data-name="{{ original.name }}">{{ cat.name }}</a>
{% endif %}
{% endwith %}
{% endfor %}
</div>
</div>

Просмотреть файл

@ -68,13 +68,13 @@
{# 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 %}
</div>

Просмотреть файл

@ -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 %}
<a
class="multipage-link {% check_active_category current_category cat %}{% if cat.featured is True %} featured{% endif %}"
class="multipage-link {% check_active_category current_category cat %}{% if original.featured is True %} featured{% endif %}"
href="{{ cat_url }}"
data-name="{{ cat.name }}">
data-name="{{ original.name }}">
{{ cat.name }}
</a>
{% endif %}
{% endwith %}
{% endfor %}

Просмотреть файл

@ -1,20 +1,21 @@
{% extends "partials/primary_nav.html" %}
{% load bg_nav_tags i18n %}
{% load bg_nav_tags i18n localization %}
{% get_current_language as lang_code %}
{% block nav_logo %}
<div class="d-flex align-items-center logo-section">
<a href="/" class="moz-logo mb-0 mr-3"><span class="sr-only">Mozilla</span></a>
<p class="mb-0 h3-heading flex-wrap">
<a class="link-back" href="{{ home_page.url }}">{% trans "*privacy not included" context 'Buyers guide name. This can be localized. This is a reference to the “*batteries not included” mention on toys.' %}</a>
<a class="link-back" href="{% relocalized_url home_page.url lang_code %}">{% trans "*privacy not included" context 'Buyers guide name. This can be localized. This is a reference to the “*batteries not included” mention on toys.' %}</a>
</p>
</div>
{% endblock %}
{% block narrow_screen_nav_links %}
{% url 'buyersguide-home' as home_url %}
<div><a class="{% bg_active_nav request.get_full_path home_url %}" href="{{ home_url }}">{% trans "Privacy Not Included" %}</a></div>
<div><a class="{% bg_active_nav request.get_full_path home_url %}" href="{% relocalized_url home_url lang_code %}">{% trans "Privacy Not Included" %}</a></div>
{% include "buyersguide/fragments/pni_nav_links.html" with pre="<div>" post="</div>" %}
{% endblock %}

Просмотреть файл

@ -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 %}
<meta property="og:title" content="{% blocktrans context "This can be localized. This is a reference to the *batteries not included mention on toys." %}privacy not included - {{ product.title }}{% endblocktrans %}" />
@ -295,9 +297,9 @@
<h3 class="h2-heading mb-4">{% trans "Related products" %}</h3>
<div class="row">
{% 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 %}
<div class="related-product col-6 col-md-3 mb-3 mb-md-0">
<a class="d-block{% if related_product.adult_content %} adult-content{% endif %}" href="{{ related_product.url }}">
<a class="d-block{% if related_product.adult_content %} adult-content{% endif %}" href="{% relocalized_url related_product.url lang_code %}">
<div class="img-container">
{% image related_product.image width-600 as img %}
<img

Просмотреть файл

@ -1,5 +1,6 @@
{% load static i18n wagtailimages_tags %}
{% load static i18n wagtailimages_tags localization %}
{% get_current_language as lang_code %}
<figure class="product-box d-flex flex-column justify-content-between{% if product.draft %} draft-product{% endif %}{% if product.adult_content %} adult-content{% endif %}{% if product.privacy_ding %} privacy-ding{% endif%}" data-creepiness="{{ product.creepiness }}">
<div class="top-left-badge-container">
@ -15,7 +16,7 @@
{% include "fragments/adult_content_badge.html" with product=product %}
<a class="product-image text-center mt-4 h-100 d-flex flex-column justify-content-between" href="{{ product.url }}">
<a class="product-image text-center mt-4 h-100 d-flex flex-column justify-content-between" href="{% relocalized_url product.url lang_code %}">
<picture class="product-thumbnail">
<source
{% image product.image fill-360x360 as img_1x %}
@ -32,7 +33,7 @@
</a>
<figcaption class="d-block mt-md-2 text-left">
<a class="product-links" href="{{ product.url }}">
<a class="product-links" href="{% relocalized_url product.url lang_code %}">
<div class="product-company body-small">{{product.company}}</div>
<div class="product-name body">{{product.title}}</div>
</a>

Просмотреть файл

@ -23,7 +23,7 @@
</div>
<div class="col-12">
{% for child_page in child_pages %}
{% with child=child_page.child %}
{% with child=child_page.child.localized %}
<div class="row publication-row pt-3 pb-3 d-flex align-items-center">
<div class="col-sm-1 d-md-block d-none">&nbsp;</div>
<div class="col-auto">

Просмотреть файл

@ -21,7 +21,7 @@
</div>
<div class="col-12">
{% for child_page in child_pages %}
{% with child=child_page.child %}
{% with child=child_page.child.localized %}
<div class="row publication-row pt-3 pb-sm-4 pb-0">
<div class="col-auto">
<div class="publication-chapter-number"
@ -44,24 +44,28 @@
{{ child.title }} {% if child.has_unpublished_changes %}🐣{% endif %}
{% endif %}
</h3>
{% for grandchild in child_page.grandchildren %}
{% for ancestor in child_page.grandchildren %}
{% with grandchild=ancestor.localized %}
<h4 class="d-sm-block d-none body">
<a href="{{ grandchild.url }}" class="d-block w-75 publication-chapter-article-link">
{{ grandchild.title }}
</a>
</h4>
{% endwith %}
{% endfor %}
</div>
</div>
{% if child_page.grandchildren %}
{# Child listings on mobile. #}
<div class="pt-2 d-sm-none d-block">
{% for grandchild in child_page.grandchildren %}
{% for ancestor in child_page.grandchildren %}
{% with grandchild=ancestor.localized %}
<h4 class="body">
<a href="{{ grandchild.url }}" class="d-block publication-chapter-article-link">
{{ grandchild.title }} {% if child.has_unpublished_changes %}🐣{% endif %}
</a>
</h4>
{% endwith %}
{% endfor %}
</div>
{% endif %}

Просмотреть файл

@ -8,5 +8,5 @@
{% include "./product_criterion.html" with label=handles_recordings_how value=product.handles_recordings_how %}
{% endif %}
{% include "./product_criterion.html" with label=recording_alert value=product.recording_alert|extended_yes_no help=product.recording_alert_helptext %}
{% include "./product_criterion.html" with label=medical_privacy_compliant value=product.medical_privacy_compliant|yes_no help=product.medical_privacy_compliant_helptext %}
{% include "./product_criterion.html" with label=recording_alert value=product.recording_alert|extended_yes_no help=product.recording_alert_helptext %}
{% include "./product_criterion.html" with label=medical_privacy_compliant value=product.medical_privacy_compliant|extended_boolean help=product.medical_privacy_compliant_helptext %}

Просмотреть файл

@ -3,7 +3,7 @@
<div class="section-cause-statement dark-theme p-5 text-center">
<p class="h4-heading">{{ page.cause_statement }}</p>
{% if page.cause_statement_link_text and page.cause_statement_link_page %}
<a href="{{page.cause_statement_link_page.url}}" class="cta-link" id="homepage-cause-statement-cta">{{page.cause_statement_link_text}}</a>
<a href="{{page.cause_statement_link_page.localized.url}}" class="cta-link" id="homepage-cause-statement-cta">{{page.cause_statement_link_text}}</a>
{% endif %}
</div>
</div>

Просмотреть файл

@ -8,7 +8,7 @@
<p>{{ area.description }}</p>
{% if area.page %}<div class="mb-4 learn-more-link">
<a href="{{ area.page.url }}" aria-label="{% blocktrans with name=area.name %}Learn more about: {{ name }}{% endblocktrans %}">{% trans "Learn more →" %}</a>
<a href="{{ area.page.localized.url }}" aria-label="{% blocktrans with name=area.name %}Learn more about: {{ name }}{% endblocktrans %}">{% trans "Learn more →" %}</a>
</div>{% endif %}
</div>
</div>

Просмотреть файл

@ -23,7 +23,7 @@
{% endcomment %}
{% with first=items.first.blog %}
{% with first=items.first.blog.localized %}
<div class="d-none d-xl-block col-xl-12 feature mb-md-5">
<div class="position-relative h-100 d-flex justify-content-end">
<div class="feature-image">
@ -56,22 +56,24 @@
{% with item_1_class="d-xl-none mb-5 mb-xl-0" item_2_class="mb-5 mb-xl-0" item_3_class="mb-5 mb-md-0" %}
{% for item in items %}
{% with localized=item.blog.localized %}
<div class="col-12 col-md-6 col-xl-4 d-flex {% if forloop.counter == 1 %} {{item_1_class}}{% endif %} {% if forloop.counter == 2 %} {{item_2_class}}{% endif %} {% if forloop.counter == 3 %} {{item_3_class}}{% endif %}">
<div class="bg-white">
<img src="{% image_url item.blog.get_meta_image "fill-700x394" %}" alt="" class="embed-responsive-item">
<img src="{% image_url localized.get_meta_image "fill-700x394" %}" alt="" class="embed-responsive-item">
<div class="p-4">
{% if item.blog.category.count %}
{% with category=item.blog.category.first %}
{% if localized.category.count %}
{% with category=localized.category.first %}
<a class="h6-heading d-block mb-1" href="/blog/category/{{ category.slug }}">{{ category }}</a>
{% endwith %}
{% else %}
<div class="h6-heading d-md-block d-none mb-1">&nbsp;</div>
{% endif %}
<h3 class="h4-heading"><a href="{{item.blog.url}}">{{ item.blog.title }}</a></h3>
<h3 class="h4-heading"><a href="{{localized.url}}">{{ localized.title }}</a></h3>
{% include "./blog_authors.html" with blog_page=item.blog %}
</div>
</div>
</div>
{% endwith %}
{% endfor %}
{% endwith %}

Просмотреть файл

@ -11,7 +11,7 @@
{{ page.partner_intro_text }}
</p>
{% endif %}
<a href="{{ page.partner_page.url }}" class="text-white cta-link font-weight-bold" id="partner-cta">
<a href="{{ page.partner_page.localized.url }}" class="text-white cta-link font-weight-bold" id="partner-cta">
{{ page.partner_page_text }}
</a>
</div>

Просмотреть файл

@ -14,9 +14,11 @@
<div class="col-lg-8 offset-lg-2 dark-theme">
{% if is_publication_article or is_publication_page %}
<div class="mb-2 body-small publication-breadcrumb">
{% for parent_page in self.breadcrumb_list %}
{% for entry in self.breadcrumb_list %}
{% with parent_page=entry.localized %}
<a href="{{ parent_page.url }}" class="body-small">{{ parent_page.title }}</a>
<span class='body-small'> </span>
{% endwith %}
{% endfor %}
</div>
{% endif %}

Просмотреть файл

@ -13,12 +13,14 @@
<div class="col-12 col-lg-6">
{% for post in page.spotlight_posts.all %}
{% with localized=post.blog.localized %}
<div class="spotlight-post">
<h4 class="mb-2 h5-heading">
<a href="{{post.blog.url}}">{{post.blog.title}}</a>
<a href="{{localized.url}}">{{localized.title}}</a>
</h4>
{% include "./blog_authors.html" with blog_page=post.blog %}
{% include "./blog_authors.html" with blog_page=localized %}
</div>
{% endwith %}
{% endfor %}
</div>

Просмотреть файл

@ -9,18 +9,20 @@
<div class="col-lg-8 col-md-12">
<div class="row">
{% for item in page.take_action_cards.all %}
{% with target=item.internal_link.localized %}
<div class="col-md-6 d-flex">
<div class="card flex-grow-1 mb-4">
<a href="{{ item.internal_link.url }}">
<a href="{{ target.url }}">
<img src="{% image_url item.image "fill-350x130" %}" class="card-img-top" alt="{{ img.alt }}" aria-label="{{ item.text }}">
</a>
<div class="card-body">
<a href="{{ item.internal_link.url }}" class="h5-heading d-inline-block my-2">
<a href="{{ target.url }}" class="h5-heading d-inline-block my-2">
{{ item.text }}
</a>
</div>
</div>
</div>
{% endwith %}
{% endfor %}
</div>
</div>

Просмотреть файл

@ -7,6 +7,10 @@ register = template.Library()
# Determine if a category nav link should be marked active
@register.simple_tag(name='check_active_category')
def check_active_category(current_category, target_category):
# because we're working with potentially localized data,
# make sure to compare the linguistic originals.
current_category = getattr(current_category, 'original', current_category)
target_category = getattr(target_category, 'original', target_category)
return 'active' if current_category == target_category else ''
@ -16,6 +20,7 @@ def bg_active_nav(current, target):
return 'active' if urlparse(current).path == urlparse(target).path else ''
"""
# Instantiate a list of category page links based on the current page's relation to them
# NOTE: this points to the new, namespaced category_nav_links. If we need to revert to the old app, change this back.
@register.inclusion_tag('buyersguide/fragments/category_nav_links.html', takes_context=True)
@ -25,3 +30,4 @@ def category_nav(context, current_url, current_category, all_categories):
'current_category': current_category,
'sorted_categories': all_categories.order_by('-featured', 'sort_order', 'name'), # featured categories first
}
"""

Просмотреть файл

@ -9,13 +9,22 @@ def yes_no(value):
"""Converts nullish boolean to yes or no string"""
if value is False:
return gettext('No')
if value is True:
return gettext('Yes')
return gettext('Unknown')
@register.filter
def extended_boolean(value):
if value == 'Yes':
return gettext('Yes')
if value == 'No':
return gettext('No')
if value == 'U':
return gettext('Unknown')
return value
@register.filter
def extended_yes_no(value):
"""Converts quad-state to human readable string"""

Просмотреть файл

@ -0,0 +1,12 @@
from django.template import Library
from django.conf import settings
register = Library()
if settings.DEBUG:
print('→ Running in DEBUG mode: enabling debug template tag "inspect_object" in tag collection "debug_tags".\n')
@register.simple_tag
def inspect_object(instance, prefix_label=""):
output = str(dir(instance)).replace(', ', ',\n')
prefix_label = str(prefix_label)
return f'<pre>{prefix_label} {output}</pre>'

Просмотреть файл

@ -4,11 +4,10 @@ import unicodedata
from django import template
from django.conf import settings
from django.utils.translation import get_language_info
from wagtail.contrib.routable_page.templatetags.wagtailroutablepage_tags import routablepageurl
register = template.Library()
DEFAULT_LOCALE = 'en_US'
mappings = {
'en': 'en_US',
'de': 'de_DE',
@ -17,9 +16,12 @@ mappings = {
'fy-NL': 'fy_NL',
'nl': 'nl_NL',
'pl': 'pl_PL',
'pt': 'pt_BR', # our main focus is Brazilian Portuguese
'pt-BR': 'pt_BR',
}
DEFAULT_LOCALE_CODE = settings.LANGUAGE_CODE
DEFAULT_LOCALE = mappings.get(DEFAULT_LOCALE_CODE)
# This filter turns Wagtail language codes into OpenGraph locale strings
@register.filter
@ -43,3 +45,27 @@ def get_local_language_names():
for lang in settings.LANGUAGES:
languages.append([lang[0], get_language_info(lang[0])['name_local']])
return sorted(languages, key=lambda x: locale.strxfrm(unicodedata.normalize('NFD', x[1])).casefold())
# Get the url for a page, but with the locale code removed.
@register.simple_tag()
def get_unlocalized_url(page, locale):
return page.get_url().replace(f'/{locale}/', '/', 1)
# Force-relocalize a URL
@register.simple_tag()
def relocalized_url(url, locale_code):
if locale_code == DEFAULT_LOCALE_CODE:
return url
return url.replace(f'/{DEFAULT_LOCALE_CODE}/', f'/{locale_code}/')
# Overcome a limitation of the routablepageurl tag
@register.simple_tag(takes_context=True)
def localizedroutablepageurl(context, page, url_name, locale_code, *args, **kwargs):
url = relocalized_url(
routablepageurl(context, page, url_name, *args, **kwargs),
locale_code,
)
return url

Просмотреть файл

@ -47,7 +47,6 @@ class BuyersGuideViewTest(TestCase):
buyersguide = BuyersGuidePage()
buyersguide.title = 'Privacy not included'
buyersguide.slug = 'privacynotincluded'
buyersguide.slug_en = 'privacynotincluded'
homepage.add_child(instance=buyersguide)
buyersguide.save_revision().publish()
@ -147,7 +146,6 @@ class BuyersGuideTestMixin(WagtailPageTests):
buyersguide = BuyersGuidePage()
buyersguide.title = 'Privacy not included'
buyersguide.slug = 'privacynotincluded'
buyersguide.slug_en = 'privacynotincluded'
homepage.add_child(instance=buyersguide)
buyersguide.save_revision().publish()
self.homepage = Homepage.objects.first()
@ -163,9 +161,7 @@ class BuyersGuideTestMixin(WagtailPageTests):
)
product_page = ProductPage(
slug='product-page',
slug_en='product-page',
title='Product Page',
title_en='Product Page',
live=True,
image=wagtail_image
)
@ -243,7 +239,7 @@ class TestBuyersGuidePage(BuyersGuideTestMixin):
self.assertEqual(response.redirect_chain[0][0], product.url)
def test_sitemap_entries(self):
response = self.client.get('/sitemap.xml')
response = self.client.get('/en/sitemap.xml')
context = response.context
self.assertEqual(context.template_name, 'sitemap.xml')

Просмотреть файл

@ -1,3 +1,5 @@
# TODO: REmove this and other translation.py files.
# They aren't needed but keep coming back when we merge master into our localization branch
from .models import (
ModularPage,
MiniSiteNameSpace,

Просмотреть файл

@ -7,11 +7,19 @@ from django.core.cache import cache
from django.urls import reverse
from wagtail.admin.menu import MenuItem
import wagtail.admin.rich_text.editors.draftail.features as draftail_features
from wagtail.admin.rich_text.editors.draftail import features as draftail_features
from wagtail.admin.rich_text.converters.html_to_contentstate import InlineStyleElementHandler
from wagtail.core import hooks
from wagtail.core.utils import find_available_slug
from networkapi.wagtailpages.pagemodels.products import BuyersGuidePage, ProductPage
# The real code runs "instance.sync_trees()" here, but we want this to do nothing instead,
# so that locale creation creates the locale entry but does not try to sync 1300+ pages as
# part of the same web request.
from django.db.models.signals import post_save
from wagtail_localize.models import LocaleSynchronization, sync_trees_on_locale_sync_save
post_save.disconnect(sync_trees_on_locale_sync_save, sender=LocaleSynchronization)
# Extended rich text features for our site
@hooks.register('register_rich_text_features')
@ -104,6 +112,27 @@ def manage_pni_cache(request, page):
cache.clear()
@hooks.register('after_publish_page')
def sync_localized_slugs(request, page):
for translation in page.get_translations():
translation.slug = find_available_slug(
translation.get_parent(),
page.slug,
ignore_page_id=translation.id
)
if translation.alias_of_id is not None:
# This is still an alias rather than a published
# localized page, so we can only save it as we
# would any plain Django model:
translation.save()
else:
# If it's a real page, it needs to go through
# the revision/publication process.
translation.save_revision().publish()
@hooks.register('after_delete_page')
@hooks.register('after_publish_page')
@hooks.register('after_unpublish_page')

123
package-lock.json сгенерированный
Просмотреть файл

@ -421,7 +421,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-4.0.3.tgz",
"integrity": "sha512-/EnQ9UDWGGqHkn1UKAwSgh+gJHPKmD+Z+5dQ4gWT4qq2NUyez3zqAfZNwFH3eSgmgO+wjTXfhlLchx2M9/K+7Q==",
"dev": true,
"requires": {
"purgecss": "^4.0.3"
}
@ -618,8 +617,7 @@
"acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="
},
"acorn-jsx": {
"version": "5.3.1",
@ -631,7 +629,6 @@
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
"integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
"dev": true,
"requires": {
"acorn": "^7.0.0",
"acorn-walk": "^7.0.0",
@ -641,8 +638,7 @@
"acorn-walk": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
"dev": true
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA=="
},
"ajv": {
"version": "6.12.6",
@ -716,8 +712,7 @@
"arg": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.0.tgz",
"integrity": "sha512-4P8Zm2H+BRS+c/xX1LrHw0qKpEhdlZjLCgWy+d78T9vqa2Z2SiD2wMrYuWIAFy5IZUD7nnNXroRttz+0RzlrzQ==",
"dev": true
"integrity": "sha512-4P8Zm2H+BRS+c/xX1LrHw0qKpEhdlZjLCgWy+d78T9vqa2Z2SiD2wMrYuWIAFy5IZUD7nnNXroRttz+0RzlrzQ=="
},
"argparse": {
"version": "1.0.10",
@ -1295,8 +1290,7 @@
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
"dev": true
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
},
"cacheable-request": {
"version": "2.1.4",
@ -1366,8 +1360,7 @@
"camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
},
"camelcase-keys": {
"version": "2.1.0",
@ -1845,8 +1838,7 @@
"css-unit-converter": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz",
"integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==",
"dev": true
"integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA=="
},
"css-what": {
"version": "4.0.0",
@ -2210,8 +2202,7 @@
"defined": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
"integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=",
"dev": true
"integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM="
},
"dependency-graph": {
"version": "0.9.0",
@ -2222,7 +2213,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz",
"integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==",
"dev": true,
"requires": {
"acorn-node": "^1.6.1",
"defined": "^1.0.0",
@ -2232,8 +2222,7 @@
"didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
},
"dir-glob": {
"version": "3.0.1",
@ -2253,8 +2242,7 @@
"dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
},
"doctrine": {
"version": "3.0.0",
@ -3422,8 +3410,7 @@
"html-tags": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz",
"integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==",
"dev": true
"integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg=="
},
"htmlparser2": {
"version": "3.10.1",
@ -3994,8 +3981,7 @@
"lilconfig": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.3.tgz",
"integrity": "sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg==",
"dev": true
"integrity": "sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg=="
},
"lines-and-columns": {
"version": "1.1.6",
@ -4036,8 +4022,7 @@
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash.debounce": {
"version": "4.0.8",
@ -4084,14 +4069,12 @@
"lodash.toarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=",
"dev": true
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE="
},
"lodash.topath": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz",
"integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=",
"dev": true
"integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak="
},
"lodash.uniq": {
"version": "4.5.0",
@ -4381,8 +4364,7 @@
"modern-normalize": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/modern-normalize/-/modern-normalize-1.1.0.tgz",
"integrity": "sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==",
"dev": true
"integrity": "sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA=="
},
"moment": {
"version": "2.29.1",
@ -4415,7 +4397,6 @@
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz",
"integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==",
"dev": true,
"requires": {
"lodash.toarray": "^4.4.0"
}
@ -4608,8 +4589,7 @@
"object-hash": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
"dev": true
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="
},
"object-inspect": {
"version": "1.7.0",
@ -5569,7 +5549,6 @@
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.0.2.tgz",
"integrity": "sha512-BJ2pVK4KhUyMcqjuKs9RijV5tatNzNa73e/32aBVE/ejYPe37iH+6vAu9WvqUkB5OAYgLHzbSvzHnorybJCm9g==",
"dev": true,
"requires": {
"postcss-value-parser": "^4.0.0",
"read-cache": "^1.0.0",
@ -5580,7 +5559,6 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-3.0.3.tgz",
"integrity": "sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw==",
"dev": true,
"requires": {
"camelcase-css": "^2.0.1",
"postcss": "^8.1.6"
@ -5944,7 +5922,6 @@
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.5.tgz",
"integrity": "sha512-GSRXYz5bccobpTzLQZXOnSOfKl6TwVr5CyAQJUPub4nuRJSOECK5AqurxVgmtxP48p0Kc/ndY/YyS1yqldX0Ew==",
"dev": true,
"requires": {
"postcss-selector-parser": "^6.0.4"
},
@ -5953,7 +5930,6 @@
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz",
"integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@ -6810,7 +6786,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/purgecss/-/purgecss-4.0.3.tgz",
"integrity": "sha512-PYOIn5ibRIP34PBU9zohUcCI09c7drPJJtTDAc0Q6QlRz2/CHQ8ywGLdE7ZhxU2VTqB7p5wkvj5Qcm05Rz3Jmw==",
"dev": true,
"requires": {
"commander": "^6.0.0",
"glob": "^7.0.0",
@ -6821,8 +6796,7 @@
"commander": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
"dev": true
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="
}
}
},
@ -6969,7 +6943,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"requires": {
"picomatch": "^2.2.1"
}
@ -6996,7 +6969,6 @@
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz",
"integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==",
"dev": true,
"requires": {
"css-unit-converter": "^1.1.1",
"postcss-value-parser": "^3.3.0"
@ -7005,8 +6977,7 @@
"postcss-value-parser": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
"dev": true
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
}
}
},
@ -7139,7 +7110,6 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
@ -8519,7 +8489,6 @@
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-2.2.4.tgz",
"integrity": "sha512-OdBCPgazNNsknSP+JfrPzkay9aqKjhKtFhbhgxHgvEFdHy/GuRPo2SCJ4w1SFTN8H6FPI4m6qD/Jj20NWY1GkA==",
"dev": true,
"requires": {
"@fullhuman/postcss-purgecss": "^4.0.3",
"arg": "^5.0.0",
@ -8558,7 +8527,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
@ -8567,7 +8535,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@ -8577,7 +8544,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@ -8587,7 +8553,6 @@
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
"integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
"dev": true,
"requires": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
@ -8603,7 +8568,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
@ -8614,7 +8578,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz",
"integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==",
"dev": true,
"requires": {
"color-convert": "^1.9.1",
"color-string": "^1.5.4"
@ -8624,7 +8587,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
@ -8632,8 +8594,7 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
}
}
},
@ -8641,7 +8602,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
@ -8649,14 +8609,12 @@
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"color-string": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz",
"integrity": "sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==",
"dev": true,
"requires": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
@ -8666,7 +8624,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz",
"integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==",
"dev": true,
"requires": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.2.1",
@ -8679,7 +8636,6 @@
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz",
"integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
@ -8692,7 +8648,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
@ -8703,7 +8658,6 @@
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
"integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
@ -8714,7 +8668,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.0.tgz",
"integrity": "sha512-Hdd4287VEJcZXUwv1l8a+vXC1GjOQqXe+VS30w/ypihpcnu9M1n3xeYeJu5CBpeEQj2nAab2xxz28GuA3vp4Ww==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
@ -8722,14 +8675,12 @@
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true,
"requires": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
@ -8739,7 +8690,6 @@
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz",
"integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==",
"dev": true,
"requires": {
"has": "^1.0.3"
}
@ -8748,7 +8698,6 @@
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"dev": true,
"requires": {
"braces": "^3.0.1",
"picomatch": "^2.2.3"
@ -8757,8 +8706,7 @@
"picomatch": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
"dev": true
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw=="
}
}
},
@ -8766,7 +8714,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
@ -8777,14 +8724,12 @@
"path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="
},
"postcss-load-config": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.0.tgz",
"integrity": "sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==",
"dev": true,
"requires": {
"import-cwd": "^3.0.0",
"lilconfig": "^2.0.3",
@ -8794,8 +8739,7 @@
"yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
}
}
},
@ -8803,7 +8747,6 @@
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz",
"integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@ -8812,14 +8755,12 @@
"quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"dev": true
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="
},
"resolve": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
"integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
"dev": true,
"requires": {
"is-core-module": "^2.2.0",
"path-parse": "^1.0.6"
@ -8828,14 +8769,12 @@
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
@ -8843,8 +8782,7 @@
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"dev": true
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
}
}
},
@ -8913,7 +8851,6 @@
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
"integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
"dev": true,
"requires": {
"rimraf": "^3.0.0"
}
@ -9123,8 +9060,7 @@
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"util.promisify": {
"version": "1.0.1",
@ -9313,8 +9249,7 @@
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"y18n": {
"version": "4.0.3",

Просмотреть файл

@ -117,4 +117,4 @@
"svgo": "^2.2.2",
"wait-on": "^5.2.1"
}
}
}

Просмотреть файл

@ -9,8 +9,7 @@ python ./manage.py migrate --no-input
python ./manage.py block_inventory
# Wagtail translations
python ./manage.py sync_page_translation_fields
python ./manage.py update_translation_fields
# TODO: May need to add in auto syncing using wagtail-localize in the future.
# Clear cache for BuyersGuide
python ./manage.py clear_cache

Просмотреть файл

@ -1,7 +1,7 @@
basket-client
beautifulsoup4
boto3
Django==3.0.14
Django==3.1.11
dj-database-url
djangorestframework
django-admin-sortable==2.2.3
@ -20,15 +20,16 @@ python-slugify
requests
social-auth-app-django
wagtail-airtable
wagtail==2.12.5
wagtail==2.13.1
wagtail-factories
wagtail-localize==1.0rc3
wagtail-localize-git==0.9.3
wagtail-inventory==1.3
wagtailmedia
wagtail-metadata
whitenoise
wagtail-modeltranslation==0.10.17
psycopg2-binary
cloudinary
sentry-sdk
wagtail-footnotes
git+https://github.com/MozillaFoundation/wagtail-footnotes.git@localized-footnotes
scout-apm

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше