1 from django.contrib.contenttypes import generic
2 from django.contrib.contenttypes.models import ContentType
3 from django.core.exceptions import ValidationError
4 from django.db import models
5 from django.template import Template, loader, loader_tags
7 from philo.contrib.penfield.templatetags.embed import EmbedNode
10 embed_re = re.compile("{% embed (?P<app_label>\w+)\.(?P<model>\w+) (?P<pk>)\w+ %}")
13 class TemplateField(models.TextField):
14 def validate(self, value, model_instance):
15 """For value (a template), make sure that all included templates exist."""
16 super(TemplateField, self).validate(value, model_instance)
18 self.validate_template(self.to_template(value))
20 raise ValidationError("Template code invalid. Error was: %s: %s" % (e.__class__.__name__, e))
22 def validate_template(self, template):
23 for node in template.nodelist:
24 if isinstance(node, loader_tags.ExtendsNode):
25 extended_template = node.get_parent(Context())
26 self.validate_template(extended_template)
27 elif isinstance(node, loader_tags.IncludeNode):
28 included_template = loader.get_template(node.template_name.resolve(Context()))
29 self.validate_template(extended_template)
31 def to_template(self, value):
32 return Template(value)
35 class Embed(models.Model):
36 embedder_content_type = models.ForeignKey(ContentType, related_name="embedder_related")
37 embedder_object_id = models.PositiveIntegerField()
38 embedder = generic.GenericForeignKey("embedder_content_type", "embedder_object_id")
40 embedded_content_type = models.ForeignKey(ContentType, related_name="embedded_related")
41 embedded_object_id = models.PositiveIntegerField()
42 embedded = generic.GenericForeignKey("embedded_content_type", "embedded_object_id")
45 # Unclear whether this would be called by a cascading deletion.
46 super(Embed, self).delete()
47 # Cycle through all the fields in the embedder and remove all references to the embedded object.
49 def get_embed_tag(self):
50 """Convenience function to construct the embed tag that would create this instance."""
51 ct = self.embedded_content_type
52 return "{%% embed %s.%s %s %%}" % (ct.app_label, ct.model, self.embedded_object_id)
55 app_label = 'penfield'
58 def sync_embedded_objects(model_instance, embedded_instances):
59 # First, fetch all current embeds.
60 model_instance_ct = ContentType.objects.get_for_model(model_instance)
61 current_embeds = Embed.objects.filter()
64 for embedded_instance in embedded_instances:
65 embedded_instance_ct = ContentType.objects.get_for_model(embedded_instance)
66 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]
67 new_embed_pks.append(new_embed.pk)
69 # Then, delete all embed objects related to this model instance which do not relate
70 # to one of the embedded instances.
71 Embed.objects.filter(embedder_content_type=model_instance_ct, embedder_object_id=model_instance.id).exclude(pk__in=new_embed_pks).delete()
74 class EmbedField(TemplateField):
75 _embedded_instances = set()
77 def validate_template(self, template):
78 """Check to be sure that the embedded instances and templates all exist."""
79 for node in template.nodelist:
80 if isinstance(node, loader_tags.ExtendsNode):
81 extended_template = node.get_parent(Context())
82 self.validate_template(extended_template)
83 elif isinstance(node, loader_tags.IncludeNode):
84 included_template = loader.get_template(node.template_name.resolve(Context()))
85 self.validate_template(extended_template)
86 elif isinstance(node, EmbedNode):
87 if node.template_name is not None:
88 embedded_template = loader.get_template(node.template_name)
89 self.validate_template(embedded_template)
90 elif node.object_pk is not None:
91 self._embedded_instances.add(node.model.objects.get(pk=node.object_pk))
93 #def to_template(self, value):
94 # return Template("{% load embed %}" + value)
96 def pre_save(self, model_instance, add):
97 if not hasattr(model_instance, '_embedded_instances'):
98 model_instance._embedded_instances = set()
99 model_instance._embedded_instances |= self._embedded_instances
102 class Test(models.Model):
103 template = TemplateField()
104 embedder = EmbedField()
107 app_label = 'penfield'