diff --git a/district/wagtail_hooks.py b/district/wagtail_hooks.py index 461e746ed8a2b4da06de45e92deb5cadb35d368a..67eb4a399ad321ea36ade3e355f89eb56556be9b 100644 --- a/district/wagtail_hooks.py +++ b/district/wagtail_hooks.py @@ -2,53 +2,6 @@ from wagtail.core import hooks from district.models import DistrictCenterPage, DistrictHomePage -# import re -# -# from wagtail.core import hooks -# -# # FIXME hooks are loaded globally so it doesn't make sense to have it in this module -# -# -# @hooks.register("construct_page_chooser_queryset") -# def this_web_only(pages, request): -# add_result = re.search( -# "page/(.*)/", request.META.get("HTTP_REFERER") -# ) # FIXME better regex -# edit_result = re.search("pages/(.*)/edit", request.META.get("HTTP_REFERER")) -# -# if add_result: -# return handle_add_page_selection(pages=pages, add_result=add_result) -# -# if edit_result: -# return handle_edit_page_selection(pages=pages, edit_result=edit_result) -# -# return pages -# -# -# def handle_add_page_selection(pages, add_result): -# parent_page_id = add_result.group(1) -# if parent_page_id == 1: # pro novou homepage žádné podstránky nejsou -# return pages.none() -# -# parent_page = pages.model.objects.get(id=parent_page_id) -# root_page = getattr(parent_page.specific, "root_page", None) -# return get_only_root_page_descendants(pages=pages, root_page=root_page) -# -# -# def handle_edit_page_selection(pages, edit_result): -# current_page_id = edit_result.group(1) -# current_page = pages.model.objects.get(id=current_page_id) -# root_page = getattr(current_page.specific, "root_page", None) -# return get_only_root_page_descendants(pages=pages, root_page=root_page) -# -# -# def get_only_root_page_descendants(pages, root_page): -# if not root_page: -# return pages -# -# web_pages_id_list = root_page.get_descendants().live().values_list("id", flat=True) -# return pages.filter(id__in=web_pages_id_list) - @hooks.register("after_copy_page") def handle_copy_calendar(request, origin_page, copied_page): diff --git a/main/forms.py b/main/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..b5386a493b7436a3483db8adc1873e3bcb74215b --- /dev/null +++ b/main/forms.py @@ -0,0 +1,79 @@ +from django import forms +from wagtail.admin.forms import WagtailAdminPageForm +from wagtail.core.models.collections import Collection + +from .tasks import import_jekyll_articles + + +class JekyllImportForm(WagtailAdminPageForm): + do_import = forms.BooleanField( + initial=False, required=False, label="Provést import z Jekyllu" + ) + collection = forms.ModelChoiceField( + queryset=Collection.objects.all(), required=False, label="Kolekce obrázků" + ) + dry_run = forms.BooleanField( + initial=True, + required=False, + label="Jenom na zkoušku", + help_text="Žádné články se neuloží, vypíše případné problémy či " + "již existující články - 'ostrému' importu existující " + "články nevadí, přeskočí je", + ) + jekyll_repo_url = forms.URLField( + max_length=512, + required=False, + help_text="V GitHubu tlačítko Code -> a odkaz z Download zip" + "např. 'https://github.com/pirati-web/cb.pirati.cz/archive/refs/heads/gh-pages.zip'," + "pokud máte nainstalovaný Git, zvolte Použít Git a vložte" + "jednoduše URL repozitáře " + "např. 'https://github.com/pirati-web/cb.pirati.cz'", + ) + readonly_log = forms.CharField( + disabled=True, + label="Log z posledního importu", + required=False, + widget=forms.Textarea, + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["readonly_log"].initial = self.instance.last_import_log + + 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("jekyll_repo_url"): + self.add_error("jekyll_repo_url", "Pro import je toto pole povinné") + + if cleaned_data.get("jekyll_repo_url", "").endswith(".zip"): + self.add_error( + "jekyll_repo_url", "Vložte odkaz pouze na repozitář, ne na zip" + ) + + return cleaned_data + + def handle_import(self): + import_jekyll_articles.delay( + article_parent_page_id=self.instance.id, + collection_id=self.cleaned_data["collection"].id, + url=self.cleaned_data["jekyll_repo_url"], + dry_run=self.cleaned_data["dry_run"], + use_git=True, + ) + + def save(self, commit=True): + if self.cleaned_data.get("do_import"): + self.handle_import() + + return super().save(commit=commit) diff --git a/main/migrations/0012_alter_mainpeoplepage_people_delete_mainarticlespage.py b/main/migrations/0012_alter_mainpeoplepage_people_delete_mainarticlespage.py new file mode 100644 index 0000000000000000000000000000000000000000..3bf5d208f0d00bd1f76fe32e2c041cd2af9ebc90 --- /dev/null +++ b/main/migrations/0012_alter_mainpeoplepage_people_delete_mainarticlespage.py @@ -0,0 +1,56 @@ +# Generated by Django 4.0.7 on 2022-08-24 13:39 + +import wagtail.blocks +import wagtail.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("main", "0011_alter_mainarticlepage_content_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="mainpeoplepage", + name="people", + field=wagtail.fields.StreamField( + [ + ( + "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=["main.MainPersonPage"], + ), + label="Skupina osob", + ), + ), + ], + label="Seznam osob", + ), + ) + ], + blank=True, + use_json_field=None, + verbose_name="Lidé", + ), + ), + migrations.DeleteModel( + name="MainArticlesPage", + ), + ] diff --git a/main/migrations/0013_rename_mainworkpage_mainarticlespage.py b/main/migrations/0013_rename_mainworkpage_mainarticlespage.py new file mode 100644 index 0000000000000000000000000000000000000000..22ab6f107b902656e5d1ac87a505fb2e44d1e3e5 --- /dev/null +++ b/main/migrations/0013_rename_mainworkpage_mainarticlespage.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.7 on 2022-08-24 13:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("wagtailimages", "0024_index_image_file_hash"), + ("wagtailcore", "0069_log_entry_jsonfield"), + ("main", "0012_alter_mainpeoplepage_people_delete_mainarticlespage"), + ] + + operations = [ + migrations.RenameModel( + old_name="MainWorkPage", + new_name="MainArticlesPage", + ), + ] diff --git a/main/migrations/0014_alter_mainarticlespage_options_and_more.py b/main/migrations/0014_alter_mainarticlespage_options_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..58e8d88bb868869fa9ac7442e3af43458388cabb --- /dev/null +++ b/main/migrations/0014_alter_mainarticlespage_options_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.0.7 on 2022-08-24 13:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("main", "0013_rename_mainworkpage_mainarticlespage"), + ] + + operations = [ + migrations.AlterModelOptions( + name="mainarticlespage", + options={"verbose_name": "Rozcestník článků"}, + ), + migrations.AddField( + model_name="mainarticlepage", + name="article_type", + field=models.PositiveSmallIntegerField( + choices=[ + (1, "Článek na timeline Piráti pracují"), + (2, "Tisková zpráva"), + ], + default=2, + verbose_name="Typ článku", + ), + ), + ] diff --git a/main/migrations/0015_mainarticlespage_last_import_log.py b/main/migrations/0015_mainarticlespage_last_import_log.py new file mode 100644 index 0000000000000000000000000000000000000000..d4542a92fbf3194b11e68ba832ca6a023eb1b5a3 --- /dev/null +++ b/main/migrations/0015_mainarticlespage_last_import_log.py @@ -0,0 +1,20 @@ +# Generated by Django 4.0.7 on 2022-08-24 14:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("main", "0014_alter_mainarticlespage_options_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="mainarticlespage", + name="last_import_log", + field=models.TextField( + blank=True, null=True, verbose_name="Výstup z posledního importu" + ), + ), + ] diff --git a/main/models.py b/main/models.py index d7abf5e819ca1190fad8c23d603a99be8a2148a5..f69b6831ad5707e405f7736b194d905f9e148dfb 100644 --- a/main/models.py +++ b/main/models.py @@ -2,7 +2,6 @@ from datetime import timedelta from functools import cached_property from django.conf import settings -from django.core.paginator import Paginator from django.db import models from django.http import HttpResponseRedirect from django.shortcuts import render @@ -10,8 +9,14 @@ from django.utils import timezone from modelcluster.contrib.taggit import ClusterTaggableManager from modelcluster.fields import ParentalKey from taggit.models import TaggedItemBase -from wagtail.admin.edit_handlers import FieldPanel, ObjectList, TabbedInterface -from wagtail.contrib.routable_page.models import RoutablePageMixin, route +from wagtail.admin.edit_handlers import ( + FieldPanel, + HelpPanel, + MultiFieldPanel, + ObjectList, + TabbedInterface, +) +from wagtail.contrib.routable_page.models import route from wagtail.core.blocks import CharBlock, PageChooserBlock, RichTextBlock from wagtail.core.fields import RichTextField, StreamField from wagtail.core.models import Page @@ -31,9 +36,15 @@ from tuning import admin_help from twitter_utils.models import Tweet from . import blocks +from .forms import JekyllImportForm from .menu import MenuMixin +class ARTICLE_TYPES(models.IntegerChoices): + WORK_TIMELINE = 1, "Článek na timeline Piráti pracují" + PRESS_RELEASE = 2, "Tisková zpráva" + + class MainHomePage(MenuMixin, ExtendedMetadataHomePageMixin, MetadataPageMixin, Page): # header @@ -129,7 +140,6 @@ class MainHomePage(MenuMixin, ExtendedMetadataHomePageMixin, MetadataPageMixin, ### RELATIONS subpage_types = [ - "main.MainWorkPage", "main.MainArticlesPage", "main.MainProgramPage", "main.MainPeoplePage", @@ -181,7 +191,9 @@ class MainHomePage(MenuMixin, ExtendedMetadataHomePageMixin, MetadataPageMixin, return HttpResponseRedirect(self.url) -class MainWorkPage(ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, Page): +class MainArticlesPage( + ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, Page +): perex = models.TextField() timeline = StreamField( # TODO delete [ @@ -195,19 +207,56 @@ class MainWorkPage(ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, P verbose_name="Timeline", blank=True, ) + last_import_log = models.TextField( + "Výstup z posledního importu", null=True, blank=True + ) + + import_panels = [ + MultiFieldPanel( + [ + FieldPanel("do_import"), + FieldPanel("collection"), + FieldPanel("dry_run"), + FieldPanel("jekyll_repo_url"), + FieldPanel("readonly_log"), + HelpPanel( + "Import provádějte vždy až po vytvoření stránky aktualit. " + 'Pro uložení logu je nutné volit možnost "Publikovat", nikoliv' + 'pouze "Uložit koncept". ' + "Import proběhne na pozadí a může trvat až několik minut. " + "Dejte si po spuštění importu kávu a potom obnovte stránku pro " + "zobrazení výsledku importu." + ), + ], + "import z Jekyll repozitáře", + ), + ] ### RELATIONS parent_page_types = ["main.MainHomePage"] - subpage_types = [] + subpage_types = ["main.MainArticlePage"] ### PANELS content_panels = Page.content_panels + [FieldPanel("perex"), FieldPanel("timeline")] + promote_panels = make_promote_panels() + + ### EDIT HANDLERS + + edit_handler = TabbedInterface( + [ + ObjectList(content_panels, heading="Obsah"), + ObjectList(promote_panels, heading="Propagovat"), + ObjectList(import_panels, heading="Import"), + ] + ) ### OTHERS + base_form_class = JekyllImportForm + class Meta: - verbose_name = "Piráti pracují" + verbose_name = "Rozcestník článků" def get_article_data_list(self): last_month = timezone.now().today().replace(day=1) - timedelta(days=1) @@ -260,13 +309,16 @@ class MainArticlePage( ): ### FIELDS + article_type = models.PositiveSmallIntegerField( + "Typ článku", choices=ARTICLE_TYPES.choices, default=ARTICLE_TYPES.PRESS_RELEASE + ) content = StreamField( [ - ('text', RichTextBlock(template="")), - ('quote', blocks.ArticleQuoteBlock()), - ('download', blocks.ArticleDownloadBlock()), - ('image', blocks.ArticleImageBlock()) + ("text", RichTextBlock(template="")), + ("quote", blocks.ArticleQuoteBlock()), + ("download", blocks.ArticleDownloadBlock()), + ("image", blocks.ArticleImageBlock()), ], verbose_name="Článek", blank=True, @@ -291,6 +343,7 @@ class MainArticlePage( ### PANELS content_panels = ArticleMixin.content_panels + [ + FieldPanel("article_type"), FieldPanel("author_page"), FieldPanel("region"), FieldPanel("tags"), @@ -322,36 +375,6 @@ class MainArticlePage( # return context -class MainArticlesPage( - RoutablePageMixin, ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, Page -): - def get_context(self, request): - context = super().get_context(request) - context["articles"] = Paginator( - self.get_children().live().specific().order_by("-mainarticlepage__date"), - 12, # nevím, návrh nemáme - ).get_page(request.GET.get("page")) - return context - - @route(r"^tagy/$", name="tags") - def tags(self, request): - return render( - request, - "main/main_tags_page.html", - context=self.get_tags_page_context(request=request), - ) - - ### RELATIONS - - parent_page_types = ["main.MainHomePage"] - subpage_types = ["main.MainArticlePage"] - - ### OTHERS - - class Meta: - verbose_name = "Aktuality" - - class MainProgramPage(ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, Page): ### FIELDS diff --git a/main/tasks.py b/main/tasks.py new file mode 100644 index 0000000000000000000000000000000000000000..03f20935d0b4781478c45652a429db1e02b31184 --- /dev/null +++ b/main/tasks.py @@ -0,0 +1,27 @@ +import logging + +from majak.celery import app +from shared.jekyll_import import JekyllArticleImporter + +logger = logging.getLogger(__name__) + + +@app.task +def import_jekyll_articles( + article_parent_page_id: int, + collection_id: int, + url: str, + dry_run: bool, + use_git: bool, +): + from .models import MainArticlePage, MainArticlesPage + + return JekyllArticleImporter( + article_parent_page_id=article_parent_page_id, + collection_id=collection_id, + url=url, + dry_run=dry_run, + use_git=use_git, + parent_page_model=MainArticlesPage, + page_model=MainArticlePage, + ).perform_import() diff --git a/main/templates/main/includes/work_article_preview.html b/main/templates/main/includes/work_article_preview.html index 90c35f97d516146df4a83c376ed49a7203a82c40..6d423edbe8e55ed9827ac334a249f29ff0996ae6 100644 --- a/main/templates/main/includes/work_article_preview.html +++ b/main/templates/main/includes/work_article_preview.html @@ -15,6 +15,6 @@ {{ article_page.perex }} </p> <div> - {% include 'main/includes/button_animated.html' with btn_text="Číst dále" %} + {% include 'main/includes/button_animated.html' with btn_link=article_page.url btn_text="Číst dále" %} </div> </div> diff --git a/main/templates/main/main_articles_page.html b/main/templates/main/main_articles_page.html index 470fad2457821eb85c04caa07802169acfbce53d..d57689731a43f899e2cdbc7dcd108a110cf67484 100644 --- a/main/templates/main/main_articles_page.html +++ b/main/templates/main/main_articles_page.html @@ -1,14 +1,55 @@ {% extends "main/base.html" %} +{% load wagtailcore_tags wagtailimages_tags shared_filters %} {% block content %} - {# nemame design #} - <header> - <h1 itemprop="headline" class="head-alt-md md:head-alt-lg max-w-5xl mb-8">{{ page.title }}</h1> - </header> + {% include 'main/includes/layout/simple_page_header.html' %} - <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 lg:gap-8"> - {% for a in articles %} - {% include "shared/article_preview.html" with article=a %} - {% endfor %} - </div> -{% endblock %} + <main role="main"> + <div class="grid-container mb-2 xl:mb-12"> + <div class="grid-content leading-6"> + <h2 class="head-xl mb-2"> + {{ page.perex }} + </h2> + <h2 class="head-xl mb-2"> + Projděte si archiv tiskových zpráv a souhrn našich nejvýraznějších aktivit + </h2> + </div> + </div> + + <div class="grid-container article-section"> + <div class="grid-full mb-8"> + {% for month_article_data in article_data_list %} + <div class="mb-4"> + <h3 class="head-7xl mb-4 xl:hidden"> + {{ month_article_data.month_text }} + </h3> + <div class="flex flex-col justify-between xl:flex-row"> + <div class="xl:pt-8"> + {% for article_page in month_article_data.left_column %} + {% include 'main/includes/work_article_preview.html' %} + {% empty %} + <div class="p-7 flex flex-col max-w-xl mb-8" /> + {% endfor %} + </div> + <div class="relative border border-violet-400 mx-8 hidden xl:block"> + <div class="absolute bg-violet-400 p-1 text-white font-bold" style="transform: translateX(-50%); top: -1rem"> + {{ month_article_data.month_text }} + </div> + </div> + <div class="xl:pt-14"> + {% for article_page in month_article_data.right_column %} + {% include 'main/includes/work_article_preview.html' %} + {% empty %} + <div class="p-7 flex flex-col max-w-xl mb-8" /> + {% endfor %} + </div> + </div> + </div> + {% endfor %} + <div class="flex justify-center"> + {% include 'main/includes/button_animated.html' with btn_text="Zobrazit další" %} + </div> + </div> + </div> + </main> +{% endblock content %} diff --git a/main/templates/main/main_program_page.html b/main/templates/main/main_program_page.html index 05020476de073698f509c086a9f8ceabb73513c1..97d2b8f38e6b64e2d0b9ec290184b7db3cb1a2a8 100644 --- a/main/templates/main/main_program_page.html +++ b/main/templates/main/main_program_page.html @@ -2,6 +2,8 @@ {% load wagtailcore_tags wagtailimages_tags shared_filters %} {% block content %} + {% include 'main/includes/layout/simple_page_header.html' %} + <main role="main"> <div class="grid-container"> <div class="grid-content"> @@ -15,13 +17,13 @@ <div class="mb-12"> <div class="switch"> {% for program_group in page.program %} - <a - class="switch__item {% if forloop.first %}switch__item--active{% endif %}">{{ program_group.value.title }}</a> + <a class="switch__item {% if forloop.first %}switch__item--active{% endif %}"> + {{ program_group.value.title }} + </a> {% endfor %} </div> </div> - <div class="mb-12"> {% for program_group in page.program %} {% for item in program_group.value.point_list %} diff --git a/main/templates/main/main_work_page.html b/main/templates/main/main_work_page.html deleted file mode 100644 index 436e75c78093cfec8f36a5cb5929c425007794b0..0000000000000000000000000000000000000000 --- a/main/templates/main/main_work_page.html +++ /dev/null @@ -1,58 +0,0 @@ -{% extends "main/base.html" %} -{% load wagtailcore_tags wagtailimages_tags shared_filters %} - -{% block content %} - {% include 'main/includes/layout/simple_page_header.html' %} - - <main role="main"> - <div class="grid-container mb-2 xl:mb-12"> - <div class="grid-left-side"> - TODO menu - </div> - <div class="grid-content leading-6"> - <h2 class="head-xl mb-2"> - {{ page.perex }} - </h2> - <h2 class="head-xl mb-2"> - Projděte si archiv tiskových zpráv a souhrn našich nejvýraznějších aktivit - </h2> - </div> - </div> - - <div class="grid-container article-section"> - <div class="grid-full mb-8"> - {% for month_article_data in article_data_list %} - <div class="mb-4"> - <h3 class="head-7xl mb-4 xl:hidden"> - {{ month_article_data.month_text }} - </h3> - <div class="flex flex-col justify-between xl:flex-row"> - <div class="xl:pt-8"> - {% for article_page in month_article_data.left_column %} - {% include 'main/includes/work_article_preview.html' %} - {% empty %} - <div class="p-7 flex flex-col max-w-xl mb-8" /> - {% endfor %} - </div> - <div class="relative border border-violet-400 mx-8 hidden xl:block"> - <div class="absolute bg-violet-400 p-1 text-white font-bold" style="transform: translateX(-50%); top: -1rem"> - {{ month_article_data.month_text }} - </div> - </div> - <div class="xl:pt-14"> - {% for article_page in month_article_data.right_column %} - {% include 'main/includes/work_article_preview.html' %} - {% empty %} - <div class="p-7 flex flex-col max-w-xl mb-8" /> - {% endfor %} - </div> - </div> - </div> - {% endfor %} - <div class="flex justify-center"> - {% include 'main/includes/button_animated.html' with btn_text="Zobrazit další" %} - </div> - </div> - </div> - </main> -{% endblock content %}