From a752922a230a38c3273d8e9b354bc18d041cc24f Mon Sep 17 00:00:00 2001 From: Stephen Burrows Date: Mon, 4 Oct 2010 11:09:54 -0400 Subject: [PATCH] Adjusted to match the new master TemplateField changes. Switched the setting of _embedded_instances to the EmbedField clean method so that validationerrors can be raised. Added South introspection rules. Switched NewsletterArticle to use EmbedFields. Set EmbedNode to pre-calculate templates and instances if appropriate and to suppress any errors that might occur. Made invalid content type references raise a TemplateSyntaxError, in line with the container template tag. --- contrib/penfield/admin.py | 6 +- contrib/penfield/embed.py | 77 +++++++++----------------- contrib/penfield/models.py | 4 +- contrib/penfield/templatetags/embed.py | 70 +++++++++++++++-------- 4 files changed, 77 insertions(+), 80 deletions(-) diff --git a/contrib/penfield/admin.py b/contrib/penfield/admin.py index a326d22..4be78c0 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, Test +from philo.contrib.penfield.models import BlogEntry, Blog, BlogView, Newsletter, NewsletterArticle, NewsletterIssue, NewsletterView, Embed class TitledAdmin(EntityAdmin): @@ -36,11 +36,11 @@ class NewsletterViewAdmin(EntityAdmin): pass -class TestAdmin(admin.ModelAdmin): +class EmbedAdmin(admin.ModelAdmin): pass -admin.site.register(Test, TestAdmin) +admin.site.register(Embed, EmbedAdmin) 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 index 7f875cc..ca49baa 100644 --- a/contrib/penfield/embed.py +++ b/contrib/penfield/embed.py @@ -2,36 +2,16 @@ 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 +from django.template import loader, loader_tags, Parser, Lexer, Template import re +from philo.models.fields import TemplateField from philo.contrib.penfield.templatetags.embed import EmbedNode +from philo.utils import nodelist_crawl 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 Embed(models.Model): embedder_content_type = models.ForeignKey(ContentType, related_name="embedder_related") embedder_object_id = models.PositiveIntegerField() @@ -81,29 +61,30 @@ def sync_embedded_instances(model_instance, embedded_instances): class EmbedField(TemplateField): - _embedded_instances = set() + def process_node(self, node, results): + if isinstance(node, EmbedNode) and node.instance is not None: + if not node.instance: + raise ValidationError("Instance with content type %s.%s and id %s does not exist." % (node.content_type.app_label, node.content_type.model, node.object_pk)) + + results.append(node.instance) - 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: - self._embedded_instances.add(node.model.objects.get(pk=node.object_pk)) - - def pre_save(self, model_instance, add): + def clean(self, value, model_instance): + value = super(EmbedField, self).clean(value, model_instance) + if not hasattr(model_instance, '_embedded_instances'): model_instance._embedded_instances = set() - model_instance._embedded_instances |= self._embedded_instances - return getattr(model_instance, self.attname) + + model_instance._embedded_instances |= set(nodelist_crawl(Template(value).nodelist, self.process_node)) + + return value + + +try: + from south.modelsinspector import add_introspection_rules +except ImportError: + pass +else: + add_introspection_rules([], ["^philo\.contrib\.penfield\.embed\.EmbedField"]) # Add a post-save signal function to run the syncer. @@ -122,12 +103,4 @@ def post_delete_cascade(sender, instance, **kwargs): for embed in embeds: embed.delete() Embed.objects.filter(embedder_content_type=ct, embedder_object_id=instance.id).delete() -models.signals.post_delete.connect(post_delete_cascade) - - -class Test(models.Model): - template = TemplateField() - embedder = EmbedField() - - class Meta: - app_label = 'penfield' \ No newline at end of file +models.signals.post_delete.connect(post_delete_cascade) \ No newline at end of file diff --git a/contrib/penfield/models.py b/contrib/penfield/models.py index 555d9a2..30480eb 100644 --- a/contrib/penfield/models.py +++ b/contrib/penfield/models.py @@ -259,8 +259,8 @@ class NewsletterArticle(Entity, Titled): newsletter = models.ForeignKey(Newsletter, related_name='articles') authors = models.ManyToManyField(getattr(settings, 'PHILO_PERSON_MODULE', 'auth.User'), related_name='newsletterarticles') date = models.DateTimeField(default=datetime.now) - lede = models.TextField(null=True, blank=True) - full_text = models.TextField() + lede = EmbedField(null=True, blank=True, verbose_name='Summary') + full_text = EmbedField() tags = models.ManyToManyField(Tag, related_name='newsletterarticles', blank=True, null=True) class Meta: diff --git a/contrib/penfield/templatetags/embed.py b/contrib/penfield/templatetags/embed.py index f8ab3f0..8da99af 100644 --- a/contrib/penfield/templatetags/embed.py +++ b/contrib/penfield/templatetags/embed.py @@ -1,52 +1,69 @@ from django import template from django.contrib.contenttypes.models import ContentType from django.conf import settings +from philo.utils import LOADED_TEMPLATE_ATTR register = template.Library() class EmbedNode(template.Node): - def __init__(self, model, varname, object_pk=None, template_name=None): + def __init__(self, content_type, 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.content_type = content_type 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) + if object_pk is not None: + self.object_pk = object_pk + try: + self.instance = content_type.get_object_for_this_type(pk=object_pk) + except content_type.model_class().DoesNotExist: + self.instance = False + else: + self.instance = None + + if template_name is not None: try: - t = template.loader.get_template(template_name) + self.template = template.loader.get_template(template_name) except template.TemplateDoesNotExist: + self.template = False + else: + self.template = None + + def render(self, context): + if self.template_name is not None: + if self.template is False: return settings.TEMPLATE_STRING_IF_INVALID - else: - if self.varname not in context: - context[self.varname] = {} - context[self.varname][self.model] = t + + if self.varname not in context: + context[self.varname] = {} + context[self.varname][self.content_type] = self.template + 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: + # Otherwise self.instance should be set. Render the instance with the appropriate template! + if self.instance is None or self.instance is False: return settings.TEMPLATE_STRING_IF_INVALID try: - t = context[self.varname][self.model] + t = context[self.varname][self.content_type] except KeyError: return settings.TEMPLATE_STRING_IF_INVALID context.push() - context['embedded'] = instance + context['embedded'] = self.instance t_rendered = t.render(context) context.pop() return t_rendered +def get_embedded(self): + return template.loader.get_template(self.template_name) + + +setattr(EmbedNode, LOADED_TEMPLATE_ATTR, property(get_embedded)) + + def do_embed(parser, token): """ The {% embed %} tag can be used in three ways: @@ -67,10 +84,17 @@ def do_embed(parser, token): 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) + raise template.TemplateSyntaxError('"%s" template tag expects the first argument to be of the form app_label.model' % tag) + + app_label, model = args[1].split('.') + try: + ct = ContentType.objects.get(app_label=app_label, model=model) + except ContentType.DoesNotExist: + raise template.TemplateSyntaxError('"%s" template tag option "references" requires an argument of the form app_label.model which refers to an installed content type (see django.contrib.contenttypes)' % tag) if len(args) == 3: - return EmbedNode(args[1], object_pk=args[2], varname=getattr(parser, '_embedNodeVarName', 'embed')) + + return EmbedNode(ct, object_pk=args[2], varname=getattr(parser, '_embedNodeVarName', 'embed')) else: # 3 args if args[2] != "with": @@ -83,7 +107,7 @@ def do_embed(parser, token): template_name = args[3].strip('"\'') - return EmbedNode(args[1], template_name=template_name, varname=getattr(parser, '_embedNodeVarName', 'embed')) + return EmbedNode(ct, template_name=template_name, varname=getattr(parser, '_embedNodeVarName', 'embed')) register.tag('embed', do_embed) \ No newline at end of file -- 2.20.1