Skip to content
Browse files

added an extensible hook to add different types of variables to

a template
  • Loading branch information...
1 parent 1a83ee3 commit eb4ec7e7cc6f650b41e76f234beb59e98a604024 @piquadrat piquadrat committed Mar 19, 2012
View
61 cmsplugin_text_ng/cms_plugins.py
@@ -1,19 +1,68 @@
-from cms.plugins.text.cms_plugins import TextPlugin
-from cmsplugin_text_ng.models import TextNG
-from django.utils.translation import ugettext_lazy as _
-from cms.plugin_pool import plugin_pool
+from collections import defaultdict
from django import template
-from django.template.context import Context
+from django.contrib.admin import StackedInline
+from django.utils.translation import ugettext_lazy as _
from django.utils.safestring import mark_safe
+from cms.plugins.text.cms_plugins import TextPlugin
+from cms.plugin_pool import plugin_pool
+from cmsplugin_text_ng.forms import PluginAddForm, PluginEditForm
+
+from cmsplugin_text_ng.models import TextNG
+from cmsplugin_text_ng.utils import get_variables_from_template
+from cmsplugin_text_ng import type_registry
+
+
class TextPluginNextGeneration(TextPlugin):
model = TextNG
name = _("Text NG")
+ form = PluginEditForm
+
+ def __init__(self, model=None, admin_site=None):
+ self.inlines = []
+ for model_class in type_registry.get_type_list():
+ class TypeStackedInline(StackedInline):
+ model = model_class
+ extra = 0
+ can_delete = False
+ readonly_fields = ('label',)
+ self.inlines.append(TypeStackedInline)
+ super(TextPluginNextGeneration, self).__init__(model, admin_site)
+
+ def get_form(self, request, obj=None, **kwargs):
+ if not obj:
+ return PluginAddForm
+ else:
+ return super(TextPluginNextGeneration, self).get_form(request, obj, **kwargs)
+
+ def get_readonly_fields(self, request, obj=None):
+ if obj and obj.pk:
+ return []
+
+ def save_model(self, request, obj, form, change):
+ super(TextPluginNextGeneration, self).save_model(request, obj, form, change)
+ for label, variable in get_variables_from_template(obj.template.path).items():
+ variable['type'].objects.get_or_create(text_ng=obj, label=label)
+
+ def get_formsets(self, request, obj=None):
+ if not obj:
+ raise StopIteration()
+ types = defaultdict(lambda: 0)
+ for label, variable in get_variables_from_template(obj.template.path).items():
+ types[variable['type']] += 1
+ for inline in self.inline_instances:
+ if inline.model in types:
+ inline.max_num = types[inline.model]
+ yield inline.get_formset(request, obj)
def render(self, context, instance, placeholder):
context = super(TextPluginNextGeneration, self).render(context, instance, placeholder)
t = template.loader.get_template(instance.template.path)
- context.update({'body': mark_safe(t.render(Context(context)))})
+ variables = get_variables_from_template(t)
+ for label, variable in variables.items():
+ model_type = variable['type']
+ context[label] = model_type.objects.select_related(*model_type.select_related).get(text_ng=instance, label=label).value
+ context.update({'body': mark_safe(t.render(context))})
return context
View
15 cmsplugin_text_ng/forms.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from django import forms
+from cmsplugin_text_ng.models import TextNG
+
+class PluginAddForm(forms.ModelForm):
+ class Meta:
+ fields = ('template',)
+ model = TextNG
+
+
+class PluginEditForm(forms.ModelForm):
+ class Meta:
+ fields = ('body',)
+ model = TextNG
View
81 ...migrations/0002_auto__add_textngvariabletext__add_unique_textngvariabletext_text_ng_la.py
@@ -0,0 +1,81 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding model 'TextNGVariableText'
+ db.create_table('cmsplugin_text_ng_textngvariabletext', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('text_ng', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', to=orm['cmsplugin_text_ng.TextNG'])),
+ ('label', self.gf('django.db.models.fields.CharField')(max_length=20)),
+ ('value', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
+ ))
+ db.send_create_signal('cmsplugin_text_ng', ['TextNGVariableText'])
+
+ # Adding unique constraint on 'TextNGVariableText', fields ['text_ng', 'label']
+ db.create_unique('cmsplugin_text_ng_textngvariabletext', ['text_ng_id', 'label'])
+
+
+ def backwards(self, orm):
+
+ # Removing unique constraint on 'TextNGVariableText', fields ['text_ng', 'label']
+ db.delete_unique('cmsplugin_text_ng_textngvariabletext', ['text_ng_id', 'label'])
+
+ # Deleting model 'TextNGVariableText'
+ db.delete_table('cmsplugin_text_ng_textngvariabletext')
+
+
+ models = {
+ 'cms.cmsplugin': {
+ 'Meta': {'object_name': 'CMSPlugin'},
+ 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}),
+ 'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}),
+ 'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+ 'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
+ },
+ 'cms.placeholder': {
+ 'Meta': {'object_name': 'Placeholder'},
+ 'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'slot': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'})
+ },
+ 'cmsplugin_text_ng.textng': {
+ 'Meta': {'object_name': 'TextNG', 'db_table': "'cmsplugin_textng'"},
+ 'body': ('django.db.models.fields.TextField', [], {}),
+ 'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}),
+ 'template': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cmsplugin_text_ng.TextNGTemplate']"})
+ },
+ 'cmsplugin_text_ng.textngtemplate': {
+ 'Meta': {'ordering': "['title']", 'object_name': 'TextNGTemplate'},
+ 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cmsplugin_text_ng.TextNGTemplateCategory']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'path': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'cmsplugin_text_ng.textngtemplatecategory': {
+ 'Meta': {'ordering': "['title']", 'object_name': 'TextNGTemplateCategory'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'cmsplugin_text_ng.textngvariabletext': {
+ 'Meta': {'unique_together': "(('text_ng', 'label'),)", 'object_name': 'TextNGVariableText'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'label': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+ 'text_ng': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['cmsplugin_text_ng.TextNG']"}),
+ 'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['cmsplugin_text_ng']
View
26 cmsplugin_text_ng/models.py
@@ -1,7 +1,11 @@
+from django.core.validators import RegexValidator
from django.db import models
-from cms.plugins.text.models import AbstractText
from django.utils.translation import ugettext_lazy as _
+from cms.plugins.text.models import AbstractText
+
+from cmsplugin_text_ng.type_registry import register_type
+
class TextNGTemplateCategory(models.Model):
title = models.CharField(max_length=128)
@@ -13,6 +17,7 @@ class Meta:
verbose_name_plural = _('template categories')
ordering = ['title']
+
class TextNGTemplate(models.Model):
category = models.ForeignKey(TextNGTemplateCategory, blank=True, null=True)
title = models.CharField(max_length=128)
@@ -28,9 +33,26 @@ class Meta:
verbose_name_plural = _('templates')
ordering = ['title']
+
class TextNG(AbstractText):
template = models.ForeignKey(TextNGTemplate)
class Meta:
verbose_name = _('text')
- verbose_name_plural = _('texts')
+ verbose_name_plural = _('texts')
+
+
+class TextNGVariableBase(models.Model):
+ select_related = []
+ text_ng = models.ForeignKey(TextNG, related_name='+')
+ label = models.CharField(_('label'), max_length=20, validators=[RegexValidator(regex='[_a-z]+', message=_('Only lower case characters.'))])
+
+ class Meta:
+ abstract = True
+ unique_together = ('text_ng', 'label')
+
+
+class TextNGVariableText(TextNGVariableBase):
+ value = models.TextField(_('value'), null=True, blank=True)
+
+register_type('text', TextNGVariableText)
View
2 cmsplugin_text_ng/templatetags/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+
View
39 cmsplugin_text_ng/templatetags/text_ng_tags.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+from django import template
+
+register = template.Library()
+
+
+class DefineNode(template.Node):
+ def __init__(self, variable_name, variable_type, optional=False):
+ self.variable_name = variable_name
+ self.variable_type = variable_type
+ self.optional = optional
+
+ def render(self, context):
+ """
+ This template tag does not materialize itself in the template
+ """
+ return ''
+
+
+error_message = 'Syntax: {% define variable_name as variable_type [optional] %}'
+
+@register.tag(name='define')
+def do_define(parser, token):
+ bits = token.split_contents()[1:]
+ if len(bits) < 3 and bits[1] != 'as':
+ raise template.TemplateSyntaxError(error_message)
+ del bits[1] # "as"
+ if len(bits) == 2:
+ variable_name, variable_type = bits
+ optional = False
+ elif len(bits) == 3:
+ if bits[2] == 'optional':
+ variable_name, variable_type = bits[:2]
+ optional = True
+ else:
+ raise template.TemplateSyntaxError(error_message)
+ else:
+ raise template.TemplateSyntaxError(error_message)
+ return DefineNode(variable_name, variable_type, optional)
View
31 cmsplugin_text_ng/type_registry.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+_registry = {}
+
+class VariableTypeAlreadyRegistered(Exception):
+ pass
+
+
+class InvalidType(Exception):
+ pass
+
+def register_type(type_name, model_class):
+ from cmsplugin_text_ng.models import TextNGVariableBase
+ if type_name in _registry:
+ if _registry[type_name] == model_class:
+ # already registered
+ return
+ else:
+ raise VariableTypeAlreadyRegistered(
+ 'The type "%s" is already registered by %s' % (type_name, _registry[type_name].__name__)
+ )
+ if not issubclass(model_class, TextNGVariableBase):
+ raise InvalidType('%s is not a subclass of TextNGVariableBase' % model_class.__name__)
+ # TODO: validate that there is a "value" field that is nullable
+ _registry[type_name] = model_class
+
+def get_type(type_name):
+ return _registry[type_name]
+
+def get_type_list():
+ return _registry.values()
View
17 cmsplugin_text_ng/utils.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+from django.template import loader
+
+from cmsplugin_text_ng.templatetags.text_ng_tags import DefineNode
+from cmsplugin_text_ng.type_registry import get_type
+
+def get_variables_from_template(template):
+ if isinstance(template, basestring):
+ template = loader.get_template(template)
+ variable_nodes = [n for n in template.nodelist if isinstance(n, DefineNode)]
+ variables = {}
+ for node in variable_nodes:
+ variables[node.variable_name] = {
+ 'type': get_type(node.variable_type),
+ 'optional': node.optional,
+ }
+ return variables

0 comments on commit eb4ec7e

Please sign in to comment.
Something went wrong with that request. Please try again.