diff --git a/district/blocks.py b/district/blocks.py index a306911e55a737c634a24d99de48217a1692c636..1943afcf55b95ee3d172df085f9e8fb443791b9c 100644 --- a/district/blocks.py +++ b/district/blocks.py @@ -12,8 +12,8 @@ from wagtail.blocks import ( TextBlock, URLBlock, ) +from django.template.defaultfilters import slugify from wagtail.images.blocks import ImageChooserBlock - from shared.blocks import CandidateBlock as SharedCandidateBlockMixin from shared.blocks import CandidateListBlock as SharedCandidateListBlockMixin from shared.blocks import ( @@ -127,6 +127,39 @@ class PersonCustomPositionBlock(PersonCustomPositionBlockMixin): ) +class OctopusGroupBlock(blocks.StructBlock): + title = CharBlock(label="Titulek", required=True) + + slug = blocks.CharBlock( + label="Slug skupiny", + required=False, + help_text="Není třeba vyplňovat, bude automaticky vyplněno", + ) + + group_shortcut = CharBlock(label="Zkratka skupiny", required=True) + + def get_prep_value(self, value): + value = super().get_prep_value(value) + value["slug"] = slugify(value["title"]) + return value + + def get_context(self, value, *args, **kwargs): + from .models import DistrictOctopusPersonPage + + context = super().get_context(value, *args, **kwargs) + + context["person_list"] = DistrictOctopusPersonPage.objects.filter( + originating_group=value["group_shortcut"] + ).order_by("title").all() + + return context + + class Meta: + label = "Skupina osob z Chobotnice" + icon = "group" + template = "styleguide2/includes/organisms/cards/people_card_list.html" + + class PeopleGroupBlock(PeopleGroupBlockMixin): person_list = blocks.ListBlock( blocks.PageChooserBlock( diff --git a/district/forms.py b/district/forms.py index f03087ba0875dd3982d870c5199e311ecd456a49..830224afde0cf46166c6a52a9da173d2bb13c527 100644 --- a/district/forms.py +++ b/district/forms.py @@ -1,7 +1,7 @@ import os +import tempfile from shared.forms import JekyllImportForm as SharedJekyllImportForm -from shared.forms import OctopusPeopleImportForm as SharedOctopusPeopleImportForm from .tasks import import_jekyll_articles from .tasks import import_people_from_group @@ -9,7 +9,10 @@ from .tasks import import_people_from_group class JekyllImportForm(SharedJekyllImportForm): def handle_import(self): - lock_file_name = f"/tmp/.{self.instance.id}.articles-import-lock" + lock_file_name = os.path.join( + tempfile.gettempdir(), + f".{self.instance.id}.articles-import-lock" + ) if os.path.isfile(lock_file_name): return @@ -22,21 +25,4 @@ class JekyllImportForm(SharedJekyllImportForm): url=self.cleaned_data["jekyll_repo_url"], dry_run=self.cleaned_data["dry_run"], use_git=True, - ) - - -class OctopusImportForm(SharedOctopusPeopleImportForm): - def handle_import_from_group(self): - lock_file_name = f"/tmp/.{self.instance.id}.people-from-group-import-lock" - - if os.path.isfile(lock_file_name): - return - - open(lock_file_name, "w").close() - - import_people_from_group.delay( - people_parent_page_id=self.instance.id, - collection_id=self.cleaned_data["collection"].id, - group_shortcut=self.cleaned_data["group_shortcut"], - lock_file_name=lock_file_name ) \ No newline at end of file diff --git a/district/management/commands/district_import_jekyll.py b/district/management/commands/district_import_jekyll.py index bd4fa44c81f39aef7249c15e7d8358cec9dffdaa..09ea8183b469ff3c3e80784b0814d7f3a1958eac 100644 --- a/district/management/commands/district_import_jekyll.py +++ b/district/management/commands/district_import_jekyll.py @@ -4,9 +4,7 @@ from ...jekyll_import import JekyllArticleImporter class Command(BaseCommand): - help = """Importuje články z pirátského jekyll webu. - - """ + help = """Importuje články z pirátského jekyll webu.""" def add_arguments(self, parser): parser.add_argument("path", help="Cesta k jekyll repu") diff --git a/district/management/commands/octopus_people_import.py b/district/management/commands/octopus_people_import.py new file mode 100644 index 0000000000000000000000000000000000000000..7f44ce93a23bf94b66b3586d6dda1ca866401546 --- /dev/null +++ b/district/management/commands/octopus_people_import.py @@ -0,0 +1,22 @@ +from django.core.management.base import BaseCommand +from district.models import DistrictPeoplePage +from district.tasks import import_people_from_group +from wagtail.models.media import Collection + + +class Command(BaseCommand): + help = """Importuje Osoby z Chobotnice.""" + + def handle(self, *args, **options): + for people_page in DistrictPeoplePage.objects.all(): + for shortcut in people_page.get_syncable_octopus_groups(): + collection_id = people_page.root_page.image_collection_id + + if collection_id is None: + collection_id = Collection.objects.first().id + + import_people_from_group.delay( + people_page.id, + collection_id, + shortcut, + ) \ No newline at end of file diff --git a/district/migrations/0264_alter_districtpeoplepage_content.py b/district/migrations/0264_alter_districtpeoplepage_content.py new file mode 100644 index 0000000000000000000000000000000000000000..c912da123c5d984d37dfdaee21bc6b6e7317fec6 --- /dev/null +++ b/district/migrations/0264_alter_districtpeoplepage_content.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.7 on 2024-07-31 09:54 + +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('district', '0263_districtoctopuspersonpage_is_automatically_created_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='districtpeoplepage', + name='content', + field=wagtail.fields.StreamField([('octopus_group', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(label='Titulek', required=True)), ('slug', wagtail.blocks.CharBlock(help_text='Není třeba vyplňovat, bude automaticky vyplněno', label='Slug skupiny', required=False)), ('group_shortcut', wagtail.blocks.CharBlock(label='Zkratka skupiny', required=True))], label='Skupina z Chobotnice')), ('people_group', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(label='Titulek')), ('slug', wagtail.blocks.CharBlock(help_text='Není třeba vyplňovat, bude automaticky vyplněno', label='Slug skupiny', required=False)), ('person_list', wagtail.blocks.ListBlock(wagtail.blocks.PageChooserBlock(label='Detail osoby', page_type=['district.DistrictPersonPage', 'district.DistrictOctopusPersonPage']), default=[], help_text='S pozicemi z jejich podstránek', label='Osoby')), ('person_list_with_custom_positions', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('page', wagtail.blocks.PageChooserBlock(label='Detail osoby', page_type=['district.DistrictOctopusPersonPage', 'district.DistrictPersonPage'])), ('position', wagtail.blocks.CharBlock(help_text='Pokud není pozice vyplněná, použije se pozice ze stránky osoby.', label='Pozice', required=False))]), default=[], help_text='S nastavitelnými pozicemi', label='Osoby'))], group='', label='Seznam osob')), ('team_group', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(label='Název sekce týmů')), ('slug', wagtail.blocks.CharBlock(help_text='Není třeba vyplňovat, bude automaticky vyplněno', label='Slug sekce', required=False)), ('team_list', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('headline', wagtail.blocks.CharBlock(label='Titulek bloku', required=False)), ('card_items', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock(label='Obrázek')), ('title', wagtail.blocks.CharBlock(label='Titulek', required=True)), ('text', wagtail.blocks.RichTextBlock(label='Krátký text pod nadpisem', required=False)), ('page', wagtail.blocks.PageChooserBlock(label='Stránka', page_type=['district.DistrictArticlesPage', 'district.DistrictCenterPage', 'district.DistrictContactPage', 'district.DistrictCrossroadPage', 'district.DistrictCustomPage', 'district.DistrictPeoplePage', 'district.DistrictGeoFeatureCollectionPage', 'district.DistrictCalendarPage', 'district.DistrictPdfPage', 'district.DistrictNewProgramPage'], required=False)), ('link', wagtail.blocks.URLBlock(label='Odkaz', required=False))], template='styleguide2/includes/molecules/boxes/card_box_block.html'), label='Karty s odkazy'))], label='Karta týmu'), label='Týmy'))]))], blank=True, verbose_name='Lidé a týmy'), + ), + ] diff --git a/district/migrations/0265_alter_districtoctopuspersonpage_is_automatically_created_and_more.py b/district/migrations/0265_alter_districtoctopuspersonpage_is_automatically_created_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..b2ac3d023dcdcd4655c56c665dcf724afe2cf1e3 --- /dev/null +++ b/district/migrations/0265_alter_districtoctopuspersonpage_is_automatically_created_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.7 on 2024-07-31 10:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('district', '0264_alter_districtpeoplepage_content'), + ] + + operations = [ + migrations.AlterField( + model_name='districtoctopuspersonpage', + name='is_automatically_created', + field=models.BooleanField(verbose_name='Profil vytvořen automaticky'), + ), + migrations.AlterField( + model_name='districtoctopuspersonpage', + name='originating_group', + field=models.CharField(help_text='Skupina, ze které byla tato osba importována.', max_length=128, verbose_name='Skupina'), + ), + ] diff --git a/district/migrations/0266_districthomepage_image_collection.py b/district/migrations/0266_districthomepage_image_collection.py new file mode 100644 index 0000000000000000000000000000000000000000..ede22a529030b22751e4ae2a1a805d082b84b24c --- /dev/null +++ b/district/migrations/0266_districthomepage_image_collection.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.7 on 2024-07-31 10:43 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('district', '0265_alter_districtoctopuspersonpage_is_automatically_created_and_more'), + ('wagtailcore', '0093_uploadedfile'), + ] + + operations = [ + migrations.AddField( + model_name='districthomepage', + name='image_collection', + field=models.ForeignKey(blank=True, 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.", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailcore.collection', verbose_name='Default kolekce obrázků'), + ), + ] diff --git a/district/models.py b/district/models.py index 2ab805900cfc3eb9ffc6b227f9112e6543d24055..c87bab1d0a2fa6af3fa10a21944bf62f46534265 100644 --- a/district/models.py +++ b/district/models.py @@ -1,6 +1,7 @@ import json from functools import cached_property +from wagtail.models.media import Collection from django.conf import settings from django.contrib import messages from django.core.cache import cache @@ -70,7 +71,7 @@ from shared.utils import ( ) from . import blocks -from .forms import JekyllImportForm, OctopusImportForm +from .forms import JekyllImportForm CONTENT_BLOCKS = DEFAULT_CONTENT_BLOCKS + [ ("chart", ChartBlock()), @@ -130,6 +131,19 @@ class DistrictHomePage(CalendarMixin, MainHomePageMixin): 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ář" @@ -180,6 +194,7 @@ class DistrictHomePage(CalendarMixin, MainHomePageMixin): "Formulář pro odběr newsletteru", ), FieldPanel("matomo_id"), + FieldPanel("image_collection"), FieldPanel("custom_css"), FieldPanel("fallback_image"), ] @@ -323,25 +338,20 @@ class DistrictOctopusPersonPage(ExtendedMetadataPageMixin, SubpageMixin, Metadat is_automatically_created = models.BooleanField( verbose_name="Profil vytvořen automaticky", - help_text=( - "Pokud vytváříš stránku pro osobu z Chobotnice manuálně, " - "toto pole by nemělo být zaškrtlé. V ostatních případech " - "ho zaškrtlé nech." - ) ) - # Shouldn't be visible in the admin interface originating_group = models.CharField( verbose_name="Skupina", - help_text="Skupina, ze které byla tato osba importována", - max_length=128 + help_text="Skupina, ze které byla tato osba importována.", + max_length=128, ) ### PANELS content_panels = Page.content_panels + [ FieldPanel("person"), - FieldPanel("is_automatically_created") + FieldPanel("is_automatically_created", read_only=True), + FieldPanel("originating_group", read_only=True) ] ### RELATIONS @@ -565,11 +575,12 @@ class DistrictPersonPage(MainPersonPageMixin): class DistrictPeoplePage(MainPeoplePageMixin): - base_form_class = OctopusImportForm + ### FIELDS content = StreamField( [ - ("people_group", blocks.PeopleGroupBlock(label="Seznam osob")), + ("octopus_group", blocks.OctopusGroupBlock(label="Skupina z Chobotnice")), + ("people_group", blocks.PeopleGroupBlock(label="Seznam osob", group="")), ("team_group", blocks.TeamBlock()), ], verbose_name="Lidé a týmy", @@ -577,31 +588,34 @@ class DistrictPeoplePage(MainPeoplePageMixin): use_json_field=True, ) + ### RELATIONS + parent_page_types = ["district.DistrictHomePage"] subpage_types = [ "district.DistrictPersonPage", "district.DistrictOctopusPersonPage" ] - import_panels = [ - MultiFieldPanel( - [ - FieldPanel("do_import"), - FieldPanel("collection"), - FieldPanel("group_shortcut") - ], - "import osob z Chobotnice", - ), - ] + ### PANELS edit_handler = TabbedInterface( [ ObjectList(MainPeoplePageMixin.content_panels, heading="Obsah"), ObjectList(MainPeoplePageMixin.promote_panels, heading="Metadata"), - ObjectList(import_panels, heading="Import"), ] ) + ### OTHERS + + def get_syncable_octopus_groups(self): + group_shortcuts = [] + + for block in self.content: + if block.block_type == "octopus_group": + group_shortcuts.append(block.value["group_shortcut"]) + + return group_shortcuts + class DistrictCalendarPage(SubpageMixin, MetadataPageMixin, CalendarMixin, Page): ### PANELS diff --git a/district/tasks.py b/district/tasks.py index 1f03829a8fe3fc1c7754b248e453bd63ae4a71c8..8f8a0048821943982578b3d0c020f576b147b83b 100644 --- a/district/tasks.py +++ b/district/tasks.py @@ -1,4 +1,6 @@ import logging +import tempfile +import os from celery import shared_task @@ -34,10 +36,19 @@ def import_people_from_group( people_parent_page_id, collection_id, group_shortcut, - lock_file_name ): from .models import DistrictPeoplePage, DistrictOctopusPersonPage + lock_file_name = os.path.join( + tempfile.gettempdir(), + f"{people_parent_page_id}-{group_shortcut}.people-from-group-import-lock" + ) + + if os.path.isfile(lock_file_name): + return + + open(lock_file_name, "w").close() + return PeopleGroupImporter( people_parent_page_id=people_parent_page_id, people_parent_page_model=DistrictPeoplePage, diff --git a/district/templatetags/__init__.py b/district/templatetags/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/district/templatetags/district_people_filters.py b/district/templatetags/district_people_filters.py new file mode 100644 index 0000000000000000000000000000000000000000..ed0728e12a20f5834635f5cf7211a89e487db0f8 --- /dev/null +++ b/district/templatetags/district_people_filters.py @@ -0,0 +1,12 @@ +from django import template + +register = template.Library() + + +@register.filter +def get_block_octopus_person_list(block): + from district.models import DistrictOctopusPersonPage + + return DistrictOctopusPersonPage.objects.filter( + originating_group=block.value["group_shortcut"] + ).order_by("title").all() \ No newline at end of file diff --git a/elections/forms.py b/elections/forms.py index 45b12efafb86376aa240cb2a98fcf530fa4e5405..b2cd1562b7255e8304b558a0997276058bb6c21e 100644 --- a/elections/forms.py +++ b/elections/forms.py @@ -1,4 +1,5 @@ import os +import tempfile from shared.forms import JekyllImportForm as SharedJekyllImportForm @@ -7,7 +8,10 @@ from .tasks import import_jekyll_articles class JekyllImportForm(SharedJekyllImportForm): def handle_import(self): - lock_file_name = f"/tmp/.{self.instance.id}.import-lock" + lock_file_name = os.path.join( + tempfile.gettempdir(), + f".{self.instance.id}.articles-import-lock" + ) if os.path.isfile(lock_file_name): return diff --git a/main/forms.py b/main/forms.py index 45b12efafb86376aa240cb2a98fcf530fa4e5405..b2cd1562b7255e8304b558a0997276058bb6c21e 100644 --- a/main/forms.py +++ b/main/forms.py @@ -1,4 +1,5 @@ import os +import tempfile from shared.forms import JekyllImportForm as SharedJekyllImportForm @@ -7,7 +8,10 @@ from .tasks import import_jekyll_articles class JekyllImportForm(SharedJekyllImportForm): def handle_import(self): - lock_file_name = f"/tmp/.{self.instance.id}.import-lock" + lock_file_name = os.path.join( + tempfile.gettempdir(), + f".{self.instance.id}.articles-import-lock" + ) if os.path.isfile(lock_file_name): return diff --git a/shared/forms.py b/shared/forms.py index ace673824f8e6ac7e11ccf0f316e969ba80a401a..b9e90c8fd88acbdb3705287259401c0a14781354 100644 --- a/shared/forms.py +++ b/shared/forms.py @@ -9,44 +9,6 @@ class SubscribeForm(forms.Form): return_page_id = forms.IntegerField() -class OctopusPeopleImportForm(WagtailAdminPageForm): - do_import = forms.BooleanField( - initial=False, required=False, label="Provést import osob z Chobotnice" - ) - collection = forms.ModelChoiceField( - queryset=Collection.objects.all(), required=False, label="Kolekce obrázků" - ) - group_shortcut = forms.CharField( - label="Zkratka skupiny osob", - required=False, - ) - - def clean(self): - cleaned_data = super().clean() - - if not cleaned_data.get("do_import"): - return cleaned_data - - if cleaned_data.get("do_import") and not self.instance.id: - self.add_error( - "do_import", "Import proveďte prosím až po vytvoření stránky" - ) - - if not cleaned_data.get("collection"): - self.add_error("collection", "Pro import je toto pole povinné") - - if not cleaned_data.get("group_shortcut"): - self.add_error("group_shortcut", "Pro import je toto pole povinné") - - return cleaned_data - - def save(self, commit=True): - if self.cleaned_data.get("do_import"): - self.handle_import_from_group() - - return super().save(commit=commit) - - class JekyllImportForm(WagtailAdminPageForm): do_import = forms.BooleanField( initial=False, required=False, label="Provést import z Jekyllu" diff --git a/shared/templates/styleguide2/includes/organisms/header/people_header.html b/shared/templates/styleguide2/includes/organisms/header/people_header.html index 20643b427ed2cf2cbb48c5b443a41a3ea50bae7a..62ced372f93182b2e6e421df79bd6d4515c3aa80 100644 --- a/shared/templates/styleguide2/includes/organisms/header/people_header.html +++ b/shared/templates/styleguide2/includes/organisms/header/people_header.html @@ -25,7 +25,7 @@ {% block switch %} {% for content_item in content %} - {% if content_item.block_type == "people_group" or content_item.block_type == "team_group" %} + {% if content_item.block_type == "octopus_group" or content_item.block_type == "people_group" or content_item.block_type == "team_group" %} <a @click="toggleView('{{ content_item.value.slug }}-{{ forloop.counter }}')" class="switch__item" :class="{'switch__item--active': isCurrentView('{{ content_item.value.slug }}-{{ forloop.counter }}')}" > diff --git a/shared/templates/styleguide2/people_page.html b/shared/templates/styleguide2/people_page.html index d60b0dd4bfa28c033f1b7496bb912ddaa621c75b..0a8d0909b8272616323280ae2104a9c9d52a823e 100644 --- a/shared/templates/styleguide2/people_page.html +++ b/shared/templates/styleguide2/people_page.html @@ -1,5 +1,5 @@ {% extends "styleguide2/base.html" %} -{% load wagtailcore_tags wagtailimages_tags shared_filters people_filters %} +{% load wagtailcore_tags wagtailimages_tags shared_filters people_filters district_people_filters %} {% block content %} @@ -11,7 +11,7 @@ <ui-view-provider :initial="{ {% for content_item in page.content %} - {% if content_item.block_type == "people_group" or content_item.block_type == "team_group" %} + {% if content_item.block_type == "octopus_group" or content_item.block_type == "people_group" or content_item.block_type == "team_group" %} '{{ content_item.value.slug }}-{{ forloop.counter }}': {% if page.content|is_first_people_type:forloop %}false{% else %}true{% endif %}, {% endif %} {% endfor %} @@ -29,6 +29,24 @@ xl:justify-start " > + <div class="grid grid-cols-1 gap-4 md:grid-cols-2 w-full"> + {% for block in page.content %} + {% if block.block_type == "octopus_group" %} + <template v-if="isCurrentView('{{ block.value.slug }}-{{ forloop.counter }}')"> + {% with block|get_block_octopus_person_list as person_list %} + {% if person_list|length %} + {% for person_page in person_list %} + {% image person_page.specific.get_profile_image fill-480x480 as profile_image %} + + {% include 'styleguide2/includes/molecules/contact/contact_person_large_box.html' with image=profile_image name=person_page.title function=person_page.position telephone=person_page.phone mail=person_page.email url=person_page.url %} + {% endfor %} + {% endif %} + {% endwith %} + </template> + {% endif %} + {% endfor %} + </div> + <div class="grid grid-cols-1 gap-4 md:grid-cols-2 w-full"> {% for block in page.content %} {% if block.block_type == "people_group" %} @@ -81,7 +99,7 @@ </div> {% for block in page.content %} - {% if block.block_type != "people_group" and block.block_type != "team_group" %} + {% if block.block_type != "octopus_group" and block.block_type != "people_group" and block.block_type != "team_group" %} {% include_block block %} {% endif %} {% endfor %} diff --git a/shared/templatetags/people_filters.py b/shared/templatetags/people_filters.py index 8f501eae0b4c3af82f5f7ad34c24021397668fa1..2a172bbcd7cabe3a8c613eea00abbd3a9d1ce6e6 100644 --- a/shared/templatetags/people_filters.py +++ b/shared/templatetags/people_filters.py @@ -19,4 +19,4 @@ def is_first_people_type(content, forloop): ): return True - return False + return False \ No newline at end of file