forked from ungleich-public/dynamicweb
638 lines
24 KiB
Python
638 lines
24 KiB
Python
import hashlib
|
|
|
|
from aldryn_apphooks_config.fields import AppHookConfigField
|
|
from aldryn_apphooks_config.managers.parler import AppHookConfigTranslatableManager
|
|
from cms.models import CMSPlugin
|
|
from cms.models.fields import PlaceholderField, PlaceholderRelationField
|
|
from django.conf import settings as dj_settings
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.sites.shortcuts import get_current_site
|
|
from django.core.cache import cache
|
|
from django.db import models
|
|
from django.db.models.signals import post_save, pre_delete
|
|
from django.dispatch import receiver
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
from django.utils.encoding import force_bytes, force_str
|
|
from django.utils.functional import cached_property
|
|
from django.utils.html import strip_tags
|
|
from django.utils.translation import get_language, gettext, gettext_lazy as _
|
|
from djangocms_text_ckeditor.fields import HTMLField
|
|
from easy_thumbnails.files import get_thumbnailer
|
|
from filer.fields.image import FilerImageField
|
|
from filer.models import ThumbnailOption
|
|
from meta.models import ModelMeta
|
|
from parler.models import TranslatableModel, TranslatedFields
|
|
from parler.utils.context import switch_language
|
|
from sortedm2m.fields import SortedManyToManyField
|
|
from taggit_autosuggest.managers import TaggableManager
|
|
|
|
from .cms_appconfig import BlogConfig
|
|
from .fields import slugify
|
|
from .managers import GenericDateTaggedManager
|
|
from .settings import get_setting
|
|
|
|
BLOG_CURRENT_POST_IDENTIFIER = get_setting("CURRENT_POST_IDENTIFIER")
|
|
BLOG_CURRENT_NAMESPACE = get_setting("CURRENT_NAMESPACE")
|
|
BLOG_PLUGIN_TEMPLATE_FOLDERS = get_setting("PLUGIN_TEMPLATE_FOLDERS")
|
|
|
|
|
|
thumbnail_model = "{}.{}".format(ThumbnailOption._meta.app_label, ThumbnailOption.__name__)
|
|
|
|
|
|
try:
|
|
from knocker.mixins import KnockerModel
|
|
except ImportError: # pragma: no cover
|
|
|
|
class KnockerModel:
|
|
"""
|
|
Stub class if django-knocker is not installed
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
def _get_language(instance, language):
|
|
available_languages = instance.get_available_languages()
|
|
if language and language in available_languages:
|
|
return language
|
|
language = get_language()
|
|
if language and language in available_languages:
|
|
return language
|
|
language = instance.get_current_language()
|
|
if language and language in available_languages:
|
|
return language
|
|
if get_setting("USE_FALLBACK_LANGUAGE_IN_URL"):
|
|
for fallback_language in instance.get_fallback_languages():
|
|
if fallback_language in available_languages:
|
|
return fallback_language
|
|
return language
|
|
|
|
|
|
class BlogMetaMixin(ModelMeta):
|
|
def get_meta_attribute(self, param):
|
|
"""
|
|
Retrieves django-meta attributes from apphook config instance
|
|
:param param: django-meta attribute passed as key
|
|
"""
|
|
return self._get_meta_value(param, getattr(self.app_config, param)) or ""
|
|
|
|
def get_locale(self):
|
|
return self.get_current_language()
|
|
|
|
def get_full_url(self):
|
|
"""
|
|
Return the url with protocol and domain url
|
|
"""
|
|
return self.build_absolute_uri(self.get_absolute_url())
|
|
|
|
|
|
class BlogCategory(BlogMetaMixin, TranslatableModel):
|
|
"""
|
|
Blog category
|
|
"""
|
|
|
|
parent = models.ForeignKey(
|
|
"self", verbose_name=_("parent"), null=True, blank=True, related_name="children", on_delete=models.CASCADE
|
|
)
|
|
date_created = models.DateTimeField(_("created at"), auto_now_add=True)
|
|
date_modified = models.DateTimeField(_("modified at"), auto_now=True)
|
|
app_config = AppHookConfigField(BlogConfig, null=True, verbose_name=_("app. config"))
|
|
|
|
translations = TranslatedFields(
|
|
name=models.CharField(_("name"), max_length=752),
|
|
slug=models.SlugField(_("slug"), max_length=752, blank=True, db_index=True),
|
|
meta_description=models.TextField(verbose_name=_("category meta description"), blank=True, default=""),
|
|
meta={"unique_together": (("language_code", "slug"),)},
|
|
)
|
|
|
|
objects = AppHookConfigTranslatableManager()
|
|
|
|
_metadata = {
|
|
"title": "get_title",
|
|
"description": "get_description",
|
|
"og_description": "get_description",
|
|
"twitter_description": "get_description",
|
|
"schemaorg_description": "get_description",
|
|
"schemaorg_type": "get_meta_attribute",
|
|
"locale": "get_locale",
|
|
"object_type": "get_meta_attribute",
|
|
"og_type": "get_meta_attribute",
|
|
"og_app_id": "get_meta_attribute",
|
|
"og_profile_id": "get_meta_attribute",
|
|
"og_publisher": "get_meta_attribute",
|
|
"og_author_url": "get_meta_attribute",
|
|
"og_author": "get_meta_attribute",
|
|
"twitter_type": "get_meta_attribute",
|
|
"twitter_site": "get_meta_attribute",
|
|
"twitter_author": "get_meta_attribute",
|
|
"url": "get_absolute_url",
|
|
}
|
|
|
|
class Meta:
|
|
verbose_name = _("blog category")
|
|
verbose_name_plural = _("blog categories")
|
|
|
|
def descendants(self):
|
|
children = []
|
|
if self.children.exists():
|
|
children.extend(self.children.all())
|
|
for child in self.children.all():
|
|
children.extend(child.descendants())
|
|
return children
|
|
|
|
@cached_property
|
|
def linked_posts(self):
|
|
return self.blog_posts.namespace(self.app_config.namespace)
|
|
|
|
@cached_property
|
|
def count(self):
|
|
return self.linked_posts.published().count()
|
|
|
|
@cached_property
|
|
def count_all_sites(self):
|
|
return self.linked_posts.published(current_site=False).count()
|
|
|
|
def get_absolute_url(self, lang=None):
|
|
lang = _get_language(self, lang)
|
|
if self.has_translation(lang):
|
|
slug = self.safe_translation_getter("slug", language_code=lang)
|
|
return reverse(
|
|
"%s:posts-category" % self.app_config.namespace,
|
|
kwargs={"category": slug},
|
|
current_app=self.app_config.namespace,
|
|
)
|
|
# in case category doesn't exist in this language, gracefully fallback
|
|
# to posts-latest
|
|
return reverse("%s:posts-latest" % self.app_config.namespace, current_app=self.app_config.namespace)
|
|
|
|
def __str__(self):
|
|
default = gettext("BlogCategory (no translation)")
|
|
return self.safe_translation_getter("name", any_language=True, default=default)
|
|
|
|
def save(self, *args, **kwargs):
|
|
super().save(*args, **kwargs)
|
|
for lang in self.get_available_languages():
|
|
self.set_current_language(lang)
|
|
if not self.slug and self.name:
|
|
self.slug = slugify(force_str(self.name))
|
|
self.save_translations()
|
|
|
|
def get_title(self):
|
|
title = self.safe_translation_getter("name", any_language=True)
|
|
return title.strip()
|
|
|
|
def get_description(self):
|
|
description = self.safe_translation_getter("meta_description", any_language=True)
|
|
return strip_tags(description).strip()
|
|
|
|
|
|
class Post(KnockerModel, BlogMetaMixin, TranslatableModel):
|
|
"""
|
|
Blog post
|
|
"""
|
|
|
|
author = models.ForeignKey(
|
|
dj_settings.AUTH_USER_MODEL,
|
|
verbose_name=_("author"),
|
|
null=True,
|
|
blank=True,
|
|
related_name="djangocms_blog_post_author",
|
|
on_delete=models.PROTECT,
|
|
)
|
|
|
|
date_created = models.DateTimeField(_("created"), auto_now_add=True)
|
|
date_modified = models.DateTimeField(_("last modified"), auto_now=True)
|
|
date_published = models.DateTimeField(_("published since"), null=True, blank=True)
|
|
date_published_end = models.DateTimeField(_("published until"), null=True, blank=True)
|
|
date_featured = models.DateTimeField(_("featured date"), null=True, blank=True)
|
|
publish = models.BooleanField(_("publish"), default=False)
|
|
include_in_rss = models.BooleanField(_("include in RSS feed"), default=True)
|
|
categories = models.ManyToManyField(
|
|
"djangocms_blog.BlogCategory", verbose_name=_("category"), related_name="blog_posts", blank=True
|
|
)
|
|
main_image = FilerImageField(
|
|
verbose_name=_("main image"),
|
|
blank=True,
|
|
null=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name="djangocms_blog_post_image",
|
|
)
|
|
main_image_thumbnail = models.ForeignKey(
|
|
thumbnail_model,
|
|
verbose_name=_("main image thumbnail"),
|
|
related_name="djangocms_blog_post_thumbnail",
|
|
on_delete=models.SET_NULL,
|
|
blank=True,
|
|
null=True,
|
|
)
|
|
main_image_full = models.ForeignKey(
|
|
thumbnail_model,
|
|
verbose_name=_("main image full"),
|
|
related_name="djangocms_blog_post_full",
|
|
on_delete=models.SET_NULL,
|
|
blank=True,
|
|
null=True,
|
|
)
|
|
enable_comments = models.BooleanField(
|
|
verbose_name=_("enable comments on post"), default=get_setting("ENABLE_COMMENTS")
|
|
)
|
|
sites = models.ManyToManyField(
|
|
"sites.Site",
|
|
verbose_name=_("Site(s)"),
|
|
blank=True,
|
|
help_text=_(
|
|
"Select sites in which to show the post. "
|
|
"If none is set it will be "
|
|
"visible in all the configured sites."
|
|
),
|
|
)
|
|
app_config = AppHookConfigField(BlogConfig, null=True, verbose_name=_("app. config"))
|
|
|
|
translations = TranslatedFields(
|
|
title=models.CharField(_("title"), max_length=752),
|
|
slug=models.SlugField(
|
|
_("slug"), max_length=752, blank=True, db_index=True, allow_unicode=get_setting("UNICODE_SLUGS")
|
|
),
|
|
subtitle=models.CharField(verbose_name=_("subtitle"), max_length=767, blank=True, default=""),
|
|
abstract=HTMLField(_("abstract"), blank=True, default="", configuration="BLOG_ABSTRACT_CKEDITOR"),
|
|
meta_description=models.TextField(verbose_name=_("post meta description"), blank=True, default=""),
|
|
meta_keywords=models.TextField(verbose_name=_("post meta keywords"), blank=True, default=""),
|
|
meta_title=models.CharField(
|
|
verbose_name=_("post meta title"),
|
|
help_text=_("used in title tag and social sharing"),
|
|
max_length=2000,
|
|
blank=True,
|
|
default="",
|
|
),
|
|
post_text=HTMLField(_("text"), default="", blank=True, configuration="BLOG_POST_TEXT_CKEDITOR"),
|
|
meta={"unique_together": (("language_code", "slug"),)},
|
|
)
|
|
media = PlaceholderRelationField("media", related_name="media")
|
|
content = PlaceholderRelationField("post_content", related_name="post_content")
|
|
liveblog = PlaceholderRelationField("live_blog", related_name="live_blog")
|
|
enable_liveblog = models.BooleanField(verbose_name=_("enable liveblog on post"), default=False)
|
|
|
|
objects = GenericDateTaggedManager()
|
|
tags = TaggableManager(
|
|
blank=True,
|
|
related_name="djangocms_blog_tags",
|
|
help_text=_("Type a tag and hit tab, or start typing and select from autocomplete list."),
|
|
)
|
|
|
|
related = SortedManyToManyField("self", verbose_name=_("Related Posts"), blank=True, symmetrical=False)
|
|
|
|
_metadata = {
|
|
"title": "get_title",
|
|
"description": "get_description",
|
|
"keywords": "get_keywords",
|
|
"og_description": "get_description",
|
|
"twitter_description": "get_description",
|
|
"schemaorg_description": "get_description",
|
|
"locale": "get_locale",
|
|
"image": "get_image_full_url",
|
|
"image_width": "get_image_width",
|
|
"image_height": "get_image_height",
|
|
"object_type": "get_meta_attribute",
|
|
"og_type": "get_meta_attribute",
|
|
"og_app_id": "get_meta_attribute",
|
|
"og_profile_id": "get_meta_attribute",
|
|
"og_publisher": "get_meta_attribute",
|
|
"og_author_url": "get_meta_attribute",
|
|
"og_author": "get_meta_attribute",
|
|
"twitter_type": "get_meta_attribute",
|
|
"twitter_site": "get_meta_attribute",
|
|
"twitter_author": "get_meta_attribute",
|
|
"schemaorg_type": "get_meta_attribute",
|
|
"published_time": "date_published",
|
|
"modified_time": "date_modified",
|
|
"expiration_time": "date_published_end",
|
|
"tag": "get_tags",
|
|
"url": "get_absolute_url",
|
|
}
|
|
|
|
class Meta:
|
|
verbose_name = _("blog article")
|
|
verbose_name_plural = _("blog articles")
|
|
ordering = ("-date_published", "-date_created")
|
|
get_latest_by = "date_published"
|
|
|
|
def __str__(self):
|
|
default = gettext("Post (no translation)")
|
|
return self.safe_translation_getter("title", any_language=True, default=default)
|
|
|
|
@property
|
|
def guid(self, language=None):
|
|
if not language:
|
|
language = self.get_current_language()
|
|
base_string = "-{0}-{2}-{1}-".format(
|
|
language,
|
|
self.app_config.namespace,
|
|
self.safe_translation_getter("slug", language_code=language, any_language=True),
|
|
)
|
|
return hashlib.sha256(force_bytes(base_string)).hexdigest()
|
|
|
|
@property
|
|
def date(self):
|
|
if self.date_featured:
|
|
return self.date_featured
|
|
return self.date_published
|
|
|
|
def save(self, *args, **kwargs):
|
|
"""
|
|
Handle some auto configuration during save
|
|
"""
|
|
if self.publish and self.date_published is None:
|
|
self.date_published = timezone.now()
|
|
if not self.slug and self.title:
|
|
self.slug = slugify(self.title)
|
|
super().save(*args, **kwargs)
|
|
|
|
def save_translation(self, translation, *args, **kwargs):
|
|
"""
|
|
Handle some auto configuration during save
|
|
"""
|
|
if not translation.slug and translation.title:
|
|
translation.slug = slugify(translation.title)
|
|
super().save_translation(translation, *args, **kwargs)
|
|
|
|
def get_absolute_url(self, lang=None):
|
|
lang = _get_language(self, lang)
|
|
with switch_language(self, lang):
|
|
category = self.categories.first()
|
|
kwargs = {}
|
|
if self.date_published:
|
|
current_date = self.date_published
|
|
else:
|
|
current_date = self.date_created
|
|
urlconf = get_setting("PERMALINK_URLS")[self.app_config.url_patterns]
|
|
if "<int:year>" in urlconf:
|
|
kwargs["year"] = current_date.year
|
|
if "<int:month>" in urlconf:
|
|
kwargs["month"] = "%02d" % current_date.month
|
|
if "<int:day>" in urlconf:
|
|
kwargs["day"] = "%02d" % current_date.day
|
|
if "<str:slug>" in urlconf or "<slug:slug>" in urlconf:
|
|
kwargs["slug"] = self.safe_translation_getter("slug", language_code=lang, any_language=True) # NOQA
|
|
if "<slug:category>" in urlconf or "<str:category>" in urlconf:
|
|
kwargs["category"] = category.safe_translation_getter(
|
|
"slug", language_code=lang, any_language=True
|
|
) # NOQA
|
|
return reverse(
|
|
"%s:post-detail" % self.app_config.namespace, kwargs=kwargs, current_app=self.app_config.namespace
|
|
)
|
|
|
|
def get_title(self):
|
|
title = self.safe_translation_getter("meta_title", any_language=True)
|
|
if not title:
|
|
title = self.safe_translation_getter("title", any_language=True)
|
|
return title.strip()
|
|
|
|
def get_keywords(self):
|
|
"""
|
|
Returns the list of keywords (as python list)
|
|
:return: list
|
|
"""
|
|
return self.safe_translation_getter("meta_keywords", default="").strip().split(",")
|
|
|
|
def get_description(self):
|
|
description = self.safe_translation_getter("meta_description", any_language=True)
|
|
if not description:
|
|
description = self.safe_translation_getter("abstract", any_language=True)
|
|
return strip_tags(description).strip()
|
|
|
|
def get_image_full_url(self):
|
|
if self.main_image:
|
|
thumbnail_options = get_setting("META_IMAGE_SIZE")
|
|
if thumbnail_options:
|
|
thumbnail_url = get_thumbnailer(self.main_image).get_thumbnail(thumbnail_options).url
|
|
return self.build_absolute_uri(thumbnail_url)
|
|
return self.build_absolute_uri(self.main_image.url)
|
|
return ""
|
|
|
|
def get_image_width(self):
|
|
if self.main_image:
|
|
thumbnail_options = get_setting("META_IMAGE_SIZE")
|
|
if thumbnail_options:
|
|
return get_thumbnailer(self.main_image).get_thumbnail(thumbnail_options).width
|
|
return self.main_image.width
|
|
|
|
def get_image_height(self):
|
|
if self.main_image:
|
|
thumbnail_options = get_setting("META_IMAGE_SIZE")
|
|
if thumbnail_options:
|
|
return get_thumbnailer(self.main_image).get_thumbnail(thumbnail_options).height
|
|
return self.main_image.height
|
|
|
|
def get_tags(self):
|
|
"""
|
|
Returns the list of object tags as comma separated list
|
|
"""
|
|
taglist = [tag.name for tag in self.tags.all()]
|
|
return ",".join(taglist)
|
|
|
|
def get_author(self):
|
|
"""
|
|
Return the author (user) objects
|
|
"""
|
|
return self.author
|
|
|
|
def _set_default_author(self, current_user):
|
|
if not self.author_id and self.app_config.set_author:
|
|
if get_setting("AUTHOR_DEFAULT") is True:
|
|
user = current_user
|
|
else:
|
|
user = get_user_model().objects.get(username=get_setting("AUTHOR_DEFAULT"))
|
|
self.author = user
|
|
|
|
def thumbnail_options(self):
|
|
if self.main_image_thumbnail_id:
|
|
return self.main_image_thumbnail.as_dict
|
|
else:
|
|
return get_setting("IMAGE_THUMBNAIL_SIZE")
|
|
|
|
def full_image_options(self):
|
|
if self.main_image_full_id:
|
|
return self.main_image_full.as_dict
|
|
else:
|
|
return get_setting("IMAGE_FULL_SIZE")
|
|
|
|
@property
|
|
def is_published(self):
|
|
"""
|
|
Checks wether the blog post is *really* published by checking publishing dates too
|
|
"""
|
|
return (
|
|
self.publish
|
|
and (self.date_published and self.date_published <= timezone.now())
|
|
and (self.date_published_end is None or self.date_published_end > timezone.now())
|
|
)
|
|
|
|
def should_knock(self, signal_type, created=False):
|
|
"""
|
|
Returns whether to emit knocks according to the post state
|
|
"""
|
|
new = self.app_config.send_knock_create and self.is_published and self.date_published == self.date_modified
|
|
updated = self.app_config.send_knock_update and self.is_published
|
|
return (new or updated) and signal_type in ("post_save", "post_delete")
|
|
|
|
def get_cache_key(self, language, prefix):
|
|
return "djangocms-blog:{2}:{0}:{1}".format(language, self.guid, prefix)
|
|
|
|
@property
|
|
def liveblog_group(self):
|
|
return "liveblog-{apphook}-{lang}-{post}".format(
|
|
lang=self.get_current_language(),
|
|
apphook=self.app_config.namespace,
|
|
post=self.safe_translation_getter("slug", any_language=True),
|
|
)
|
|
|
|
|
|
class BasePostPlugin(CMSPlugin):
|
|
app_config = AppHookConfigField(BlogConfig, null=True, verbose_name=_("app. config"), blank=True)
|
|
current_site = models.BooleanField(
|
|
_("current site"), default=True, help_text=_("Select items from the current site only")
|
|
)
|
|
template_folder = models.CharField(
|
|
max_length=200,
|
|
verbose_name=_("Plugin template"),
|
|
help_text=_("Select plugin template to load for this instance"),
|
|
default=BLOG_PLUGIN_TEMPLATE_FOLDERS[0][0],
|
|
choices=BLOG_PLUGIN_TEMPLATE_FOLDERS,
|
|
)
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
def optimize(self, qs):
|
|
"""
|
|
Apply select_related / prefetch_related to optimize the view queries
|
|
:param qs: queryset to optimize
|
|
:return: optimized queryset
|
|
"""
|
|
return qs.select_related("app_config").prefetch_related(
|
|
"translations", "categories", "categories__translations", "categories__app_config"
|
|
)
|
|
|
|
def post_queryset(self, request=None, published_only=True, selected_posts=None):
|
|
language = get_language()
|
|
posts = Post.objects if not selected_posts else selected_posts
|
|
if self.app_config:
|
|
posts = posts.namespace(self.app_config.namespace)
|
|
if self.current_site:
|
|
posts = posts.on_site(get_current_site(request))
|
|
posts = posts.active_translations(language_code=language)
|
|
if (
|
|
published_only
|
|
or not request
|
|
or not getattr(request, "toolbar", False)
|
|
or not request.toolbar.edit_mode_active
|
|
):
|
|
posts = posts.published(current_site=self.current_site)
|
|
return self.optimize(posts.all())
|
|
|
|
|
|
class LatestPostsPlugin(BasePostPlugin):
|
|
latest_posts = models.IntegerField(
|
|
_("articles"),
|
|
default=get_setting("LATEST_POSTS"),
|
|
help_text=_("The number of latests " "articles to be displayed."),
|
|
)
|
|
tags = TaggableManager(
|
|
_("filter by tag"),
|
|
blank=True,
|
|
help_text=_("Show only the blog articles tagged with chosen tags."),
|
|
related_name="djangocms_blog_latest_post",
|
|
)
|
|
categories = models.ManyToManyField(
|
|
"djangocms_blog.BlogCategory",
|
|
blank=True,
|
|
verbose_name=_("filter by category"),
|
|
help_text=_("Show only the blog articles tagged " "with chosen categories."),
|
|
)
|
|
|
|
def __str__(self):
|
|
return force_str(_("%s latest articles by tag") % self.latest_posts)
|
|
|
|
def copy_relations(self, oldinstance):
|
|
for tag in oldinstance.tags.all():
|
|
self.tags.add(tag)
|
|
for category in oldinstance.categories.all():
|
|
self.categories.add(category)
|
|
|
|
def get_posts(self, request, published_only=True):
|
|
posts = self.post_queryset(request, published_only)
|
|
if self.tags.exists():
|
|
posts = posts.filter(tags__in=list(self.tags.all()))
|
|
if self.categories.exists():
|
|
posts = posts.filter(categories__in=list(self.categories.all()))
|
|
return self.optimize(posts.distinct())[: self.latest_posts]
|
|
|
|
|
|
class AuthorEntriesPlugin(BasePostPlugin):
|
|
authors = models.ManyToManyField(
|
|
dj_settings.AUTH_USER_MODEL,
|
|
verbose_name=_("authors"),
|
|
limit_choices_to={"djangocms_blog_post_author__publish": True},
|
|
)
|
|
latest_posts = models.IntegerField(
|
|
_("articles"),
|
|
default=get_setting("LATEST_POSTS"),
|
|
help_text=_("The number of author articles to be displayed."),
|
|
)
|
|
|
|
def __str__(self):
|
|
return force_str(_("%s latest articles by author") % self.latest_posts)
|
|
|
|
def copy_relations(self, oldinstance):
|
|
self.authors.set(oldinstance.authors.all())
|
|
|
|
def get_posts(self, request, published_only=True):
|
|
posts = self.post_queryset(request, published_only)
|
|
return posts
|
|
|
|
def get_authors(self, request):
|
|
authors = self.authors.all()
|
|
for author in authors:
|
|
qs = self.get_posts(request).filter(author=author)
|
|
# total nb of articles
|
|
author.count = qs.count()
|
|
# "the number of author articles to be displayed"
|
|
author.posts = qs[: self.latest_posts]
|
|
return authors
|
|
|
|
|
|
class FeaturedPostsPlugin(BasePostPlugin):
|
|
posts = SortedManyToManyField(Post, verbose_name=_("Featured posts"))
|
|
|
|
def __str__(self):
|
|
return force_str(_("Featured posts"))
|
|
|
|
def copy_relations(self, oldinstance):
|
|
self.posts.set(oldinstance.posts.all())
|
|
|
|
def get_posts(self, request, published_only=True):
|
|
posts = self.post_queryset(request, published_only, selected_posts=self.posts.all())
|
|
return posts
|
|
|
|
|
|
class GenericBlogPlugin(BasePostPlugin):
|
|
class Meta:
|
|
abstract = False
|
|
|
|
def __str__(self):
|
|
return force_str(_("generic blog plugin"))
|
|
|
|
|
|
@receiver(pre_delete, sender=Post)
|
|
def pre_delete_post(sender, instance, **kwargs):
|
|
for language in instance.get_available_languages():
|
|
key = instance.get_cache_key(language, "feed")
|
|
cache.delete(key)
|
|
|
|
|
|
@receiver(post_save, sender=Post)
|
|
def post_save_post(sender, instance, **kwargs):
|
|
for language in instance.get_available_languages():
|
|
key = instance.get_cache_key(language, "feed")
|
|
cache.delete(key)
|