from __future__ import annotations
from cms.apphook_pool import apphook_pool
from django.db import models
from django.http import HttpRequest
from django.urls import Resolver404, resolve
from django.utils.translation import get_language_from_request, gettext_lazy as _, override
from filer.models import ThumbnailOption
from parler.models import TranslatableModel, TranslatedFields
from .settings import MENU_TYPE_COMPLETE, get_setting
config_defaults = {
"default_image_full": None,
"default_image_thumbnail": None,
"url_patterns": get_setting("AVAILABLE_PERMALINK_STYLES")[0][0],
"use_placeholder": get_setting("USE_PLACEHOLDER"),
"use_abstract": get_setting("USE_ABSTRACT"),
"use_related": int(get_setting("USE_RELATED")),
"set_author": get_setting("AUTHOR_DEFAULT"),
"paginate_by": get_setting("PAGINATION"),
"template_prefix": "",
"menu_structure": MENU_TYPE_COMPLETE,
"menu_empty_categories": get_setting("MENU_EMPTY_CATEGORIES"),
"sitemap_changefreq": get_setting("SITEMAP_CHANGEFREQ_DEFAULT"),
"sitemap_priority": get_setting("SITEMAP_PRIORITY_DEFAULT"),
"object_type": get_setting("TYPE"),
"og_type": get_setting("FB_TYPE"),
"og_app_id": get_setting("FB_PROFILE_ID"),
"og_profile_id": get_setting("FB_PROFILE_ID"),
"og_publisher": get_setting("FB_PUBLISHER"),
"og_author_url": get_setting("FB_AUTHOR_URL"),
"og_author": get_setting("FB_AUTHOR"),
"twitter_type": get_setting("TWITTER_TYPE"),
"twitter_site": get_setting("TWITTER_SITE"),
"twitter_author": get_setting("TWITTER_AUTHOR"),
"gplus_type": get_setting("SCHEMAORG_TYPE"),
"gplus_author": get_setting("SCHEMAORG_AUTHOR"),
"send_knock_create": False,
"send_knock_update": False,
}
[docs]
class StoriesConfig(TranslatableModel):
"""
Class representing a stories configuration.
This class inherits from TranslatableModel.
Attributes:
type (models.CharField): Represents the type of the stories config.
namespace (models.CharField): Represents the namespace of the instance.
translations (TranslatedFields): Represents the translated fields of the stories config.
default_image_full (models.ForeignKey): Represents the default size of full images.
default_image_thumbnail (models.ForeignKey): Represents the default size of thumbnail images.
url_patterns (models.CharField): Represents the structure of permalinks.
use_placeholder (models.BooleanField): Represents whether to use placeholder and plugins for article body.
use_abstract (models.BooleanField): Represents whether to use abstract field.
use_related (models.SmallIntegerField): Represents whether to enable related posts.
urlconf (models.CharField): Represents the URL config.
set_author (models.BooleanField): Represents whether to set author by default.
paginate_by (models.SmallIntegerField): Represents the number of articles per page for pagination.
template_prefix (models.CharField): Represents the alternative directory to load the stories templates from.
menu_structure (models.CharField): Represents the menu structure.
menu_empty_categories (models.BooleanField): Represents whether to show empty categories in menu.
sitemap_changefreq (models.CharField): Represents the changefreq attribute for sitemap items.
sitemap_priority (models.DecimalField): Represents the priority attribute for sitemap items.
object_type (models.CharField): Represents the object type.
og_type (models.CharField): Represents the Facebook type.
og_app_id (models.CharField): Represents the Facebook application ID.
og_profile_id (models.CharField): Represents the Facebook profile ID.
og_publisher (models.CharField): Represents the Facebook page URL.
og_author_url (models.CharField): Represents the Facebook author URL.
og_author (models.CharField): Represents the Facebook author.
twitter_type (models.CharField): Represents the Twitter type field.
twitter_site (models.CharField): Represents the Twitter site handle.
twitter_author (models.CharField): Represents the Twitter author handle.
gplus_type (models.CharField): Represents the Schema.org object type.
gplus_author (models.CharField): Represents the Schema.org author name abstract field.
"""
class Meta:
verbose_name = _("stories config")
verbose_name_plural = _("stories configs")
type = models.CharField(
verbose_name=_("type"),
max_length=100,
)
namespace = models.CharField(
verbose_name=_("instance namespace"),
default=None,
max_length=100,
unique=True,
)
translations = TranslatedFields(
app_title=models.CharField(_("application title"), max_length=200, default="+"),
object_name=models.CharField(_("object name"), max_length=200, default="+"),
)
#: Default size of full images
default_image_full = models.ForeignKey(
ThumbnailOption,
related_name="default_images_full",
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name=_("default size of full images"),
help_text=_("If left empty the image size will have to be set for every newly created post."),
)
#: Default size of thumbnail images
default_image_thumbnail = models.ForeignKey(
ThumbnailOption,
related_name="default_images_thumbnail",
blank=True,
null=True,
on_delete=models.SET_NULL,
verbose_name=_("default size of thumbnail images"),
help_text=_("If left empty the thumbnail image size will have to be set for every newly created post."),
)
#: Structure of permalinks (get_absolute_url); see :ref:`AVAILABLE_PERMALINK_STYLES <AVAILABLE_PERMALINK_STYLES>`
url_patterns = models.CharField(
max_length=12,
verbose_name=_("Permalink structure"),
)
#: Use placeholder and plugins for article body (default: :ref:`USE_PLACEHOLDER <USE_PLACEHOLDER>`)
use_placeholder = models.BooleanField(
verbose_name=_("Use placeholder and plugins for article body"),
default=config_defaults["use_placeholder"],
)
#: Use abstract field (default: :ref:`USE_ABSTRACT <USE_ABSTRACT>`)
use_abstract = models.BooleanField(verbose_name=_("Use abstract field"), default=config_defaults["use_abstract"])
#: Enable related posts (default: :ref:`USE_RELATED <USE_RELATED>`)
use_related = models.SmallIntegerField(
verbose_name=_("Enable related posts"),
default=config_defaults["use_related"],
choices=(
(0, _("No")),
(1, _("Yes, from this stories config")),
(2, _("Yes, from this site")),
),
)
#: Set author by default (default: :ref:`AUTHOR_DEFAULT <AUTHOR_DEFAULT>`)
set_author = models.BooleanField(
verbose_name=_("Set author by default"),
)
#: When paginating list views, how many articles per page? (default: :ref:`PAGINATION <PAGINATION>`)
paginate_by = models.SmallIntegerField(
verbose_name=_("Paginate size"),
null=True,
help_text=_("When paginating list views, how many articles per page?"),
)
#: Alternative directory to load the stories templates from (default: "")
template_prefix = models.CharField(
max_length=200,
blank=True,
default="",
verbose_name=_("Template prefix"),
help_text=_("Alternative directory to load the stories templates from"),
)
#: Menu structure (default: ``MENU_TYPE_COMPLETE``, see :ref:`MENU_TYPES <MENU_TYPES>`)
menu_structure = models.CharField(
max_length=200,
verbose_name=_("Menu structure"),
help_text=_("Structure of the django CMS menu"),
)
#: Show empty categories in menu (default: :ref:`MENU_EMPTY_CATEGORIES <MENU_EMPTY_CATEGORIES>`)
menu_empty_categories = models.BooleanField(
verbose_name=_("Show empty categories in menu"),
help_text=_("Show categories with no post attached in the menu"),
)
#: Sitemap changefreq (default: :ref:`SITEMAP_CHANGEFREQ_DEFAULT <SITEMAP_CHANGEFREQ_DEFAULT>`,
#: see: :ref:`SITEMAP_CHANGEFREQ <SITEMAP_CHANGEFREQ>`)
sitemap_changefreq = models.CharField(
max_length=12,
verbose_name=_("Sitemap changefreq"),
help_text=_("Changefreq attribute for sitemap items"),
)
#: Sitemap priority (default: :ref:`SITEMAP_PRIORITY_DEFAULT <SITEMAP_PRIORITY_DEFAULT>`)
sitemap_priority = models.DecimalField(
decimal_places=3,
max_digits=5,
verbose_name=_("Sitemap priority"),
help_text=_("Priority attribute for sitemap items"),
)
#: Object type (default: :ref:`TYPE <TYPE>`, see :ref:`TYPES <TYPES>`)
object_type = models.CharField(
max_length=200,
blank=True,
verbose_name=_("Object type"),
)
#: Facebook type (default: :ref:`FB_TYPE <FB_TYPE>`, see :ref:`FB_TYPES <FB_TYPES>`)
og_type = models.CharField(
max_length=200,
verbose_name=_("Facebook type"),
blank=True,
)
#: Facebook application ID (default: :ref:`FB_PROFILE_ID <FB_PROFILE_ID>`)
og_app_id = models.CharField(max_length=200, verbose_name=_("Facebook application ID"), blank=True)
#: Facebook profile ID (default: :ref:`FB_PROFILE_ID <FB_PROFILE_ID>`)
og_profile_id = models.CharField(max_length=200, verbose_name=_("Facebook profile ID"), blank=True)
#: Facebook page URL (default: :ref:`FB_PUBLISHER <FB_PUBLISHER>`)
og_publisher = models.CharField(max_length=200, verbose_name=_("Facebook page URL"), blank=True)
#: Facebook author URL (default: :ref:`FB_AUTHOR_URL <FB_AUTHOR_URL>`)
og_author_url = models.CharField(max_length=200, verbose_name=_("Facebook author URL"), blank=True)
#: Facebook author (default: :ref:`FB_AUTHOR <FB_AUTHOR>`)
og_author = models.CharField(max_length=200, verbose_name=_("Facebook author"), blank=True)
#: Twitter type field (default: :ref:`TWITTER_TYPE <TWITTER_TYPE>`)
twitter_type = models.CharField(
max_length=200,
verbose_name=_("Twitter type"),
blank=True,
)
#: Twitter site handle (default: :ref:`TWITTER_SITE <TWITTER_SITE>`)
twitter_site = models.CharField(max_length=200, verbose_name=_("Twitter site handle"), blank=True)
#: Twitter author handle (default: :ref:`TWITTER_AUTHOR <TWITTER_AUTHOR>`)
twitter_author = models.CharField(max_length=200, verbose_name=_("Twitter author handle"), blank=True)
#: Schema.org object type (default: :ref:`SCHEMAORG_TYPE <SCHEMAORG_TYPE>`)
gplus_type = models.CharField(
max_length=200,
verbose_name=_("Schema.org type"),
blank=True,
)
#: Schema.org author name abstract field (default: :ref:`SCHEMAORG_AUTHOR <SCHEMAORG_AUTHOR>`)
gplus_author = models.CharField(max_length=200, verbose_name=_("Schema.org author name"), blank=True)
#: Send notifications on post update. Requires channels integration
send_knock_create = models.BooleanField(
verbose_name=_("Send notifications on post publish"),
default=False,
help_text=_("Emits a desktop notification -if enabled- when publishing a new post"),
)
#: Send notifications on post update. Requires channels integration
send_knock_update = models.BooleanField(
verbose_name=_("Send notifications on post update"),
default=False,
help_text=_("Emits a desktop notification -if enabled- when editing a published post"),
)
[docs]
def get_app_title(self):
return getattr(self, "app_title", _("untitled"))
[docs]
def save(self, *args, **kwargs):
"""Delete menu cache upon safe"""
from menus.menu_pool import menu_pool
menu_pool.clear(all=True)
return super().save(*args, **kwargs)
[docs]
def delete(self, *args, **kwargs):
"""Delete menu cache upon delete"""
from menus.menu_pool import menu_pool
menu_pool.clear(all=True)
return super().delete(*args, **kwargs)
@property
def schemaorg_type(self):
"""Compatibility shim to fetch data from legacy gplus_type field."""
return self.gplus_type
def __str__(self):
try:
return f"{self.namespace}: {self.get_app_title()} / {self.object_name}"
except Exception as e:
return str(e)
def get_namespace_from_request(request: HttpRequest) -> str:
"""
Return current app instance namespace
"""
try:
if hasattr(request, "toolbar") and hasattr(request.toolbar, "request_path"):
path = request.toolbar.request_path # If v4 endpoint take request_path from toolbar
else:
path = request.path_info
return resolve(path).namespace
except Resolver404:
return None
def get_app_instance(request: HttpRequest) -> tuple[str, StoriesConfig | None]:
"""
Return current app instance namespace and config
"""
namespace, config = get_namespace_from_request(request), None
if getattr(request, "current_page", None) and request.current_page.application_urls:
app = apphook_pool.get_apphook(request.current_page.application_urls)
if app and app.app_config:
config = None
with override(get_language_from_request(request, check_path=True)):
namespace = get_namespace_from_request(request)
config = app.get_config(namespace)
else:
try:
config = StoriesConfig.objects.get(namespace=namespace)
except (StoriesConfig.DoesNotExist, StoriesConfig.MultipleObjectsReturned):
pass
return namespace, config