Tweaked nodeview error reraising to preserve traceback information.
[philo.git] / models / pages.py
1 # encoding: utf-8
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, IncludeNode
12 from django.http import HttpResponse
13 from philo.models.base import TreeModel, register_value_model
14 from philo.models.nodes import View
15 from philo.utils import fattr
16 from philo.templatetags.containers import ContainerNode
17 from philo.signals import page_about_to_render_to_string, page_finished_rendering_to_string
18
19
20 class Template(TreeModel):
21         name = models.CharField(max_length=255)
22         documentation = models.TextField(null=True, blank=True)
23         mimetype = models.CharField(max_length=255, default=getattr(settings, 'DEFAULT_CONTENT_TYPE', 'text/html'))
24         code = models.TextField(verbose_name='django template code')
25         
26         @property
27         def origin(self):
28                 return 'philo.models.Template: ' + self.path
29         
30         @property
31         def django_template(self):
32                 return DjangoTemplate(self.code)
33         
34         @property
35         def containers(self):
36                 """
37                 Returns a tuple where the first item is a list of names of contentlets referenced by containers,
38                 and the second item is a list of tuples of names and contenttypes of contentreferences referenced by containers.
39                 This will break if there is a recursive extends or includes in the template code.
40                 Due to the use of an empty Context, any extends or include tags with dynamic arguments probably won't work.
41                 """
42                 def container_nodes(template):
43                         def nodelist_container_nodes(nodelist):
44                                 nodes = []
45                                 for node in nodelist:
46                                         try:
47                                                 if hasattr(node, 'child_nodelists'):
48                                                         for nodelist_name in node.child_nodelists:
49                                                                 if hasattr(node, nodelist_name):
50                                                                         nodes.extend(nodelist_container_nodes(getattr(node, nodelist_name)))
51                                                 if isinstance(node, ContainerNode):
52                                                         nodes.append(node)
53                                                 elif isinstance(node, ExtendsNode):
54                                                         extended_template = node.get_parent(Context())
55                                                         if extended_template:
56                                                                 nodes.extend(container_nodes(extended_template))
57                                                 elif isinstance(node, ConstantIncludeNode):
58                                                         included_template = node.template
59                                                         if included_template:
60                                                                 nodes.extend(container_nodes(included_template))
61                                                 elif isinstance(node, IncludeNode):
62                                                         included_template = get_template(node.template_name.resolve(Context()))
63                                                         if included_template:
64                                                                 nodes.extend(container_nodes(included_template))
65                                         except:
66                                                 raise # fail for this node
67                                 return nodes
68                         return nodelist_container_nodes(template.nodelist)
69                 all_nodes = container_nodes(self.django_template)
70                 contentlet_node_names = set([node.name for node in all_nodes if not node.references])
71                 contentreference_node_names = []
72                 contentreference_node_specs = []
73                 for node in all_nodes:
74                         if node.references and node.name not in contentreference_node_names:
75                                 contentreference_node_specs.append((node.name, node.references))
76                                 contentreference_node_names.append(node.name)
77                 return contentlet_node_names, contentreference_node_specs
78         
79         def __unicode__(self):
80                 return self.get_path(pathsep=u' › ', field='name')
81         
82         @staticmethod
83         @fattr(is_usable=True)
84         def loader(template_name, template_dirs=None): # load_template_source
85                 try:
86                         template = Template.objects.get_with_path(template_name)
87                 except Template.DoesNotExist:
88                         raise TemplateDoesNotExist(template_name)
89                 return (template.code, template.origin)
90         
91         class Meta:
92                 app_label = 'philo'
93
94
95 class Page(View):
96         """
97         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.
98         """
99         template = models.ForeignKey(Template, related_name='pages')
100         title = models.CharField(max_length=255)
101         
102         def get_containers(self):
103                 if not hasattr(self, '_containers'):
104                         self._containers = self.template.containers
105                 return self._containers
106         containers = property(get_containers)
107         
108         def render_to_string(self, node=None, request=None, path=None, subpath=None, extra_context=None):
109                 context = {}
110                 context.update(extra_context or {})
111                 context.update({'page': self, 'attributes': self.attributes, 'relationships': self.relationships})
112                 if node and request:
113                         context.update({'node': node, 'attributes': self.attributes_with_node(node), 'relationships': self.relationships_with_node(node)})
114                         page_about_to_render_to_string.send(sender=self, node=node, request=request, extra_context=context)
115                         string = self.template.django_template.render(RequestContext(request, context))
116                 else:
117                         page_about_to_render_to_string.send(sender=self, node=node, request=request, extra_context=context)
118                         string = self.template.django_template.render(Context(context))
119                 page_finished_rendering_to_string.send(sender=self, string=string)
120                 return string
121         
122         def actually_render_to_response(self, node, request, path=None, subpath=None, extra_context=None):
123                 return HttpResponse(self.render_to_string(node, request, path, subpath, extra_context), mimetype=self.template.mimetype)
124         
125         def __unicode__(self):
126                 return self.title
127         
128         class Meta:
129                 app_label = 'philo'
130
131
132 class Contentlet(models.Model):
133         page = models.ForeignKey(Page, related_name='contentlets')
134         name = models.CharField(max_length=255)
135         content = models.TextField()
136         dynamic = models.BooleanField(default=False)
137         
138         def __unicode__(self):
139                 return self.name
140         
141         class Meta:
142                 app_label = 'philo'
143
144
145 class ContentReference(models.Model):
146         page = models.ForeignKey(Page, related_name='contentreferences')
147         name = models.CharField(max_length=255)
148         content_type = models.ForeignKey(ContentType, verbose_name='Content type')
149         content_id = models.PositiveIntegerField(verbose_name='Content ID', blank=True, null=True)
150         content = generic.GenericForeignKey('content_type', 'content_id')
151         
152         def __unicode__(self):
153                 return self.name
154         
155         class Meta:
156                 app_label = 'philo'
157
158
159 register_templatetags('philo.templatetags.containers')
160
161
162 register_value_model(Template)
163 register_value_model(Page)