From 961cf1679630d343f0282136a14fb7c1e0f59386 Mon Sep 17 00:00:00 2001 From: Ram Date: Mon, 23 Sep 2024 14:01:18 -0600 Subject: [PATCH] Homepage refresh new intro section (#12858) * New section markup and layout (mobile + desktop) * Add model updates, migration and layout tags * Remove `cause_statement` fields from `Homepage` model, `page` from `FocusArea` model * Remove remaining `cause_statement` references * Fix import order in migration * Update migration timestamp * Fix invalid html classname in markup * Fix focus area styling * Remove remaining `page` references for `FocusArea` models * Fix `FocusArea` responsive width * Update migration to replace seed data and field lengths * Remove character limit for updated fields since it crashes with existing data * Fix migration to adapt to format and include locale_id for newly created FocusArea objects * Test fix for CI factory workflow * Assign a page to hero_bottom_page so button shows up in visual regression tests * Streamline migration to use existing record instead of deleting and creating * Migration formatting * Update migration to keep cause_statement section and name the "Hero intro" section accordingly * Update migration order * Update model and migration to use `LinkBlock` field type to store `hero_intro_xxxx` data * Fix default text line width * Fix linting * Separate migrations --- .../utility/faker/streamfield_provider.py | 12 ++ .../wagtailpages/factory/homepage.py | 5 + .../migrations/0163_remove_focusarea_page.py | 17 +++ ...dy_homepage_hero_intro_heading_and_more.py | 106 ++++++++++++++++++ .../wagtailpages/pagemodels/base.py | 40 +++++-- .../wagtailpages/fragments/focus_area.html | 14 +-- .../wagtailpages/fragments/focus_areas.html | 13 +-- .../fragments/hero_intro_box.html | 11 ++ .../templates/wagtailpages/homepage.html | 3 + .../networkapi/wagtailpages/wagtail_hooks.py | 2 +- 10 files changed, 195 insertions(+), 28 deletions(-) create mode 100644 network-api/networkapi/wagtailpages/migrations/0163_remove_focusarea_page.py create mode 100644 network-api/networkapi/wagtailpages/migrations/0164_homepage_hero_intro_body_homepage_hero_intro_heading_and_more.py create mode 100644 network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/hero_intro_box.html diff --git a/network-api/networkapi/utility/faker/streamfield_provider.py b/network-api/networkapi/utility/faker/streamfield_provider.py index 65b8c7ce108..bbc5f315853 100644 --- a/network-api/networkapi/utility/faker/streamfield_provider.py +++ b/network-api/networkapi/utility/faker/streamfield_provider.py @@ -742,6 +742,17 @@ def generate_bg_cta_link_field(): return generate_field("link", link_value) +def generate_homepage_hero_intro_link(): + link_value = { + "link_to": "external_url", + "external_url": fake.url(schemes=["https"]), + "label": " ".join(fake.words(nb=3)), + "new_window": True, + } + + return generate_field("link", link_value) + + class StreamfieldProvider(BaseProvider): """ A custom Faker Provider for relative image urls, for use with factory_boy @@ -804,6 +815,7 @@ def streamfield(self, fields=None): "mixed_content": generate_mixed_content_field, "app_install_download_button": generate_app_install_download_button_field, "buyersguide_call_to_action_link": generate_bg_cta_link_field, + "homepage_hero_intro_link": generate_homepage_hero_intro_link, } streamfield_data = [] diff --git a/network-api/networkapi/wagtailpages/factory/homepage.py b/network-api/networkapi/wagtailpages/factory/homepage.py index fbb9fc6e9ed..9055c31679a 100644 --- a/network-api/networkapi/wagtailpages/factory/homepage.py +++ b/network-api/networkapi/wagtailpages/factory/homepage.py @@ -26,6 +26,9 @@ class Meta: hero_image = SubFactory(ImageFactory) cause_statement = Faker("text", max_nb_chars=150) # cause_statement_link_text and cause_statement_link_page are created at a later state + hero_intro_heading = Faker("text", max_nb_chars=60) + hero_intro_body = Faker("text", max_nb_chars=250) + hero_intro_link = Faker("streamfield", fields=["homepage_hero_intro_link"]) quote_image = SubFactory(ImageFactory) quote_text = Faker("text", max_nb_chars=300) quote_source_name = Faker("text", max_nb_chars=30) @@ -102,6 +105,8 @@ def generate(seed): print('Generating "who we are" Page (PrimaryPage)') wwa_page = PrimaryPageFactory.create(parent=home_page, title="Who we are", show_in_menus=True) + home_page.save() + reseed(seed) print('Generating child pages for "who we are" page') diff --git a/network-api/networkapi/wagtailpages/migrations/0163_remove_focusarea_page.py b/network-api/networkapi/wagtailpages/migrations/0163_remove_focusarea_page.py new file mode 100644 index 00000000000..c7a2f142baf --- /dev/null +++ b/network-api/networkapi/wagtailpages/migrations/0163_remove_focusarea_page.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.15 on 2024-09-23 19:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("wagtailpages", "0162_alter_articlepage_body"), + ] + + operations = [ + migrations.RemoveField( + model_name="focusarea", + name="page", + ), + ] diff --git a/network-api/networkapi/wagtailpages/migrations/0164_homepage_hero_intro_body_homepage_hero_intro_heading_and_more.py b/network-api/networkapi/wagtailpages/migrations/0164_homepage_hero_intro_body_homepage_hero_intro_heading_and_more.py new file mode 100644 index 00000000000..d5a2512429a --- /dev/null +++ b/network-api/networkapi/wagtailpages/migrations/0164_homepage_hero_intro_body_homepage_hero_intro_heading_and_more.py @@ -0,0 +1,106 @@ +# Generated by Django 4.2.15 on 2024-09-23 19:42 + +import wagtail.blocks +import wagtail.documents.blocks +import wagtail.fields +from django.db import migrations, models + +import networkapi.wagtailpages.validators + + +class Migration(migrations.Migration): + + dependencies = [ + ("wagtailpages", "0163_remove_focusarea_page"), + ] + + operations = [ + migrations.AddField( + model_name="homepage", + name="hero_intro_body", + field=models.TextField( + blank=True, + default="Mozilla empowers consumers to demand better online privacy, trustworthy AI, and safe online experiences from Big Tech and governments. We work across borders, disciplines, and technologies to uphold principles like privacy, inclusion and decentralization online.", + max_length=300, + ), + ), + migrations.AddField( + model_name="homepage", + name="hero_intro_heading", + field=models.CharField( + blank=True, + default="A healthy internet is one in which privacy, openness, and inclusion are the norms.", + max_length=100, + ), + ), + migrations.AddField( + model_name="homepage", + name="hero_intro_link", + field=wagtail.fields.StreamField( + [ + ( + "link", + wagtail.blocks.StructBlock( + [ + ("label", wagtail.blocks.CharBlock()), + ( + "link_to", + wagtail.blocks.ChoiceBlock( + choices=[ + ("page", "Page"), + ("external_url", "External URL"), + ("relative_url", "Relative URL"), + ("email", "Email"), + ("anchor", "Anchor"), + ("file", "File"), + ("phone", "Phone"), + ], + label="Link to", + ), + ), + ("page", wagtail.blocks.PageChooserBlock(label="Page", required=False)), + ( + "external_url", + wagtail.blocks.URLBlock( + help_text="Enter a full URL including http:// or https://", + label="External URL", + max_length=300, + required=False, + ), + ), + ( + "relative_url", + wagtail.blocks.CharBlock( + help_text='A path relative to this domain. For example, "/foo/bar" or "?foo=bar".', + label="Relative URL", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.RelativeURLValidator()], + ), + ), + ( + "anchor", + wagtail.blocks.CharBlock( + help_text='An id attribute of an element on the current page. For example, "#section-1"', + label="#", + max_length=300, + required=False, + validators=[networkapi.wagtailpages.validators.AnchorLinkValidator()], + ), + ), + ("email", wagtail.blocks.EmailBlock(required=False)), + ("file", wagtail.documents.blocks.DocumentChooserBlock(label="File", required=False)), + ("phone", wagtail.blocks.CharBlock(label="Phone", max_length=30, required=False)), + ( + "new_window", + wagtail.blocks.BooleanBlock(label="Open in new window", required=False), + ), + ] + ), + ) + ], + blank=True, + use_json_field=True, + ), + ), + ] diff --git a/network-api/networkapi/wagtailpages/pagemodels/base.py b/network-api/networkapi/wagtailpages/pagemodels/base.py index e191bdb4d04..06473963b45 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/base.py +++ b/network-api/networkapi/wagtailpages/pagemodels/base.py @@ -9,6 +9,8 @@ from wagtail.search import index from wagtail_localize.fields import SynchronizedField, TranslatableField +from networkapi.wagtailpages.pagemodels.customblocks.link_block import LinkBlock + # TODO: https://github.com/mozilla/foundation.mozilla.org/issues/2362 from ..donation_modal import DonationModals # noqa: F401 from ..utils import CharCountWidget, get_page_tree_information @@ -18,6 +20,14 @@ from .mixin.foundation_metadata import FoundationMetadataPageMixin from .mixin.foundation_navigation import FoundationNavigationPageMixin +hero_intro_heading_default_text = "A healthy internet is one in which privacy, openness, and inclusion are the norms." +hero_intro_body_default_text = ( + "Mozilla empowers consumers to demand better online privacy, trustworthy AI, " + "and safe online experiences from Big Tech and governments. We work across " + "borders, disciplines, and technologies to uphold principles like privacy, " + "inclusion and decentralization online." +) + class BasePage(FoundationMetadataPageMixin, FoundationNavigationPageMixin, Page): class Meta: @@ -604,19 +614,10 @@ class FocusArea(TranslatableMixin, models.Model): help_text="Description of this area of focus. Max. 300 characters.", ) - page = models.ForeignKey( - "wagtailcore.Page", - blank=True, - null=True, - on_delete=models.SET_NULL, - related_name="+", - ) - panels = [ FieldPanel("interest_icon"), FieldPanel("name"), FieldPanel("description"), - FieldPanel("page"), ] translatable_fields = [ @@ -764,6 +765,15 @@ def get_banner(self): hero_button_url = models.URLField(blank=True) + hero_intro_heading = models.CharField(max_length=100, blank=True, default=hero_intro_heading_default_text) + hero_intro_body = models.TextField(max_length=300, blank=True, default=hero_intro_body_default_text) + hero_intro_link = StreamField( + [("link", LinkBlock())], + use_json_field=True, + blank=True, + max_num=1, + ) + ideas_image = models.ForeignKey( "wagtailimages.Image", null=True, @@ -865,6 +875,15 @@ def get_banner(self): heading="cause statement", classname="collapsible collapsed", ), + MultiFieldPanel( + [ + FieldPanel("hero_intro_heading"), + FieldPanel("hero_intro_body"), + FieldPanel("hero_intro_link"), + ], + heading="Hero Intro Box", + classname="collapsible", + ), MultiFieldPanel( [ InlinePanel("focus_areas", min_num=3, max_num=3), @@ -933,6 +952,9 @@ def get_banner(self): SynchronizedField("hero_image"), TranslatableField("hero_button_text"), SynchronizedField("hero_button_url"), + TranslatableField("hero_intro_heading"), + TranslatableField("hero_intro_body"), + SynchronizedField("hero_intro_link"), SynchronizedField("ideas_image"), TranslatableField("ideas_headline"), TranslatableField("cause_statement"), diff --git a/network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/focus_area.html b/network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/focus_area.html index 0e57ebd3003..22c233d10ae 100644 --- a/network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/focus_area.html +++ b/network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/focus_area.html @@ -1,15 +1,11 @@ -{% load wagtailimages_tags i18n %} +{% load wagtailimages_tags %} -
- {% image area.interest_icon width-100 class="icon flex-shrink-0 mb-3 mb-sm-0 mb-lg-3 mr-sm-5 align-self-start" alt=area.interest_icon.title %} +
+ {% image area.interest_icon width-100 class="tw-max-w-[4em] tw-max-h-[4em]" alt=area.interest_icon.title %} -
-

{{ area.name }}

+
+

{{ area.name }}

{{ area.description }}

- - {% if area.page %}{% endif %}
diff --git a/network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/focus_areas.html b/network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/focus_areas.html index c65d02c73ce..c93c2aab00d 100644 --- a/network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/focus_areas.html +++ b/network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/focus_areas.html @@ -1,12 +1,7 @@ {% load wagtailimages_tags i18n %} -
-
-

{% trans "Our areas of focus" %}

-
- {% for focus in page.focus_areas.all %} - {% include "./focus_area.html" with area=focus.area %} - {% endfor %} -
-
+
+ {% for focus in page.focus_areas.all %} + {% include "./focus_area.html" with area=focus.area %} + {% endfor %}
diff --git a/network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/hero_intro_box.html b/network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/hero_intro_box.html new file mode 100644 index 00000000000..ef7d4fab879 --- /dev/null +++ b/network-api/networkapi/wagtailpages/templates/wagtailpages/fragments/hero_intro_box.html @@ -0,0 +1,11 @@ +{% load wagtailcore_tags %} + +
+

{{ page.hero_intro_heading }}

+

{{ page.hero_intro_body }}

+ {% if page.hero_intro_link %} + {% with link=page.hero_intro_link.0.value %} + {{ link.label }} + {% endwith %} + {% endif %} +
diff --git a/network-api/networkapi/wagtailpages/templates/wagtailpages/homepage.html b/network-api/networkapi/wagtailpages/templates/wagtailpages/homepage.html index 34f7deb936a..d2f4763703a 100644 --- a/network-api/networkapi/wagtailpages/templates/wagtailpages/homepage.html +++ b/network-api/networkapi/wagtailpages/templates/wagtailpages/homepage.html @@ -33,6 +33,9 @@ {% if page.show_cause_statement %} {% include "./fragments/cause_statement.html" %} {% endif %} +
+
+ {% include "./fragments/hero_intro_box.html" %} {% include "./fragments/focus_areas.html" %}
diff --git a/network-api/networkapi/wagtailpages/wagtail_hooks.py b/network-api/networkapi/wagtailpages/wagtail_hooks.py index 286d8790447..d6a9bf05855 100644 --- a/network-api/networkapi/wagtailpages/wagtail_hooks.py +++ b/network-api/networkapi/wagtailpages/wagtail_hooks.py @@ -533,7 +533,7 @@ class AreasOfFocusViewSet(SnippetViewSet): menu_order = 000 menu_label = "Areas of Focus" menu_name = "Areas of Focus" - list_display = ("name", "interest_icon", "page") + list_display = ("name", "interest_icon") search_fields = ("name",) ordering = ("name",)