Skip to content

Commit 49b6d0c

Browse files
authored
Communities page v0 (#4566)
1 parent 36ac87b commit 49b6d0c

File tree

8 files changed

+216
-1
lines changed

8 files changed

+216
-1
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from typing import Self
2+
import strawberry
3+
from api.cms.page.registry import register_page_block
4+
5+
6+
@strawberry.type
7+
class Community:
8+
name: str
9+
description: str
10+
logo: str | None
11+
banner_photo: str | None
12+
banner_background_color: str | None
13+
14+
mastodon_url: str | None
15+
facebook_url: str | None
16+
instagram_url: str | None
17+
linkedin_url: str | None
18+
twitter_url: str | None
19+
20+
@classmethod
21+
def from_block(cls, block) -> Self:
22+
logo_url = None
23+
if block["logo"]:
24+
logo_url = block["logo"].get_rendition("width-300|jpegquality-60").full_url
25+
26+
banner_photo_url = None
27+
if block["banner_photo"]:
28+
banner_photo_url = (
29+
block["banner_photo"].get_rendition("width-300|jpegquality-60").full_url
30+
)
31+
32+
return cls(
33+
name=block["name"],
34+
description=block["description"],
35+
logo=logo_url,
36+
banner_photo=banner_photo_url,
37+
banner_background_color=block["banner_background_color"],
38+
mastodon_url=block["mastodon_url"],
39+
facebook_url=block["facebook_url"],
40+
instagram_url=block["instagram_url"],
41+
linkedin_url=block["linkedin_url"],
42+
twitter_url=block["twitter_url"],
43+
)
44+
45+
46+
@register_page_block()
47+
@strawberry.type
48+
class CommunitiesSection:
49+
id: strawberry.ID
50+
title: str
51+
communities: list[Community]
52+
53+
@classmethod
54+
def from_block(cls, block) -> Self:
55+
return cls(
56+
id=block.id,
57+
title=block.value["title"],
58+
communities=[
59+
Community.from_block(community)
60+
for community in block.value["communities"]
61+
],
62+
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from wagtail.images.blocks import ImageChooserBlock
2+
from wagtail import blocks
3+
4+
5+
class Community(blocks.StructBlock):
6+
name = blocks.CharBlock(required=True)
7+
description = blocks.RichTextBlock(required=True)
8+
logo = ImageChooserBlock(required=True)
9+
banner_photo = ImageChooserBlock(required=False)
10+
banner_background_color = blocks.CharBlock(required=False)
11+
12+
mastodon_url = blocks.URLBlock(required=False)
13+
facebook_url = blocks.URLBlock(required=False)
14+
instagram_url = blocks.URLBlock(required=False)
15+
linkedin_url = blocks.URLBlock(required=False)
16+
twitter_url = blocks.URLBlock(required=False)
17+
18+
19+
class CommunitiesSection(blocks.StructBlock):
20+
title = blocks.CharBlock(required=True)
21+
communities = blocks.ListBlock(Community)
22+
23+
class Meta:
24+
label = "Communities Section"
25+
icon = "crosshairs"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 5.2.8 on 2026-02-26 04:08
2+
3+
import cms.components.base.blocks.accordion
4+
import cms.components.page.blocks.communities_section
5+
import wagtail.fields
6+
from django.db import migrations
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
('page', '0007_alter_genericpage_body'),
13+
]
14+
15+
operations = [
16+
migrations.AlterField(
17+
model_name='genericpage',
18+
name='body',
19+
field=wagtail.fields.StreamField([('text_section', 7), ('map', 11), ('slider_cards_section', 18), ('sponsors_section', 21), ('home_intro_section', 22), ('keynoters_section', 23), ('schedule_preview_section', 26), ('socials_section', 27), ('special_guest_section', 30), ('information_section', 33), ('news_grid_section', 34), ('checkout_section', 35), ('live_streaming_section', 34), ('homepage_hero', 37), ('dynamic_content_display_section', 39), ('communities_section', 41)], block_lookup={0: ('wagtail.blocks.CharBlock', (), {'required': False}), 1: ('wagtail.blocks.BooleanBlock', (), {'default': False, 'required': False}), 2: ('wagtail.blocks.RichTextBlock', (), {'required': False}), 3: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('text-1', 'Text-1'), ('text-2', 'Text-2')], 'required': False}), 4: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('cathedral', 'Cathedral'), ('florence', 'Florence'), ('florence2', 'Florence2'), ('handWithSnakeInside', 'Hand With Snake Inside'), ('snake1', 'Snake1'), ('snake2', 'Snake2'), ('snake4', 'Snake4'), ('snake5', 'Snake5'), ('snakeBody', 'Snake Body'), ('snakeCouple', 'Snake Couple'), ('snakeDNA', 'Snake D N A'), ('snakeHead', 'Snake Head'), ('snakeInDragon', 'Snake In Dragon'), ('snakeInDragonInverted', 'Snake In Dragon Inverted'), ('snakeLetter', 'Snake Letter'), ('snakeLongNeck', 'Snake Long Neck'), ('snakePencil', 'Snake Pencil'), ('snakeTail', 'Snake Tail'), ('snakeWithBalloon', 'Snake With Balloon'), ('snakeWithContacts', 'Snake With Contacts'), ('snakesWithBanner', 'Snakes With Banner'), ('snakesWithCocktail', 'Snakes With Cocktail'), ('snakesWithDirections', 'Snakes With Directions'), ('snakesWithOutlines', 'Snakes With Outlines'), ('tripleSnakes', 'Triple Snakes')], 'required': False}), 5: ('wagtail.blocks.ListBlock', (cms.components.base.blocks.accordion.Accordion,), {}), 6: ('wagtail.blocks.StructBlock', [[('label', 0), ('link', 0)]], {}), 7: ('wagtail.blocks.StructBlock', [[('title', 0), ('is_main_title', 1), ('subtitle', 0), ('body', 2), ('body_text_size', 3), ('illustration', 4), ('accordions', 5), ('cta', 6)]], {}), 8: ('wagtail.blocks.DecimalBlock', (), {'decimal_places': 8, 'max_digits': 11}), 9: ('wagtail.blocks.IntegerBlock', (), {'default': 15}), 10: ('wagtail.blocks.URLBlock', (), {}), 11: ('wagtail.blocks.StructBlock', [[('longitude', 8), ('latitude', 8), ('zoom', 9), ('link', 10)]], {}), 12: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('xl', 'Extra Large'), ('3xl', '3 Extra Large')]}), 13: ('wagtail.blocks.CharBlock', (), {}), 14: ('wagtail.blocks.RichTextBlock', (), {}), 15: ('wagtail.blocks.StructBlock', [[('title', 13), ('body', 14), ('cta', 6)]], {}), 16: ('wagtail.blocks.StructBlock', [[('title', 13), ('body', 14), ('price', 13), ('price_tier', 13), ('cta', 6)]], {}), 17: ('wagtail.blocks.StreamBlock', [[('simple_text_card', 15), ('price_card', 16)]], {}), 18: ('wagtail.blocks.StructBlock', [[('title', 0), ('spacing', 12), ('snake_background', 1), ('cards', 17)]], {}), 19: ('wagtail.blocks.CharBlock', (), {'required': True}), 20: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('side-by-side', 'Side-by-side'), ('vertical', 'Vertical')], 'required': False}), 21: ('wagtail.blocks.StructBlock', [[('title', 19), ('body', 19), ('cta', 6), ('layout', 20)]], {}), 22: ('wagtail.blocks.StructBlock', [[('pretitle', 0), ('title', 0)]], {}), 23: ('wagtail.blocks.StructBlock', [[('title', 19), ('cta', 6)]], {}), 24: ('wagtail.blocks.StructBlock', [[('label', 0), ('link', 0)]], {'label': 'Primary CTA'}), 25: ('wagtail.blocks.StructBlock', [[('label', 0), ('link', 0)]], {'label': 'Secondary CTA'}), 26: ('wagtail.blocks.StructBlock', [[('title', 19), ('primary_cta', 24), ('secondary_cta', 25)]], {}), 27: ('wagtail.blocks.StructBlock', [[('label', 19), ('hashtag', 19)]], {}), 28: ('wagtail.images.blocks.ImageChooserBlock', (), {'required': True}), 29: ('wagtail.blocks.DateBlock', (), {'required': True}), 30: ('wagtail.blocks.StructBlock', [[('title', 19), ('guest_name', 19), ('guest_photo', 28), ('guest_job_title', 19), ('event_date', 29), ('cta', 6)]], {}), 31: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('coral', 'coral'), ('caramel', 'caramel'), ('cream', 'cream'), ('yellow', 'yellow'), ('green', 'green'), ('purple', 'purple'), ('pink', 'pink'), ('blue', 'blue'), ('red', 'red'), ('success', 'success'), ('warning', 'warning'), ('neutral', 'neutral'), ('error', 'error'), ('black', 'black'), ('grey', 'grey'), ('white', 'white'), ('milk', 'milk')]}), 32: ('wagtail.blocks.DateTimeBlock', (), {'required': False}), 33: ('wagtail.blocks.StructBlock', [[('title', 19), ('body', 2), ('illustration', 4), ('background_color', 31), ('countdown_to_datetime', 32), ('countdown_to_deadline', 0), ('cta', 6)]], {}), 34: ('wagtail.blocks.StructBlock', [[]], {}), 35: ('wagtail.blocks.StructBlock', [[('show_conference_tickets_products', 1), ('show_social_events_products', 1), ('show_tours_products', 1), ('show_gadgets_products', 1), ('show_membership_products', 1)]], {}), 36: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('florence', 'Florence'), ('bologna', 'Bologna')]}), 37: ('wagtail.blocks.StructBlock', [[('city', 36)]], {}), 38: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('speakers', 'Speakers'), ('keynoters', 'Keynoters'), ('proposals', 'Proposals')]}), 39: ('wagtail.blocks.StructBlock', [[('source', 38)]], {}), 40: ('wagtail.blocks.ListBlock', (cms.components.page.blocks.communities_section.Community,), {}), 41: ('wagtail.blocks.StructBlock', [[('title', 19), ('communities', 40)]], {})}),
20+
),
21+
]

backend/cms/components/page/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from cms.components.page.blocks.communities_section import CommunitiesSection
12
from cms.components.page.blocks.dynamic_content_display_section import (
23
DynamicContentDisplaySection,
34
)
@@ -41,6 +42,7 @@ class BodyBlock(blocks.StreamBlock):
4142
live_streaming_section = LiveStreamingSection()
4243
homepage_hero = HomepageHero()
4344
dynamic_content_display_section = DynamicContentDisplaySection()
45+
communities_section = CommunitiesSection()
4446

4547

4648
class GenericPage(CustomHeadlessMixin, Page):

frontend/src/components/blocks-renderer/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { TextSection } from "~/components/blocks/text-section";
55
import type { Block } from "~/types";
66

77
import { CheckoutSection } from "../blocks/checkout-section";
8+
import { CommunitiesSection } from "../blocks/communities-section";
89
import { DynamicContentDisplaySection } from "../blocks/dynamic-content-display-section";
910
import { HomeIntroSection } from "../blocks/home-intro-section";
1011
import { HomepageHero } from "../blocks/homepage-hero";
@@ -38,6 +39,7 @@ const REGISTRY: Registry = {
3839
LiveStreamingSection,
3940
HomepageHero,
4041
DynamicContentDisplaySection,
42+
CommunitiesSection,
4143
};
4244

