Skip to content

Commit

Permalink
added an extensible hook to add different types of variables to
Browse files Browse the repository at this point in the history
a template
  • Loading branch information
beniwohli committed Mar 19, 2012
1 parent 1a83ee3 commit eb4ec7e
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 8 deletions.
61 changes: 55 additions & 6 deletions cmsplugin_text_ng/cms_plugins.py
Original file line number Diff line number Diff line change
@@ -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


Expand Down
15 changes: 15 additions & 0 deletions cmsplugin_text_ng/forms.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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']
26 changes: 24 additions & 2 deletions cmsplugin_text_ng/models.py
Original file line number Diff line number Diff line change
@@ -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)

Expand All @@ -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)
Expand All @@ -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)
2 changes: 2 additions & 0 deletions cmsplugin_text_ng/templatetags/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-

39 changes: 39 additions & 0 deletions cmsplugin_text_ng/templatetags/text_ng_tags.py
Original file line number Diff line number Diff line change
@@ -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)
31 changes: 31 additions & 0 deletions cmsplugin_text_ng/type_registry.py
Original file line number Diff line number Diff line change
@@ -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()
17 changes: 17 additions & 0 deletions cmsplugin_text_ng/utils.py
Original file line number Diff line number Diff line change
@@ -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.