Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

added an extensible hook to add different types of variables to

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

0 comments on commit eb4ec7e

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