4345
type Props = {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import {
2+
CardPart,
3+
Grid,
4+
Heading,
5+
MultiplePartsCard,
6+
MultiplePartsCardCollection,
7+
SocialLinks,
8+
Spacer,
9+
StyledHTMLText,
10+
Text,
11+
VerticalStack,
12+
} from "@python-italia/pycon-styleguide";
13+
import { Section } from "@python-italia/pycon-styleguide";
14+
import type { Community } from "~/types";
15+
16+
type Props = {
17+
title: string;
18+
communities: Community[];
19+
};
20+
21+
export const CommunitiesSection = ({ title, communities }: Props) => {
22+
return (
23+
<Section>
24+
<Grid cols={3} mdCols={2}>
25+
{communities.map((community) => {
26+
const socialLinks = [];
27+
if (community.mastodonUrl) {
28+
socialLinks.push({
29+
icon: "mastodon",
30+
link: community.mastodonUrl,
31+
});
32+
}
33+
if (community.facebookUrl) {
34+
socialLinks.push({
35+
icon: "facebook",
36+
link: community.facebookUrl,
37+
});
38+
}
39+
if (community.instagramUrl) {
40+
socialLinks.push({
41+
icon: "instagram",
42+
link: community.instagramUrl,
43+
});
44+
}
45+
if (community.linkedinUrl) {
46+
socialLinks.push({
47+
icon: "linkedin",
48+
link: community.linkedinUrl,
49+
});
50+
}
51+
if (community.twitterUrl) {
52+
socialLinks.push({
53+
icon: "twitter",
54+
link: community.twitterUrl,
55+
});
56+
}
57+
58+
return (
59+
<MultiplePartsCard key={community.name}>
60+
<CardPart>
61+
<Heading size={4}>{community.name}</Heading>
62+
</CardPart>
63+
<CardPart background="milk">
64+
{community.logo && (
65+
<>
66+
<VerticalStack alignItems="center" gap="small">
67+
<img src={community.logo} alt={community.name} />
68+
</VerticalStack>
69+
<Spacer size="small" />
70+
</>
71+
)}
72+
73+
<StyledHTMLText text={community.description} baseTextSize={2} />
74+
</CardPart>
75+
<CardPart background="milk">
76+
<VerticalStack alignItems="center" gap="small">
77+
<SocialLinks hoverColor="green" socials={socialLinks} />
78+
</VerticalStack>
79+
</CardPart>
80+
</MultiplePartsCard>
81+
);
82+
})}
83+
</Grid>
84+
</Section>
85+
);
86+
};

frontend/src/pages/schedule/fragments/blocks.graphql

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,23 @@ fragment Blocks on Block {
135135
id
136136
source
137137
}
138+
139+
... on CommunitiesSection {
140+
id
141+
title
142+
communities {
143+
name
144+
description
145+
logo
146+
bannerPhoto
147+
bannerBackgroundColor
148+
mastodonUrl
149+
facebookUrl
150+
instagramUrl
151+
linkedinUrl
152+
twitterUrl
153+
}
154+
}
138155
}
139156

140157
fragment CTAInfo on CTA {

frontend/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"moduleResolution": "node",
1414
"resolveJsonModule": true,
1515
"isolatedModules": true,
16-
"jsx": "preserve",
16+
"jsx": "react-jsx",
1717
"baseUrl": ".",
1818
"paths": {
1919
"~/*": ["./src/*"],

0 commit comments

Comments
 (0)