Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • feat-more-blocks
  • feat-rework-election-page
  • feat/custom-css
  • feat/dary-improvements
  • feat/geo-feature-collections
  • feat/hideable-tweets
  • feat/instagram-feed
  • feat/people-octopus-imports
  • feat/pirstan-changes
  • feat/redesign-fixes-3
  • feat/redesign-improvements-10
  • feat/redesign-improvements-8
  • feat/separate-import-thread
  • feature/crypto-widget
  • features/add-custom-numbering-for-candidates
  • features/add-dynamic-candidate-numbers
  • features/add-embed-to-articles
  • features/add-feature-enlarging-sub-block
  • features/add-link-to-images
  • features/add-pdf-page
  • features/add-redirects
  • features/add-thumbnail-principle-to-uniweb-and-senate
  • features/add-timeline
  • features/add-typed-table
  • features/create-collapsible-extra-legal-info
  • features/create-mastodon-feed-block
  • features/create-wordcloud-from-article-page
  • features/donation-panel-should-be-optional
  • features/extend-hero-banner
  • features/fix-broken-calendar-categories
  • master
  • test
32 results

Target

Select target project
  • to/majak
  • b1242/majak
2 results
Select Git revision
  • fix1
  • localwebs
  • master
  • pdp
  • seo1
  • target-groups
  • test
