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