Removed referential integrity code. Moved embed templatetag into core. Differentiated...
authorStephen Burrows <stephen.r.burrows@gmail.com>
Tue, 5 Oct 2010 21:16:21 +0000 (17:16 -0400)
committerStephen Burrows <stephen.r.burrows@gmail.com>
Tue, 5 Oct 2010 21:16:21 +0000 (17:16 -0400)
contrib/penfield/admin.py
contrib/penfield/embed.py [deleted file]
contrib/penfield/models.py
models/__init__.py
templatetags/embed.py [moved from contrib/penfield/templatetags/embed.py with 59% similarity]

index 4be78c0..85888aa 100644 (file)
@@ -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 (file)
index 522205d..0000000
+++ /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
index 30480eb..c9c024c 100644 (file)
@@ -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
index 5d39ac6..76d7812 100644 (file)
@@ -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
similarity index 59%
rename from contrib/penfield/templatetags/embed.py
rename to templatetags/embed.py
index 7d1b26a..c0fcff0 100644 (file)
@@ -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: