2 from django.db import models
3 from django.contrib.contenttypes.models import ContentType
4 from django.contrib.contenttypes import generic
5 from django.conf import settings
6 from django.template import add_to_builtins as register_templatetags
7 from django.template import Template as DjangoTemplate
8 from django.template import TemplateDoesNotExist
9 from django.template import Context, RequestContext
10 from django.template.loader import get_template
11 from django.template.loader_tags import ExtendsNode, ConstantIncludeNode
12 from django.http import HttpResponse
13 from philo.models.base import TreeModel, register_value_model
14 from philo.models.fields import TemplateField
15 from philo.models.nodes import View
16 from philo.utils import fattr
17 from philo.templatetags.containers import ContainerNode
18 from philo.validators import LOADED_TEMPLATE_ATTR
19 from philo.signals import page_about_to_render_to_string, page_finished_rendering_to_string
22 BLANK_CONTEXT = Context()
25 def get_extended(self):
26 return self.get_parent(BLANK_CONTEXT)
29 def get_included(self):
33 # We ignore the IncludeNode because it will never work in a blank context.
34 setattr(ExtendsNode, LOADED_TEMPLATE_ATTR, property(get_extended))
35 setattr(ConstantIncludeNode, LOADED_TEMPLATE_ATTR, property(get_included))
38 class Template(TreeModel):
39 name = models.CharField(max_length=255)
40 documentation = models.TextField(null=True, blank=True)
41 mimetype = models.CharField(max_length=255, default=getattr(settings, 'DEFAULT_CONTENT_TYPE', 'text/html'))
42 code = TemplateField(secure=False, verbose_name='django template code')
46 return 'philo.models.Template: ' + self.path
49 def django_template(self):
50 return DjangoTemplate(self.code)
55 Returns a tuple where the first item is a list of names of contentlets referenced by containers,
56 and the second item is a list of tuples of names and contenttypes of contentreferences referenced by containers.
57 This will break if there is a recursive extends or includes in the template code.
58 Due to the use of an empty Context, any extends or include tags with dynamic arguments probably won't work.
60 def container_nodes(template):
61 def nodelist_container_nodes(nodelist):
65 if hasattr(node, 'child_nodelists'):
66 for nodelist_name in node.child_nodelists:
67 if hasattr(node, nodelist_name):
68 nodes.extend(nodelist_container_nodes(getattr(node, nodelist_name)))
70 # LOADED_TEMPLATE_ATTR contains the name of an attribute philo uses to declare a
71 # node as rendering an additional template. Philo monkeypatches the attribute onto
72 # the relevant default nodes.
73 if hasattr(node, LOADED_TEMPLATE_ATTR):
74 loaded_template = getattr(node, LOADED_TEMPLATE_ATTR)
76 nodes.extend(container_nodes(loaded_template))
78 if isinstance(node, ContainerNode):
81 raise # fail for this node
83 return nodelist_container_nodes(template.nodelist)
84 all_nodes = container_nodes(self.django_template)
85 contentlet_node_names = set([node.name for node in all_nodes if not node.references])
86 contentreference_node_names = []
87 contentreference_node_specs = []
88 for node in all_nodes:
89 if node.references and node.name not in contentreference_node_names:
90 contentreference_node_specs.append((node.name, node.references))
91 contentreference_node_names.append(node.name)
92 return contentlet_node_names, contentreference_node_specs
94 def __unicode__(self):
95 return self.get_path(pathsep=u' › ', field='name')
98 @fattr(is_usable=True)
99 def loader(template_name, template_dirs=None): # load_template_source
101 template = Template.objects.get_with_path(template_name)
102 except Template.DoesNotExist:
103 raise TemplateDoesNotExist(template_name)
104 return (template.code, template.origin)
112 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.
114 template = models.ForeignKey(Template, related_name='pages')
115 title = models.CharField(max_length=255)
117 def get_containers(self):
118 if not hasattr(self, '_containers'):
119 self._containers = self.template.containers
120 return self._containers
121 containers = property(get_containers)
123 def render_to_string(self, node=None, request=None, path=None, subpath=None, extra_context=None):
125 context.update(extra_context or {})
126 context.update({'page': self, 'attributes': self.attributes, 'relationships': self.relationships})
128 context.update({'node': node, 'attributes': self.attributes_with_node(node), 'relationships': self.relationships_with_node(node)})
129 page_about_to_render_to_string.send(sender=self, node=node, request=request, extra_context=context)
130 string = self.template.django_template.render(RequestContext(request, context))
132 page_about_to_render_to_string.send(sender=self, node=node, request=request, extra_context=context)
133 string = self.template.django_template.render(Context(context))
134 page_finished_rendering_to_string.send(sender=self, string=string)
137 def actually_render_to_response(self, node, request, path=None, subpath=None, extra_context=None):
138 return HttpResponse(self.render_to_string(node, request, path, subpath, extra_context), mimetype=self.template.mimetype)
140 def __unicode__(self):
147 class Contentlet(models.Model):
148 page = models.ForeignKey(Page, related_name='contentlets')
149 name = models.CharField(max_length=255)
150 content = TemplateField()
152 def __unicode__(self):
159 class ContentReference(models.Model):
160 page = models.ForeignKey(Page, related_name='contentreferences')
161 name = models.CharField(max_length=255)
162 content_type = models.ForeignKey(ContentType, verbose_name='Content type')
163 content_id = models.PositiveIntegerField(verbose_name='Content ID', blank=True, null=True)
164 content = generic.GenericForeignKey('content_type', 'content_id')
166 def __unicode__(self):
173 register_templatetags('philo.templatetags.containers')
176 register_value_model(Template)
177 register_value_model(Page)