From 353614c3d8e5af2872d2d0ef0c494e36548f6370 Mon Sep 17 00:00:00 2001 From: Stephen Burrows Date: Mon, 27 Sep 2010 13:43:32 -0400 Subject: [PATCH] Initial embed commit. Implements TemplateField and EmbedField - model fields that validate their contents as templates and templates with embedded content, respectively. Implements the {% embed %} template tag, which allows arbitrary instances to be rendered with a given template. Initial sketch of the embed tracker (which should handle cascading deletions) also in place. --- contrib/penfield/admin.py | 7 +- contrib/penfield/embed.py | 81 +++++++++++++++++++++++ contrib/penfield/models.py | 1 + contrib/penfield/templatetags/embed.py | 90 ++++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 contrib/penfield/embed.py create mode 100644 contrib/penfield/templatetags/embed.py diff --git a/contrib/penfield/admin.py b/contrib/penfield/admin.py index 85888aa..a326d22 100644 --- a/contrib/penfield/admin.py +++ b/contrib/penfield/admin.py @@ -1,6 +1,6 @@ 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): @@ -36,6 +36,11 @@ class NewsletterViewAdmin(EntityAdmin): 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) diff --git a/contrib/penfield/embed.py b/contrib/penfield/embed.py new file mode 100644 index 0000000..b279fc7 --- /dev/null +++ b/contrib/penfield/embed.py @@ -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\w+)\.(?P\w+) (?P)\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 diff --git a/contrib/penfield/models.py b/contrib/penfield/models.py index f927a58..57e6ae3 100644 --- a/contrib/penfield/models.py +++ b/contrib/penfield/models.py @@ -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.embed import * class Blog(Entity, Titled): diff --git a/contrib/penfield/templatetags/embed.py b/contrib/penfield/templatetags/embed.py new file mode 100644 index 0000000..abdb1de --- /dev/null +++ b/contrib/penfield/templatetags/embed.py @@ -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 %} :: This sets which variable will be used to track embedding template names for the current context. Default: "embed" + {% embed . with