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

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)