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