You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

266 lines
8.0 KiB

# TODO Always use Django templates for rendering, replace .format() class
import os
import re
from django.conf import settings
from django.db import models
from django.template import Template
from django.utils.html import format_html, mark_safe
from django.utils.translation import ugettext_lazy as _
from feincms3.cleanse import CleansedRichTextField
from shared.utils.fields import AutoSlugField
from shared.multilingual.utils.fields import TranslatableCharField, TranslatableTextField
from .admin import ContentInlineBase, RichTextInlineBase
from .fields import TranslatableCleansedRichTextField
# FIXME Implement USE_TRANSLATABLE_FIELDS
from .mixins import USE_TRANSLATABLE_FIELDS
"""
TODO Class hierarchy should be
BasePlugin
StringRendererPlugin
TemplateRendererPlugin
FilesystemRendererPlugin (is specific for RomArchive)
"""
class BasePlugin(models.Model):
class Meta:
abstract = True
verbose_name = _("Element")
verbose_name_plural = _("Elements")
def __str__(self):
return "{} ({})".format(self._meta.verbose_name, self.pk)
@classmethod
def admin_inline(cls, base_class=ContentInlineBase):
class Inline(base_class):
model = cls
regions = cls.regions
return Inline
@classmethod
def register_with_renderer(cls, renderer):
renderer.register_template_renderer(cls, cls.get_template, cls.get_context_data)
def get_template_names(self):
return getattr(self, 'template_name', None)
def get_template(self):
"""
Might return a single template name, a list of template names
or an instance with a "render" method (i.e. a Template instance).
Default implementation is to return the result of self.get_template_names().
"""
return self.get_template_names()
def get_context_data(self, request_context, **kwargs):
context = kwargs.get('context', {})
context['parent'] = self.parent
context['request'] = request_context['request']
context['content'] = self
return context
# For rendering the template's render() method is used
class StringRendererPlugin(BasePlugin):
class Meta:
abstract = True
@classmethod
def register_with_renderer(cls, renderer):
renderer.register_template_renderer(cls, None, cls.render)
def render(self):
raise NotImplementedError
class StyleField(models.CharField):
# Allow overriding of STYLE_CHOICES constant in subclasses
def contribute_to_class(self, cls, name, **kwargs):
if hasattr(cls, 'STYLE_CHOICES'):
self.choices = cls.STYLE_CHOICES
super().contribute_to_class(cls, name, **kwargs)
class StyleMixin(models.Model):
STYLE_CHOICES = (
('default', _("default")),
)
style = StyleField(_("style"), max_length=50, null=True, blank=True)
class Meta:
abstract = True
def get_style_slug(self):
return getattr(self, 'style', None) or 'default'
class FilesystemTemplateRendererPlugin(BasePlugin):
# TODO Join FilesystemTemplateRendererPlugin code with BaseObjectElement code
template_name = None
path_prefix = None # Potential values: "richtext", "image"
class Meta:
abstract = True
def get_path_prefix(self):
if self.path_prefix:
return "{}{}".format(self.path_prefix, os.path.sep)
else:
return ""
def get_template_names(self):
# TODO Style related logic should be part of the StyleMixin: maybe get_template_names should call super()
if hasattr(self, 'style'):
template_names = [
"curatorialcontent/elements/{path_prefix}style/_{style}.html".format(
path_prefix=self.get_path_prefix(), style=self.get_style_slug()),
]
else:
template_names = []
return template_names + [
"curatorialcontent/elements/{path_prefix}_default.html".format(
path_prefix=self.get_path_prefix())
] + ([self.template_name] if getattr(self, 'template_name', None) else [])
class RichTextBase(StyleMixin, FilesystemTemplateRendererPlugin):
richtext = TranslatableCleansedRichTextField(_("text"), blank=True)
path_prefix = 'richtext'
class Meta:
abstract = True
verbose_name = _("text")
verbose_name_plural = _("texts")
@classmethod
def admin_inline(cls, base_class=None):
return super().admin_inline(base_class=RichTextInlineBase)
class SectionBase(StyleMixin, BasePlugin):
subheading = TranslatableCharField(_("subheading"), max_length=500)
slug = AutoSlugField(_("slug"), max_length=200, blank=True, populate_from='subheading', unique_slug=False)
class Meta:
abstract = True
verbose_name = _("Abschnittswechsel")
verbose_name_plural = _("Abschnittswechsel")
def get_template(self):
return Template("""
</div>
</section>
<section id="{{ slug }}">
<h2>{{ heading }}</h2>
<div class="text">
""")
def get_context_data(self, request_context, **kwargs):
context = super().get_context_data(request_context, **kwargs)
context['slug'] = self.slug
context['heading'] = self.heading
return context
# class ImageBase(StyleMixin, BasePlugin):
# image = models.ForeignKey(Image, on_delete=models.PROTECT)
# alt_caption = TranslatableCharField(_("caption"), max_length=500, null=True, blank=True, help_text=_("Optional, used instead of the caption of the image object."))#
# class Meta:
# abstract = True
# verbose_name = _("image")
# verbose_name_plural = _("images")#
# def render(self):
# template = """
# <figure class="image">
# <img src="{src}">#
# <figcaption>
# {caption_text}
# </figcaption>
# </figure>
# """
# # TOOD Assemble caption from image's captions if empty
# return mark_safe(template.format(
# src=self.image.figure_image.url,
# caption_text=mark_safe(self.alt_caption or "")
# ))
class DownloadBase(StyleMixin, BasePlugin):
file = models.FileField(upload_to='downloads/%Y/%m/')
class Meta:
abstract = True
verbose_name = _("download")
verbose_name_plural = _("downloads")
def render(self):
template = """
<a href="{url}">{name}</a>
"""
return mark_safe(template.format(
url=self.file.url,
name=self.file.name,
))
class FootnoteBase(BasePlugin):
# TODO Validators: index might only contain alphanumeric characters
index = models.CharField(_("footnote index"), max_length=10)
richtext = TranslatableCleansedRichTextField(_("footnote text"))
html_tag = '<li>'
class Meta:
abstract = True
verbose_name = _("footnote")
verbose_name_plural = _("footnote")
def render(self, html_tag=None):
template = """
{opening_tag}
<div class="text">
<a id="fn{number}" class="footnote-index" href="#back{number}">{number}</a>
{text}
</div>
{closing_tag}
"""
context = {
'number': self.index,
'text': mark_safe(self.richtext or ""),
'opening_tag': "",
'closing_tag': "",
}
html_tag = html_tag or self.html_tag
if html_tag:
context['opening_tag'] = html_tag
context['closing_tag'] = '{0}/{1}'.format(html_tag[:1], html_tag[1:])
return mark_safe(template.format(**context))
class RichTextFootnoteMixin:
MATCH_FOOTNOTES = re.compile("<sup>(\w+)</sup>")
def render(self):
# Find all footnotes and convert them into links
rv = self.MATCH_FOOTNOTES.subn(
'<sup id=\"back\g<1>\" class="footnote"><a href=\"#fn\g<1>\">\g<1></a></sup>',
self.richtext)[0]
return mark_safe(rv)