Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TWE-7 - BE - Division page #324

Merged
merged 22 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e4abc32
Move PartersBlock to core.blocks
SharmaineLim Dec 18, 2024
84a7048
Add divisions app
SharmaineLim Dec 19, 2024
7144e99
Prepare division page blocks
SharmaineLim Dec 19, 2024
643ff22
Add DivisionPage
SharmaineLim Dec 19, 2024
a38b9d1
Make schema migration
SharmaineLim Dec 19, 2024
aa3dfd6
Add faker & factory-boy as explicit dependencies since we use them
SharmaineLim Dec 19, 2024
6663d23
Create DivisionPage factory
SharmaineLim Dec 19, 2024
bc24e99
Do something different if there's only 1 dynamic text
SharmaineLim Dec 19, 2024
685f6cc
Add 'caption' to four-photo collage
SharmaineLim Dec 20, 2024
7f5d684
Merge branch 'integration/2024-evolution' into feature/twe-7-division…
SharmaineLim Jan 6, 2025
2f380c1
Draft intro with images block
albinazs Jan 8, 2025
aa12999
Add small_caption to FourPhotoCollageBlock
SharmaineLim Jan 9, 2025
0fc85ec
Division page FE
albinazs Jan 9, 2025
7ba6958
Fine-tune layout
albinazs Jan 9, 2025
8010faa
Remove gettext from tbx/core/blocks.py
SharmaineLim Jan 13, 2025
958434b
Rename small_caption to description
SharmaineLim Jan 13, 2025
60249d2
Remove empty line
SharmaineLim Jan 13, 2025
d23d6c9
Remove stream_block_division.html
SharmaineLim Jan 13, 2025
7d82a84
Add other blocks to the Division page story container
SharmaineLim Jan 13, 2025
0072a1b
Fix aspect ratio for mobile, alt text, and rich-text styles
albinazs Jan 13, 2025
1e0359b
Only allow creation of DivisionPage under HomePage
SharmaineLim Jan 14, 2025
252bc62
Merge pull request #333 from torchbox/feature/twe-8-division-page-fe
albinazs Jan 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ mkdocs-material = "^9.5.41"
pymdown-extensions = "^10.11.2"

# Testing
factory-boy = "^3.3.0"
faker = "^24.11.0"
SharmaineLim marked this conversation as resolved.
Show resolved Hide resolved
wagtail-factories = "^4.2.1"


Expand Down
79 changes: 79 additions & 0 deletions tbx/core/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,85 @@ class Meta:
template = "patterns/molecules/streamfield/blocks/contact_call_to_action.html"


class DynamicHeroBlock(blocks.StructBlock):
"""
This block displays text that will be cycled through.
"""

static_text = blocks.CharBlock(required=False)
dynamic_text = blocks.ListBlock(
blocks.CharBlock(),
help_text=(
"The hero will cycle through these texts on larger screen sizes "
"and only show the first text on smaller screen sizes."
),
required=False,
)

class Meta:
icon = "title"
template = "patterns/molecules/streamfield/blocks/dynamic_hero_block.html"


class FourPhotoCollageBlock(blocks.StructBlock):
"""Accepts 4 photos shown as a collage + text below. Used on the division page."""

images = blocks.ListBlock(
ImageWithAltTextBlock(label="Photo"),
min_num=4,
max_num=4,
label="Photos",
help_text="Exactly four required.",
default=[{"image": None, "alt_text": ""}] * 4,
)
caption = blocks.RichTextBlock(
features=settings.PARAGRAPH_RICH_TEXT_FEATURES, required=False
)
description = blocks.RichTextBlock(
features=settings.PARAGRAPH_RICH_TEXT_FEATURES, required=False
SharmaineLim marked this conversation as resolved.
Show resolved Hide resolved
)

class Meta:
group = "Custom"
icon = "image"
template = "patterns/molecules/streamfield/blocks/four_photo_collage_block.html"


class IntroductionWithImagesBlock(blocks.StructBlock):
"""Used on the division page."""

introduction = blocks.RichTextBlock(features=settings.PARAGRAPH_RICH_TEXT_FEATURES)
description = blocks.RichTextBlock(
blank=True, features=settings.NO_HEADING_RICH_TEXT_FEATURES
)
images = blocks.ListBlock(
ImageWithAltTextBlock(label="Photo"),
min_num=2,
max_num=2,
label="Photos",
help_text="Exactly two required.",
default=[{"image": None, "alt_text": ""}] * 2,
)

