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.
283 lines
9.6 KiB
283 lines
9.6 KiB
import re
|
|
|
|
from django.core.cache import cache
|
|
from django.db import models
|
|
from django.core.exceptions import ValidationError
|
|
from django.template import Template, TemplateSyntaxError, \
|
|
TemplateDoesNotExist, loader
|
|
from django.db.models import signals
|
|
from django.contrib.sites.models import Site
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
from cms.models import CMSPlugin
|
|
|
|
from sekizai.helpers import (
|
|
Watcher as sekizai_context_watcher,
|
|
get_varname as sekizai_cache_key,
|
|
)
|
|
|
|
from .resources_processor import get_resources
|
|
from .settings import (
|
|
snippet_caching_time, caching_enabled, allow_inheritance,
|
|
inherit_variable_pattern)
|
|
|
|
|
|
class SmartSnippet(models.Model):
|
|
name = models.CharField(unique=True, max_length=255)
|
|
template_code = models.TextField(_("Template code"), blank=True)
|
|
template_path = models.CharField(
|
|
_("Template path"),
|
|
max_length=100, blank=True,
|
|
help_text=_(
|
|
'Enter a template (i.e. "snippets/plugin_xy.html")'
|
|
' which will be rendered.'))
|
|
sites = models.ManyToManyField(
|
|
Site, null=False, blank=True,
|
|
help_text=_('Select on which sites the snippet will be available.'),
|
|
verbose_name='sites')
|
|
description = models.TextField(_("Description"), blank=True)
|
|
documentation_link = models.CharField(
|
|
_("Documentation link"),
|
|
max_length=100, blank=True,
|
|
help_text=_('Enter URL (i.e. "http://snippets/docs/plugin_xy.html")'
|
|
' to the extended documentation.'))
|
|
|
|
class Meta:
|
|
ordering = ['name']
|
|
verbose_name = 'Smart Snippet'
|
|
verbose_name_plural = 'Smart Snippets'
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
#hack due to
|
|
# https://code.djangoproject.com/ticket/16433#no1
|
|
for rel_obj in self._meta.get_all_related_objects():
|
|
rel_obj.help_text = ""
|
|
super(SmartSnippet, self).__init__(*args, **kwargs)
|
|
|
|
def get_template(self):
|
|
if self.template_path:
|
|
return loader.get_template(self.template_path)
|
|
else:
|
|
return Template(self.template_code)
|
|
|
|
def clean_template_code(self):
|
|
try:
|
|
self.get_template()
|
|
except (TemplateSyntaxError, TemplateDoesNotExist), e:
|
|
raise ValidationError(str(e))
|
|
|
|
def get_cache_key(self):
|
|
return 'smartsnippet-%s' % self.pk
|
|
|
|
def render(self, context):
|
|
return self.get_template().render(context)
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
|
|
class SmartSnippetVariable(models.Model):
|
|
name = models.CharField(
|
|
max_length=50,
|
|
help_text=_('Enter the name of the variable defined in the smart snippet '
|
|
'template. Unallowed characters will be removed when the form is saved.'))
|
|
widget = models.CharField(
|
|
max_length=50,
|
|
help_text=_('Select the type of the variable defined '
|
|
'in the smart snippet template.'))
|
|
snippet = models.ForeignKey(SmartSnippet, related_name="variables")
|
|
|
|
resources = models.TextField(_('Admin resources'), null=True, blank=True)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(SmartSnippetVariable, self).__init__(*args, **kwargs)
|
|
self._resource_dict = None
|
|
|
|
@property
|
|
def resources_dict(self):
|
|
if self._resource_dict is None:
|
|
self._resource_dict = get_resources(self.resources)
|
|
return self._resource_dict
|
|
|
|
class Meta:
|
|
unique_together = (('snippet', 'name'))
|
|
ordering = ['name']
|
|
verbose_name = "Standard variable"
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.name = clean_variable_name(self.name)
|
|
super(SmartSnippetVariable, self).save(*args, **kwargs)
|
|
# auto-generate missing variables for all snippet plugins
|
|
plugin_ids = SmartSnippetPointer.objects \
|
|
.filter(snippet=self.snippet) \
|
|
.exclude(variables__snippet_variable=self) \
|
|
.values_list('id', flat=True)
|
|
# in order to improve performance do a bulk create
|
|
Variable.objects.bulk_create([
|
|
Variable(snippet_id=plugin_id, snippet_variable=self)
|
|
for plugin_id in plugin_ids
|
|
])
|
|
if not caching_enabled:
|
|
return
|
|
# invalidate cache for changed plugins
|
|
cache_keys = [
|
|
SmartSnippetPointer.cache_key_format.format(primary_key=plugin_id)
|
|
for plugin_id in plugin_ids
|
|
]
|
|
cache.delete_many(cache_keys)
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
|
|
class SmartSnippetPointer(CMSPlugin):
|
|
snippet = models.ForeignKey(SmartSnippet)
|
|
cache_key_format = 'smartsnippet-pointer-{primary_key}'
|
|
|
|
class Meta:
|
|
db_table = 'cmsplugin_smartsnippetpointer'
|
|
|
|
def get_cache_key(self):
|
|
return self.cache_key_format.format(primary_key=self.pk)
|
|
|
|
def _do_restore_sekizai_context(self, context, changes):
|
|
"""Sekizai tags involve magic with the context object.
|
|
When need to restore the sekizai content to the context"""
|
|
cache_key = sekizai_cache_key()
|
|
sekizai_container = context.get(cache_key)
|
|
for key, values in changes.items():
|
|
sekizai_namespace = sekizai_container[key]
|
|
for value in values:
|
|
sekizai_namespace.append(value)
|
|
|
|
def fetch_cached(self, context):
|
|
cache_key = self.get_cache_key()
|
|
user = context['request'].user
|
|
if not user.is_staff and caching_enabled and cache.has_key(cache_key):
|
|
cached_value = cache.get(cache_key)
|
|
rendered_content = cached_value.get('content')
|
|
sekizai = cached_value.get('sekizai')
|
|
self._do_restore_sekizai_context(context, sekizai)
|
|
return rendered_content
|
|
|
|
def set_and_get_cache(self, user, sekizai_diff, content):
|
|
if not user.is_staff and caching_enabled:
|
|
value = {'content': content, 'sekizai': sekizai_diff}
|
|
key = self.get_cache_key()
|
|
cache.set(key, value, snippet_caching_time)
|
|
return content
|
|
|
|
def render_pointer(self, context):
|
|
vars_qs = self.variables.select_related('snippet_variable').all()
|
|
|
|
def var_for_context(var):
|
|
default, overwrite = var.formatted_value, None
|
|
if allow_inheritance:
|
|
overwrite = context.get(
|
|
inherit_variable_pattern.format(identifier=var.pk))
|
|
name = var.snippet_variable.name
|
|
value = overwrite if overwrite is not None else default
|
|
return (name, value)
|
|
|
|
variables = dict(var_for_context(var) for var in vars_qs)
|
|
context.update(variables)
|
|
sekizai_differ = sekizai_context_watcher(context)
|
|
content = self.snippet.render(context)
|
|
sekizai_diff = sekizai_differ.get_changes()
|
|
user = context.get('request').user
|
|
return self.set_and_get_cache(user, sekizai_diff, content)
|
|
|
|
def render(self, context):
|
|
return self.fetch_cached(context) or self.render_pointer(context)
|
|
|
|
def copy_relations(self, old_instance):
|
|
for variable in old_instance.variables.all():
|
|
variable.pk = None
|
|
variable.snippet = self
|
|
variable.save()
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.snippet)
|
|
|
|
|
|
class Variable(models.Model):
|
|
snippet_variable = models.ForeignKey(SmartSnippetVariable,
|
|
related_name='variables')
|
|
value = models.TextField()
|
|
snippet = models.ForeignKey(SmartSnippetPointer, related_name='variables')
|
|
|
|
class Meta:
|
|
unique_together = (('snippet_variable', 'snippet'))
|
|
|
|
@property
|
|
def formatted_value(self):
|
|
from widgets_pool import widget_pool
|
|
widget_instance = widget_pool.get_widget(self.widget)(self)
|
|
return widget_instance.formatted_value
|
|
|
|
@property
|
|
def name(self):
|
|
return self.snippet_variable.name
|
|
|
|
@property
|
|
def widget(self):
|
|
return self.snippet_variable.widget
|
|
|
|
@property
|
|
def templates(self):
|
|
return self.snippet_variable.resources_dict.get('html', set())
|
|
|
|
@property
|
|
def js(self):
|
|
return self.snippet_variable.resources_dict.get('js', set())
|
|
|
|
@property
|
|
def css(self):
|
|
return self.snippet_variable.resources_dict.get('css', set())
|
|
|
|
|
|
class DropDownVariable(SmartSnippetVariable):
|
|
choices = models.CharField(
|
|
max_length=512,
|
|
help_text=_(
|
|
'Enter a comma separated list of choices that will be '
|
|
'available in the dropdown variable when adding and '
|
|
'configuring the smart snippet on a page.'))
|
|
|
|
@property
|
|
def choices_list(self):
|
|
return ([choice.strip() for choice in self.choices.split(',') if choice.strip()]
|
|
if self.choices else [])
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.widget = 'DropDownField'
|
|
super(DropDownVariable, self).save(*args, **kwargs)
|
|
|
|
|
|
def remove_cached_pointers(instance, **kwargs):
|
|
if not caching_enabled:
|
|
return
|
|
|
|
pointer_pks = SmartSnippetPointer.objects.filter(
|
|
snippet=instance
|
|
).values_list('pk', flat=True)
|
|
cache_keys = [
|
|
SmartSnippetPointer.cache_key_format.format(primary_key=pk)
|
|
for pk in pointer_pks
|
|
]
|
|
cache.delete_many(cache_keys)
|
|
|
|
|
|
def remove_cached_variables(instance, **kwargs):
|
|
if not caching_enabled:
|
|
return
|
|
key = instance.snippet.get_cache_key()
|
|
if key in cache:
|
|
cache.delete(key)
|
|
|
|
def clean_variable_name(variable_name):
|
|
""" Convert the variable name to a name that can be used as a template variable. """
|
|
return re.sub(r"[^a-zA-Z0-9_]", '', variable_name.replace(' ', '_'))
|
|
|
|
signals.post_save.connect(remove_cached_pointers, sender=SmartSnippet)
|
|
signals.post_save.connect(remove_cached_variables, sender=Variable)
|
|
|