Added embeddable_content_types registry to track what content types are embeddable...
[philo.git] / contrib / penfield / embed.py
index 7f875cc..522205d 100644 (file)
@@ -2,34 +2,14 @@ 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, ContentTypeRegistryLimiter
 
 
-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)
+embeddable_content_types = ContentTypeRegistryLimiter()
 
 
 class Embed(models.Model):
@@ -51,14 +31,17 @@ class Embed(models.Model):
                for field in embedder._meta.fields:
                        if isinstance(field, EmbedField):
                                attr = getattr(embedder, field.attname)
-                               setattr(embedder, field.attname, attr.replace(self.get_embed_tag(), ''))
+                               setattr(embedder, field.attname, self.embed_re.sub('', attr))
                
                embedder.save()
        
-       def get_embed_tag(self):
-               """Convenience function to construct the embed tag that would create this instance."""
-               ct = self.embedded_content_type
-               return "{%% embed %s.%s %s %%}" % (ct.app_label, ct.model, self.embedded_object_id)
+       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'
@@ -81,29 +64,33 @@ 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 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 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.
@@ -117,17 +104,21 @@ models.signals.post_save.connect(post_save_embed_sync)
 # 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):
-       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()
-       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()
+       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()
        
-       class Meta:
-               app_label = 'penfield'
\ No newline at end of file
+       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