from django.conf import settings from django.core.cache import cache from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator from django.db import models from django.shortcuts import redirect from django.utils.translation import gettext_lazy from wagtail.admin.edit_handlers import ( FieldPanel, HelpPanel, MultiFieldPanel, StreamFieldPanel, ) from wagtail.core import blocks from wagtail.core.fields import RichTextField, StreamField from wagtail.core.models import Page from wagtail.images.blocks import ImageChooserBlock from wagtail.images.edit_handlers import ImageChooserPanel from wagtailmetadata.models import MetadataPageMixin from tuning import help from .forms import DonateForm from .utils import get_donated_amount_from_api class SubpageMixin: """Must be used in class definition before MetadataPageMixin!""" # flag for rendering anchor links in menu is_home = False @property def root_page(self): if not hasattr(self, "_root_page"): self._root_page = self.get_ancestors().type(DonateHomePage).specific().get() return self._root_page def get_meta_image(self): return self.search_image or self.root_page.get_meta_image() class DonateFormMixin(models.Model): """Pages which has donate form. Must be in class definition before Page!""" portal_project_id = models.IntegerField( "ID projektu v darovacím portálu", blank=True, null=True ) class Meta: abstract = True def serve(self, request): if request.method == "POST": form = DonateForm(request.POST) if form.is_valid(): url = form.get_redirect_url() return redirect(url) return super().serve(request) @property def show_donate_form(self): return bool(self.portal_project_id) def get_url(page, dest_page_type): try: return page.get_children().type(dest_page_type).live().first().get_url() except (Page.DoesNotExist, AttributeError): return "#" class DonateHomePage(DonateFormMixin, Page, MetadataPageMixin): ### FIELDS # lead section lead_title = models.CharField("hlavní nadpis", max_length=250, blank=True) lead_body = models.TextField("hlavní popis", blank=True) lead_video = models.URLField("video na youtube", blank=True, null=True) lead_preview = models.ForeignKey( "wagtailimages.Image", on_delete=models.PROTECT, blank=True, null=True, verbose_name="náhled videa", ) # support section support_title = models.CharField("podpoř stranu nadpis", max_length=250, blank=True) support_body = models.TextField("podpoř stranu popis", blank=True) # projects section project_title = models.CharField( "podpoř projekt nadpis", max_length=250, blank=True ) project_body = models.TextField("podpoř projekt popis", blank=True) # regions section region_title = models.CharField("podpoř kraj nadpis", max_length=250, blank=True) region_body = models.TextField("podpoř kraj popis", blank=True) # settings facebook = models.URLField("Facebook URL", blank=True, null=True) instagram = models.URLField("Instagram URL", blank=True, null=True) twitter = models.URLField("Twitter URL", blank=True, null=True) flickr = models.URLField("Flickr URL", blank=True, null=True) matomo_id = models.IntegerField( "Matomo ID pro sledování návštěvnosti", blank=True, null=True ) ### PANELS content_panels = Page.content_panels + [ MultiFieldPanel( [ FieldPanel("lead_title"), FieldPanel("lead_body"), FieldPanel("lead_video"), ImageChooserPanel("lead_preview"), ], "hlavní sekce", ), MultiFieldPanel( [FieldPanel("support_title"), FieldPanel("support_body")], "podpoř stranu", ), MultiFieldPanel( [FieldPanel("project_title"), FieldPanel("project_body")], "podpoř projekt", ), MultiFieldPanel( [FieldPanel("region_title"), FieldPanel("region_body")], "podpoř kraj", ), ] promote_panels = [ MultiFieldPanel( [ FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("search_image"), HelpPanel(help.build(help.IMPORTANT_TITLE)), ], gettext_lazy("Common page configuration"), ), ] settings_panels = [ MultiFieldPanel( [ FieldPanel("facebook"), FieldPanel("instagram"), FieldPanel("twitter"), FieldPanel("flickr"), ], "sociální sítě", ), FieldPanel("matomo_id"), FieldPanel("portal_project_id"), ] ### RELATIONS subpage_types = [ "donate.DonateRegionIndexPage", "donate.DonateProjectIndexPage", "donate.DonateInfoPage", "donate.DonateTextPage", ] ### OTHERS # flag for rendering anchor links in menu is_home = True class Meta: verbose_name = "Dary" @property def root_page(self): return self @property def info_page_url(self): return get_url(self, DonateInfoPage) @property def projects_page_url(self): return get_url(self, DonateProjectIndexPage) @property def regions_page_url(self): return get_url(self, DonateRegionIndexPage) @property def has_projects(self): return self.get_descendants().type(DonateProjectPage).live().exists() def get_context(self, request): context = super().get_context(request) context["regions"] = ( self.get_descendants().type(DonateRegionPage).live().specific() ) context["projects"] = ( self.get_descendants() .type(DonateProjectPage) .live() .specific() .order_by("-donateprojectpage__date")[:3] ) return context class DonateRegionIndexPage(Page, SubpageMixin, MetadataPageMixin): ### PANELS promote_panels = [ MultiFieldPanel( [ FieldPanel("slug"), FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("search_image"), HelpPanel(help.build(help.NO_SEO_TITLE, help.NO_SEARCH_IMAGE)), ], gettext_lazy("Common page configuration"), ), ] settings_panels = [] ### RELATIONS parent_page_types = ["donate.DonateHomePage"] subpage_types = ["donate.DonateRegionPage"] ### OTHERS class Meta: verbose_name = "Přehled krajů" def get_context(self, request): context = super().get_context(request) context["regions"] = self.get_children().live().specific() return context class DonateRegionPage(DonateFormMixin, Page, SubpageMixin, MetadataPageMixin): ### FIELDS main_title = models.CharField("hlavní nadpis na stránce", max_length=250) body = RichTextField("obsah") ### PANELS content_panels = Page.content_panels + [ FieldPanel("main_title"), FieldPanel("body", classname="full"), ] promote_panels = [ MultiFieldPanel( [ FieldPanel("slug"), FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("search_image"), HelpPanel( help.build( "Pokud není zadán <strong>Titulek stránky</strong>, použije " "se <strong>Hlavní nadpis</strong> (tab obsah).", help.NO_SEARCH_IMAGE, ) ), ], gettext_lazy("Common page configuration"), ), ] settings_panels = [FieldPanel("portal_project_id")] ### RELATIONS parent_page_types = ["donate.DonateRegionIndexPage"] subpage_types = ["donate.DonateTargetedDonationsPage"] ### OTHERS class Meta: verbose_name = "Kraj" def get_meta_title(self): return self.seo_title or self.main_title @property def targeted_donations_page_url(self): return get_url(self, DonateTargetedDonationsPage) @property def has_targeted_donations(self): return self.get_descendants().type(DonateTargetedDonationsPage).live().exists() class DonateProjectIndexPage(Page, SubpageMixin, MetadataPageMixin): ### PANELS promote_panels = [ MultiFieldPanel( [ FieldPanel("slug"), FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("search_image"), HelpPanel(help.build(help.NO_SEO_TITLE, help.NO_SEARCH_IMAGE)), ], gettext_lazy("Common page configuration"), ), ] settings_panels = [] ### RELATIONS parent_page_types = ["donate.DonateHomePage"] subpage_types = ["donate.DonateProjectPage"] ### OTHERS class Meta: verbose_name = "Přehled projektů" def get_context(self, request): context = super().get_context(request) paginator = Paginator( self.get_children().live().specific().order_by("-donateprojectpage__date"), 6, ) page = request.GET.get("page") try: projects = paginator.page(page) except PageNotAnInteger: projects = paginator.page(1) except EmptyPage: projects = paginator.page(paginator.num_pages) context["projects"] = projects return context class DonateProjectPage(DonateFormMixin, Page, SubpageMixin, MetadataPageMixin): ### FIELDS date = models.DateField("běží od") perex = models.TextField("krátký popis") body = RichTextField("obsah") is_new = models.BooleanField('označení "nový projekt"', default=False) allow_periodic_donations = models.BooleanField( "umožnit pravidelné dary", default=False ) photo = models.ForeignKey( "wagtailimages.Image", verbose_name="fotka", on_delete=models.PROTECT, null=True, blank=True, ) gallery = StreamField( [("photo", ImageChooserBlock(label="fotka"))], verbose_name="galerie fotek", blank=True, ) expected_amount = models.IntegerField("očekávaná částka", blank=True, null=True) donated_amount = models.IntegerField("vybraná částka", blank=True, null=True) # we will use photo as search image search_image = None ### PANELS content_panels = Page.content_panels + [ MultiFieldPanel( [FieldPanel("is_new"), FieldPanel("perex"), ImageChooserPanel("photo")], "info do přehledu projektů", ), FieldPanel("date"), FieldPanel("expected_amount"), FieldPanel("body", classname="full"), StreamFieldPanel("gallery"), ] promote_panels = [ MultiFieldPanel( [ FieldPanel("slug"), FieldPanel("seo_title"), FieldPanel("search_description"), HelpPanel( help.build( "Pokud není zadán <strong>Titulek stránky</strong>, použije " "se „Podpoř projekt <strong>Název</strong>“ (tab obsah).", "Pokud není zadán <strong>Popis vyhledávání</strong>, použije " "se prvních 150 znaků <strong>Perexu</strong> (tab obsah).", ) ), ], gettext_lazy("Common page configuration"), ), ] settings_panels = Page.settings_panels + [ MultiFieldPanel( [FieldPanel("portal_project_id"), FieldPanel("allow_periodic_donations"),], "nastavení darů", ), ] ### RELATIONS parent_page_types = ["donate.DonateProjectIndexPage"] subpage_types = [] ### OTHERS class Meta: verbose_name = "Projekt" def get_meta_image(self): return self.photo def get_meta_title(self): return self.seo_title or self.title def get_meta_description(self): if self.search_description: return self.search_description if len(self.perex) > 150: return str(self.perex)[:150] + "..." return self.perex def get_donated_amount(self): if self.portal_project_id is None: return 0 # instance caching for multiple method calls during one request if not hasattr(self, "_donated_amount"): # cache portal API calls (defaults to 5 min) key = f"donated_amount_{self.portal_project_id}" amount = cache.get(key) if amount is None: amount = get_donated_amount_from_api(self.portal_project_id) if amount is not None: # save amount into database to be used if next API calls fails self.donated_amount = amount self.save() cache.set(key, amount, settings.DONATE_PORTAL_API_CACHE_TIMEOUT) self._donated_amount = self.donated_amount or 0 return self._donated_amount @property def donated_percentage(self): if not self.expected_amount: return 0 if self.get_donated_amount() >= self.expected_amount: return 100 return round(self.get_donated_amount() / self.expected_amount * 100) def get_context(self, request): context = super().get_context(request) context["other_projects"] = ( self.get_siblings(inclusive=False) .live() .specific() .order_by("-donateprojectpage__date")[:3] ) return context class DonateTextPage(Page, SubpageMixin, MetadataPageMixin): ### FIELDS body = RichTextField("obsah", blank=True) ### PANELS content_panels = Page.content_panels + [ FieldPanel("body", classname="full"), ] promote_panels = [ MultiFieldPanel( [ FieldPanel("slug"), FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("search_image"), HelpPanel(help.build(help.NO_SEO_TITLE, help.NO_SEARCH_IMAGE)), ], gettext_lazy("Common page configuration"), ), ] settings_panels = [] ### RELATIONS parent_page_types = ["donate.DonateHomePage"] subpage_types = [] ### OTHERS class Meta: verbose_name = "Stránka s textem" class DonateInfoPage(DonateFormMixin, Page, SubpageMixin, MetadataPageMixin): ### FIELDS body = RichTextField("obsah", blank=True) ### PANELS content_panels = Page.content_panels + [ FieldPanel("body", classname="full"), ] promote_panels = [ MultiFieldPanel( [ FieldPanel("slug"), FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("search_image"), HelpPanel(help.build(help.NO_SEO_TITLE, help.NO_SEARCH_IMAGE)), ], gettext_lazy("Common page configuration"), ), ] settings_panels = [] ### RELATIONS parent_page_types = ["donate.DonateHomePage"] subpage_types = [] ### OTHERS class Meta: verbose_name = "Infostránka s formulářem" # use portal_project_id from home page @property def portal_project_id(self): return self.get_parent().specific.portal_project_id class TargetedDonationBlock(blocks.StructBlock): title = blocks.CharBlock(label="název") description = blocks.CharBlock(label="popis", required=False) portal_project_id = blocks.IntegerBlock( label="ID projektu v darovacím portálu", required=False, help_text="Pokud není zadáno ID projektu, tak se adresný dar nezobrazí.", ) class Meta: label = "adresný dar" class DonateTargetedDonationsPage( DonateFormMixin, Page, SubpageMixin, MetadataPageMixin ): ### FIELDS targeted_donations = StreamField( [("item", TargetedDonationBlock())], verbose_name="adresné dary", blank=True ) # page does not have specific portal_project_id portal_project_id = None ### PANELS content_panels = Page.content_panels + [ StreamFieldPanel("targeted_donations"), ] promote_panels = [ MultiFieldPanel( [ FieldPanel("slug"), FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("search_image"), HelpPanel(help.build(help.NO_SEO_TITLE, help.NO_SEARCH_IMAGE)), ], gettext_lazy("Common page configuration"), ), ] settings_panels = [] ### RELATIONS parent_page_types = ["donate.DonateRegionPage"] subpage_types = [] ### OTHERS class Meta: verbose_name = "Adresné dary" @property def show_donate_form(self): return bool(self.targeted_donations)