import re
from django . conf import settings
from django . core . exceptions import ImproperlyConfigured
from django . db import models
from django . utils . html import mark_safe , strip_tags
from django . utils . text import Truncator
from django . utils . translation import ugettext_lazy as _
from feincms3 . cleanse import CleansedRichTextField
from shared . utils . models . slugs import DowngradingSlugField
# TODO Rename ContentInlineBase to PluginInlineBase
from . admin import ContentInlineBase , RichTextInlineBase
from . plugins . mixins import StyleMixin
from . import USE_TRANSLATABLE_FIELDS
if USE_TRANSLATABLE_FIELDS :
from shared . multilingual . utils . fields import TranslatableCharField
from . fields import TranslatableCleansedRichTextField
class BasePlugin ( models . Model ) :
admin_inline_baseclass = ContentInlineBase
class Meta :
abstract = True
verbose_name = _ ( " plugin " )
verbose_name_plural = _ ( " plugins " )
@classmethod
def register_with_renderer ( cls , renderer ) :
pass
def __str__ ( self ) :
return " {} ( {} ) " . format ( self . _meta . verbose_name , self . pk )
@classmethod
def admin_inline ( cls , base_class = None ) :
# TODO Cache inline
class Inline ( base_class or cls . admin_inline_baseclass ) :
model = cls
regions = cls . regions
return Inline
def get_plugin_context ( self , context = None , * * kwargs ) :
"""
Returns a dict .
"""
plugin_context = { }
plugin_context [ ' content ' ] = self
plugin_context [ ' parent ' ] = self . parent
if ' request_context ' in kwargs :
plugin_context [ ' request ' ] = getattr ( kwargs [ ' request_context ' ] , ' request ' , None )
return plugin_context
class StringRendererPlugin ( BasePlugin ) :
class Meta :
abstract = True
@classmethod
def register_with_renderer ( cls , renderer ) :
renderer . register_string_renderer ( cls , cls . render )
def render ( self ) :
raise NotImplementedError ( " render method must be implemented by subclass " )
class TemplateRendererPlugin ( BasePlugin ) :
class Meta :
abstract = True
@classmethod
def register_with_renderer ( cls , renderer ) :
renderer . register_template_renderer ( cls , cls . get_template , cls . get_plugin_context )
def get_template_names ( self ) :
t = getattr ( self , ' template_name ' , None )
if t :
return [ t ]
else :
return [ ]
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 ( ) .
See rendering logic in feincms3 . TemplateRendererPlugin .
"""
return self . get_template_names ( )
# For rendering the template's render() method is used
class FilesystemTemplateRendererPlugin ( TemplateRendererPlugin ) :
# template_name_prefix
template_name = None
class Meta :
abstract = True
def get_template_name_prefix ( self ) :
return getattr ( self , ' template_name_prefix ' , ' ' )
def prefixed_path ( self , path ) :
return " {} {} " . format ( self . get_template_name_prefix ( ) , path )
def get_template_names ( self ) :
"""
Look first for template_name ,
second for prefixed template_name ,
then super ' s template names,
finally prefixed _default . html .
"""
if self . template_name is None :
raise ImproperlyConfigured (
" FilesystemTemplateRendererPlugin requires either a definition of "
" ' template_name ' or an implementation of ' get_template_names() ' " )
else :
return [
self . prefixed_path ( self . template_name ) ,
]
class PrepareRichtextMixin :
@property
def prepared_richtext ( self ) :
return mark_safe ( self . get_prepared_richtext ( self . richtext ) )
def get_prepared_richtext ( self , richtext ) :
return richtext
class RichTextBase ( PrepareRichtextMixin , FilesystemTemplateRendererPlugin ) :
if USE_TRANSLATABLE_FIELDS :
richtext = TranslatableCleansedRichTextField ( _ ( " text " ) , blank = True )
else :
richtext = CleansedRichTextField ( _ ( " text " ) , blank = True )
admin_inline_baseclass = RichTextInlineBase
template_name = ' plugins/_richtext.html '
class Meta :
abstract = True
verbose_name = _ ( " text " )
verbose_name_plural = _ ( " texts " )
def __str__ ( self ) :
return Truncator ( strip_tags ( self . richtext ) ) . words ( 10 , truncate = " ... " )
# TODO Rename to SectionBreakBase
class SectionBase ( StyleMixin , FilesystemTemplateRendererPlugin ) :
if USE_TRANSLATABLE_FIELDS :
subheading = TranslatableCharField ( _ ( " subheading " ) , null = True , blank = True , max_length = 500 )
else :
subheading = models . CharField ( _ ( " subheading " ) , null = True , blank = True , max_length = 500 )
slug = DowngradingSlugField ( _ ( " slug " ) , max_length = 200 , blank = True , populate_from = ' subheading ' , unique_slug = False )
template_name = ' _sectionbreak.html '
class Meta :
abstract = True
verbose_name = _ ( " section " )
verbose_name_plural = _ ( " section " )
def __str__ ( self ) :
return Truncator ( strip_tags ( self . subheading ) ) . words ( 10 , truncate = " ... " )
# FIXME Not need, members are accessible through {{ content.slug }} etc.
def get_plugin_context ( self , context = None , * * kwargs ) :
context = super ( ) . get_plugin_context ( context = context , * * kwargs )
context [ ' slug ' ] = self . slug
context [ ' subheading ' ] = self . subheading
return context
# ImageBase can't live here because image managment is different from project to project
# 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 "")
# ))
# TODO See comments on ImageBase; remove DownloadBase
class DownloadBase ( StyleMixin , StringRendererPlugin ) :
file = models . FileField ( upload_to = ' downloads/ % Y/ % m/ ' )
class Meta :
abstract = True
verbose_name = _ ( " download " )
verbose_name_plural = _ ( " downloads " )
def __str__ ( self ) :
return getattr ( self . file , ' name ' , " " )
def render ( self ) :
template = """
< a href = " {url} " > { name } < / a >
"""
return mark_safe ( template . format (
url = self . file . url ,
name = self . file . name ,
) )
class FootnoteBase ( PrepareRichtextMixin , FilesystemTemplateRendererPlugin ) :
# TODO Validators: index might only contain alphanumeric characters
index = models . CharField ( _ ( " footnote index " ) , max_length = 10 )
if USE_TRANSLATABLE_FIELDS :
richtext = TranslatableCleansedRichTextField ( _ ( " footnote text " ) , null = True , blank = True )
else :
richtext = CleansedRichTextField ( _ ( " footnote text " ) , null = True , blank = True )
html_tag = getattr ( settings , ' FOOTNOTE_TAG ' , ' div ' )
template_name = ' plugins/_footnote.html '
class Meta :
abstract = True
verbose_name = _ ( " footnote " )
verbose_name_plural = _ ( " footnote " )
def __str__ ( self ) :
return " [ {} ] {} " . format (
self . index ,
Truncator ( strip_tags ( self . richtext ) ) . words ( 10 , truncate = " ... " )
)
class RichTextFootnoteMixin :
OO_FOOTNOTES = re . compile ( " <a.*?>(<sup>(.*?)</sup>)</a> " )
MATCH_FOOTNOTES = re . compile ( " <sup>( \ w+)</sup> " )
def get_prepared_richtext ( self , richtext ) :
# Find all footnotes and convert them into links
richtext = super ( ) . get_prepared_richtext ( richtext )
richtext = self . OO_FOOTNOTES . subn ( ' \ g<1> ' , richtext ) [ 0 ]
rv = self . MATCH_FOOTNOTES . subn (
' <sup id= \" back \ g<1> \" class= " footnote " ><a href= \" #fn \ g<1> \" > \ g<1></a></sup> ' ,
richtext ) [ 0 ]
return rv