From fe6e0392a99cb7e7402c50ee46d7b5129f2dc2a1 Mon Sep 17 00:00:00 2001
From: "jindra12.underdark" <jindra12.underdark@gmail.com>
Date: Sat, 15 Jul 2023 02:48:43 +0200
Subject: [PATCH] Commit experimental changes for the queries

#210
---
 district/models.py        |  13 ++-
 instagram_utils/models.py |   8 +-
 main/models.py            |  21 +++--
 shared/models.py          | 183 ++++++++++++++++++++++++++++++--------
 uniweb/models.py          |   9 +-
 5 files changed, 179 insertions(+), 55 deletions(-)

diff --git a/district/models.py b/district/models.py
index 5a68c0fb..dc836526 100644
--- a/district/models.py
+++ b/district/models.py
@@ -53,6 +53,7 @@ from shared.models import (
     ExtendedMetadataPageMixin,
     FooterMixin,
     MenuMixin,
+    SharedArticleTypes,
     SharedTaggedDistrictArticle,
     SubpageMixin,
 )
@@ -406,7 +407,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
+        verbose_name="Tagy pro sdĂ­lenĂ­ mezi weby",
+        through=SharedTaggedDistrictArticle,
+        blank=True,
     )
     thumb_image = models.ForeignKey(
         "wagtailimages.Image",
@@ -535,7 +538,9 @@ class DistrictArticlesPage(
     def get_context(self, request):
         context = super().get_context(request)
         context["articles"] = Paginator(
-            self.append_all_shared_articles(self.get_children().live().specific()),
+            self.append_all_shared_articles(
+                SharedArticleTypes.DISTRICT, DistrictArticlePage.objects.child_of(self)
+            ),
             self.max_items,
         ).get_page(request.GET.get("page"))
         return context
@@ -559,7 +564,7 @@ class DistrictArticlesPage(
         context = super().get_context(request)
 
         site_article_ids = self.append_all_shared_articles(
-            self.get_children().live().specific()
+            SharedArticleTypes.DISTRICT, DistrictArticlePage.objects.child_of(self)
         ).values_list("id", flat=True)
 
         # NaplnĂ­m "tag" a "article_page_list" parametry
@@ -577,7 +582,7 @@ class DistrictArticlesPage(
         separátně, ale pak by se musel rozpadnout ten try/except na více bloků.
         """
         article_page_qs = self.append_all_shared_articles(
-            DistrictArticlePage.objects
+            SharedArticleTypes.DISTRICT, DistrictArticlePage.objects
         ).filter(id__in=site_article_ids)
 
         try:
diff --git a/instagram_utils/models.py b/instagram_utils/models.py
index 579f9d56..d0b80901 100644
--- a/instagram_utils/models.py
+++ b/instagram_utils/models.py
@@ -1,4 +1,5 @@
 import datetime
+import logging
 import mimetypes
 import uuid
 
@@ -10,6 +11,9 @@ def get_current_datetime() -> datetime.datetime:
     return datetime.datetime.now(tz=datetime.timezone.utc)
 
 
+logger = logging.getLogger(__name__)
+
+
 def get_instagram_image_path(instance, filename) -> str:
     mimetypes_instance = mimetypes.MimeTypes()
     guessed_type = mimetypes_instance.guess_type(filename, strict=False)[0]
@@ -92,9 +96,7 @@ class InstagramMixin(models.Model):
             )
             service.perform_update()
         except Exception:
-            logger.error(
-                "Instagram post update failed", exc_info=True
-            )
+            logger.error("Instagram post update failed", exc_info=True)
 
         super().save(*args, **kwargs)
 
diff --git a/main/models.py b/main/models.py
index 2afb57ff..6d4d882b 100644
--- a/main/models.py
+++ b/main/models.py
@@ -1,13 +1,10 @@
-from datetime import date, timedelta
 from functools import cached_property
 
 from dateutil.relativedelta import relativedelta
 from django.conf import settings
 from django.contrib import messages
 from django.core.paginator import Paginator
-from django.core.validators import RegexValidator
 from django.db import models
-from django.forms import ValidationError
 from django.http import HttpResponseRedirect, JsonResponse
 from django.shortcuts import render
 from django.utils import timezone
@@ -22,7 +19,7 @@ from wagtail.admin.panels import (
     PageChooserPanel,
     TabbedInterface,
 )
-from wagtail.blocks import CharBlock, RichTextBlock
+from wagtail.blocks import RichTextBlock
 from wagtail.contrib.routable_page.models import RoutablePageMixin, route
 from wagtail.fields import RichTextField, StreamField
 from wagtail.models import Page
@@ -38,6 +35,7 @@ from shared.models import (  # MenuMixin,
     ArticlesMixin,
     ExtendedMetadataHomePageMixin,
     ExtendedMetadataPageMixin,
+    SharedArticleTypes,
     SharedTaggedMainArticle,
     SubpageMixin,
 )
@@ -515,9 +513,9 @@ class MainArticlesPage(
 
     def get_all_articles_search_response(self, request):
         article_paginator = Paginator(
-            self.append_all_shared_articles(MainArticlePage.objects.live()).search(
-                request.GET["q"]
-            ),
+            self.append_all_shared_articles(
+                SharedArticleTypes.MAIN, MainArticlePage.objects
+            ).search(request.GET["q"]),
             10,
         )
         article_page = article_paginator.get_page(request.GET.get("page", 1))
@@ -545,7 +543,8 @@ class MainArticlesPage(
             query = request.GET["q"]
 
             article_results = self.append_all_shared_articles(
-                MainArticlePage.objects.live()
+                SharedArticleTypes.MAIN,
+                MainArticlePage.objects,
             ).search(query)[:11]
 
             return render(
@@ -629,7 +628,11 @@ 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)
+    shared_tags = ClusterTaggableManager(
+        verbose_name="Tagy pro sdĂ­lenĂ­ mezi weby",
+        through=SharedTaggedMainArticle,
+        blank=True,
+    )
 
     search_fields = Page.search_fields + [
         index.SearchField("title"),
diff --git a/shared/models.py b/shared/models.py
index 8082e2cc..43cc1577 100644
--- a/shared/models.py
+++ b/shared/models.py
@@ -1,8 +1,9 @@
 import logging
-import sys
+from enum import Enum
 
+from django.apps import apps
 from django.db import models
-from django.db.models.functions import Coalesce
+from django.db.models.expressions import F, Value
 from django.utils import timezone
 from modelcluster.fields import ParentalKey, ParentalManyToManyField
 from taggit.models import ItemBase, TagBase
@@ -257,61 +258,169 @@ class SharedTaggedMainArticle(ItemBase):
     )
 
 
+class SharedArticleTypes(Enum):
+    DISTRICT = "district"
+    UNIWEB = "uniweb"
+    MAIN = "main"
+
+
 class ArticlesMixin(models.Model):
     shared_tags = ParentalManyToManyField(
-        "shared.SharedTag", related_name="Sdílené tagy", blank=True
+        "shared.SharedTag",
+        verbose_name="Výběr tagů pro články sdílené mezi sítěmi",
+        help_text="Pro výběr jednoho tagu klikněte na tag a uložte nebo publikujte stránku. Pro výběr více tagů využijte podržte Ctrl a vyberte příslušné 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
+    def append_all_shared_articles(
+        self, source: SharedArticleTypes | None, previous_query: models.QuerySet | None
+    ):
+        """
+        To prevent circular deps, we get class models during runtime
+        """
+        DistrictArticlePage = apps.get_model(app_label="district.DistrictArticlePage")
+        UniwebArticlePage = apps.get_model(app_label="uniweb.UniwebArticlePage")
+        MainArticlePage = apps.get_model(app_label="main.MainArticlePage")
+
+        """
+            In order to balance union() requirements for tables with same-fields only, we are adding null fields using values().
+            These values must be in correct order
+        """
+
+        main_fields = {
+            "page_ptr_id": F("page_ptr_id"),
+            "perex": F("perex"),
+            "date": F("date"),
+            "author": F("author"),
+            "image_id": F("image_id"),
+            "search_image_id": F("search_image_id"),
+            "content": F("content"),
+            "author_page_id": F("author_page_id"),
+            "region": F("region"),
+            "article_type": F("article_type"),
+            "is_black": F("is_black"),
+            "thumb_image_id": F("search_image_id"),
+        }
+
+        district_fields = {
+            "page_ptr_id": F("page_ptr_id"),
+            "perex": F("perex"),
+            "date": F("date"),
+            "author": F("author"),
+            "image_id": F("image_id"),
+            "search_image_id": F("search_image_id"),
+            "content": F("content"),
+            "author_page_id": F("author_page_id"),
+            "region": Value("", models.CharField()),
+            "article_type": Value(0, models.PositiveSmallIntegerField()),
+            "is_black": F("is_black"),
+            "thumb_image_id": F("thumb_image_id"),
+        }
+
+        uniweb_fields = {
+            "page_ptr_id": F("page_ptr_id"),
+            "perex": F("perex"),
+            "date": F("date"),
+            "author": F("author"),
+            "image_id": F("image_id"),
+            "search_image_id": F("search_image_id"),
+            "content": F("content"),
+            "author_page_id": Value(
+                None,
+                output_field=models.ForeignKey(
+                    DistrictArticlePage, blank=True, on_delete=models.SET_NULL
+                ),
+            ),
+            "region": Value("", models.CharField()),
+            "article_type": Value(0, models.PositiveSmallIntegerField()),
+            "is_black": Value(False, models.BooleanField()),
+            "thumb_image_id": F("search_image_id"),
+        }
+
+        districtArticleQuery: models.QuerySet = DistrictArticlePage.objects
+        uniwebArticlePageQuery: models.QuerySet = UniwebArticlePage.objects
+        mainArticlePageQuery: models.QuerySet = MainArticlePage.objects
 
         district_by_slug = (
-            districtArticleQuery.live()
-            .specific()
-            .filter(
-                shared_tags__slug__in=self.shared_tags.values_list("slug", flat=True)
+            (
+                districtArticleQuery.filter(
+                    shared_tags__slug__in=self.shared_tags.values_list(
+                        "slug", flat=True
+                    )
+                )
             )
+            .live()
+            .specific()
         )
         uniweb_by_slug = (
-            uniwebArticlePageQuery.live()
-            .specific()
-            .filter(
-                shared_tags__slug__in=self.shared_tags.values_list("slug", flat=True)
+            (
+                uniwebArticlePageQuery.filter(
+                    shared_tags__slug__in=self.shared_tags.values_list(
+                        "slug", flat=True
+                    )
+                )
             )
+            .live()
+            .specific()
         )
         main_by_slug = (
-            mainArticlePageQuery.live()
-            .specific()
-            .filter(
-                shared_tags__slug__in=self.shared_tags.values_list("slug", flat=True)
+            (
+                mainArticlePageQuery.filter(
+                    shared_tags__slug__in=self.shared_tags.values_list(
+                        "slug", flat=True
+                    )
+                )
             )
+            .live()
+            .specific()
         )
 
-        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,
+        results = (
+            main_by_slug.values(
+                **main_fields, shared=Value(True, output_field=models.BooleanField())
+            )
+            .union(
+                uniweb_by_slug.values(
+                    **uniweb_fields,
+                    shared=Value(True, output_field=models.BooleanField()),
+                )
+            )
+            .union(
+                district_by_slug.values(
+                    **district_fields,
+                    shared=Value(True, output_field=models.BooleanField()),
+                )
             )
-            .order_by("aritcle_date")
         )
 
-        return ordered_articles
+        if previous_query is not None:
+            prepared_query = previous_query.live().specific()
+
+            if source is SharedArticleTypes.DISTRICT:
+                prepared_query = prepared_query.values(
+                    **district_fields,
+                    shared=Value(False, output_field=models.BooleanField()),
+                )
+            elif source is SharedArticleTypes.UNIWEB:
+                prepared_query = prepared_query.values(
+                    **uniweb_fields,
+                    shared=Value(False, output_field=models.BooleanField()),
+                )
+            elif source is SharedArticleTypes.MAIN:
+                prepared_query = prepared_query.values(
+                    **main_fields,
+                    shared=Value(False, output_field=models.BooleanField()),
+                )
+
+            results = results.union(prepared_query)
+
+        results = results.order_by("date")
+
+        print(results.query)
+
+        return results
 
     def get_article_page_by_slug(self, slug: str):
         articles = self.append_all_shared_articles()
diff --git a/uniweb/models.py b/uniweb/models.py
index 520635a1..72bc8b9e 100644
--- a/uniweb/models.py
+++ b/uniweb/models.py
@@ -35,6 +35,7 @@ from shared.models import (
     ExtendedMetadataHomePageMixin,
     ExtendedMetadataPageMixin,
     FooterMixin,
+    SharedArticleTypes,
     SharedTaggedUniwebArticle,
     SubpageMixin,
 )
@@ -549,7 +550,7 @@ class UniwebArticlesIndexPage(
         tag = request.GET.get("tag")
 
         articles = self.append_all_shared_articles(
-            self.get_children().live().specific()
+            SharedArticleTypes.UNIWEB, UniwebArticlePage.objects.child_of(self)
         )
 
         if tag is not None:
@@ -569,7 +570,11 @@ class UniwebArticlePage(
     ### FIELDS
 
     tags = ClusterTaggableManager(through=UniwebArticleTag, blank=True)
-    shared_tags = ClusterTaggableManager(through=SharedTaggedUniwebArticle, blank=True)
+    shared_tags = ClusterTaggableManager(
+        verbose_name="Tagy pro sdĂ­lenĂ­ mezi weby",
+        through=SharedTaggedUniwebArticle,
+        blank=True,
+    )
 
     ### PANELS
 
-- 
GitLab