2 from django.conf import settings
3 from django.contrib.contenttypes.models import ContentType
4 from django.contrib.contenttypes import generic
5 from django.core.exceptions import ValidationError
6 from django.db import models
7 from django.http import HttpResponse
8 from django.template import TemplateDoesNotExist, Context, RequestContext, Template as DjangoTemplate, add_to_builtins as register_templatetags, TextNode, VariableNode
9 from django.template.loader_tags import BlockNode, ExtendsNode, BlockContext
10 from philo.models.base import TreeModel, register_value_model
11 from philo.models.fields import TemplateField
12 from philo.models.nodes import View
13 from philo.templatetags.containers import ContainerNode
14 from philo.utils import fattr, nodelist_crawl
15 from philo.validators import LOADED_TEMPLATE_ATTR
16 from philo.signals import page_about_to_render_to_string, page_finished_rendering_to_string
19 class Template(TreeModel):
20 name = models.CharField(max_length=255)
21 documentation = models.TextField(null=True, blank=True)
22 mimetype = models.CharField(max_length=255, default=getattr(settings, 'DEFAULT_CONTENT_TYPE', 'text/html'))
23 code = TemplateField(secure=False, verbose_name='django template code')
28 Returns a tuple where the first item is a list of names of contentlets referenced by containers,
29 and the second item is a list of tuples of names and contenttypes of contentreferences referenced by containers.
30 This will break if there is a recursive extends or includes in the template code.
31 Due to the use of an empty Context, any extends or include tags with dynamic arguments probably won't work.
33 def process_node(node, contentlet_specs, contentreference_specs, contentreference_names, block_context=None):
34 if isinstance(node, ContainerNode):
35 if not node.references:
36 if node.name not in contentlet_specs:
37 contentlet_specs.append(node.name)
39 if node.name not in contentreference_names:
40 contentreference_specs.append((node.name, node.references))
41 contentreference_names.add(node.name)
42 if isinstance(node, ExtendsNode) and block_context is not None:
43 block_context.add_blocks(node.blocks)
44 parent = getattr(node, LOADED_TEMPLATE_ATTR)
45 for node in parent.nodelist:
46 if not isinstance(node, TextNode):
47 if not isinstance(node, ExtendsNode):
48 blocks = dict([(n.name, n) for n in parent.nodelist.get_nodes_by_type(BlockNode)])
49 block_context.add_blocks(blocks)
52 if hasattr(node, 'child_nodelists') and not isinstance(node, BlockNode):
53 for nodelist_name in node.child_nodelists:
54 if hasattr(node, nodelist_name):
55 nodelist_crawl(getattr(node, nodelist_name), process_node, contentlet_specs, contentreference_specs, contentreference_names, block_context)
57 # LOADED_TEMPLATE_ATTR contains the name of an attribute philo uses to declare a
58 # node as rendering an additional template. Philo monkeypatches the attribute onto
59 # the relevant default nodes and declares it on any native nodes.
60 if hasattr(node, LOADED_TEMPLATE_ATTR):
61 loaded_template = getattr(node, LOADED_TEMPLATE_ATTR)
63 nodelist_crawl(loaded_template.nodelist, process_node, contentlet_specs, contentreference_specs, contentreference_names, block_context)
65 contentreference_names = set()
67 contentreference_specs = []
68 block_context = BlockContext()
69 nodelist_crawl(DjangoTemplate(self.code).nodelist, process_node, contentlet_specs, contentreference_specs, contentreference_names, block_context)
71 def process_block_node(node, contentlet_specs, contentreference_specs, contentreference_names, block_super):
72 if isinstance(node, VariableNode) and node.filter_expression.var.lookups == (u'block', u'super'):
73 block_super.append(node)
74 process_node(node, contentlet_specs, contentreference_specs, contentreference_names, block_context=None)
76 for block_list in block_context.blocks.values():
77 for block in block_list[::-1]:
79 nodelist_crawl(block.nodelist, process_block_node, contentlet_specs, contentreference_specs, contentreference_names, block_super)
83 return contentlet_specs, contentreference_specs
85 def __unicode__(self):
86 return self.get_path(pathsep=u' › ', field='name')
94 Represents a page - something which is rendered according to a template. The page will have a number of related Contentlets depending on the template selected - but these will appear only after the page has been saved with that template.
96 template = models.ForeignKey(Template, related_name='pages')
97 title = models.CharField(max_length=255)
99 def get_containers(self):
100 if not hasattr(self, '_containers'):
101 self._containers = self.template.containers
102 return self._containers
103 containers = property(get_containers)
105 def render_to_string(self, request=None, extra_context=None):
107 context.update(extra_context or {})
108 context.update({'page': self, 'attributes': self.attributes})
109 template = DjangoTemplate(self.template.code)
111 context.update({'node': request.node, 'attributes': self.attributes_with_node(request.node)})
112 page_about_to_render_to_string.send(sender=self, request=request, extra_context=context)
113 string = template.render(RequestContext(request, context))
115 page_about_to_render_to_string.send(sender=self, request=request, extra_context=context)
116 string = template.render(Context(context))
117 page_finished_rendering_to_string.send(sender=self, string=string)
120 def actually_render_to_response(self, request, extra_context=None):
121 return HttpResponse(self.render_to_string(request, extra_context), mimetype=self.template.mimetype)
123 def __unicode__(self):
126 def clean_fields(self, exclude=None):
128 super(Page, self).clean_fields(exclude)
129 except ValidationError, e:
130 errors = e.message_dict
134 if 'template' not in errors and 'template' not in exclude:
136 self.template.clean_fields()
137 self.template.clean()
138 except ValidationError, e:
139 errors['template'] = e.messages
142 raise ValidationError(errors)
148 class Contentlet(models.Model):
149 page = models.ForeignKey(Page, related_name='contentlets')
150 name = models.CharField(max_length=255, db_index=True)
151 content = TemplateField()
153 def __unicode__(self):
160 class ContentReference(models.Model):
161 page = models.ForeignKey(Page, related_name='contentreferences')
162 name = models.CharField(max_length=255, db_index=True)
163 content_type = models.ForeignKey(ContentType, verbose_name='Content type')
164 content_id = models.PositiveIntegerField(verbose_name='Content ID', blank=True, null=True)
165 content = generic.GenericForeignKey('content_type', 'content_id')
167 def __unicode__(self):
174 register_templatetags('philo.templatetags.containers')
177 register_value_model(Template)
178 register_value_model(Page)