Initial embed commit. Implements TemplateField and EmbedField - model fields that...
authorStephen Burrows <stephen.r.burrows@gmail.com>
Mon, 27 Sep 2010 17:43:32 +0000 (13:43 -0400)
committerStephen Burrows <stephen.r.burrows@gmail.com>
Mon, 4 Oct 2010 14:46:05 +0000 (10:46 -0400)
contrib/penfield/admin.py
contrib/penfield/embed.py [new file with mode: 0644]
contrib/penfield/models.py
contrib/penfield/templatetags/embed.py [new file with mode: 0644]

index 85888aa..a326d22 100644 (file)
@@ -1,6 +1,6 @@
 from django.contrib import admin
 from philo.admin import EntityAdmin
 from django.contrib import admin
 from philo.admin import EntityAdmin
-from philo.contrib.penfield.models import BlogEntry, Blog, BlogView, Newsletter, NewsletterArticle, NewsletterIssue, NewsletterView
+from philo.contrib.penfield.models import BlogEntry, Blog, BlogView, Newsletter, NewsletterArticle, NewsletterIssue, NewsletterView, Test
 
 
 class TitledAdmin(EntityAdmin):
 
 
 class TitledAdmin(EntityAdmin):
@@ -36,6 +36,11 @@ class NewsletterViewAdmin(EntityAdmin):
        pass
 
 
        pass
 
 
+class TestAdmin(admin.ModelAdmin):
+       pass
+
+
+admin.site.register(Test, TestAdmin)
 admin.site.register(Blog, BlogAdmin)
 admin.site.register(BlogEntry, BlogEntryAdmin)
 admin.site.register(BlogView, BlogViewAdmin)
 admin.site.register(Blog, BlogAdmin)
 admin.site.register(BlogEntry, BlogEntryAdmin)
 admin.site.register(BlogView, BlogViewAdmin)
diff --git a/contrib/penfield/embed.py b/contrib/penfield/embed.py
new file mode 100644 (file)
index 0000000..b279fc7
--- /dev/null
@@ -0,0 +1,81 @@
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import ValidationError
+from django.db import models
+from django.template import Template, loader, loader_tags
+import re
+from philo.contrib.penfield.templatetags.embed import EmbedNode
+
+
+embed_re = re.compile("{% embed (?P<app_label>\w+)\.(?P<model>\w+) (?P<pk>)\w+ %}")
+
+
+class TemplateField(models.TextField):
+       def validate(self, value, model_instance):
+               """For value (a template), make sure that all included templates exist."""
+               super(TemplateField, self).validate(value, model_instance)
+               try:
+                       self.validate_template(self.to_template(value))
+               except Exception, e:
+                       raise ValidationError("Template code invalid. Error was: %s: %s" % (e.__class__.__name__, e))
+       
+       def validate_template(self, template):
+               for node in template.nodelist:
+                       if isinstance(node, loader_tags.ExtendsNode):
+                               extended_template = node.get_parent(Context())
+                               self.validate_template(extended_template)
+                       elif isinstance(node, loader_tags.IncludeNode):
+                               included_template = loader.get_template(node.template_name.resolve(Context()))
+                               self.validate_template(extended_template)
+       
+       def to_template(self, value):
+               return Template(value)
+
+
+class EmbedField(TemplateField):
+       def validate_template(self, template):
+               """Check to be sure that the embedded instances and templates all exist."""
+               for node in template.nodelist:
+                       if isinstance(node, loader_tags.ExtendsNode):
+                               extended_template = node.get_parent(Context())
+                               self.validate_template(extended_template)
+                       elif isinstance(node, loader_tags.IncludeNode):
+                               included_template = loader.get_template(node.template_name.resolve(Context()))
+                               self.validate_template(extended_template)
+                       elif isinstance(node, EmbedNode):
+                               if node.template_name is not None:
+                                       embedded_template = loader.get_template(node.template_name)
+                                       self.validate_template(embedded_template)
+                               elif node.object_pk is not None:
+                                       embedded_instance = node.model.objects.get(pk=node.object_pk)
+       
+       def to_template(self, value):
+               return Template("{% load embed %}" + value)
+
+
+class Embed(models.Model):
+       embedder_embed_field = models.CharField(max_length=255)
+       
+       embedder_contenttype = models.ForeignKey(ContentType, related_name="embedder_related")
+       embedder_object_id = models.PositiveIntegerField()
+       embedder = generic.GenericForeignKey("embedder_contenttype", "embedder_object_id")
+       
+       embedded_contenttype = models.ForeignKey(ContentType, related_name="embedded_related")
+       embedded_object_id = models.PositiveIntegerField()
+       embedded = generic.GenericForeignKey("embedded_contenttype", "embedded_object_id")
+       
+       def delete(self):
+               # Unclear whether this would be called by a cascading deletion.
+               
+               super(Embed, self).delete()
+       
+       class Meta:
+               app_label = 'penfield'
+
+
+class Test(models.Model):
+       template = TemplateField()
+       embedder = EmbedField()
+       
+       class Meta:
+               app_label = 'penfield'
\ No newline at end of file
index f927a58..57e6ae3 100644 (file)
@@ -10,6 +10,7 @@ from philo.utils import paginate
 from philo.contrib.penfield.validators import validate_pagination_count
 from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
 from philo.contrib.penfield.utils import FeedMultiViewMixin
 from philo.contrib.penfield.validators import validate_pagination_count
 from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
 from philo.contrib.penfield.utils import FeedMultiViewMixin
