Added NodeOverrideInlineFormSet to clean up admin editing of node overrides by only...
[philo.git] / models / pages.py
1 # encoding: utf-8
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
9 from philo.models.base import TreeModel, register_value_model
10 from philo.models.fields import TemplateField
11 from philo.models.nodes import View
12 from philo.templatetags.containers import ContainerNode
13 from philo.utils import fattr, nodelist_crawl
14 from philo.validators import LOADED_TEMPLATE_ATTR
15 from philo.signals import page_about_to_render_to_string, page_finished_rendering_to_string
16
17
18 class Template(TreeModel):
19         name = models.CharField(max_length=255)
20         documentation = models.TextField(null=True, blank=True)
21         mimetype = models.CharField(max_length=255, default=getattr(settings, 'DEFAULT_CONTENT_TYPE', 'text/html'))
22         code = TemplateField(secure=False, verbose_name='django template code')
23         
24         @property
25         def containers(self):
26                 """
27                 Returns a tuple where the first item is a list of names of contentlets referenced by containers,
28                 and the second item is a list of tuples of names and contenttypes of contentreferences referenced by containers.
29                 This will break if there is a recursive extends or includes in the template code.
30                 Due to the use of an empty Context, any extends or include tags with dynamic arguments probably won't work.
31                 """
32                 def process_node(node, nodes):
33                         if isinstance(node, ContainerNode):
34                                 nodes.append(node)
35                 
36                 all_nodes = nodelist_crawl(DjangoTemplate(self.code).nodelist, process_node)
37                 contentlet_node_names = set([node.name for node in all_nodes if not node.references])
38                 contentreference_node_names = []
39                 contentreference_node_specs = []
40                 for node in all_nodes:
41                         if node.references and node.name not in contentreference_node_names:
42                                 contentreference_node_specs.append((node.name, node.references))
43                                 contentreference_node_names.append(node.name)
44                 return contentlet_node_names, contentreference_node_specs
45         
46         def __unicode__(self):
47                 return self.get_path(pathsep=u' › ', field='name')
48         
49         class Meta:
50                 app_label = 'philo'
51
52
53 class Page(View):
54         """
55         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.
56         """
57         template = models.ForeignKey(Template, related_name='pages')
58         title = models.CharField(max_length=255)
59         
60         def get_containers(self):
61                 if not hasattr(self, '_containers'):
62                         self._containers = self.template.containers
63                 return self._containers
64         containers = property(get_containers)
65         
66         def render_to_string(self, request=None, extra_context=None):
67                 context = {}
68                 context.update(extra_context or {})
69                 context.update({'page': self, 'attributes': self.attributes})
70                 template = DjangoTemplate(self.template.code)
71                 if request:
72                         context.update({'node': request.node, 'attributes': self.attributes_with_node(request.node)})
73                         page_about_to_render_to_string.send(sender=self, request=request, extra_context=context)
74                         string = template.render(RequestContext(request, context))
75                 else:
76                         page_about_to_render_to_string.send(sender=self, request=request, extra_context=context)
77                         string = template.render(Context(context))
78                 page_finished_rendering_to_string.send(sender=self, string=string)
79                 return string
80         
81         def actually_render_to_response(self, request, extra_context=None):
82                 return HttpResponse(self.render_to_string(request, extra_context), mimetype=self.template.mimetype)
83         
84         def __unicode__(self):
85                 return self.title
86         
87         def clean_fields(self, exclude=None):
88                 try:
89                         super(Page, self).clean_fields(exclude)
90                 except ValidationError, e:
91                         errors = e.message_dict
92                 else:
93                         errors = {}
94                 
95                 if 'template' not in errors and 'template' not in exclude:
96                         try:
97                                 self.template.clean_fields()
98                                 self.template.clean()
99                         except ValidationError, e:
100                                 errors['template'] = e.messages
101                 
102                 if errors:
103                         raise ValidationError(errors)
104         
105         def get_navigation(self, node, max_depth):
106                 return {
107                         'url': node.get_absolute_url(),
108                         'title': self.title,
109                         'order': None,
110                 }
111         
112         class Meta:
113                 app_label = 'philo'
114
115
116 class Contentlet(models.Model):
117         page = models.ForeignKey(Page, related_name='contentlets')
118         name = models.CharField(max_length=255)
119         content = TemplateField()
120         
121         def __unicode__(self):
122                 return self.name
123         
124         class Meta:
125                 app_label = 'philo'
126
127
128 class ContentReference(models.Model):
129         page = models.ForeignKey(Page, related_name='contentreferences')
130         name = models.CharField(max_length=255)
131         content_type = models.ForeignKey(ContentType, verbose_name='Content type')
132         content_id = models.PositiveIntegerField(verbose_name='Content ID', blank=True, null=True)
133         content = generic.GenericForeignKey('content_type', 'content_id')
134         
135         def __unicode__(self):
136                 return self.name
137         
138         class Meta:
139                 app_label = 'philo'
140
141
142 register_templatetags('philo.templatetags.containers')
143
144
145 register_value_model(Template)
146 register_value_model(Page)