From 22c4e979b5ad061e448318310fddc4bb8fc56465 Mon Sep 17 00:00:00 2001 From: Stephen Burrows Date: Tue, 5 Oct 2010 17:16:21 -0400 Subject: [PATCH] Removed referential integrity code. Moved embed templatetag into core. Differentiated ConstantEmbedNode (context-independent) and EmbedNode, analogous to django's ConstantIncludeNode/IncludeNode. --- contrib/penfield/admin.py | 7 +- contrib/penfield/embed.py | 124 ------------------ contrib/penfield/models.py | 13 +- models/__init__.py | 3 +- .../templatetags => templatetags}/embed.py | 98 ++++++++++---- 5 files changed, 80 insertions(+), 165 deletions(-) delete mode 100644 contrib/penfield/embed.py rename {contrib/penfield/templatetags => templatetags}/embed.py (59%) diff --git a/contrib/penfield/admin.py b/contrib/penfield/admin.py index 4be78c0..85888aa 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, Embed +from philo.contrib.penfield.models import BlogEntry, Blog, BlogView, Newsletter, NewsletterArticle, NewsletterIssue, NewsletterView class TitledAdmin(EntityAdmin): @@ -36,11 +36,6 @@ class NewsletterViewAdmin(EntityAdmin): pass -class EmbedAdmin(admin.ModelAdmin): - pass - - -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 deleted file mode 100644 index 522205d..0000000 --- a/contrib/penfield/embed.py +++ /dev/null @@ -1,124 +0,0 @@ -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 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, ContentTypeRegistryLimiter - - -embeddable_content_types = ContentTypeRegistryLimiter() - - -class Embed(models.Model): - embedder_content_type = models.ForeignKey(ContentType, related_name="embedder_related") - embedder_object_id = models.PositiveIntegerField() - embedder = generic.GenericForeignKey("embedder_content_type", "embedder_object_id") - - embedded_content_type = models.ForeignKey(ContentType, related_name="embedded_related") - embedded_object_id = models.PositiveIntegerField() - embedded = generic.GenericForeignKey("embedded_content_type", "embedded_object_id") - - def delete(self): - # This needs to be called manually. - super(Embed, self).delete() - - # Cycle through all the fields in the embedder and remove all references - # to the embedded object. - embedder = self.embedder - for field in embedder._meta.fields: - if isinstance(field, EmbedField): - attr = getattr(embedder, field.attname) - setattr(embedder, field.attname, self.embed_re.sub('', attr)) - - embedder.save() - - def get_embed_re(self): - """Convenience function to return a compiled regular expression to find embed tags that would create this instance.""" - if not hasattr(self, '_embed_re'): - ct = self.embedded_content_type - self._embed_re = re.compile("{%% ?embed %s.%s %s( .*?)? ?%%}" % (ct.app_label, ct.model, self.embedded_object_id)) - return self._embed_re - embed_re = property(get_embed_re) - - class Meta: - app_label = 'penfield' - - -def sync_embedded_instances(model_instance, embedded_instances): - model_instance_ct = ContentType.objects.get_for_model(model_instance) - - # Cycle through all the embedded instances and make sure that they are linked to - # the model instance. Track their pks. - new_embed_pks = [] - for embedded_instance in embedded_instances: - embedded_instance_ct = ContentType.objects.get_for_model(embedded_instance) - new_embed = Embed.objects.get_or_create(embedder_content_type=model_instance_ct, embedder_object_id=model_instance.id, embedded_content_type=embedded_instance_ct, embedded_object_id=embedded_instance.id)[0] - new_embed_pks.append(new_embed.pk) - - # Then, delete all embed objects related to this model instance which do not relate - # to one of the newly embedded instances. - Embed.objects.filter(embedder_content_type=model_instance_ct, embedder_object_id=model_instance.id).exclude(pk__in=new_embed_pks).delete() - - -class EmbedField(TemplateField): - def process_node(self, node, results): - if isinstance(node, EmbedNode) and node.instance is not None: - if node.content_type.model_class() not in embeddable_content_types.classes: - raise ValidationError("Class %s.%s cannot be embedded." % (node.content_type.app_label, node.content_type.model)) - - 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 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 |= 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. -def post_save_embed_sync(sender, instance, **kwargs): - if hasattr(instance, '_embedded_instances') and instance._embedded_instances: - sync_embedded_instances(instance, instance._embedded_instances) -models.signals.post_save.connect(post_save_embed_sync) - - -# Deletions can't cascade automatically without a GenericRelation - but there's no good way of -# knowing what models should have one. Anything can be embedded! Also, cascading would probably -# bypass the Embed model's delete method. -def post_delete_cascade(sender, instance, **kwargs): - if sender in embeddable_content_types.classes: - # Don't bother looking for Embed objects that embed a contenttype that can't be embedded. - ct = ContentType.objects.get_for_model(sender) - embeds = Embed.objects.filter(embedded_content_type=ct, embedded_object_id=instance.id) - for embed in embeds: - embed.delete() - - if not hasattr(sender._meta, '_has_embed_fields'): - sender._meta._has_embed_fields = False - for field in sender._meta.fields: - if isinstance(field, EmbedField): - sender._meta._has_embed_fields = True - break - - if sender._meta._has_embed_fields: - # If it doesn't have embed fields, then it can't be an embedder. - Embed.objects.filter(embedder_content_type=ct, embedder_object_id=instance.id).delete() -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 30480eb..c9c024c 100644 --- a/contrib/penfield/models.py +++ b/contrib/penfield/models.py @@ -1,6 +1,6 @@ from django.db import models from django.conf import settings -from philo.models import Tag, Titled, Entity, MultiView, Page, register_value_model +from philo.models import Tag, Titled, Entity, MultiView, Page, register_value_model, TemplateField from philo.exceptions import ViewCanNotProvideSubpath from django.conf.urls.defaults import url, patterns, include from django.core.urlresolvers import reverse @@ -10,8 +10,6 @@ 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 * -from django.template import add_to_builtins as register_templatetags class Blog(Entity, Titled): @@ -259,8 +257,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 = EmbedField(null=True, blank=True, verbose_name='Summary') - full_text = EmbedField() + lede = TemplateField(null=True, blank=True, verbose_name='Summary') + full_text = TemplateField() tags = models.ManyToManyField(Tag, related_name='newsletterarticles', blank=True, null=True) class Meta: @@ -440,7 +438,4 @@ class NewsletterView(MultiView, FeedMultiViewMixin): 'title': title } defaults.update(kwargs or {}) - return super(NewsletterView, self).get_feed(feed_type, extra_context, defaults) - - -register_templatetags('philo.contrib.penfield.templatetags.embed') \ No newline at end of file + return super(NewsletterView, self).get_feed(feed_type, extra_context, defaults) \ No newline at end of file diff --git a/models/__init__.py b/models/__init__.py index 5d39ac6..76d7812 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -9,4 +9,5 @@ from django.contrib.sites.models import Site register_value_model(User) register_value_model(Group) -register_value_model(Site) \ No newline at end of file +register_value_model(Site) +register_templatetags('philo.templatetags.embed') \ No newline at end of file diff --git a/contrib/penfield/templatetags/embed.py b/templatetags/embed.py similarity index 59% rename from contrib/penfield/templatetags/embed.py rename to templatetags/embed.py index 7d1b26a..c0fcff0 100644 --- a/contrib/penfield/templatetags/embed.py +++ b/templatetags/embed.py @@ -7,7 +7,8 @@ from philo.utils import LOADED_TEMPLATE_ATTR register = template.Library() -class EmbedNode(template.Node): +class ConstantEmbedNode(template.Node): + """Analogous to the ConstantIncludeNode, this node precompiles several variables necessary for correct rendering - namely the referenced instance or the included template.""" def __init__(self, content_type, varname, object_pk=None, template_name=None, kwargs=None): assert template_name is not None or object_pk is not None self.content_type = content_type @@ -19,24 +20,35 @@ class EmbedNode(template.Node): self.kwargs = kwargs 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 + self.compile_instance(object_pk) + if self.instance is False and settings.TEMPLATE_DEBUG: + raise else: self.instance = None if template_name is not None: - try: - self.template = template.loader.get_template(template_name) - except template.TemplateDoesNotExist: - self.template = False + self.compile_template(template_name[1:-1]) + if self.template is False and settings.TEMPLATE_DEBUG: + raise else: self.template = None + def compile_instance(self, object_pk): + self.object_pk = object_pk + model = self.content_type.model_class() + try: + self.instance = model.objects.get(pk=object_pk) + except model.DoesNotExist: + self.instance = False + + def compile_template(self, template_name): + try: + self.template = template.loader.get_template(template_name) + except template.TemplateDoesNotExist: + self.template = False + def render(self, context): - if self.template_name is not None: + if self.template is not None: if self.template is False: return settings.TEMPLATE_STRING_IF_INVALID @@ -50,26 +62,65 @@ class EmbedNode(template.Node): if self.instance is None or self.instance is False: return settings.TEMPLATE_STRING_IF_INVALID + return self.render_template(context, self.instance) + + def render_template(self, context, instance): try: t = context[self.varname][self.content_type] except KeyError: return settings.TEMPLATE_STRING_IF_INVALID context.push() - context['embedded'] = self.instance + context['embedded'] = instance + kwargs = {} for k, v in self.kwargs.items(): - self.kwargs[k] = v.resolve(context) - context.update(self.kwargs) + kwargs[k] = v.resolve(context) + context.update(kwargs) t_rendered = t.render(context) context.pop() return t_rendered +class EmbedNode(ConstantEmbedNode): + def __init__(self, content_type, varname, object_pk=None, template_name=None, kwargs=None): + assert template_name is not None or object_pk is not None + self.content_type = content_type + self.varname = varname + + kwargs = kwargs or {} + for k, v in kwargs.items(): + kwargs[k] = template.Variable(v) + self.kwargs = kwargs + + if object_pk is not None: + self.object_pk = template.Variable(object_pk) + else: + self.object_pk = None + self.instance = None + + if template_name is not None: + self.template_name = template.Variable(template_name) + else: + self.template_name = None + self.template = None + + def render(self, context): + if self.template_name is not None: + template_name = self.template_name.resolve(context) + self.compile_template(template_name) + + if self.object_pk is not None: + object_pk = self.object_pk.resolve(context) + self.compile_instance(object_pk) + + return super(EmbedNode, self).render(context) + + def get_embedded(self): - return template.loader.get_template(self.template_name) + return self.template -setattr(EmbedNode, LOADED_TEMPLATE_ATTR, property(get_embedded)) +setattr(ConstantEmbedNode, LOADED_TEMPLATE_ATTR, property(get_embedded)) def do_embed(parser, token): @@ -97,21 +148,18 @@ def do_embed(parser, token): 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) + varname = getattr(parser, '_embedNodeVarName', 'embed') + if args[2] == "with": if len(args) > 4: raise template.TemplateSyntaxError('"%s" template tag may have no more than four arguments.' % 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) + if args[3][0] in ['"', "'"] and args[3][0] == args[3][-1]: + return ConstantEmbedNode(ct, template_name=args[3], varname=varname) - template_name = args[3].strip('"\'') - - return EmbedNode(ct, template_name=template_name, varname=getattr(parser, '_embedNodeVarName', 'embed')) - object_pk = args[2] - varname = getattr(parser, '_embedNodeVarName', 'embed') + return EmbedNode(ct, template_name=args[3], varname=varname) + object_pk = args[2] remaining_args = args[3:] kwargs = {} for arg in remaining_args: -- 2.20.1