Skip to content
Snippets Groups Projects
Commit 46ece396 authored by Tomáš Valenta's avatar Tomáš Valenta
Browse files

move further models to mixins

parent f331981c
Branches
No related tags found
2 merge requests!876Fix shared articles & release elections web,!863Add elections web
Pipeline #16082 passed
......@@ -9,7 +9,6 @@ from wagtail.blocks import (
TextBlock,
URLBlock,
)
from wagtail.documents.blocks import DocumentChooserBlock
from wagtail.images.blocks import ImageChooserBlock
from shared.blocks import CardLinkBlockMixin, CardLinkWithHeadlineBlockMixin, CTAMixin
......@@ -250,16 +249,6 @@ class PersonContactBoxBlock(StructBlock):
# ARTICLE BLOCKS
class ArticleQuoteBlock(StructBlock):
quote = CharBlock(label="Citace")
autor_name = CharBlock(label="Jméno autora")
class Meta:
icon = "user"
label = "Blok citace"
template = "main/includes/legacy/article_quote_block.html"
class ArticleImageMixin(StructBlock):
image = ImageChooserBlock(label="Obrázek")
image_source = CharBlock(
......@@ -285,15 +274,6 @@ class ArticleRightImageBlock(ArticleImageMixin):
template = "main/includes/molecules/articles/article_richtext_content_with_right_image.html"
class ArticleDownloadBlock(StructBlock):
file = DocumentChooserBlock(label="Stáhnutelný soubor")
class Meta:
icon = "user"
label = "Blok stáhnutelného dokumentu"
template = "main/includes/molecules/blocks/article_download_block.html"
class TwoTextColumnBlock(StructBlock):
text_column_1 = RichTextBlock(label="První sloupec textu")
text_column_2 = RichTextBlock(label="Druhý sloupec textu")
......@@ -352,5 +332,6 @@ class TeamBlock(StructBlock):
# --- TODO: Remove legacy blocks used in migrations only
class LinkBlock(StructBlock):
pass
# Generated by Django 4.1.10 on 2024-01-03 19:04
from django.db import migrations
import shared.blocks.main
import wagtail.blocks
import wagtail.fields
from django.db import migrations
import shared.blocks.main
class Migration(migrations.Migration):
......
......@@ -29,6 +29,7 @@ from wagtail.search import index
from wagtailmetadata.models import MetadataPageMixin
from calendar_utils.models import CalendarMixin
from shared import blocks as shared_blocks
from shared.forms import SubscribeForm
from shared.models import ( # MenuMixin,
ArticleMixin,
......@@ -36,22 +37,20 @@ from shared.models import ( # MenuMixin,
ArticlesPageMixin,
ExtendedMetadataHomePageMixin,
ExtendedMetadataPageMixin,
MainArticlePageMixin,
MainArticlesPageMixin,
MainHomePageMixin,
SharedTaggedMainArticle,
SubpageMixin,
)
from shared.utils import make_promote_panels, subscribe_to_newsletter
from shared import blocks as shared_blocks
from tuning import admin_help
from . import blocks
from .constants import MONTH_NAMES
from .forms import JekyllImportForm
from .menu import MenuMixin, PageInMenuMixin
from shared.models import MainHomePageMixin
class MainHomePage(MainHomePageMixin):
# content
content = StreamField(
......@@ -128,251 +127,10 @@ class MainHomePage(MainHomePageMixin):
return self.setup_article_page_context(request)
class MainArticlesPage(
RoutablePageMixin,
ExtendedMetadataPageMixin,
SubpageMixin,
MetadataPageMixin,
ArticlesPageMixin,
PageInMenuMixin,
Page,
):
last_import_log = models.TextField(
"Výstup z posledního importu", null=True, blank=True
)
perex = models.TextField()
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
class MainArticlesPage(MainArticlesPageMixin):
parent_page_types = ["main.MainHomePage"]
subpage_types = ["main.MainArticlePage"]
### PANELS
content_panels = Page.content_panels + [
FieldPanel("perex"),
FieldPanel("shared_tags"),
]
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 = "Rozcestník článků"
def get_base_shared_articles_query(self, filter: models.Q):
return self.materialize_shared_articles_query(
self.append_all_shared_articles_query(
MainArticlePage.objects.filter(filter)
)
.live()
.order_by("-union_date")
)
def get_article_data_list(
self,
months_back: int = 1,
search_filter: models.Q | None = None,
):
if search_filter is None:
search_filter = models.Q()
target_date_list = (
self.append_all_shared_articles_query(
MainArticlePage.objects.filter(search_filter)
)
.order_by("-union_date")
.live()
.values_list("union_date", flat=True)
)
if not target_date_list:
return []
target_date = target_date_list[0] - relativedelta(months=months_back)
first_day_of_target_month = target_date.replace(day=1)
filter = models.Q(date__gte=first_day_of_target_month) & search_filter
sorted_article_qs = self.get_base_shared_articles_query(filter)
article_data_list = []
current_month_data = self.get_empty_month_data(timezone.now().date())
for article in sorted_article_qs:
if article.date.month != current_month_data["month_number"]:
if len(current_month_data["articles"]) != 0:
# append completed month if it contains any articles
article_data_list.append(current_month_data)
current_month_data = self.get_empty_month_data(article.date)
current_month_data["articles"].append(article)
article_data_list.append(current_month_data) # last iteration
return article_data_list
def get_search_filters(self, request):
filter = models.Q()
if "tag_id" in request.GET:
tag = self.get_filtered_tag(request)
if tag is not None:
filter = filter & models.Q(tags__id=tag.id)
if "q" in request.GET:
filter = filter & models.Q(title__icontains=self.get_search_query(request))
return filter
def get_filtered_tag(self, request) -> Tag | None:
if "tag_id" in request.GET:
try:
return Tag.objects.filter(id=int(request.GET["tag_id"])).first()
except Exception:
pass
return None
def get_search_query(self, request) -> str | None:
if "q" in request.GET:
return request.GET["q"]
def get_context(self, request, get_articles: bool = True, *args, **kwargs):
ctx = super().get_context(request, args, kwargs)
if get_articles:
filtered_tag = self.get_filtered_tag(request)
if filtered_tag is not None:
ctx["filtered_tag"] = filtered_tag
search_query = self.get_search_query(request)
if search_query is not None:
ctx["search_query"] = search_query
search_filter = self.get_search_filters(request)
article_timeline_list = self.get_article_data_list(1, search_filter)
ctx["article_timeline_list"] = article_timeline_list
ctx["show_next_timeline_articles"] = len(
self.get_base_shared_articles_query(search_filter)
) != 0 and (
self.materialize_shared_articles_query(
self.append_all_shared_articles_query(
MainArticlePage.objects.filter(search_filter)
)
.live()
.order_by("union_date")[:2] # LIMIT 2
)[0]
not in article_timeline_list[-1]["articles"]
)
tags = []
for article in MainArticlePage.objects.all()[:50]:
for tag in article.tags.all():
if tag in tags:
continue
tags.append(tag)
ctx["tags"] = tags
# meow
return ctx
def get_timeline_articles_response(self, request):
try:
months = int(request.GET.get("months", None))
except ValueError:
months = 1
search_filter = self.get_search_filters(request)
article_timeline_list = self.get_article_data_list(months, search_filter)
context = {"article_timeline_list": article_timeline_list}
data = {
"html": render(
request,
"main/includes/organisms/articles/articles_timeline_list.html",
context,
).content.decode("utf-8"),
"has_next": (
len(self.get_base_shared_articles_query(search_filter)) != 0
and (
self.materialize_shared_articles_query(
self.append_all_shared_articles_query(
MainArticlePage.objects.filter(search_filter)
)
.live()
.order_by("union_date")[:2] # LIMIT 2
)[0]
not in article_timeline_list[-1]["articles"]
)
),
}
return JsonResponse(data=data, safe=False)
@route(r"^sdilene/$", name="shared")
def shared(self, request):
return self.setup_article_page_context(request)
def serve(self, request, *args, **kwargs):
if request.META.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest":
if "months" in request.GET:
return self.get_timeline_articles_response(request)
return super().serve(request, *args, **kwargs)
@staticmethod
def get_empty_month_data(date_obj):
return {
"month_number": date_obj.month,
"month_text": MONTH_NAMES[date_obj.month - 1],
"articles": [],
}
class MainArticleTag(TaggedItemBase):
content_object = ParentalKey(
......@@ -382,29 +140,7 @@ class MainArticleTag(TaggedItemBase):
)
class MainArticlePage(
ArticleMixin,
ExtendedMetadataPageMixin,
SubpageMixin,
MetadataPageMixin,
PageInMenuMixin,
Page,
):
### FIELDS
content = StreamField(
[
(
"text",
RichTextBlock(template="main/includes/atoms/text/prose_richtext.html"),
),
("quote", blocks.ArticleQuoteBlock()),
("download", blocks.ArticleDownloadBlock()),
],
verbose_name="Článek",
blank=True,
use_json_field=True,
)
class MainArticlePage(MainArticlePageMixin):
author_page = models.ForeignKey(
"main.MainPersonPage",
on_delete=models.SET_NULL,
......@@ -415,50 +151,10 @@ class MainArticlePage(
tags = ClusterTaggableManager(
through=MainArticleTag, related_name="tagged_articles", blank=True
)
shared_tags = ClusterTaggableManager(
verbose_name="Tagy pro sdílení mezi weby",
through=SharedTaggedMainArticle,
blank=True,
)
search_fields = ArticleMixin.search_fields + [
index.SearchField("author_page"),
index.FilterField("slug"),
]
### PANELS
content_panels = ArticleMixin.content_panels + [
FieldPanel("author_page"),
FieldPanel("tags"),
FieldPanel("shared_tags"),
]
promote_panels = make_promote_panels(
admin_help.build(admin_help.NO_SEO_TITLE, admin_help.NO_DESCRIPTION_USE_PEREX),
search_image=False,
)
### RELATIONS
parent_page_types = ["main.MainArticlesPage"]
subpage_types = []
### OTHERS
class Meta:
verbose_name = "Aktualita"
# def get_context(self, request): chceme/nechceme?
# context = super().get_context(request)
# context["related_articles"] = (
# self.get_siblings(inclusive=False)
# .live()
# .specific()
# .order_by("-mainarticlepage__date")[:3]
# )
# return context
class MainProgramPage(
ExtendedMetadataPageMixin, SubpageMixin, MetadataPageMixin, PageInMenuMixin, Page
......
from wagtail import blocks
from wagtail.blocks import (
CharBlock,
ListBlock,
......@@ -8,12 +7,13 @@ from wagtail.blocks import (
StructBlock,
URLBlock,
)
from wagtail.documents.blocks import DocumentChooserBlock
from .base import MenuItemBlock as MenuItemBlockBase
# Mixins (or used as such)
class CTAMixin(StructBlock):
button_link = URLBlock(label="Odkaz tlačítka")
button_text = CharBlock(label="Text tlačítka")
......@@ -34,6 +34,7 @@ class LinkBlock(StructBlock):
# Navbar
class MainMenuItemBlock(MenuItemBlockBase):
title = blocks.CharBlock(
label="Titulek",
......@@ -63,8 +64,31 @@ class SocialLinkBlock(LinkBlock):
label = "Odkaz"
# Articles
class ArticleQuoteBlock(StructBlock):
quote = CharBlock(label="Citace")
autor_name = CharBlock(label="Jméno autora")
class Meta:
icon = "user"
label = "Blok citace"
template = "main/includes/legacy/article_quote_block.html"
class ArticleDownloadBlock(StructBlock):
file = DocumentChooserBlock(label="Stáhnutelný soubor")
class Meta:
icon = "user"
label = "Blok stáhnutelného dokumentu"
template = "main/includes/molecules/blocks/article_download_block.html"
# People
class PersonContactBlock(StructBlock):
position = CharBlock(label="Název pozice", required=False)
# email, phone?
......@@ -80,6 +104,7 @@ class PersonContactBlock(StructBlock):
# Footer
class OtherLinksBlock(StructBlock):
title = CharBlock(label="Titulek")
list = ListBlock(LinkBlock, label="Seznam odkazů s titulkem")
......
......@@ -17,3 +17,18 @@ RICH_TEXT_DEFAULT_FEATURES = [
"blockquote",
"embed",
]
MONTH_NAMES = [
"Leden",
"Únor",
"Březen",
"Duben",
"Květen",
"Červen",
"Červenec",
"Srpen",
"Září",
"Říjen",
"Listopad",
"Prosinec",
]
from django import forms
from wagtail.admin.forms import WagtailAdminPageForm
from wagtail.models.collections import Collection
from shared.jekyll_import import JekyllArticleImporter
class SubscribeForm(forms.Form):
email = forms.EmailField()
confirmed = forms.BooleanField()
return_page_id = forms.IntegerField()
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="např. https://github.com/pirati-web/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, model):
# TODO: Portable function
from .models import MainArticlePage
JekyllArticleImporter(
article_parent_page=self.instance,
collection_id=self.cleaned_data["collection"].id,
url=self.cleaned_data["jekyll_repo_url"],
dry_run=self.cleaned_data["dry_run"],
use_git=True,
page_model=MainArticlePage,
).perform_import()
def save(self, commit=True):
if self.cleaned_data.get("do_import"):
self.handle_import()
return super().save(commit=commit)
......@@ -29,28 +29,29 @@ from wagtail.search import index
from wagtailmetadata.models import MetadataPageMixin
from calendar_utils.models import CalendarMixin
from shared.forms import SubscribeForm
from shared.blocks import (
ArticleDownloadBlock,
ArticleQuoteBlock,
MainMenuItemBlock,
NavbarMenuItemBlock,
OtherLinksBlock,
PersonContactBlock,
SocialLinkBlock,
)
from shared.const import MONTH_NAMES
from shared.forms import JekyllImportForm, SubscribeForm
from shared.utils import make_promote_panels, subscribe_to_newsletter
from tuning import admin_help
from .base import ( # MenuMixin,
ArticleMixin,
ArticlesMixin,
ArticlesPageMixin,
ExtendedMetadataHomePageMixin,
ExtendedMetadataPageMixin,
SharedTaggedMainArticle,
SubpageMixin,
)
from shared.utils import make_promote_panels, subscribe_to_newsletter
from tuning import admin_help
from wagtail.models import Page
from django.db import models
from wagtail.admin.panels import FieldPanel, MultiFieldPanel
from wagtail.fields import StreamField
from wagtail.models import Page
from .base import MenuMixin as MenuMixinBase
from shared.blocks import MainMenuItemBlock, NavbarMenuItemBlock, OtherLinksBlock, PersonContactBlock, SocialLinkBlock
from .base import SharedTaggedMainArticle, SubpageMixin # MenuMixin,
class MainMenuMixin(MenuMixinBase):
......@@ -261,7 +262,9 @@ class MainHomePageMixin(
context = super().get_context(request, args, kwargs)
context["article_data_list"] = self.materialize_shared_articles_query(
self.append_all_shared_articles_query(self.article_page_model.objects.live().all())
self.append_all_shared_articles_query(
self.article_page_model.objects.live().all()
)
.live()
.order_by("-union_date")[:3]
)
......@@ -373,3 +376,316 @@ class MainHomePageMixin(
@route(r"^sdilene/$", name="shared")
def shared(self, request):
return self.setup_article_page_context(request)
class MainArticlesPageMixin(
RoutablePageMixin,
ExtendedMetadataPageMixin,
SubpageMixin,
MetadataPageMixin,
ArticlesPageMixin,
PageInMenuMixin,
Page,
):
last_import_log = models.TextField(
"Výstup z posledního importu", null=True, blank=True
)
perex = models.TextField()
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 = [] # NOTE: Must be implemented
subpage_types = [] # NOTE: Must be implemented
### PANELS
content_panels = Page.content_panels + [
FieldPanel("perex"),
FieldPanel("shared_tags"),
]
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 = "Rozcestník článků"
abstract = True
def get_base_shared_articles_query(self, filter: models.Q):
return self.materialize_shared_articles_query(
self.append_all_shared_articles_query(
self.root_page.article_page_model.objects.filter(filter)
)
.live()
.order_by("-union_date")
)
def get_article_data_list(
self,
months_back: int = 1,
search_filter: models.Q | None = None,
):
if search_filter is None:
search_filter = models.Q()
target_date_list = (
self.append_all_shared_articles_query(
self.root_page.article_page_model.objects.filter(search_filter)
)
.order_by("-union_date")
.live()
.values_list("union_date", flat=True)
)
if not target_date_list:
return []
target_date = target_date_list[0] - relativedelta(months=months_back)
first_day_of_target_month = target_date.replace(day=1)
filter = models.Q(date__gte=first_day_of_target_month) & search_filter
sorted_article_qs = self.get_base_shared_articles_query(filter)
article_data_list = []
current_month_data = self.get_empty_month_data(timezone.now().date())
for article in sorted_article_qs:
if article.date.month != current_month_data["month_number"]:
if len(current_month_data["articles"]) != 0:
# append completed month if it contains any articles
article_data_list.append(current_month_data)
current_month_data = self.get_empty_month_data(article.date)
current_month_data["articles"].append(article)
article_data_list.append(current_month_data) # last iteration
return article_data_list
def get_search_filters(self, request):
filter = models.Q()
if "tag_id" in request.GET:
tag = self.get_filtered_tag(request)
if tag is not None:
filter = filter & models.Q(tags__id=tag.id)
if "q" in request.GET:
filter = filter & models.Q(title__icontains=self.get_search_query(request))
return filter
def get_filtered_tag(self, request) -> Tag | None:
if "tag_id" in request.GET:
try:
return Tag.objects.filter(id=int(request.GET["tag_id"])).first()
except Exception:
pass
return None
def get_search_query(self, request) -> str | None:
if "q" in request.GET:
return request.GET["q"]
def get_context(self, request, get_articles: bool = True, *args, **kwargs):
ctx = super().get_context(request, args, kwargs)
if get_articles:
filtered_tag = self.get_filtered_tag(request)
if filtered_tag is not None:
ctx["filtered_tag"] = filtered_tag
search_query = self.get_search_query(request)
if search_query is not None:
ctx["search_query"] = search_query
search_filter = self.get_search_filters(request)
article_timeline_list = self.get_article_data_list(1, search_filter)
ctx["article_timeline_list"] = article_timeline_list
ctx["show_next_timeline_articles"] = len(
self.get_base_shared_articles_query(search_filter)
) != 0 and (
self.materialize_shared_articles_query(
self.append_all_shared_articles_query(
self.root_page.article_page_model.objects.filter(search_filter)
)
.live()
.order_by("union_date")[:2] # LIMIT 2
)[0]
not in article_timeline_list[-1]["articles"]
)
tags = []
for article in self.root_page.article_page_model.objects.all()[:50]:
for tag in article.tags.all():
if tag in tags:
continue
tags.append(tag)
ctx["tags"] = tags
# meow
return ctx
def get_timeline_articles_response(self, request):
try:
months = int(request.GET.get("months", None))
except ValueError:
months = 1
search_filter = self.get_search_filters(request)
article_timeline_list = self.get_article_data_list(months, search_filter)
context = {"article_timeline_list": article_timeline_list}
data = {
"html": render(
request,
"main/includes/organisms/articles/articles_timeline_list.html",
context,
).content.decode("utf-8"),
"has_next": (
len(self.get_base_shared_articles_query(search_filter)) != 0
and (
self.materialize_shared_articles_query(
self.append_all_shared_articles_query(
self.root_page.article_page_model.objects.filter(
search_filter
)
)
.live()
.order_by("union_date")[:2] # LIMIT 2
)[0]
not in article_timeline_list[-1]["articles"]
)
),
}
return JsonResponse(data=data, safe=False)
@route(r"^sdilene/$", name="shared")
def shared(self, request):
return self.setup_article_page_context(request)
def serve(self, request, *args, **kwargs):
if request.META.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest":
if "months" in request.GET:
return self.get_timeline_articles_response(request)
return super().serve(request, *args, **kwargs)
@staticmethod
def get_empty_month_data(date_obj):
return {
"month_number": date_obj.month,
"month_text": MONTH_NAMES[date_obj.month - 1],
"articles": [],
}
class MainArticlePageMixin(
ArticleMixin,
ExtendedMetadataPageMixin,
SubpageMixin,
MetadataPageMixin,
PageInMenuMixin,
Page,
):
### FIELDS
content = StreamField(
[
(
"text",
RichTextBlock(template="main/includes/atoms/text/prose_richtext.html"),
),
("quote", ArticleQuoteBlock()),
("download", ArticleDownloadBlock()),
],
verbose_name="Článek",
blank=True,
use_json_field=True,
)
@property
def tags(self):
# NOTE: Must be implemented
raise NotImplementedError
shared_tags = ClusterTaggableManager(
verbose_name="Tagy pro sdílení mezi weby",
through=SharedTaggedMainArticle,
blank=True,
)
search_fields = ArticleMixin.search_fields + [
index.SearchField("author_page"),
index.FilterField("slug"),
]
### PANELS
content_panels = ArticleMixin.content_panels + [
FieldPanel("author_page"),
FieldPanel("tags"),
FieldPanel("shared_tags"),
]
promote_panels = make_promote_panels(
admin_help.build(admin_help.NO_SEO_TITLE, admin_help.NO_DESCRIPTION_USE_PEREX),
search_image=False,
)
### RELATIONS
parent_page_types = [] # NOTE: Must be implemented
subpage_types = [] # NOTE: Must be implemented
### OTHERS
class Meta:
verbose_name = "Aktualita"
abstract = True
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment