diff --git a/district/models.py b/district/models.py index 17bbde63e932181651be2a3ac1db4d37227d4f67..5a68c0fb7d1e8bbeb72bad8639f40a47d0215a0c 100644 --- a/district/models.py +++ b/district/models.py @@ -48,10 +48,12 @@ from shared.blocks import ( from shared.const import RICH_TEXT_DEFAULT_FEATURES from shared.models import ( ArticleMixin, + ArticlesMixin, ExtendedMetadataHomePageMixin, ExtendedMetadataPageMixin, FooterMixin, MenuMixin, + SharedTaggedDistrictArticle, SubpageMixin, ) from shared.utils import make_promote_panels, strip_all_html_tags, trim_to_length @@ -403,6 +405,9 @@ class DistrictArticlePage( ) is_black = models.BooleanField("Má tmavé pozadí?", default=False) tags = ClusterTaggableManager(through=DistrictArticleTag, blank=True) + shared_tags = ClusterTaggableManager( + through=SharedTaggedDistrictArticle, blank=True + ) thumb_image = models.ForeignKey( "wagtailimages.Image", on_delete=models.PROTECT, @@ -418,6 +423,7 @@ class DistrictArticlePage( FieldPanel("author_page"), FieldPanel("is_black"), FieldPanel("tags"), + FieldPanel("shared_tags"), FieldPanel("thumb_image"), ] @@ -460,7 +466,12 @@ class DistrictArticlePage( class DistrictArticlesPage( - RoutablePageMixin, ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, Page + RoutablePageMixin, + ExtendedMetadataPageMixin, + SubpageMixin, + MetadataPageMixin, + ArticlesMixin, + Page, ): ### FIELDS @@ -471,7 +482,7 @@ class DistrictArticlesPage( ### PANELS - content_panels = Page.content_panels + [ + content_panels = ArticlesMixin.content_panels + [ FieldPanel("max_items"), ] @@ -524,10 +535,7 @@ class DistrictArticlesPage( def get_context(self, request): context = super().get_context(request) context["articles"] = Paginator( - self.get_children() - .live() - .specific() - .order_by("-districtarticlepage__date"), + self.append_all_shared_articles(self.get_children().live().specific()), self.max_items, ).get_page(request.GET.get("page")) return context @@ -540,15 +548,19 @@ class DistrictArticlesPage( context=self.get_tags_page_context(request=request), ) + @route(r"^sdilene/$", name="shared") + def shared(self, request): + return self.setup_article_page_context(request) + def get_tags_page_context(self, request) -> dict: # Potřebujeme IDčka článků pro správnou root_page pro filtrování zobrazených # tagů i samotných stránek, protože se filtrují přes specifický field # (tags__slug) context = super().get_context(request) - site_article_ids = ( - self.get_children().live().specific().values_list("id", flat=True) - ) + site_article_ids = self.append_all_shared_articles( + self.get_children().live().specific() + ).values_list("id", flat=True) # Naplním "tag" a "article_page_list" parametry context.update(**self.get_tag_and_articles(request, site_article_ids)) @@ -564,10 +576,12 @@ class DistrictArticlesPage( pro danou stránku (site_article_ids). Lepší by bylo články a tag řešit separátně, ale pak by se musel rozpadnout ten try/except na více bloků. """ - article_page_qs = DistrictArticlePage.objects.filter(id__in=site_article_ids) + article_page_qs = self.append_all_shared_articles( + DistrictArticlePage.objects + ).filter(id__in=site_article_ids) try: - tag = DistrictArticleTag.objects.filter(tag__slug=request.GET["tag"])[0].tag + tag = Tag.objects.filter(tag__slug=request.GET["tag"])[0].tag article_page_qs = article_page_qs.filter(tags__slug=tag.slug) except (KeyError, IndexError): tag = None @@ -580,14 +594,13 @@ class DistrictArticlesPage( "tag": tag, } - @staticmethod - def get_tag_qs(site_article_ids: list) -> models.QuerySet: + def get_tag_qs(self, site_article_ids: list) -> models.QuerySet: """ Getuje Tagy pouze pro DistrictArticlePage omezeno IDčky getnutých přes root_page. Počítá, kolik článků je s daným tagem. """ return ( - Tag.objects.filter(districtarticlepage__id__in=site_article_ids) + Tag.objects.filter(**self.get_search_tags_params(site_article_ids)) .order_by("slug") .annotate(count=models.Count("slug")) .values("name", "slug", "count") diff --git a/main/models.py b/main/models.py index 26928fc3503195efdfca26a94dff8ebb759feb34..2afb57ff684863a53f54fe4c22a9a1fe7b1e17dc 100644 --- a/main/models.py +++ b/main/models.py @@ -31,12 +31,14 @@ from wagtailmetadata.models import MetadataPageMixin from calendar_utils.models import CalendarMixin from elections2021.constants import REGION_CHOICES # pozor, import ze sousedního modulu -from instagram_utils.models import InstagramPost, InstagramMixin +from instagram_utils.models import InstagramMixin, InstagramPost from shared.forms import SubscribeForm from shared.models import ( # MenuMixin, ArticleMixin, + ArticlesMixin, ExtendedMetadataHomePageMixin, ExtendedMetadataPageMixin, + SharedTaggedMainArticle, SubpageMixin, ) from shared.utils import make_promote_panels, subscribe_to_newsletter @@ -59,7 +61,7 @@ class MainHomePage( ExtendedMetadataHomePageMixin, MetadataPageMixin, InstagramMixin, - Page + Page, ): # header @@ -357,7 +359,12 @@ class MainHomePage( class MainArticlesPage( - RoutablePageMixin, ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, Page + RoutablePageMixin, + ExtendedMetadataPageMixin, + SubpageMixin, + MetadataPageMixin, + ArticlesMixin, + Page, ): perex = models.TextField() last_import_log = models.TextField( @@ -391,7 +398,7 @@ class MainArticlesPage( subpage_types = ["main.MainArticlePage"] ### PANELS - content_panels = Page.content_panels + [FieldPanel("perex")] + content_panels = ArticlesMixin.content_panels + [FieldPanel("perex")] promote_panels = make_promote_panels() ### EDIT HANDLERS @@ -508,7 +515,9 @@ class MainArticlesPage( def get_all_articles_search_response(self, request): article_paginator = Paginator( - MainArticlePage.objects.live().order_by("-date").search(request.GET["q"]), + self.append_all_shared_articles(MainArticlePage.objects.live()).search( + request.GET["q"] + ), 10, ) article_page = article_paginator.get_page(request.GET.get("page", 1)) @@ -526,14 +535,18 @@ class MainArticlesPage( def search_url(self): return self.url + self.reverse_subpage("search") + @route(r"^sdilene/$", name="shared") + def shared(self, request): + return self.setup_article_page_context(request) + @route(r"^search") def search(self, request): if request.method == "GET" and "q" in request.GET: query = request.GET["q"] - article_results = ( - MainArticlePage.objects.live().order_by("-date").search(query)[:11] - ) + article_results = self.append_all_shared_articles( + MainArticlePage.objects.live() + ).search(query)[:11] return render( request, @@ -616,6 +629,7 @@ class MainArticlePage( help_text="Kraj, ke kterému se článek vztahuje", ) tags = ClusterTaggableManager(through=MainArticleTag, blank=True) + shared_tags = ClusterTaggableManager(through=SharedTaggedMainArticle, blank=True) search_fields = Page.search_fields + [ index.SearchField("title"), @@ -633,6 +647,7 @@ class MainArticlePage( FieldPanel("is_black"), FieldPanel("region"), FieldPanel("tags"), + FieldPanel("shared_tags"), ] promote_panels = make_promote_panels( diff --git a/shared/models.py b/shared/models.py index c4e7c0cb95346a32241692cfaecbd1302ae5e4d6..8082e2cc9988ed0865e72b74e4465afd08181b77 100644 --- a/shared/models.py +++ b/shared/models.py @@ -1,10 +1,11 @@ -import json import logging +import sys from django.db import models -from django.http import HttpResponse +from django.db.models.functions import Coalesce from django.utils import timezone -from requests import request +from modelcluster.fields import ParentalKey, ParentalManyToManyField +from taggit.models import ItemBase, TagBase from wagtail.admin.panels import FieldPanel, MultiFieldPanel, PublishingPanel from wagtail.fields import StreamField from wagtail.models import Page @@ -215,3 +216,124 @@ class FooterMixin(models.Model): class Meta: abstract = True + + +class SharedTag(TagBase): + class Meta: + verbose_name = "sdílený tag" + verbose_name_plural = "sdílené tagy" + + +class SharedTaggedDistrictArticle(ItemBase): + tag = models.ForeignKey( + SharedTag, related_name="shared_district_tags", on_delete=models.CASCADE + ) + content_object = ParentalKey( + to="district.DistrictArticlePage", + on_delete=models.CASCADE, + related_name="shared_district_articles", + ) + + +class SharedTaggedUniwebArticle(ItemBase): + tag = models.ForeignKey( + SharedTag, related_name="shared_uniweb_tags", on_delete=models.CASCADE + ) + content_object = ParentalKey( + to="uniweb.UniwebArticlePage", + on_delete=models.CASCADE, + related_name="shared_uniweb_articles", + ) + + +class SharedTaggedMainArticle(ItemBase): + tag = models.ForeignKey( + SharedTag, related_name="shared_main_tags", on_delete=models.CASCADE + ) + content_object = ParentalKey( + to="main.MainArticlePage", + on_delete=models.CASCADE, + related_name="shared_main_articles", + ) + + +class ArticlesMixin(models.Model): + shared_tags = ParentalManyToManyField( + "shared.SharedTag", related_name="Sdílené tagy", blank=True + ) + + content_panels = Page.content_panels + [FieldPanel("shared_tags")] + + def append_all_shared_articles(self, previous_query): + districtArticleQuery: models.QuerySet = getattr( + sys.modules["district"], "DistrictArticlePage" + ).objects + uniwebArticlePageQuery: models.QuerySet = getattr( + sys.modules["uniweb"], "UniwebArticlePage" + ).objects + mainArticlePageQuery: models.QuerySet = getattr( + sys.modules["main"], "MainArticlePage" + ).objects + + district_by_slug = ( + districtArticleQuery.live() + .specific() + .filter( + shared_tags__slug__in=self.shared_tags.values_list("slug", flat=True) + ) + ) + uniweb_by_slug = ( + uniwebArticlePageQuery.live() + .specific() + .filter( + shared_tags__slug__in=self.shared_tags.values_list("slug", flat=True) + ) + ) + main_by_slug = ( + mainArticlePageQuery.live() + .specific() + .filter( + shared_tags__slug__in=self.shared_tags.values_list("slug", flat=True) + ) + ) + + ordered_articles = ( + district_by_slug.union(uniweb_by_slug) + .union(main_by_slug) + .union(previous_query) + .distinct() + .annotate( + article_date=Coalesce( + "-districtarticlepage__date", "-uniwebarticlepage__date", "-date" + ), + shared=True, + ) + .order_by("aritcle_date") + ) + + return ordered_articles + + def get_article_page_by_slug(self, slug: str): + articles = self.append_all_shared_articles() + return articles.filter(page_ptr__path=slug).first() + + def setup_article_page_context(self, request): + slug = request.GET.get("slug", "") + return self.get_article_page_by_slug(slug).serve(request) + + def get_search_tags_params(self, site_article_ids: list): + return { + "districtarticlepage__id__in": site_article_ids, + "uniwebarticlepage__id__in": site_article_ids, + "mainarticlepage__id__in": site_article_ids, + } + + def get_search_article_params_by_tags(self, tag: str): + return { + "uniwebarticlepage__tags__name": tag, + "districtarticlepage__tags__name": tag, + "mainarticlepage__tags__name": tag, + } + + class Meta: + abstract = True diff --git a/uniweb/models.py b/uniweb/models.py index 13718a586eddf97d7ccfc9897787e7a1d6646fc3..520635a14180541dea00b5b5d6c298969682bcc1 100644 --- a/uniweb/models.py +++ b/uniweb/models.py @@ -7,7 +7,7 @@ from django.db import models from django.utils.translation import gettext_lazy from modelcluster.contrib.taggit import ClusterTaggableManager from modelcluster.fields import ParentalKey -from taggit.models import TaggedItemBase +from taggit.models import Tag, TaggedItemBase from wagtail import blocks from wagtail.admin.panels import ( FieldPanel, @@ -19,6 +19,7 @@ from wagtail.admin.panels import ( ) from wagtail.contrib.forms.models import AbstractForm, AbstractFormField from wagtail.contrib.forms.panels import FormSubmissionsPanel +from wagtail.contrib.routable_page.models import RoutablePageMixin, route from wagtail.contrib.table_block.blocks import TableBlock from wagtail.fields import RichTextField, StreamField from wagtail.images.blocks import ImageChooserBlock @@ -30,9 +31,11 @@ from shared.blocks import ChartBlock, NewsletterSubscriptionBlock from shared.const import RICH_TEXT_DEFAULT_FEATURES from shared.models import ( ArticleMixin, + ArticlesMixin, ExtendedMetadataHomePageMixin, ExtendedMetadataPageMixin, FooterMixin, + SharedTaggedUniwebArticle, SubpageMixin, ) from shared.utils import make_promote_panels, strip_all_html_tags, trim_to_length @@ -509,12 +512,19 @@ class UniwebCalendarPage(SubpageMixin, MetadataPageMixin, CalendarMixin, Page): class UniwebArticlesIndexPage( - Page, ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin + RoutablePageMixin, + Page, + ExtendedMetadataPageMixin, + SubpageMixin, + MetadataPageMixin, + ArticlesMixin, ): ### FIELDS ### PANELS + content_panels = ArticlesMixin.content_panels + promote_panels = make_promote_panels() settings_panels = [] @@ -526,6 +536,10 @@ class UniwebArticlesIndexPage( ### OTHERS + @route(r"^sdilene/$", name="shared") + def shared(self, request): + return self.setup_article_page_context(request) + class Meta: verbose_name = "Sekce článků" @@ -534,15 +548,16 @@ class UniwebArticlesIndexPage( num = request.GET.get("page") tag = request.GET.get("tag") - articles = ( - self.get_children().live().specific().order_by("-uniwebarticlepage__date") + articles = self.append_all_shared_articles( + self.get_children().live().specific() ) + if tag is not None: - articles = articles.filter(uniwebarticlepage__tags__name=tag) + articles = articles.filter(**self.get_search_article_params_by_tags(tag)) context["articles"] = Paginator(articles, ARTICLES_PER_PAGE).get_page(num) - context["tags"] = UniwebArticleTag.tags_for(UniwebArticlePage).filter( - uniwebarticlepage__in=articles + context["tags"] = Tag.objects.filter( + **self.get_search_tags_params(articles.values_list("id", flat=True)) ) context["active_tag"] = tag return context @@ -554,10 +569,14 @@ class UniwebArticlePage( ### FIELDS tags = ClusterTaggableManager(through=UniwebArticleTag, blank=True) + shared_tags = ClusterTaggableManager(through=SharedTaggedUniwebArticle, blank=True) ### PANELS - content_panels = ArticleMixin.content_panels + [FieldPanel("tags")] + content_panels = ArticleMixin.content_panels + [ + FieldPanel("tags"), + FieldPanel("shared_tags"), + ] promote_panels = make_promote_panels( admin_help.build(admin_help.NO_SEO_TITLE, admin_help.NO_DESCRIPTION_USE_PEREX),