Splitting models into submodules beside models.base.
[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 django.contrib.sites.models import Site
14 from philo.models.base import TreeModel, register_value_model
15 from philo.models.nodes import Node
16 from philo.utils import fattr
17 from philo.templatetags.containers import ContainerNode
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, null=True, blank=True, help_text='Default: %s' % settings.DEFAULT_CONTENT_TYPE)
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                                                 for nodelist_name in ('nodelist', 'nodelist_loop', 'nodelist_empty', 'nodelist_true', 'nodelist_false', 'nodelist_main'):
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(u' › ', '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(Node):
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 render_to_response(self, request, path=None, subpath=None):
102                 return HttpResponse(self.template.django_template.render(RequestContext(request, {'page': self})), mimetype=self.template.mimetype)
103         
104         def __unicode__(self):
105                 return self.get_path(u' › ', 'title')
106         
107         class Meta:
108                 app_label = 'philo'
109
110
111 # the following line enables the selection of a node as the root for a given django.contrib.sites Site object
112 models.ForeignKey(Node, related_name='sites', null=True, blank=True).contribute_to_class(Site, 'root_node')
113
114
115 class Contentlet(models.Model):
116         page = models.ForeignKey(Page, related_name='contentlets')
117         name = models.CharField(max_length=255)
118         content = models.TextField()
119         dynamic = models.BooleanField(default=False)
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)