class Meta:
group = "Custom"
icon = "pilcrow"
template = (
"patterns/molecules/streamfield/blocks/introduction_with_images_block.html"
)


class PartnersBlock(blocks.StructBlock):
title = blocks.CharBlock(max_length=255, required=False)
partner_logos = blocks.ListBlock(CustomImageChooserBlock(), label="Logos")

class Meta:
icon = "openquote"
label = "Partner logos"
template = "patterns/molecules/streamfield/blocks/partners_block.html"
group = "Custom"


class ShowcaseBlock(blocks.StructBlock):
"""
This block is a standard ShowcaseBlock, available on the home page and
Expand Down
23 changes: 20 additions & 3 deletions tbx/core/factories.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
import factory
import wagtail_factories
from tbx.core.blocks import StoryBlock
from faker import Faker
from tbx.core.blocks import DynamicHeroBlock, StoryBlock
from tbx.core.models import HomePage, StandardPage
from wagtail.blocks import RichTextBlock
from wagtail import blocks

fake = Faker()


class DynamicHeroBlockFactory(wagtail_factories.StructBlockFactory):
class Meta:
model = DynamicHeroBlock

static_text = fake.sentence()

@factory.post_generation
SharmaineLim marked this conversation as resolved.
Show resolved Hide resolved
def dynamic_text(obj, create, extracted, **kwargs):
values = extracted or fake.sentences(nb=5)
obj["dynamic_text"] = blocks.list_block.ListValue(
blocks.ListBlock(blocks.CharBlock()), values
)


class RichTextBlockFactory(wagtail_factories.blocks.BlockFactory):
class Meta:
model = RichTextBlock
model = blocks.RichTextBlock


class StoryBlockFactory(wagtail_factories.StreamBlockFactory):
Expand Down
3 changes: 3 additions & 0 deletions tbx/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ class HomePagePartnerLogo(Orderable):
# Home Page
class HomePage(ColourThemeMixin, ContactMixin, SocialFields, NavigationFields, Page):
template = "patterns/pages/home/home_page.html"

parent_page_types = ["wagtailcore.Page"]

hero_heading_1 = models.CharField(max_length=255)
hero_heading_2 = models.CharField(max_length=255)
hero_introduction = RichTextField(blank=True, features=["bold", "italic", "link"])
Expand Down
41 changes: 34 additions & 7 deletions tbx/core/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from django.apps import apps
from django.test import TestCase
from django.utils.module_loading import import_string, module_has_submodule

from tbx.core.factories import HomePageFactory, StandardPageFactory
from tbx.core.models import HomePage, StandardPage
from wagtail.models import Site
from wagtail.models import Page, Site
from wagtail.test.utils import WagtailPageTestCase
from wagtail.test.utils.form_data import (
nested_form_data,
Expand All @@ -9,14 +13,37 @@
)


class TestHomePageFactory(WagtailPageTestCase):
def test_create(self):
HomePageFactory()
class TestPageFactory(TestCase):
"""Sanity tests to make sure all pages have a factory."""

# Exclude these modules from the check.
# (They currently don't have factories. Un-exclude once they have factories.)
EXCLUDE = ["tbx.events", "tbx.impact_reports", "tbx.services"]

def test_pages(self):
app_configs = apps.get_app_configs()
home_page = HomePageFactory()

# Create one of every page type using their factory.
for app in app_configs:
for model in app.models.values():
if issubclass(model, Page) and model not in [Page, HomePage]:
if app.name in self.EXCLUDE:
continue

with self.subTest(model=model.__name__):
# Get the model's factory
assert module_has_submodule(
app.module, "factories"
), f"App '{app.name}' does not have a factories module."

page_factory = import_string(
f"{app.module.__name__}.factories.{model.__name__}Factory"
)

page = page_factory(parent=home_page)

class TestStandardPageFactory(WagtailPageTestCase):
def test_create(self):
StandardPageFactory()
self.assertIsInstance(page, model)


class TestStandardPage(WagtailPageTestCase):
Expand Down
Empty file added tbx/divisions/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions tbx/divisions/blocks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from tbx.core.blocks import (
FourPhotoCollageBlock,
IntroductionWithImagesBlock,
PartnersBlock,
StoryBlock,
)


class DivisionStoryBlock(StoryBlock):
four_photo_collage = FourPhotoCollageBlock()
introduction_with_images = IntroductionWithImagesBlock()
partners_block = PartnersBlock()
36 changes: 36 additions & 0 deletions tbx/divisions/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import factory
import wagtail_factories
from tbx.core.blocks import DynamicHeroBlock
from tbx.core.factories import DynamicHeroBlockFactory, StoryBlockFactory
from wagtail import blocks

from .models import DivisionPage


class DynamicHeroStreamBlock(blocks.StreamBlock):
hero = DynamicHeroBlock()


class DynamicHeroStreamBlockFactory(wagtail_factories.StreamBlockFactory):
class Meta:
model = DynamicHeroStreamBlock

hero = factory.SubFactory(DynamicHeroBlockFactory)


class DivisionPageFactory(wagtail_factories.PageFactory):
class Meta:
model = DivisionPage

title = "Charity"
label = "Charity"

@factory.post_generation
def hero(obj, create, extracted, **kwargs):
blocks = kwargs or {"0": "hero"}
obj.hero = DynamicHeroStreamBlockFactory(**blocks)

@factory.post_generation
def body(obj, create, extracted, **kwargs):
blocks = kwargs or {"0": "paragraph"}
obj.body = StoryBlockFactory(**blocks)
89 changes: 89 additions & 0 deletions tbx/divisions/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Generated by Django 4.2.16 on 2024-12-18 08:18

from django.db import migrations, models
import django.db.models.deletion
import tbx.core.utils.fields


class Migration(migrations.Migration):

initial = True

dependencies = [
("wagtailcore", "0094_alter_page_locale"),
("people", "0011_update_theme_colour_choices"),
("images", "0001_initial"),
]

operations = [
migrations.CreateModel(
name="DivisionPage",
fields=[
(
"page_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="wagtailcore.page",
),
),
(
"navigation_text",
models.CharField(
blank=True,
help_text="\n Text entered here will appear instead of the page title in the navigation menu.\n For top-level menu items do this in the navigaiton settings instead.\n ",
max_length=255,
),
),
("social_text", models.CharField(blank=True, max_length=255)),
(
"theme",
models.CharField(
blank=True,
choices=[
("", "None"),
("theme-coral", "Coral"),
("theme-nebuline", "Nebuline"),
("theme-lagoon", "Lagoon"),
("theme-green", "Green"),
],
max_length=25,
),
),
("label", models.CharField(blank=True, max_length=50)),
("hero", tbx.core.utils.fields.StreamField(block_lookup={})),
(
"body",
tbx.core.utils.fields.StreamField(blank=True, block_lookup={}),
),
(
"contact",
models.ForeignKey(
blank=True,
help_text="The contact will be applied to this page's footer and all of its descendants.\nIf no contact is selected, it will be derived from this page's ancestors, eventually falling back to the default contact.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="people.contact",
),
),
(
"social_image",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="images.customimage",
),
),
],
options={
"abstract": False,
},
bases=("wagtailcore.page", models.Model),
),
]
Empty file.
52 changes: 52 additions & 0 deletions tbx/divisions/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from django.db import models

from tbx.core.blocks import DynamicHeroBlock
from tbx.core.utils.fields import StreamField
from tbx.core.utils.models import (
ColourThemeMixin,
NavigationFields,
SocialFields,
)
from tbx.people.models import ContactMixin
from wagtail.admin.panels import FieldPanel, MultiFieldPanel
from wagtail.models import Page

from .blocks import DivisionStoryBlock


class DivisionPage(
ColourThemeMixin, ContactMixin, SocialFields, NavigationFields, Page
):
template = "patterns/pages/divisions/division_page.html"

parent_page_types = ["torchbox.HomePage"]

label = models.CharField(blank=True, max_length=50)

hero = StreamField([("hero", DynamicHeroBlock())], max_num=1, min_num=1)
body = StreamField(DivisionStoryBlock(), blank=True)

content_panels = Page.content_panels + [
FieldPanel(
"label",
heading="Division label",
help_text=(
"Label displayed beside the logo for this page and any other pages"
" under this division. (e.g. Charity)"
),
),
FieldPanel("hero"),
FieldPanel("body"),
]

promote_panels = (
[
MultiFieldPanel(Page.promote_panels, "Common page configuration"),
]
+ NavigationFields.promote_panels
+ ColourThemeMixin.promote_panels
+ ContactMixin.promote_panels
+ [
MultiFieldPanel(SocialFields.promote_panels, "Social fields"),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% include "patterns/molecules/streamfield/blocks/introduction_with_images_block.html" %}
SharmaineLim marked this conversation as resolved.
Show resolved Hide resolved
{% include "patterns/molecules/streamfield/blocks/partners_block.html" %}
{% include "patterns/molecules/streamfield/blocks/four_photo_collage_block.html" %}
Loading
Loading