7 results
Show changes
Showing
with 60773 additions and 0 deletions
# Generated by Django 5.0.7 on 2024-12-09 14:52
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
(
"district",
"0304_remove_districtoctopuspersonpage_originating_group_and_more",
),
]
operations = [
migrations.AlterModelOptions(
name="districtcustompage",
options={"verbose_name": "Jednoduchá stránka"},
),
]
# Generated by Django 5.0.7 on 2025-01-13 18:23
import wagtail.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("district", "0305_alter_districtcustompage_options"),
]
operations = [
migrations.RemoveField(
model_name="districthomepage",
name="menu_button_name",
),
migrations.AlterField(
model_name="districthomepage",
name="menu_button_content",
field=wagtail.fields.StreamField(
[("navbar_menu_item", 2)],
blank=True,
block_lookup={
0: ("wagtail.blocks.URLBlock", (), {"label": "Odkaz tlačítka"}),
1: ("wagtail.blocks.CharBlock", (), {"label": "Text tlačítka"}),
2: (
"wagtail.blocks.StructBlock",
[[("button_link", 0), ("button_text", 1)]],
{},
),
},
verbose_name="Zvýrazněná tlačítka",
),
),
]
# Generated by Django 5.0.7 on 2025-02-10 11:09
import wagtail.fields
from django.db import migrations
import shared.blocks.children.misc
class Migration(migrations.Migration):
dependencies = [
("district", "0306_remove_districthomepage_menu_button_name_and_more"),
]
operations = [
migrations.AlterField(
model_name="districthomepage",
name="content",
field=wagtail.fields.StreamField(
[
("fullscreen_header_block", 11),
("news_block", 14),
("elections_block", 23),
("people_block", 27),
("calendar_block", 30),
("carousel_program", 41),
("newsletter_block", 42),
],
blank=True,
block_lookup={
0: (
"wagtail.images.blocks.ImageChooserBlock",
(),
{
"help_text": "Pokud není vybráno video, ukáže se na desktopu.",
"label": "Obrázek na pozadí (desktop)",
"required": False,
},
),
1: (
"wagtail.images.blocks.ImageChooserBlock",
(),
{
"help_text": "Pokud není vybráno video, ukáže se na mobilu.",
"label": "Obrázek na pozadí (mobil)",
"required": False,
},
),
2: (
"wagtail.blocks.URLBlock",
(),
{
"help_text": "Pokud je vybráno, ukáže se na desktopech s povoleným autoplayem místo obrázku.",
"label": "Video (desktop)",
"required": False,
},
),
3: (
"wagtail.blocks.URLBlock",
(),
{
"help_text": "Pokud je vybráno, ukáže se na mobilech s povoleným autoplayem místo obrázku.",
"label": "Video (mobil)",
"required": False,
},
),
4: (
"wagtail.blocks.TextBlock",
(),
{"label": "Desktop první řádek", "required": False},
),
5: (
"wagtail.blocks.TextBlock",
(),
{"label": "Desktop druhý řádek", "required": False},
),
6: (
"wagtail.blocks.TextBlock",
(),
{"label": "První mobilní řádek", "required": False},
),
7: (
"wagtail.blocks.TextBlock",
(),
{"label": "Druhý mobilní řádek", "required": False},
),
8: (
"wagtail.blocks.TextBlock",
(),
{"label": "Třetí mobilní řádek", "required": False},
),
9: (
"wagtail.blocks.URLBlock",
(),
{
"help_text": "Bez odkazu tlačítko nebude viditelné.",
"label": "Odkaz tlačítka",
"required": False,
},
),
10: (
"wagtail.blocks.CharBlock",
(),
{
"help_text": "Odkaz funguje i bez tlačítka. Pokud chceš tlačítko skrýt, nevyplňuj text.",
"label": "Text tlačítka",
"required": False,
},
),
11: (
"wagtail.blocks.StructBlock",
[
[
("desktop_image", 0),
("mobile_image", 1),
("desktop_video_url", 2),
("mobile_video_url", 3),
("desktop_line_1", 4),
("desktop_line_2", 5),
("mobile_line_1", 6),
("mobile_line_2", 7),
("mobile_line_3", 8),
("button_url", 9),
("button_text", 10),
]
],
{},
),
12: (
"wagtail.blocks.CharBlock",
(),
{
"help_text": "Nejnovější články se načtou automaticky",
"label": "Titulek",
},
),
13: (
"wagtail.blocks.TextBlock",
(),
{"label": "Popis", "required": False},
),
14: (
"wagtail.blocks.StructBlock",
[[("title", 12), ("description", 13)]],
{
"template": "styleguide2/includes/organisms/articles/district/articles_section.html"
},
),
15: (
"wagtail.blocks.CharBlock",
(),
{"label": "Titulek", "required": True},
),
16: (
"wagtail.blocks.CharBlock",
(),
{
"default": "Aktuálně zbývá",
"label": "Text před odpočtem",
"required": True,
},
),
17: (
"wagtail.blocks.DateTimeBlock",
(),
{"label": "Datum & čas voleb", "required": True},
),
18: (
"wagtail.blocks.CharBlock",
(),
{"label": "Titulek", "max_length": 128, "required": True},
),
19: (
"wagtail.blocks.PageChooserBlock",
(),
{"label": "Stránka", "required": False},
),
20: (
"wagtail.blocks.URLBlock",
(),
{"label": "Odkaz", "required": False},
),
21: (
"wagtail.blocks.StructBlock",
[[("title", 18), ("page", 19), ("link", 20)]],
{},
),
22: (
"wagtail.blocks.ListBlock",
(21,),
{"label": "Tlačítka", "required": False},
),
23: (
"wagtail.blocks.StructBlock",
[
[
("title", 15),
("text_before_countdown", 16),
("countdown_timestamp", 17),
("buttons", 22),
]
],
{},
),
24: (
"wagtail.blocks.CharBlock",
(),
{"label": "První řádek titulku"},
),
25: (
"wagtail.blocks.CharBlock",
(),
{"label": "Druhý řádek titulku", "required": False},
),
26: (
"wagtail.blocks.ListBlock",
(shared.blocks.children.misc.PersonBoxBlock,),
{"label": "Boxíky"},
),
27: (
"wagtail.blocks.StructBlock",
[
[
("title_line_1", 24),
("title_line_2", 25),
("description", 13),
("list", 26),
]
],
{
"template": "styleguide2/includes/organisms/main_section/district/representatives_section.html"
},
),
28: (
"wagtail.blocks.CharBlock",
(),
{"label": "Titulek", "required": False},
),
29: (
"wagtail.blocks.static_block.StaticBlock",
(),
{
"admin_text": "Adresa kalendáře se zadává v nastavení hlavní stránky webu",
"label": "Volba kalendáře",
},
),
30: (
"wagtail.blocks.StructBlock",
[[("title", 28), ("info", 29)]],
{},
),
31: (
"wagtail.blocks.CharBlock",
(),
{
"default": "Program",
"help_text": "Např. 'Program'",
"label": "Nadpis",
},
),
32: ("wagtail.blocks.IntegerBlock", (), {"label": "Číslo"}),
33: ("wagtail.blocks.CharBlock", (), {"label": "Název"}),
34: ("wagtail.blocks.TextBlock", (), {"label": "Obsah"}),
35: ("wagtail.blocks.StructBlock", [[("content", 34)]], {}),
36: ("wagtail.blocks.ListBlock", (35,), {"label": "Body"}),
37: (
"wagtail.blocks.StructBlock",
[[("number", 32), ("name", 33), ("points", 36)]],
{},
),
38: ("wagtail.blocks.ListBlock", (37,), {"label": "Kategorie"}),
39: (
"wagtail.blocks.URLBlock",
(),
{
"help_text": "Pro zobrazení odkazu na celou verzi programu musí být obě následující pole vyplněná.",
"label": "Odkaz na celou verzi programu",
"required": False,
},
),
40: (
"wagtail.blocks.CharBlock",
(),
{
"label": "Nadpis odkazu na celou verzi programu",
"required": False,
},
),
41: (
"wagtail.blocks.StructBlock",
[
[
("label", 31),
("categories", 38),
("long_version_url", 39),
("long_version_text", 40),
]
],
{},
),
42: ("wagtail.blocks.StructBlock", [[]], {}),
},
verbose_name="Obsah",
),
),
]
# Generated by Django 5.0.7 on 2025-02-24 21:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("district", "0307_alter_districthomepage_content"),
]
operations = [
migrations.AddField(
model_name="districthomepage",
name="has_expanded_navbar",
field=models.BooleanField(
default=True,
help_text="Pokud je toto pole zaškrtlé, navigační lišta bude vždy mít rezervované místo na obrazovce.",
verbose_name="Má rozšířený navbar?",
),
),
]
Source diff could not be displayed: it is too large. Options to address this: view the blob.
Source diff could not be displayed: it is too large. Options to address this: view the blob.
Source diff could not be displayed: it is too large. Options to address this: view the blob.
Source diff could not be displayed: it is too large. Options to address this: view the blob.
Source diff could not be displayed: it is too large. Options to address this: view the blob.
# Generated by Django 5.0.7 on 2025-03-19 07:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("district", "0313_alter_districtarticlepage_content_and_more"),
]
operations = [
migrations.AddField(
model_name="districthomepage",
name="ecomail_newsletter_list_categories",
field=models.CharField(
blank=True,
help_text="Oddělte čárkou, například 'Kategorie1,Kategorie2,Kategorie3'.",
max_length=128,
null=True,
verbose_name="Kategorie k přidání novým odběratelům na Ecomailu",
),
),
migrations.AddField(
model_name="districthomepage",
name="ecomail_newsletter_list_id",
field=models.IntegerField(
blank=True, null=True, verbose_name="ID Ecomail newsletteru"
),
),
migrations.AlterField(
model_name="districthomepage",
name="newsletter_list_id",
field=models.CharField(
blank=True,
help_text="ID newsletteru z Mailtrainu. Po vyplnění se formulář pro odběr newsletteru zobrazí na úvodní stránce a na stránce s kontakty.",
max_length=20,
null=True,
verbose_name="ID Mailtrain newsletteru",
),
),
]
# Generated by Django 5.0.7 on 2025-03-19 08:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"district",
"0314_districthomepage_ecomail_newsletter_list_categories_and_more",
),
]
operations = [
migrations.RemoveField(
model_name="districthomepage",
name="ecomail_newsletter_list_categories",
),
migrations.AddField(
model_name="districthomepage",
name="ecomail_newsletter_list_tags",
field=models.CharField(
blank=True,
help_text="Oddělte čárkou, například 'Tag1,Tag2,Tag3'. Bez mezer.",
max_length=128,
null=True,
verbose_name="Tagy k přidání novým odběratelům na Ecomailu",
),
),
]
# Generated by Django 5.0.7 on 2025-03-19 12:01
import wagtail.fields
from django.db import migrations
import shared.blocks.children.chart
class Migration(migrations.Migration):
dependencies = [
(
"district",
"0315_remove_districthomepage_ecomail_newsletter_list_categories_and_more",
),
]
operations = [
migrations.AlterField(
model_name="districtcustompage",
name="content",
field=wagtail.fields.StreamField(
[
("text", 0),
("advanced_text", 4),
("two_columns_text", 7),
("headline", 12),
("headline_with_picture", 15),
("picture_list", 19),
("flip_cards", 30),
("table", 31),
("popout_table", 32),
("gallery", 35),
("figure", 38),
("card", 63),
("two_columns", 73),
("three_columns", 75),
("youtube", 76),
("map_point", 53),
("map_collection", 60),
("button", 68),
("button_group", 70),
("popout_point", 79),
("chart", 101),
("related", 104),
("related_links", 104),
("badge", 107),
("new_people_group", 116),
("newsletter", 121),
],
blank=True,
block_lookup={
0: (
"wagtail.blocks.RichTextBlock",
(),
{
"features": [
"h2",
"h3",
"h4",
"h5",
"bold",
"italic",
"ol",
"ul",
"hr",
"link",
"document-link",
"image",
"superscript",
"subscript",
"strikethrough",
"blockquote",
"embed",
],
"group": "1. Text",
"label": "Textový editor",
"template": "styleguide2/includes/atoms/text/prose_richtext.html",
},
),
1: (
"wagtail.blocks.ChoiceBlock",
[],
{
"choices": [
("left", "vlevo"),
("center", "uprostřed"),
("right", "vpravo"),
],
"label": "zarovnání",
},
),
2: (
"wagtail.blocks.ChoiceBlock",
[],
{
"choices": [
("black_on_white", "černá na bílé"),
("black_on_yellow", "černá na žluté"),
("white_on_black", "bílá na černé"),
("white_on_blue", "bílá na modré"),
("white_on_cyan", "bílá na tyrkysové"),
("white_on_violet", "bílá na fialové"),
],
"label": "barva",
},
),
3: (
"wagtail.blocks.RichTextBlock",
(),
{
"features": [
"h2",
"h3",
"h4",
"h5",
"bold",
"italic",
"ol",
"ul",
"hr",
"link",
"document-link",
"image",
"superscript",
"subscript",
"strikethrough",
"blockquote",
"embed",
],
"group": "1. Text",
"label": "Textový editor",
},
),
4: (
"wagtail.blocks.StructBlock",
[[("align", 1), ("color", 2), ("text", 3)]],
{},
),
5: (
"wagtail.blocks.RichTextBlock",
(),
{
"features": [
"h2",
"h3",
"h4",
"h5",
"bold",
"italic",
"ol",
"ul",
"hr",
"link",
"document-link",
"image",
"superscript",
"subscript",
"strikethrough",
"blockquote",
"embed",
],
"label": "levý sloupec",
},
),
6: (
"wagtail.blocks.RichTextBlock",
(),
{
"features": [
"h2",
"h3",
"h4",
"h5",
"bold",
"italic",
"ol",
"ul",
"hr",
"link",
"document-link",
"image",
"superscript",
"subscript",
"strikethrough",
"blockquote",
"embed",
],
"label": "pravý sloupec",
},
),
7: (
"wagtail.blocks.StructBlock",
[[("left_text", 5), ("right_text", 6)]],
{},
),
8: (
"wagtail.blocks.CharBlock",
(),
{"label": "Nadpis", "max_length": 300, "required": True},
),
9: (
"wagtail.blocks.ChoiceBlock",
[],
{
"choices": [
("h1", "H1"),
("h2", "H2"),
("h3", "H3"),
("h4", "H4"),
("h5", "H5"),
("h6", "H6"),
],
"help_text": "Čím nižší číslo, tím vyšší úroveň.",
"label": "Úroveň nadpisu",
},
),
10: (
"wagtail.blocks.ChoiceBlock",
[],
{
"choices": [
("head-alt-xl", "Velký, Bebas Neue - 6XL"),
("head-alt-lg", "Střední, Bebas Neue - 4XL"),
("head-alt-md", "Základní velikost - Roboto - MD"),
("head-alt-sm", "Malý - Roboto - SM"),
("head-alt-xs", "Extra malý - Roboto - XS"),
],
"help_text": "Náhled si prohlédněte na https://styleguide2.pirati.cz/pattern/patterns/atoms/text/headings.html.",
"label": "Velikost",
},
),
11: (
"wagtail.blocks.ChoiceBlock",
[],
{
"choices": [
("auto", "Automaticky"),
("center", "Na střed"),
],
"label": "Zarovnání",
},
),
12: (
"wagtail.blocks.StructBlock",
[[("headline", 8), ("tag", 9), ("style", 10), ("align", 11)]],
{},
),
13: ("wagtail.blocks.CharBlock", (), {"label": "nadpis"}),
14: (
"wagtail.images.blocks.ImageChooserBlock",
(),
{
"help_text": "rozměr na výšku 75px nebo více (obrázek bude zmenšen na výšku 75px)",
"label": "obrázek",
},
),
15: (
"wagtail.blocks.StructBlock",
[[("color", 2), ("title", 13), ("picture", 14)]],
{},
),
16: (
"wagtail.blocks.RichTextBlock",
(),
{
"features": [
"h2",
"h3",
"h4",
"h5",
"bold",
"italic",
"ol",
"ul",
"hr",
"link",
"document-link",
"image",
"superscript",
"subscript",
"strikethrough",
"blockquote",
"embed",
],
"label": "Odstavec",
},
),
17: ("wagtail.blocks.ListBlock", (16,), {"label": "Odstavce"}),
18: (
"wagtail.images.blocks.ImageChooserBlock",
(),
{
"help_text": "Rozměr 30x30px nebo více (obrázek bude zmenšen na 30x30px)",
"label": "Obrázek",
},
),
19: (
"wagtail.blocks.StructBlock",
[[("color", 2), ("items", 17), ("picture", 18)]],
{},
),
20: (
"wagtail.blocks.CharBlock",
(),
{
"default": "FEC900",
"help_text": "Kód barvy lze vytvořit např. <a href='https://mdn.github.io/css-examples/tools/color-picker/' target='_blank'>zde</a>.",
"label": "Barva pozadí",
},
),
21: (
"wagtail.images.blocks.ImageChooserBlock",
(),
{
"help_text": "Nahrazuje pozadí. Nelze vybrat obě najednou.",
"label": "Obrázek",
"required": False,
},
),
22: (
"wagtail.images.blocks.ImageChooserBlock",
(),
{
"help_text": "Nahrazuje obrázek. Nelze vybrat obě najednou.",
"label": "Pozadí",
"required": False,
},
),
23: (
"wagtail.blocks.TextBlock",
(),
{"help_text": "Řádkování je manuální.", "label": "Nadpis"},
),
24: (
"wagtail.blocks.CharBlock",
(),
{
"default": "000000",
"help_text": "Kód barvy lze vytvořit např. <a href='https://mdn.github.io/css-examples/tools/color-picker/' target='_blank'>zde</a>.",
"label": "Barva textu",
},
),
25: ("wagtail.blocks.RichTextBlock", (), {"label": "Obsah"}),
26: (
"wagtail.blocks.CharBlock",
(),
{
"help_text": "Pokud není vyplněn, tlačítko se neukáže.",
"label": "Nadpis tlačítka",
"required": False,
},
),
27: (
"wagtail.blocks.CharBlock",
(),
{"label": "Odkaz tlačítka", "required": False},
),
28: (
"wagtail.blocks.StructBlock",
[
[
("bg_color", 20),
("image", 21),
("background", 22),
("title", 23),
("title_color", 24),
("content", 25),
("button_text", 26),
("button_url", 27),
]
],
{"label": "Karta"},
),
29: ("wagtail.blocks.ListBlock", (28,), {"label": "Karty"}),
30: ("wagtail.blocks.StructBlock", [[("cards", 29)]], {}),
31: (
"wagtail.contrib.table_block.blocks.TableBlock",
(),
{
"group": "3. Ostatní",
"label": "Tabulka",
"template": "styleguide2/includes/atoms/table/table.html",
},
),
32: (
"wagtail.contrib.table_block.blocks.TableBlock",
(),
{
"group": "3. Ostatní",
"label": "Rozbalovací tabulka",
"template": "styleguide2/includes/atoms/table/popout_table.html",
},
),
33: (
"wagtail.images.blocks.ImageChooserBlock",
(),
{"label": "obrázek", "required": True},
),
34: (
"wagtail.blocks.ListBlock",
(33,),
{"group": "3. Ostatní", "icon": "image", "label": "Galerie"},
),
35: (
"wagtail.blocks.StructBlock",
[[("gallery_items", 34)]],
{"label": "Galerie"},
),
36: (
"wagtail.images.blocks.ImageChooserBlock",
(),
{"label": "Obrázek", "required": True},
),
37: (
"wagtail.blocks.TextBlock",
(),
{"label": "Popisek", "required": False},
),
38: (
"wagtail.blocks.StructBlock",
[[("img", 36), ("caption", 37)]],
{},
),
39: (
"wagtail.images.blocks.ImageChooserBlock",
(),
{"label": "Obrázek", "required": False},
),
40: (
"wagtail.blocks.TextBlock",
(),
{"label": "Titulek", "required": False},
),
41: (
"wagtail.images.blocks.ImageChooserBlock",
(),
{
"help_text": "Není třeba vyplňovat, náhled bude dohledán automaticky.",
"label": "Náhled videa (automatické pole)",
"required": False,
},
),
42: (
"wagtail.blocks.URLBlock",
(),
{
"help_text": "Odkaz na YouTube video bude automaticky zkonvertován na ID videa a NEBUDE uložen.",
"label": "Odkaz na video",
"required": False,
},
),
43: (
"wagtail.blocks.CharBlock",
(),
{
"help_text": "Není třeba vyplňovat, bude automaticky načteno z odkazu.",
"label": "ID videa (automatické pole)",
"required": False,
},
),
44: (
"wagtail.blocks.BooleanBlock",
(),
{
"default": False,
"help_text": "Pokud toto pole není zaškrtlé, video bude užší než text okolo.",
"label": "Zabírá maximální šířku povolenou pro text?",
"required": True,
},
),
45: (
"wagtail.blocks.BooleanBlock",
(),
{
"default": False,
"help_text": "Automaticky spustí video bez zvuku. Pozor, některé prohlížeče blokují i automatické přehrávání videí bez zvuku.",
"label": "Spustit video automaticky?",
"required": True,
},
),
46: (
"wagtail.blocks.StructBlock",
[
[
("poster_image", 41),
("video_url", 42),
("video_id", 43),
("is_full_width", 44),
("autoplay", 45),
]
],
{},
),
47: (
"wagtail.blocks.DecimalBlock",
(),
{"help_text": "Např. 50.04075", "label": "Zeměpisná šířka"},
),
48: (
"wagtail.blocks.DecimalBlock",
(),
{"help_text": "Např. 15.77659", "label": "Zeměpisná délka"},
),
49: (
"wagtail.blocks.CharBlock",
(),
{
"default": "000000",
"help_text": "Zadejte barvu pomocí HEX notace (bez # na začátku).",
"label": "Barva špendlíku (HEX)",
},
),
50: (
"wagtail.blocks.IntegerBlock",
(),
{
"default": 15,
"label": "Výchozí zoom",
"max_value": 18,
"min_value": 1,
},
),
51: (
"wagtail.blocks.ChoiceBlock",
[],
{
"choices": [
("osm-mapnik", "OSM Mapnik"),
("stadia-osm-bright", "Stadia OSM Bright"),
("stadia-outdoors", "Stadia Outdoors"),
("mapbox-streets", "Mapbox Streets"),
("mapbox-outdoors", "Mapbox Outdoors"),
("mapbox-light", "Mapbox Light"),
("mapbox-dark", "Mapbox Dark"),
("mapbox-satellite", "Mapbox Satellite"),
("mapbox-pirate", "Mapbox Pirate Theme"),
],
"label": "Styl",
},
),
52: (
"wagtail.blocks.IntegerBlock",
(),
{"label": "Výška v px", "max_value": 1000, "min_value": 100},
),
53: (
"wagtail.blocks.StructBlock",
[
[
("lat", 47),
("lon", 48),
("hex_color", 49),
("zoom", 50),
("style", 51),
("height", 52),
]
],
{"label": "Špendlík na mapě"},
),
54: (
"wagtail.blocks.CharBlock",
(),
{"label": "Titulek", "required": True},
),
55: (
"wagtail.blocks.TextBlock",
(),
{
"help_text": "Vložte surový GeoJSON objekt typu 'Feature'. Vyrobit jej můžete např. pomocí online služby geojson.io. Pokud u objektu poskytnete properties 'title' a 'description', zobrazí se jak na mapě, tak i v detailu.",
"label": "Geodata",
"required": True,
},
),
56: (
"wagtail.blocks.URLBlock",
(),
{"label": "Odkaz", "required": False},
),
57: (
"wagtail.blocks.CharBlock",
(),
{
"default": "000000",
"help_text": "Zadejte barvu pomocí HEX notace (bez # na začátku).",
"label": "Barva (HEX)",
},
),
58: (
"wagtail.blocks.StructBlock",
[
[
("title", 54),
("description", 37),
("geojson", 55),
("image", 39),
("link", 56),
("hex_color", 57),
]
],
{"required": True},
),
59: ("wagtail.blocks.ListBlock", (58,), {"label": "Součásti"}),
60: (
"wagtail.blocks.StructBlock",
[
[
("features", 59),
("zoom", 50),
("style", 51),
("height", 52),
]
],
{"label": "Mapová kolekce"},
),
61: (
"wagtail.blocks.StreamBlock",
[
[
("text", 3),
("table", 31),
("figure", 38),
("youtube", 46),
("map_point", 53),
("map_collection", 60),
]
],
{"label": "Obsah", "required": False},
),
62: (
"wagtail.blocks.PageChooserBlock",
(),
{"label": "Stránka", "required": False},
),
63: (
"wagtail.blocks.StructBlock",
[
[
("img", 39),
("headline", 40),
("content", 61),
("page", 62),
("link", 56),
]
],
{},
),
64: (
"wagtail.blocks.CharBlock",
(),
{"label": "Titulek", "max_length": 128, "required": True},
),
65: (
"wagtail.blocks.ChoiceBlock",
[],
{
"choices": [
("black", "Černá"),
("white", "Bílá"),
("pirati-yellow", "Žlutá"),
("grey-125", "Světle šedá"),
("blue-300", "Modrá"),
("cyan-200", "Tyrkysová"),
("green-400", "Zelená"),
("violet-400", "Vínová"),
("red-600", "Červená"),
],
"label": "Barva",
},
),
66: (
"wagtail.blocks.BooleanBlock",
(),
{
"default": True,
"help_text": "Pokud je zapnuto, tlačítko při najetí kurzorem ukáže žlutou šipku.",
"label": "Animovat na hover",
"required": False,
},
),
67: (
"wagtail.blocks.ChoiceBlock",
[],
{
"choices": [
("normal", "Normální"),
("large", "Velká"),
("huge", "Masivní"),
],
"label": "Velikost tlačítka",
},
),
68: (
"wagtail.blocks.StructBlock",
[
[
("title", 64),
("color", 65),
("hoveractive", 66),
("page", 62),
("link", 56),
("align", 11),
("size", 67),
]
],
{},
),
69: ("wagtail.blocks.ListBlock", (68,), {"label": "Tlačítka"}),
70: ("wagtail.blocks.StructBlock", [[("buttons", 69)]], {}),
71: (
"wagtail.blocks.StreamBlock",
[
[
("text", 3),
("table", 31),
("card", 63),
("figure", 38),
("youtube", 46),
("map_point", 53),
("map_collection", 60),
("button", 68),
("button_group", 70),
]
],
{"label": "Obsah levého sloupce", "required": True},
),
72: (
"wagtail.blocks.StreamBlock",
[
[
("text", 3),
("table", 31),
("card", 63),
("figure", 38),
("youtube", 46),
("map_point", 53),
("map_collection", 60),
("button", 68),
("button_group", 70),
]
],
{"label": "Obsah pravého sloupce", "required": True},
),
73: (
"wagtail.blocks.StructBlock",
[[("left_column_content", 71), ("right_column_content", 72)]],
{},
),
74: (
"wagtail.blocks.StreamBlock",
[
[
("text", 3),
("table", 31),
("card", 63),
("figure", 38),
("youtube", 46),
("map_point", 53),
("map_collection", 60),
("button", 68),
("button_group", 70),
]
],
{"label": "Obsah prostředního sloupce", "required": True},
),
75: (
"wagtail.blocks.StructBlock",
[
[
("left_column_content", 71),
("middle_column_content", 74),
("right_column_content", 72),
]
],
{},
),
76: (
"wagtail.blocks.StructBlock",
[
[
("poster_image", 41),
("video_url", 42),
("video_id", 43),
("is_full_width", 44),
("autoplay", 45),
]
],
{"label": "YouTube video"},
),
77: (
"wagtail.blocks.CharBlock",
(),
{"label": "Název", "required": True},
),
78: (
"wagtail.blocks.StreamBlock",
[[("text", 0), ("headline", 12), ("table", 31)]],
{"label": "Obsah"},
),
79: (
"wagtail.blocks.StructBlock",
[[("name", 77), ("content", 78)]],
{},
),
80: (
"wagtail.blocks.CharBlock",
(),
{"label": "Název", "max_length": 120},
),
81: (
"wagtail.blocks.ChoiceBlock",
[],
{
"choices": [
("bar", "Graf se sloupci"),
("horizontalBar", "Graf s vodorovnými sloupci"),
("pie", "Koláčový graf"),
("doughnut", "Donutový graf"),
("polarArea", "Graf polární oblasti"),
("radar", "Radarový graf"),
("line", "Graf s liniemi"),
],
"label": "Typ",
},
),
82: (
"wagtail.blocks.BooleanBlock",
(),
{
"help_text": "Mění vzhled pouze u linových grafů.",
"label": "Schovat body",
"required": False,
},
),
83: (
"wagtail.blocks.CharBlock",
(),
{"label": "Skupina", "max_length": 40},
),
84: (
"wagtail.blocks.ListBlock",
(83,),
{
"blank": True,
"collapsed": True,
"default": [],
"label": "Místně definované skupiny",
"required": False,
},
),
85: (
"wagtail.blocks.CharBlock",
(),
{"label": "Označení zdroje dat", "max_length": 120},
),
86: ("wagtail.blocks.IntegerBlock", (), {}),
87: (
"wagtail.blocks.ListBlock",
(86,),
{"default": [0], "label": "Data"},
),
88: (
"wagtail.blocks.StructBlock",
[[("label", 85), ("data", 87)]],
{},
),
89: (
"wagtail.blocks.ListBlock",
(88,),
{
"blank": True,
"collapsed": True,
"default": [],
"label": "Místní zdroje dat",
"required": False,
},
),
90: (
"wagtail.blocks.MultipleChoiceBlock",
[],
{
"choices": shared.blocks.children.chart.get_redmine_projects,
"label": "Projekty",
},
),
91: (
"wagtail.blocks.BooleanBlock",
(),
{"label": "Jen otevřené", "required": False},
),
92: (
"wagtail.blocks.BooleanBlock",
(),
{"label": "Jen uzavřené", "required": False},
),
93: (
"wagtail.blocks.DateBlock",
(),
{"label": "Min. datum vytvoření", "required": True},
),
94: (
"wagtail.blocks.DateBlock",
(),
{"label": "Max. datum vytvoření", "required": True},
),
95: (
"wagtail.blocks.CharBlock",
(),
{
"help_text": "Např. <=2023-01-01. Více informací na pi2.cz/redmine-api",
"label": "Filtr pro datum aktualizace",
"max_length": 128,
"required": False,
},
),
96: (
"wagtail.blocks.CharBlock",
(),
{
"label": "Označení úkolů uvnitř grafu",
"max_length": 128,
"required": True,
},
),
97: (
"wagtail.blocks.BooleanBlock",
(),
{"label": "Rozdělit podle projektu", "required": False},
),
98: (
"wagtail.blocks.BooleanBlock",
(),
{"label": "Pouze růst nahoru", "required": False},
),
99: (
"wagtail.blocks.StructBlock",
[
[
("projects", 90),
("is_open", 91),
("is_closed", 92),
("created_on_min_date", 93),
("created_on_max_date", 94),
("updated_on", 95),
("issue_label", 96),
("split_per_project", 97),
("only_grow", 98),
]
],
{"label": "Redmine úkoly"},
),
100: (
"wagtail.blocks.ListBlock",
(99,),
{
"blank": True,
"default": [],
"help_text": "Úkoly, podle doby vytvoření. Pokud definuješ více zdrojů, datumy v nich musí být stejné.",
"label": "Zdroje dat z Redmine (úkoly)",
"required": False,
},
),
101: (
"wagtail.blocks.StructBlock",
[
[
("title", 80),
("chart_type", 81),
("hide_points", 82),
("local_labels", 84),
("local_datasets", 89),
("redmine_issue_datasets", 100),
]
],
{},
),
102: (
"wagtail.blocks.PageChooserBlock",
("district.DistrictArticlePage",),
{"label": "Aktualita", "required": True},
),
103: (
"wagtail.blocks.ListBlock",
(102,),
{"label": "Seznam aktualit", "required": True},
),
104: ("wagtail.blocks.StructBlock", [[("articles", 103)]], {}),
105: (
"wagtail.blocks.PageChooserBlock",
(),
{
"label": "Osoba",
"page_type": [
"district.DistrictPersonPage",
"district.DistrictOctopusPersonPage",
"district.DistrictManualOctopusPersonPage",
],
"required": True,
},
),
106: (
"wagtail.blocks.CharBlock",
(),
{
"help_text": "Vlastní popisek na vizitce. Pokud není uvedeno, použije se výchozí profese osoby.",
"label": "Popisek",
"required": False,
},
),
107: (
"wagtail.blocks.StructBlock",
[[("person", 105), ("caption", 106)]],
{},
),
108: ("wagtail.blocks.CharBlock", (), {"label": "Titulek"}),
109: (
"wagtail.blocks.CharBlock",
(),
{
"help_text": "Není třeba vyplňovat, bude automaticky vyplněno",
"label": "Slug skupiny",
"required": False,
},
),
110: (
"wagtail.blocks.PageChooserBlock",
(),
{
"label": "Detail osoby",
"page_type": [
"district.DistrictPersonPage",
"district.DistrictOctopusPersonPage",
"district.DistrictManualOctopusPersonPage",
],
},
),
111: (
"wagtail.blocks.ListBlock",
(110,),
{
"default": [],
"help_text": "S pozicemi z jejich podstránek",
"label": "Osoby",
},
),
112: (
"wagtail.blocks.PageChooserBlock",
(),
{
"label": "Detail osoby",
"page_type": [
"district.DistrictOctopusPersonPage",
"district.DistrictManualOctopusPersonPage",
"district.DistrictPersonPage",
],
},
),
113: (
"wagtail.blocks.CharBlock",
(),
{
"help_text": "Pokud není pozice vyplněná, použije se pozice ze stránky osoby.",
"label": "Pozice",
"required": False,
},
),
114: (
"wagtail.blocks.StructBlock",
[[("page", 112), ("position", 113)]],
{},
),
115: (
"wagtail.blocks.ListBlock",
(114,),
{
"default": [],
"help_text": "S nastavitelnými pozicemi",
"label": "Osoby",
},
),
116: (
"wagtail.blocks.StructBlock",
[
[
("title", 108),
("slug", 109),
("person_list", 111),
("person_list_with_custom_positions", 115),
]
],
{},
),
117: (
"wagtail.blocks.CharBlock",
(),
{
"help_text": "Pokud toto pole zůstane nevyplněné, použije se nastavení tohoto webu. V takovém případě se může přidat i odběratel do Ecomailu.",
"label": "ID newsletteru v Mailtrainu",
"required": False,
},
),
118: (
"wagtail.blocks.CharBlock",
(),
{
"default": "Odebírej náš",
"label": "Nadpis bloku (1. řádek)",
"required": True,
},
),
119: (
"wagtail.blocks.CharBlock",
(),
{
"default": "newsletter",
"label": "Nadpis bloku (2. řádek)",
"required": True,
},
),
120: (
"wagtail.blocks.CharBlock",
(),
{
"default": "Fake news tam nenajdeš, ale dozvíš se, co chystáme doopravdy!",
"label": "Popis newsletteru",
"required": True,
},
),
121: (
"wagtail.blocks.StructBlock",
[
[
("list_id", 117),
("title_line_1", 118),
("title_line_2", 119),
("description", 120),
]
],
{},
),
},
verbose_name="Obsah",
),
),
]
Source diff could not be displayed: it is too large. Options to address this: view the blob.
# Generated by Django 5.0.7 on 2025-03-24 20:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("district", "0317_alter_districtarticlepage_content_and_more"),
]
operations = [
migrations.AddField(
model_name="districthomepage",
name="ecomail_newsletter_list_source",
field=models.CharField(
blank=True,
default="web-nl-generic",
help_text="Není nutno měnit.",
null=True,
verbose_name="Ecomail newsletter zdroj",
),
),
]
# Generated by Django 5.0.7 on 2025-05-08 14:42
from django.db import migrations
def add_district_search_pages(apps, schema_editor):
from district.models import DistrictHomePage, DistrictSearchPage
for home_page in DistrictHomePage.objects.all():
if DistrictSearchPage.objects.descendant_of(home_page).exists():
unlive_page = DistrictSearchPage.objects.descendant_of(home_page).first()
if unlive_page is None:
continue
if not unlive_page.live:
unlive_page.save_revision().publish()
continue
search_page = DistrictSearchPage(
title="Vyhledávací stránka",
slug="search",
)
home_page.add_child(instance=search_page)
search_page.save()
search_page.save_revision().publish()
class Migration(migrations.Migration):
dependencies = [
("district", "0318_districthomepage_ecomail_newsletter_list_source"),
]
operations = [migrations.RunPython(add_district_search_pages)]
# Generated by Django 5.0.7 on 2025-06-04 19:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('district', '0319_auto_20250508_1642'),
]
operations = [
migrations.AlterField(
model_name='districthomepage',
name='important_item_name',
field=models.CharField(blank=True, help_text='Pokud není odkazovaná stránka na Majáku, použij možnost zadání samotné adresy níže.', max_length=32, null=True, verbose_name='Jméno'),
),
]
import json
from functools import cached_property
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.db import models
from django.http import HttpResponseRedirect
from django.utils.safestring import mark_safe
from modelcluster.contrib.taggit import ClusterTaggableManager
from modelcluster.fields import ParentalKey, ParentalManyToManyField
from taggit.models import TaggedItemBase
from wagtail import hooks
from wagtail.admin.panels import (
FieldPanel,
HelpPanel,
InlinePanel,
MultiFieldPanel,
ObjectList,
PageChooserPanel,
TabbedInterface,
)
from wagtail.contrib.routable_page.models import RoutablePageMixin
from wagtail.fields import RichTextField, StreamField
from wagtail.models import Orderable, Page
from wagtail.models.media import Collection
from wagtailmetadata.models import MetadataPageMixin
from calendar_utils.models import CalendarMixin
from maps_utils.blocks import MapPointBlock
from maps_utils.const import (
DEFAULT_MAP_STYLE,
MAP_STYLES,
SUPPORTED_FEATURE_TYPES,
TILE_SERVER_CONFIG,
)
from maps_utils.validation import validators as maps_validators
from shared.blocks import (
DEFAULT_CONTENT_BLOCKS,
ButtonGroupBlock,
CalendarBlock,
CarouselProgramBlock,
ChartBlock,
NewsBlock,
NewsletterSubscriptionBlock,
PeopleOverviewBlock,
ProgramGroupBlock,
SocialLinkBlock,
)
from shared.const import RICH_TEXT_DEFAULT_FEATURES
from shared.models import (
CustomLogoMixin,
EcomailNewsletterMixin,
ExtendedMetadataPageMixin,
MainArticlePageMixin,
MainArticlesPageMixin,
MainContactPageMixin,
MainHomePageMixin,
MainPeoplePageMixin,
MainPersonPageMixin,
MainProgramPageMixin,
MainSearchPageMixin,
MainSimplePageMixin,
PageInMenuMixin,
PdfPageMixin,
SharedTaggedDistrictArticle,
SubpageMixin,
)
from shared.templatetags.shared_filters import markdown
from shared.utils import make_promote_panels, strip_all_html_tags, trim_to_length
from . import blocks
from .forms import DistrictArticlesPageForm
from .tasks import (
import_manual_person,
import_people_from_group,
import_people_from_team,
)
CONTENT_BLOCKS = DEFAULT_CONTENT_BLOCKS + [
("chart", ChartBlock()),
("related", blocks.ArticlesBlock()),
("related_links", blocks.ArticleLinksBlock()),
]
class DistrictHomePage(
CustomLogoMixin, CalendarMixin, EcomailNewsletterMixin, MainHomePageMixin
):
### FIELDS
# Main section
content = StreamField(
[
("fullscreen_header_block", blocks.FullscreenHeaderBlock()),
(
"news_block",
NewsBlock(
template="styleguide2/includes/organisms/articles/district/articles_section.html"
),
),
("elections_block", blocks.ElectionsCountdownBlock()),
(
"people_block",
PeopleOverviewBlock(
template="styleguide2/includes/organisms/main_section/district/representatives_section.html"
),
),
("calendar_block", CalendarBlock()),
("carousel_program", CarouselProgramBlock()),
("newsletter_block", blocks.NewsletterBlock()),
],
verbose_name="Obsah",
blank=True,
use_json_field=True,
)
# Footer
footer_person_list = StreamField(
[("person", blocks.PersonContactBlock())],
verbose_name="Osoby v zápatí webu",
blank=True,
max_num=6,
use_json_field=True,
)
# Settings
newsletter_list_id = models.CharField(
"ID Mailtrain newsletteru",
max_length=20,
blank=True,
null=True,
help_text="ID newsletteru z Mailtrainu. Po vyplnění se formulář pro odběr newsletteru zobrazí na úvodní stránce a na stránce s kontakty.",
)
newsletter_description = models.CharField(
"Popis newsletteru",
max_length=250,
default="Fake news tam nenajdeš, ale dozvíš se, co chystáme doopravdy!",
)
image_collection = models.ForeignKey(
Collection,
verbose_name="Default kolekce obrázků",
help_text=(
"Do této kolekce se budou importovat např. fotky osob "
"importovaných z Chobotnice. Pokud je tato hodnota "
"nevyplněna, použije se běžná 'Root' kolekce."
),
on_delete=models.SET_NULL,
related_name="+",
blank=True,
null=True,
)
calendar_button_text = models.CharField(
"Text tlačítka kalendáře", max_length=256, default="Kalendář"
)
custom_css = models.TextField(
"Vlastní CSS",
help_text=(
"Pokud si rozumíš s CSS a potřebuješ manuálně upravit něco ve vzhledu "
"webu, můžeš do tohoto pole zadat pravidla, která se aplikují napříč "
"celým webem. V opačném případě sem prosím nic nezadávej."
),
blank=True,
null=True,
)
# Extra komentar v paticce (TODO)
footer_extra_content = RichTextField(
verbose_name="Extra obsah na začátku patičky",
blank=True,
features=RICH_TEXT_DEFAULT_FEATURES,
)
### PANELS
menu_panels = [
FieldPanel("title_suffix"),
FieldPanel("meta_title_suffix"),
MultiFieldPanel(
[FieldPanel("dark_logo"), FieldPanel("light_logo")], "Vlastní logo"
),
] + MainHomePageMixin.menu_panels
footer_panels = MainHomePageMixin.footer_panels + [
FieldPanel("footer_extra_content"),
]
settings_panels = [
MultiFieldPanel(
[
FieldPanel("calendar_button_text"),
FieldPanel("calendar_url"),
],
"Kalendář",
),
MultiFieldPanel(
[
FieldPanel("newsletter_description"),
FieldPanel("newsletter_list_id"),
FieldPanel("ecomail_newsletter_list_id"),
FieldPanel("ecomail_newsletter_list_tags"),
FieldPanel("ecomail_newsletter_list_source"),
],
"Formulář pro odběr newsletteru",
),
FieldPanel("matomo_id"),
FieldPanel("image_collection"),
FieldPanel("custom_css"),
FieldPanel("fallback_image"),
]
### EDIT HANDLERS
edit_handler = TabbedInterface(
[
ObjectList(MainHomePageMixin.content_panels, heading="Obsah"),
ObjectList(menu_panels, heading="Hlavička"),
ObjectList(footer_panels, heading="Patička"),
ObjectList(settings_panels, heading="Nastavení"),
ObjectList(MainHomePageMixin.promote_panels, heading="Metadata"),
]
)
### RELATIONS
subpage_types = [
"district.DistrictArticlesPage",
"district.DistrictCenterPage",
"district.DistrictContactPage",
"district.DistrictCrossroadPage",
"district.DistrictCustomPage",
"district.DistrictPeoplePage",
"district.DistrictGeoFeatureCollectionPage",
"district.DistrictCalendarPage",
"district.DistrictPdfPage",
"district.DistrictNewProgramPage",
"district.DistrictSearchPage",
]
### OTHERS
class Meta:
verbose_name = "Oblastní sdružení"
@property
def gdpr_and_cookies_page(self):
from main.models import MainHomePage
return MainHomePage.objects.first().gdpr_and_cookies_page
@property
def articles_page_model(self):
return DistrictArticlesPage
@property
def article_page_model(self):
return DistrictArticlePage
@property
def search_page_model(self):
return DistrictSearchPage
@property
def people_page_model(self):
return DistrictPeoplePage
@property
def contact_page_model(self):
return DistrictContactPage
@property
def calendar_page(self):
return self._first_subpage_of_type(DistrictCalendarPage)
@property
def root_page(self):
return self
@property
def has_calendar(self):
return self.calendar_id is not None
class DistrictArticleTag(TaggedItemBase):
content_object = ParentalKey(
"district.DistrictArticlePage",
on_delete=models.CASCADE,
related_name="tagged_items",
)
class DistrictArticlePage(MainArticlePageMixin):
### FIELDS
author_page = models.ForeignKey(
"district.DistrictPersonPage", on_delete=models.SET_NULL, null=True, blank=True
)
tags = ClusterTaggableManager(through=DistrictArticleTag, blank=True)
shared_tags = ClusterTaggableManager(
verbose_name="Štítky pro sdílení mezi weby",
through=SharedTaggedDistrictArticle,
blank=True,
)
### RELATIONS
parent_page_types = ["district.DistrictArticlesPage"]
subpage_types = ["district.DistrictPdfPage"]
class DistrictArticlesPage(MainArticlesPageMixin):
base_form_class = DistrictArticlesPageForm
displayed_tags = ParentalManyToManyField(
"district.DistrictArticleTag",
verbose_name="Z tohoto oblastního webu",
related_name="+",
blank=True,
)
displayed_shared_tags = ParentalManyToManyField(
"shared.SharedTag",
verbose_name="Sdílecí",
related_name="+",
blank=True,
)
### FIELDS
parent_page_types = ["district.DistrictHomePage"]
subpage_types = ["district.DistrictArticlePage"]
class DistrictContactPage(MainContactPageMixin):
### FIELDS
contact_people = StreamField(
[("item", blocks.PersonContactBlock())],
verbose_name="Kontaktní osoby",
blank=True,
use_json_field=True,
)
### RELATIONS
parent_page_types = ["district.DistrictHomePage"]
subpage_types = []
class PersonPageMixin(Page):
@property
def main_image(self) -> None:
return None
@property
def profile_image(self):
if self.person is None:
return None
return self.person.photo
def get_profile_image(self):
if self.profile_image:
return self.profile_image
return self.root_page.fallback_image
@property
def before_name(self):
if self.person is None:
return None
return self.person.degree_before
@property
def after_name(self):
if self.person is None:
return None
return self.person.degree_after
def get_full_name(self) -> str:
if self.person is None:
return None
full_name = ""
if self.person.degree_before:
full_name += f"{self.person.degree_before} "
full_name += self.title
if self.person.degree_after:
full_name += f", {self.person.degree_after}"
return full_name
@property
def position(self):
if self.person is None:
return None
return self.person.position
@property
def perex(self):
if self.person is None:
return None
return self.person.short_text
@property
def text(self):
if self.person is None:
return None
return markdown(self.person.long_text)
@property
def social_links(self):
if self.person is None:
return None
link_blocks = []
for attr_name, attr_info in {
"facebook_url": {"name": "Facebook", "icon": "ico--facebook"},
"flickr_url": {
"name": "Flickr",
"icon": "ico--flickr",
},
"instagram_url": {"name": "Instagram", "icon": "ico--instagram"},
"mastodon_url": {"name": "Mastodon", "icon": "ico--mastodon"},
"twitter_url": {"name": "X", "icon": "ico--twitter"},
"tiktok_url": {"name": "TikTok", "icon": "ico--globe"},
"web_url": {"name": "Webové stránky", "icon": "ico--globe"},
"youtube_url": {"name": "YouTube", "icon": "ico--youtube"},
}.items():
link = getattr(self.person, attr_name)
if link is None:
continue
link_blocks.append(
SocialLinkBlock().to_python(
{"icon": attr_info["icon"], "text": attr_info["name"], "link": link}
)
)
return link_blocks
@property
def related_people(self):
return []
@property
def email(self):
if self.person is None:
return None
return self.person.email
@property
def phone(self):
if self.person is None:
return None
return self.person.phone
@property
def job(self) -> None:
return None
@property
def city(self) -> None:
return None
@property
def age(self) -> None:
return None
@property
def primary_group(self) -> None:
return None
@property
def is_pirate(self):
return True
@property
def other_party(self) -> None:
return None
@property
def other_party_logo(self) -> None:
return None
class Meta:
abstract = True
class DistrictManualOctopusPersonPage(
ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, PersonPageMixin, Page
):
### FIELDS
username = models.CharField(
verbose_name="Username",
help_text='Uživatelské jméno uživatele v Chobotnici, např. "igor.hnizdo".',
max_length=128,
)
person = models.ForeignKey(
"shared.OctopusPerson",
# Wagtail doesn't support CASCADE, so we'll just deal with that somewhere else.
on_delete=models.SET_NULL,
blank=True,
null=True,
related_name="+",
verbose_name="Osoba",
)
### PANELS
content_panels = Page.content_panels + [FieldPanel("username")]
### RELATIONS
parent_page_types = ["district.DistrictPeoplePage"]
subpage_types = []
### OTHERS
class Meta:
verbose_name = "Osoba z Chobotnice"
class DistrictOctopusPersonPage(
ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, PersonPageMixin, Page
):
### FIELDS
person = models.ForeignKey(
"shared.OctopusPerson",
# Wagtail doesn't support CASCADE, so we'll just deal with that somewhere else.
on_delete=models.SET_NULL,
blank=False,
null=True,
related_name="+",
verbose_name="Osoba",
)
is_automatically_created = models.BooleanField(
verbose_name="Profil vytvořen automaticky",
default=False,
)
originating_groups = models.ManyToManyField(
"shared.OctopusPersonOriginatingGroup",
verbose_name="Skupiny",
help_text="Skupiny, ze kterých byla tato osba importována.",
)
originating_teams = models.ManyToManyField(
"shared.OctopusPersonOriginatingTeam",
verbose_name="Tým",
help_text="Týmy, ze kterých byla tato osba importována.",
)
originating_role = models.CharField(
verbose_name="Role",
help_text="Požadovaná role v týmu, ze kterého byla tato osba importována.",
max_length=128,
blank=True,
null=True,
)
originating_display = models.CharField(
verbose_name="Název týmu/skupiny",
help_text="Název týmu nebo skupiny, ze kterých byla tato osba importována.",
max_length=128,
blank=True,
null=True,
)
### PANELS
content_panels = Page.content_panels + [
FieldPanel("person"),
FieldPanel("is_automatically_created", read_only=True),
FieldPanel("originating_groups", read_only=True),
FieldPanel("originating_teams", read_only=True),
]
### RELATIONS
parent_page_types = ["district.DistrictPeoplePage"]
subpage_types = []
### OTHERS
@property
def primary_group(self) -> None:
return self.originating_display
class Meta:
verbose_name = "Osoba z Chobotnice"
class DistrictPersonPage(MainPersonPageMixin):
### FIELDS
job = models.CharField(
"Povolání",
max_length=128,
blank=True,
null=True,
help_text="Např. 'Informatik'",
)
city = models.CharField("Město/obec", max_length=64, blank=True, null=True)
age = models.IntegerField("Věk", blank=True, null=True)
is_pirate = models.BooleanField("Je členem Pirátské strany?", default=True)
other_party = models.CharField(
"Strana",
max_length=64,
blank=True,
null=True,
help_text="Vyplňte pokud osoba není Pirát",
)
other_party_logo = models.ForeignKey(
"wagtailimages.Image",
on_delete=models.PROTECT,
blank=True,
null=True,
related_name="+",
verbose_name="Logo strany",
help_text="Vyplňte pokud osoba není Pirát",
)
### PANELS
content_panels = Page.content_panels + [
MultiFieldPanel(
[
FieldPanel("main_image"),
FieldPanel("profile_image"),
],
heading="Obrázky",
),
MultiFieldPanel(
[FieldPanel("before_name"), FieldPanel("after_name")],
heading="Titul",
),
MultiFieldPanel(
[
FieldPanel("is_pirate"),
FieldPanel("other_party"),
FieldPanel("other_party_logo"),
],
heading="Politická příslušnost",
),
MultiFieldPanel(
[
FieldPanel("position"),
FieldPanel("job"),
FieldPanel("city"),
FieldPanel("age"),
],
heading="Základní informace",
),
MultiFieldPanel([FieldPanel("perex"), FieldPanel("text")], heading="Popis"),
MultiFieldPanel(
[
FieldPanel("email"),
FieldPanel("phone"),
FieldPanel("social_links"),
],
heading="Kontakt",
),
FieldPanel("calendar_url"),
FieldPanel("related_people"),
]
### RELATIONS
parent_page_types = ["district.DistrictPeoplePage"]
subpage_types = []
class DistrictPeoplePage(MainPeoplePageMixin):
### FIELDS
content = StreamField(
[
("octopus_group", blocks.OctopusGroupBlock(label="Skupina z Chobotnice")),
("octopus_team", blocks.OctopusTeamBlock(label="Tým z Chobotnice")),
("people_group", blocks.PeopleGroupBlock(label="Seznam osob", group="")),
("team_group", blocks.TeamBlock()),
],
verbose_name="Lidé a týmy",
blank=True,
use_json_field=True,
)
### RELATIONS
parent_page_types = ["district.DistrictHomePage"]
subpage_types = [
"district.DistrictPersonPage",
"district.DistrictOctopusPersonPage",
"district.DistrictManualOctopusPersonPage",
]
### PANELS
content_panels = Page.content_panels + [
MultiFieldPanel(
[
FieldPanel("perex_col_1", heading="První sloupec"),
FieldPanel("perex_col_2", heading="Druhý sloupec"),
],
"Perex",
),
MultiFieldPanel(
[
HelpPanel(
content=mark_safe(
"Pokud chceš importovat osoby z Chobotnice, můžeš přidat do sekce "
"<em>Lidé a týmy</em> blok <em>Skupina z Chobotnice</em> nebo <em>"
"Tým z Chobotnice</em>. Jako zkratku skupiny můžeš vyplnit "
"například <em>cen_ao_ved</em> pro skupinu "
"<a href='https://pi2.cz/chobo-ao'>Administrativní odbor - vedení</a>. "
"Jako zkratku týmu můžeš vyplnit např. <em>TO-admin</em> pro tým "
"<a href='https://pi2.cz/chobo-to'>Administrátoři TO</a>. Při sync. "
"týmů se do pozice osoby propíše role v daném týmu.<br><br>"
"Dále je také možné manuálně importovat jednotlivé osoby přidáním "
"podstránky typu <em>Osoba z Chobotnice</em>."
),
),
FieldPanel("content"),
],
"Obsah",
),
]
edit_handler = TabbedInterface(
[
ObjectList(content_panels, heading="Obsah"),
ObjectList(MainPeoplePageMixin.promote_panels, heading="Metadata"),
]
)
### OTHERS
@classmethod
def allowed_subpage_models(cls):
# Get all page types and remove the unwanted child page type
allowed_pages = super().allowed_subpage_models()
return [
page
for page in allowed_pages
if page.__name__ != "DistrictOctopusPersonPage"
]
def get_syncable_octopus_groups(self):
groups = []
for block in self.content:
if block.block_type == "octopus_group":
groups.append(
{
"shortcut": block.value["group_shortcut"],
"title": block.value["title"],
}
)
# Remove duplicates by converting to a set of tuples and back to a list of dicts
unique_groups = [dict(g) for g in {tuple(group.items()) for group in groups}]
return unique_groups
def get_syncable_octopus_teams(self):
teams = []
for block in self.content:
if block.block_type == "octopus_team":
teams.append(
{
"shortcut": block.value["team_shortcut"],
"roles": block.value["roles"],
"title": block.value["title"],
}
)
# Remove duplicates by converting to a set of tuples and back to a list of dicts
unique_teams = [dict(t) for t in {tuple(team.items()) for team in teams}]
# Since lists can't be hashed and our unique-ification function won't work
# with them around, convert roles to lists only after we know the teams
# are unique.
for position, team in enumerate(unique_teams):
if team["roles"] is None:
continue
unique_teams[position]["roles"] = (
team["roles"].split(",") if team["roles"] else None
)
return unique_teams
class DistrictCalendarPage(SubpageMixin, MetadataPageMixin, CalendarMixin, Page):
### PANELS
content_panels = Page.content_panels + [FieldPanel("calendar_url")]
### RELATIONS
parent_page_types = [
"district.DistrictCenterPage",
"district.DistrictHomePage",
]
subpage_types = []
### OTHERS
class Meta:
verbose_name = "Stránka s kalendářem"
class DistrictPdfPage(PdfPageMixin, MetadataPageMixin, SubpageMixin, Page):
### RELATIONS
parent_page_types = [
"district.DistrictHomePage",
"district.DistrictArticlePage",
]
subpage_types = []
### PANELS
content_panels = Page.content_panels + PdfPageMixin.content_panels
### OTHER
class Meta:
verbose_name = "PDF stránka"
class DistrictCenterPage(
CalendarMixin,
ExtendedMetadataPageMixin,
SubpageMixin,
MetadataPageMixin,
PageInMenuMixin,
Page,
):
### FIELDS
calendar_page = models.ForeignKey(
"DistrictCalendarPage",
verbose_name="Stránka s kalendářem",
on_delete=models.PROTECT,
null=True,
blank=True,
)
perex = models.TextField("Perex", blank=True, null=True)
background_photo = models.ForeignKey(
"wagtailimages.Image",
on_delete=models.PROTECT,
blank=True,
null=True,
related_name="+",
)
content = StreamField(
CONTENT_BLOCKS
+ [
("badge_list", blocks.PersonBadgeListBlock()),
("badge", blocks.PersonBadgeBlock()),
("contact", blocks.CenterContactBlock()),
("badge", blocks.PersonBadgeBlock()),
],
verbose_name="Obsah",
blank=True,
use_json_field=True,
)
map_address_content = StreamField(
[
("address", blocks.AddressBlock()),
],
verbose_name="Adresa u mapy",
blank=True,
use_json_field=True,
)
map_area_content = StreamField(
[
("map", MapPointBlock()),
],
verbose_name="Mapa",
blank=True,
use_json_field=True,
)
### PANELS
content_panels = Page.content_panels + [
MultiFieldPanel(
[
FieldPanel("perex"),
PageChooserPanel("background_photo"),
],
"Hlavička",
),
MultiFieldPanel(
[
FieldPanel("map_address_content"),
PageChooserPanel("map_area_content"),
],
"Obsah vyskakovacího bloku s mapou",
),
FieldPanel("content"),
MultiFieldPanel(
[
FieldPanel("calendar_url"),
PageChooserPanel("calendar_page"),
],
"Kalendář",
),
]
promote_panels = make_promote_panels()
settings_panels = []
### RELATIONS
parent_page_types = ["district.DistrictHomePage"]
subpage_types = ["district.DistrictCalendarPage"]
### OTHERS
class Meta:
verbose_name = "Stránka pirátského centra"
@property
def has_calendar(self):
return self.calendar_id is not None
def get_meta_image(self):
return (
self.search_image
or self.background_photo
or self.root_page.get_meta_image()
)
def get_meta_description(self):
if self.search_description:
return self.search_description
if hasattr(self, "perex") and self.perex:
self.perex
elif hasattr(self, "text") and self.text:
trim_to_length(strip_all_html_tags(self.text))
return ""
class DistrictNewProgramPage(MainProgramPageMixin):
### FIELDS
program = StreamField(
[
("program_group", ProgramGroupBlock()),
("program_group_crossroad", blocks.ProgramGroupBlockCrossroad()),
("program_group_popout", blocks.ProgramGroupBlockPopout()),
("program_group_with_candidates", blocks.ProgramGroupWithCandidatesBlock()),
],
verbose_name="Programy",
blank=True,
use_json_field=True,
)
header_image = models.ForeignKey(
"wagtailimages.Image",
on_delete=models.PROTECT,
blank=True,
null=True,
verbose_name="Obrázek na pozadí hlavičky",
related_name="+",
)
### RELATIONS
parent_page_types = ["district.DistrictHomePage"]
subpage_types = ["district.DistrictCustomPage"]
### PANELS
settings_panels = [FieldPanel("header_image")]
edit_handler = TabbedInterface(
[
ObjectList(MainProgramPageMixin.content_panels, heading="Obsah"),
ObjectList(settings_panels, heading="Nastavení"),
ObjectList(MainProgramPageMixin.promote_panels, heading="Metadata"),
]
)
class DistrictCrossroadPage(
ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, PageInMenuMixin, Page
):
### FIELDS
cards_content = StreamField(
[("cards", blocks.CardLinkWithHeadlineBlock())],
verbose_name="Karty rozcestníku",
blank=True,
use_json_field=True,
)
content = StreamField(
CONTENT_BLOCKS
+ [
("badge", blocks.PersonBadgeBlock()),
("new_people_group", blocks.PeopleGroupBlock()),
],
verbose_name="Obsah stránky",
blank=True,
use_json_field=True,
)
### PANELS
content_panels = Page.content_panels + [
FieldPanel("cards_content"),
FieldPanel("content"),
]
promote_panels = make_promote_panels()
settings_panels = []
### RELATIONS
parent_page_types = ["district.DistrictHomePage"]
subpage_types = [
"district.DistrictArticlePage",
"district.DistrictArticlesPage",
"district.DistrictCenterPage",
"district.DistrictContactPage",
"district.DistrictCrossroadPage",
"district.DistrictCustomPage",
"district.DistrictGeoFeatureCollectionPage",
"district.DistrictPeoplePage",
"district.DistrictPersonPage",
]
### OTHERS
class Meta:
verbose_name = "Rozcestník s kartami"
class DistrictSearchPage(MainSearchPageMixin):
### RELATIONS
parent_page_types = ["district.DistrictHomePage"]
### OTHERS
@property
def searchable_models(self) -> list:
return [
DistrictArticlePage,
DistrictCustomPage,
DistrictCrossroadPage,
DistrictNewProgramPage,
DistrictCenterPage,
DistrictContactPage,
]
class DistrictCustomPage(RoutablePageMixin, MainSimplePageMixin):
### FIELDS
main_image = models.ForeignKey(
"wagtailimages.Image",
on_delete=models.PROTECT,
blank=True,
null=True,
verbose_name="Obrázek na pozadí hlavičky",
related_name="+",
)
content = StreamField(
CONTENT_BLOCKS
+ [
("badge", blocks.PersonBadgeBlock()),
("new_people_group", blocks.PeopleGroupBlock()),
("newsletter", NewsletterSubscriptionBlock()),
],
verbose_name="Obsah",
blank=True,
use_json_field=True,
)
### PANELS
content_panels = Page.content_panels + [
FieldPanel("main_image"),
FieldPanel("content"),
]
### RELATIONS
parent_page_types = ["district.DistrictHomePage", "district.DistrictCrossroadPage"]
subpage_types = []
### OTHERS
@cached_property
def newsletter_subscribe_url(self):
newsletter_subscribe = self.root_page.reverse_subpage("newsletter_subscribe")
return (
self.root_page.url + newsletter_subscribe
if self.root_page.url is not None
else newsletter_subscribe
) # preview fix
@property
def newsletter_list_id(self) -> str:
for block in self.content:
if block.block_type == "newsletter":
return block.value["list_id"]
return self.root_page.newsletter_list_id
class DistrictGeoFeatureCollectionPage(
ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, Page
):
### FIELDS
perex = models.TextField("Perex", null=True)
hero_cta_buttons = StreamField(
[
("button_group", ButtonGroupBlock()),
],
verbose_name="CTAs pro banner",
blank=True,
null=True,
help_text="Použije se v hlavním banneru.",
use_json_field=True,
)
content = StreamField(
CONTENT_BLOCKS
+ [
("badge", blocks.PersonBadgeBlock()),
("new_people_group", blocks.PeopleGroupBlock()),
],
verbose_name="Obsah úvodní",
blank=True,
use_json_field=True,
)
content_after = StreamField(
CONTENT_BLOCKS
+ [
("badge", blocks.PersonBadgeBlock()),
("new_people_group", blocks.PeopleGroupBlock()),
],
verbose_name="Obsah za mapou",
blank=True,
use_json_field=True,
)
content_footer = StreamField(
CONTENT_BLOCKS
+ [
("badge", blocks.PersonBadgeBlock()),
("new_people_group", blocks.PeopleGroupBlock()),
],
verbose_name="Obsah v patičkové části",
blank=True,
use_json_field=True,
)
image = models.ForeignKey(
"wagtailimages.Image",
on_delete=models.PROTECT,
blank=True,
null=True,
verbose_name="Obrázek na pozadí",
related_name="+",
)
logo_image = models.ForeignKey(
"wagtailimages.Image",
on_delete=models.PROTECT,
blank=True,
null=True,
verbose_name="Logo",
related_name="+",
)
style = models.CharField(
"Styl mapy", choices=MAP_STYLES, max_length=50, default=DEFAULT_MAP_STYLE
)
map_title = models.TextField("Titulek mapy", blank=True, null=True)
category_list_title = models.TextField(
"Titulek přehledu dle kategorie", blank=True, null=True
)
promoted_block_title = models.TextField(
"Titulek bloku propagovaných položek", blank=True, null=True
)
### PANELS
content_panels = Page.content_panels + [
MultiFieldPanel(
[
FieldPanel("perex"),
FieldPanel("hero_cta_buttons"),
FieldPanel("content"),
FieldPanel("content_after"),
FieldPanel("content_footer"),
FieldPanel("promoted_block_title"),
FieldPanel("logo_image"),
FieldPanel("image"),
],
"Obsah hlavní stránky kolekce",
),
MultiFieldPanel(
[
InlinePanel("categories"),
FieldPanel("category_list_title"),
],
"Kategorie",
),
MultiFieldPanel(
[
FieldPanel("map_title"),
FieldPanel("style"),
],
"Nastavení mapy",
),
]
settings_panels = []
### RELATIONS
parent_page_types = [
"district.DistrictHomePage",
]
subpage_types = ["district.DistrictGeoFeatureDetailPage"]
class Meta:
verbose_name = "Stránka s mapovou kolekcí"
def get_features_by_category(self):
features = (
self.get_children()
.live()
.specific()
.prefetch_related("category")
.order_by("districtgeofeaturedetailpage__sort_order")
)
categories = sorted(set(f.category for f in features), key=lambda c: c.name)
return [
(category, [f for f in features if f.category == category])
for category in categories
]
def get_promoted_features(self):
return (
self.get_children()
.live()
.specific()
.filter(districtgeofeaturedetailpage__promoted=True)
.order_by("districtgeofeaturedetailpage__sort_order")
)
def get_context(self, request):
context = super().get_context(request)
features_by_category = self.get_features_by_category()
context["features_by_category"] = features_by_category
context["promoted_features"] = self.get_promoted_features()
context["js_map"] = {
"tile_server_config": json.dumps(TILE_SERVER_CONFIG),
"style": self.style,
"categories": json.dumps(
[
# Gather all categories used in collection
{"name": c.name, "color": c.hex_color}
for c, f in features_by_category
]
),
"geojson": json.dumps(
[
f.as_geojson_object(request)
for c, features in features_by_category
for f in features
]
),
}
return context
def get_meta_image(self):
return (
self.search_image
or self.logo_image
or self.image
or self.root_page.get_meta_image()
)
def get_meta_description(self):
if self.search_description:
return self.search_description
return self.perex
class DistrictGeoFeatureCollectionCategory(Orderable):
name = models.CharField("Název", max_length=100)
hex_color = models.CharField(
"Barva (HEX)",
max_length=6,
help_text="Zadejte barvu pomocí HEX notace (bez # na začátku).",
)
page = ParentalKey(
DistrictGeoFeatureCollectionPage,
on_delete=models.CASCADE,
related_name="categories",
)
### PANELS
panels = [
FieldPanel("name"),
FieldPanel("hex_color"),
]
class Meta:
verbose_name = "Kategorie mapové kolekce"
def __str__(self) -> str:
return f"{self.page} / {self.name}"
@property
def rgb(self):
return tuple(int(self.hex_color[i : i + 2], 16) for i in (0, 2, 4))
def make_feature_index_cache_key(feature: "DistrictGeoFeatureDetailPage"):
return f"DistrictGeoFeatureDetailPage::{feature.id}::index"
class DistrictGeoFeatureDetailPage(
ExtendedMetadataPageMixin, MetadataPageMixin, SubpageMixin, Page, Orderable
):
perex = models.TextField("Perex", null=False)
geojson = models.TextField(
"Geodata",
help_text="Vložte surový GeoJSON objekt typu 'FeatureCollection'. Vyrobit jej můžete např. pomocí online služby geojson.io. Pokud u Feature objektů v kolekci poskytnete properties 'title' a 'description', zobrazí se jak na mapě, tak i v detailu.",
null=False,
)
category = models.ForeignKey(
"district.DistrictGeoFeatureCollectionCategory",
verbose_name="Kategorie",
blank=False,
null=False,
on_delete=models.PROTECT,
)
guarantor = models.ForeignKey(
"district.DistrictPersonPage",
verbose_name="Garant",
on_delete=models.PROTECT,
null=True,
blank=True,
)
image = models.ForeignKey(
"wagtailimages.Image",
on_delete=models.PROTECT,
blank=True,
null=True,
verbose_name="obrázek",
)
content = StreamField(
CONTENT_BLOCKS
+ [
("badge", blocks.PersonBadgeBlock()),
("new_people_group", blocks.PeopleGroupBlock()),
],
verbose_name="Obsah",
blank=True,
use_json_field=True,
)
parts_section_title = models.CharField(
"Titulek přehledu součástí",
help_text="Pokud ponecháte prázdné, nebude se titulek zobrazovat. Sekce se vůbec nezobrazí pokud GeoJSON FeatureCollection obsahuje jen jeden Feature.",
max_length=100,
null=True,
blank=True,
)
initial_zoom = models.IntegerField(
"Výchozí zoom",
default=15,
null=False,
blank=False,
)
promoted = models.BooleanField(
"Propagovat",
default=False,
)
sort_order = models.IntegerField(
"Index řazení",
null=True,
blank=True,
help_text="Čím větší hodnotu zadáte, tím později ve výpise položek bude. Můžete tím tedy ovlivňovat očíslování "
"položky ve výpisu.",
default=0,
)
sort_order_field = "sort_order"
### PANELS
content_panels = [
MultiFieldPanel(
[
FieldPanel("title"),
FieldPanel("perex"),
FieldPanel("content"),
FieldPanel("parts_section_title"),
FieldPanel("image"),
FieldPanel("category"),
FieldPanel("promoted"),
],
"Základní informace",
),
MultiFieldPanel(
[
FieldPanel("geojson"),
FieldPanel("initial_zoom"),
],
"Mapka",
),
PageChooserPanel("guarantor"),
FieldPanel("sort_order"),
]
settings_panels = []
### RELATIONS
parent_page_types = ["district.DistrictGeoFeatureCollectionPage"]
subpage_types = []
class Meta:
verbose_name = "Položka mapové kolekce"
ordering = ["sort_order"]
def save(self, *args, **kwargs):
# delete all sibling index cache keys to force recompute
keys = [
make_feature_index_cache_key(feature)
for feature in self.get_siblings(inclusive=True).live()
]
cache.delete_many(keys)
return super().save(*args, **kwargs)
@property
def index(self):
key = make_feature_index_cache_key(self)
cached_index = cache.get(key)
if cached_index is None:
cached_index = (
list(
self.get_siblings(inclusive=True)
.live()
.order_by("districtgeofeaturedetailpage__sort_order")
.values_list("pk", flat=True)
).index(self.pk)
+ 1
)
cache.set(key, cached_index)
return cached_index
def get_meta_image(self):
return self.search_image or self.image
def get_meta_description(self):
if self.search_description:
return self.search_description
return self.perex
def as_geojson_object(self, request):
collection = json.loads(self.geojson)
image = (
self.image.get_rendition("fill-1200x675-c75|jpegquality-80").url
if self.image
else None
)
url = self.get_url(request) if self.content else None
for idx, feature in enumerate(collection["features"]):
feature["properties"].update(
{
"id": self.pk,
"index": self.index,
"slug": f"{idx}-{self.pk}-{self.slug}",
"collectionTitle": self.title,
"collectionDescription": self.perex,
"image": image,
"link": url,
"category": self.category.name,
}
)
if not "description" in feature["properties"]:
feature["properties"]["description"] = None
# This extends GeoJSON spec to pass down more context to the map.
collection["properties"] = {}
collection["properties"]["slug"] = f"{self.pk}-{self.slug}"
collection["properties"]["collectionTitle"] = self.title
collection["properties"]["collectionDescription"] = self.perex
collection["properties"]["category"] = self.category.name
collection["properties"]["index"] = self.index
collection["properties"]["image"] = image
collection["properties"]["link"] = url
return collection
def get_context(self, request):
context = super().get_context(request)
context[
"features_by_category"
] = self.get_parent().specific.get_features_by_category()
feature_collection = self.as_geojson_object(request)
context["js_map"] = {
"tile_server_config": json.dumps(TILE_SERVER_CONFIG),
"style": self.get_parent().specific.style,
"categories": json.dumps(
[{"name": self.category.name, "color": self.category.hex_color}]
),
"geojson": json.dumps([feature_collection]),
}
context["parts"] = [f["properties"] for f in feature_collection["features"]]
return context
def clean(self):
try:
self.geojson = maps_validators.normalize_geojson_feature_collection(
self.geojson, allowed_types=SUPPORTED_FEATURE_TYPES
)
print(self.geojson)
except ValueError as exc:
print(exc)
raise ValidationError({"geojson": str(exc)}) from exc
# Hooks - DistrictManualOctopusPersonPage
@hooks.register("after_create_page")
@hooks.register("after_edit_page")
def after_create_edit_page_hook(request, page):
if isinstance(page, DistrictManualOctopusPersonPage):
collection_id = page.root_page.image_collection_id
if collection_id is None:
collection_id = Collection.objects.first().id
import_manual_person.delay(
page.id,
collection_id,
)
if isinstance(page, DistrictPeoplePage):
collection_id = page.root_page.image_collection_id
if collection_id is None:
collection_id = Collection.objects.first().id
for group in page.get_syncable_octopus_groups():
import_people_from_group.delay(
page.id,
collection_id,
group["shortcut"],
group["title"],
)
for team in page.get_syncable_octopus_teams():
import_people_from_team.delay(
page.id, collection_id, team["shortcut"], team["title"], team["roles"]
)
# Legacy models required for migrations
class LegacyProgramPageMixin(Page):
def serve(self, request, *args, **kwargs):
return HttpResponseRedirect("/programy/")
class Meta:
abstract = True
/* Main page */
#content {
width: 100%;
}
/* Wrappers */
.main-wrapper {
background: url("../svg/background.svg");
background-size: auto;
background-size: 100%;
background-position: top;
padding: 220px 200px 0 200px;
margin-bottom: 100px;
background-repeat: no-repeat;
}
.content-wrapper {
display: flex;
flex-wrap: wrap;
gap: 10px;
box-shadow: 0px 0px 33px 0px rgba(0, 0, 0, 0.25);
background: rgba(0, 0, 0, 0.125);
}
.topic-wrapper {
display: flex;
gap: 7.5px;
align-items: flex-start;
flex-wrap: wrap;
width: 345px;
flex-basis: 345px;
}
.topic-content-wrapper {
flex-grow: 1;
flex-basis: 0;
background: #f7f7f7;
padding: 10px;
max-height: 463px;
overflow-y: auto;
}
.buttons-wrapper {
margin-top: 10px;
text-align: center;
}
/* Base topics */
.topic {
text-align: center;
flex-basis: 100px;
width: 110px;
height: 110px;
flex-basis: 110px;
cursor: pointer;
transition: .15s;
padding: 5px;
background: #fff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.topic:hover {
background: #c8c8c8;
}
.topic svg {
height: 50px;
width: 50px;
fill: #000;
}
.topic label {
font-family: "Bebas Neue", sans-serif;
pointer-events: none;
font-size: 23px;
line-height: 25px;
}
/* Selected topics */
.topic.topic-selected {
background: #000;
}
.topic.topic-selected label {
color: #fff;
}
.topic.topic-selected svg {
fill: #fff;
}
/* Topic content */
.topic-content {
display: none;
}
/* Bottom buttons */
.bottom-button {
font-family: "Bebas Neue";
color: #fff;
background-image: url("../svg/button-background.svg");
background-repeat: no-repeat;
background-size: cover;
padding: 10px;
display: inline-block;
text-align: center;
font-size: 27px;
line-height: 30px;
text-decoration: none;
transition: .15s;
position: relative;
}
.bottom-button:hover, .bottom-button:focus {
color: #b0b0b0;
}
.bottom-button:not(:last-child) {
margin-right: 12px;
}
/* Topic content styling */
.topic-content-wrapper h2, .topic-content-wrapper h3 {
font-family: "Bebas Neue";
text-transform: uppercase;
}
.topic-content-wrapper h2 {
font-size: 1.9rem;
}
.topic-content-wrapper h3 {
font-size: 1.6rem;
}
/* Special button images */
#button-odebirej-novinky::before,
#button-pridej-se-k-nam::before,
#button-prihod-do-truhly::before {
background-size: cover;
position: absolute;
content: "";
top: -75px;
left: 0;
height: 75px;
width: 100%;
z-index: -1;
}
#button-odebirej-novinky::before { background-image: url("../svg/bottle.svg"); }
#button-pridej-se-k-nam::before { background-image: url("../svg/anchor.svg"); }
#button-prihod-do-truhly::before {
background-image: url("../svg/finance.svg");
background-size: 70%;
background-repeat: no-repeat;
background-position-x: center;
background-position-y: top;
height: 65px;
top: -65px;
}
/* Program points */
.program-header-wrapper {
margin-bottom: 8px;
}
.program-point-wrapper {
display: flex;
gap: 4px;
margin-bottom: 4px;
}
.program-point-content-wrapper {
flex-basis: 100%;
}
.program-point-button-wrapper {
flex-basis: 25px;
}
.program-point-like, .program-point-share {
width: 25px;
height: 25px;
display: inline-block;
fill: #c2c2c2;
transition: .15s;
cursor: pointer;
}
.program-point-like:hover, .program-point-like:focus, .program-point-like-used {
fill: #fb4f6f;
}
.program-point-share:hover, .program-point-share:focus {
fill: #000;
}
/* Sharer */
#share-form h2 {
font-family: 'Bebas Neue';
font-size: 25px;
margin-bottom: 7px;
}
#share-text {
width: 100%;
}
.share-icon-wrapper {
display: flex;
gap: 5px;
justify-content: center;
margin-top: 7px;
}
/* Mobile layout */
@media only screen and (max-width: 1120px) {
.main-wrapper {
background-image: url("../svg/background-mobile.svg");
padding: 10px;
background-size: 80%;
padding-top: 18%;
}
}
@media only screen and (max-width: 800px) {
.main-wrapper {
background-image: url("../svg/background-mobile.svg");
padding: 10px;
background-size: 80%;
padding-top: 18%;
}
.content-wrapper {
flex-direction: column;
justify-content: center;
}
.topic-wrapper {
align-items: stretch;
width: 100%;
flex-basis: 100%;
}
.topic {
flex-basis: calc(33.3% - 5px);
}
.topic-content-wrapper {
height: 463px;
flex-basis: 463px;
}
}
/* No-JS program */
#no-js-program {
margin-bottom: 110px;
}
#no-js-program h2, h3, h4 {
font-family: "Bebas Neue", sans-serif;
text-transform: uppercase;
}
#no-js-program h2 {
font-size: 35px;
margin-top: 10px;
margin-bottom: 10px;
}
#no-js-program h3 {
font-size: 25px;
margin-bottom: 6px;
margin-bottom: 6px;
}
#no-js-program h4 {
font-size: 20px;
margin-bottom: 4px;
margin-bottom: 4px;
}
#no-js-program p, ul {
margin-bottom: 6px;
}
div:not(:first-child) > .c-sf-add-button[title="Insert a block"][aria-expanded="false"] {
margin-top: 50px!important;
margin-bottom: 50px!important;
}