+from philo.contrib.penfield.embed import *
 
 
 class Blog(Entity, Titled):
 
 
 class Blog(Entity, Titled):
diff --git a/contrib/penfield/templatetags/embed.py b/contrib/penfield/templatetags/embed.py
new file mode 100644 (file)
index 0000000..abdb1de
--- /dev/null
@@ -0,0 +1,90 @@
+from django import template
+from django.contrib.contenttypes.models import ContentType
+from django.conf import settings
+
+
+register = template.Library()
+
+
+class EmbedNode(template.Node):
+       def __init__(self, model, varname, object_pk=None, template_name=None):
+               assert template_name is not None or object_pk is not None
+               
+               app_label, model = model.split('.')
+               self.model = ContentType.objects.get(app_label=app_label, model=model).model_class()
+               self.varname = varname
+               self.object_pk = object_pk
+               self.template_name = template_name
+       
+       def render(self, context):
+               if self.template_name is not None:
+                       template_name = self.template_name.resolve(context)
+               
+                       try:
+                               t = template.loader.get_template(template_name)
+                       except template.TemplateDoesNotExist:
+                               return settings.TEMPLATE_STRING_IF_INVALID
+                       else:
+                               if self.varname not in context:
+                                       context[self.varname] = {}
+                               context[self.varname][self.model] = t
+                       return ''
+               
+               # Otherwise self.object_pk is set. Render the instance with the appropriate template!
+               try:
+                       instance = self.model.objects.get(pk=self.object_pk.resolve(context))
+               except self.model.DoesNotExist:
+                       return settings.TEMPLATE_STRING_IF_INVALID
+               
+               try:
+                       t = context[self.varname][self.model]
+               except KeyError:
+                       return settings.TEMPLATE_STRING_IF_INVALID
+               
+               context.push()
+               context['embedded'] = instance
+               t_rendered = t.render(context)
+               context.pop()
+               return t_rendered
+
+
+def do_embed(parser, token):
+       """
+       The {% embed %} tag can be used in three ways:
+       {% embed as <varname> %} :: This sets which variable will be used to track embedding template names for the current context. Default: "embed"
+       {% embed <app_label>.<model_name> with <template> %} :: Sets which template will be used to render a particular model.
+       {% embed <app_label>.<model_name> <object_pk> %} :: Embeds the instance specified by the given parameters in the document with the previously-specified template.
+       """
+       args = token.split_contents()
+       tag = args[0]
+       
+       if len(args) < 2:
+               raise template.TemplateSyntaxError('"%s" template tag must have at least three arguments.' % tag)
+       elif len(args) > 4:
+               raise template.TemplateSyntaxError('"%s" template tag may have no more than four arguments.' % tag)
+       else:
+               if len(args) == 3 and args[1] == "as":
+                       parser._embedNodeVarName = args[2]
+                       return template.defaulttags.CommentNode()
+               
+               if '.' not in args[1]:
+                       raise template.TemplateSyntaxError('"%s" template tag expects the first argument to be of the type app_label.model' % tag)
+               
+               if len(args) == 3:
+                       return EmbedNode(args[1], object_pk=args[2], varname=getattr(parser, '_embedNodeVarName', 'embed'))
+               else:
+                       # 3 args
+                       if args[2] != "with":
+                               raise template.TemplateSyntaxError('"%s" template tag requires the second of three arguments to be "with"' % tag)
+                       
+                       if args[3][0] not in ['"', "'"] and args[3][-1] not in ['"', "'"]:
+                               raise template.TemplateSyntaxError('"%s" template tag expects the template name to be in quotes.' % tag)
+                       if args[3][0] != args[3][-1]:
+                               raise template.TemplateSyntaxError('"%s" template tag called with non-matching quotes.' % tag)
+                       
+                       template_name = args[3].strip('"\'')
+                       
+                       return EmbedNode(args[1], template_name=template_name, varname=getattr(parser, '_embedNodeVarName', 'embed'))
+
+
+register.tag('embed', do_embed)
\ No newline at end of file