2 from django.utils.translation import ugettext_lazy as _
3 from django.contrib.auth.models import User, Group
4 from django.contrib.contenttypes import generic
5 from django.contrib.contenttypes.models import ContentType
6 from django.db import models
7 from django.contrib.sites.models import Site
8 from utils import fattr
9 from django.template import add_to_builtins as register_templatetags
10 from django.template import Template as DjangoTemplate
11 from django.template import TemplateDoesNotExist
12 from django.template import Context, RequestContext
13 from django.core.exceptions import ObjectDoesNotExist
17 import simplejson as json
18 from UserDict import DictMixin
19 from templatetags.containers import ContainerNode
20 from django.template.loader_tags import ExtendsNode, ConstantIncludeNode, IncludeNode
21 from django.template.loader import get_template
22 from django.http import Http404, HttpResponse, HttpResponseServerError, HttpResponseRedirect
23 from django.core.servers.basehttp import FileWrapper
26 def register_value_model(model):
30 def unregister_value_model(model):
34 class Attribute(models.Model):
35 entity_content_type = models.ForeignKey(ContentType, verbose_name='Entity type')
36 entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID')
37 entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
38 key = models.CharField(max_length=255)
39 json_value = models.TextField(verbose_name='Value (JSON)', help_text='This value must be valid JSON.')
42 return json.loads(self.json_value)
44 def set_value(self, value):
45 self.json_value = json.dumps(value)
47 def delete_value(self):
48 self.json_value = json.dumps(None)
50 value = property(get_value, set_value, delete_value)
52 def __unicode__(self):
53 return u'"%s": %s' % (self.key, self.value)
56 class Relationship(models.Model):
57 entity_content_type = models.ForeignKey(ContentType, related_name='relationship_entity_set', verbose_name='Entity type')
58 entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID')
59 entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
60 key = models.CharField(max_length=255)
61 value_content_type = models.ForeignKey(ContentType, related_name='relationship_value_set', verbose_name='Value type')
62 value_object_id = models.PositiveIntegerField(verbose_name='Value ID')
63 value = generic.GenericForeignKey('value_content_type', 'value_object_id')
65 def __unicode__(self):
66 return u'"%s": %s' % (self.key, self.value)
69 class QuerySetMapper(object, DictMixin):
70 def __init__(self, queryset, passthrough=None):
71 self.queryset = queryset
72 self.passthrough = passthrough
73 def __getitem__(self, key):
75 return self.queryset.get(key__exact=key).value
76 except ObjectDoesNotExist:
78 return self.passthrough.__getitem__(key)
81 keys = set(self.queryset.values_list('key', flat=True).distinct())
83 keys += set(self.passthrough.keys())
87 class Entity(models.Model):
88 attribute_set = generic.GenericRelation(Attribute, content_type_field='entity_content_type', object_id_field='entity_object_id')
89 relationship_set = generic.GenericRelation(Relationship, content_type_field='entity_content_type', object_id_field='entity_object_id')
93 return QuerySetMapper(self.attribute_set)
96 def relationships(self):
97 return QuerySetMapper(self.relationship_set)
103 class Collection(models.Model):
104 name = models.CharField(max_length=255)
105 description = models.TextField(blank=True, null=True)
108 class CollectionMemberManager(models.Manager):
109 use_for_related_fields = True
111 def with_model(self, model):
112 return model._default_manager.filter(pk__in=self.filter(member_content_type=ContentType.objects.get_for_model(model)).values_list('member_object_id', flat=True))
115 class CollectionMember(models.Model):
116 objects = CollectionMemberManager()
117 collection = models.ForeignKey(Collection, related_name='members')
118 index = models.PositiveIntegerField(verbose_name='Index', help_text='This will determine the ordering of the item within the collection. (Optional)', null=True, blank=True)
119 member_content_type = models.ForeignKey(ContentType, verbose_name='Member type')
120 member_object_id = models.PositiveIntegerField(verbose_name='Member ID')
121 member = generic.GenericForeignKey('member_content_type', 'member_object_id')
124 class TreeManager(models.Manager):
125 use_for_related_fields = True
128 return self.filter(parent__isnull=True)
130 def get_with_path(self, path, root=None, absolute_result=True, pathsep='/'):
132 Returns the object with the path, or None if there is no object with that path,
133 unless absolute_result is set to False, in which case it returns a tuple containing
134 the deepest object found along the path, and the remainder of the path after that
135 object as a string (or None in the case that there is no remaining path).
137 slugs = path.split(pathsep)
139 remaining_slugs = list(slugs)
142 remaining_slugs.remove(slug)
143 if slug: # ignore blank slugs, handles for multiple consecutive pathseps
145 obj = self.get(slug__exact=slug, parent__exact=obj)
146 except self.model.DoesNotExist:
149 remaining_slugs.insert(0, slug)
150 remainder = pathsep.join(remaining_slugs)
156 return (obj, remainder)
157 raise self.model.DoesNotExist('%s matching query does not exist.' % self.model._meta.object_name)
160 class TreeModel(models.Model):
161 objects = TreeManager()
162 parent = models.ForeignKey('self', related_name='children', null=True, blank=True)
163 slug = models.SlugField()
165 def get_path(self, pathsep='/', field='slug'):
166 path = getattr(self, field)
169 path = getattr(parent, field) + pathsep + path
170 parent = parent.parent
172 path = property(get_path)
174 def __unicode__(self):
181 class TreeEntity(TreeModel, Entity):
183 def attributes(self):
185 return QuerySetMapper(self.attribute_set, passthrough=self.parent.attributes)
186 return super(TreeEntity, self).attributes
189 def relationships(self):
191 return QuerySetMapper(self.relationship_set, passthrough=self.parent.relationships)
192 return super(TreeEntity, self).relationships
198 class Node(TreeEntity):
199 instance_type = models.ForeignKey(ContentType, editable=False)
201 def get_path(self, pathsep='/', field='slug'):
202 path = getattr(self.instance, field)
205 path = getattr(parent.instance, field) + pathsep + path
206 parent = parent.parent
208 path = property(get_path)
210 def save(self, force_insert=False, force_update=False):
211 if not hasattr(self, 'instance_type_ptr'):
212 self.instance_type = ContentType.objects.get_for_model(self.__class__)
213 super(Node, self).save(force_insert, force_update)
217 return self.instance_type.get_object_for_this_type(id=self.id)
219 accepts_subpath = False
221 def render_to_response(self, request, path=None, subpath=None):
222 return HttpResponseServerError()
225 class MultiNode(Node):
226 accepts_subpath = True
230 def render_to_response(self, request, path=None, subpath=None):
233 subpath = "/" + subpath
234 from django.core.urlresolvers import resolve
235 view, args, kwargs = resolve(subpath, urlconf=self)
236 return view(request, *args, **kwargs)
242 class Redirect(Node):
247 target = models.URLField()
248 status_code = models.IntegerField(choices=STATUS_CODES, default=302, verbose_name='redirect type')
250 def render_to_response(self, request, path=None, subpath=None):
251 response = HttpResponseRedirect(self.target)
252 response.status_code = self.status_code
257 """ For storing arbitrary files """
258 mimetype = models.CharField(max_length=255)
259 file = models.FileField(upload_to='philo/files/%Y/%m/%d')
261 def render_to_response(self, request, path=None, subpath=None):
262 wrapper = FileWrapper(self.file)
263 response = HttpResponse(wrapper, content_type=self.mimetype)
264 response['Content-Length'] = self.file.size
268 class Template(TreeModel):
269 name = models.CharField(max_length=255)
270 documentation = models.TextField(null=True, blank=True)
271 mimetype = models.CharField(max_length=255, null=True, blank=True)
272 code = models.TextField()
276 return 'philo.models.Template: ' + self.path
279 def django_template(self):
280 return DjangoTemplate(self.code)
283 def containers(self):
285 Returns a tuple where the first item is a list of names of contentlets referenced by containers,
286 and the second item is a list of tuples of names and contenttypes of contentreferences referenced by containers.
287 This will break if there is a recursive extends or includes in the template code.
288 Due to the use of an empty Context, any extends or include tags with dynamic arguments probably won't work.
290 def container_nodes(template):
291 def nodelist_container_nodes(nodelist):
293 for node in nodelist:
295 for nodelist_name in ('nodelist', 'nodelist_loop', 'nodelist_empty', 'nodelist_true', 'nodelist_false'):
296 if hasattr(node, nodelist_name):
297 nodes.extend(nodelist_container_nodes(getattr(node, nodelist_name)))
298 if isinstance(node, ContainerNode):
300 elif isinstance(node, ExtendsNode):
301 extended_template = node.get_parent(Context())
302 if extended_template:
303 nodes.extend(container_nodes(extended_template))
304 elif isinstance(node, ConstantIncludeNode):
305 included_template = node.template
306 if included_template:
307 nodes.extend(container_nodes(included_template))
308 elif isinstance(node, IncludeNode):
309 included_template = get_template(node.template_name.resolve(Context()))
310 if included_template:
311 nodes.extend(container_nodes(included_template))
313 pass # fail for this node
315 return nodelist_container_nodes(template.nodelist)
316 all_nodes = container_nodes(self.django_template)
317 contentlet_node_names = set([node.name for node in all_nodes if not node.references])
318 contentreference_node_names = []
319 contentreference_node_specs = []
320 for node in all_nodes:
321 if node.references and node.name not in contentreference_node_names:
322 contentreference_node_specs.append((node.name, node.references))
323 contentreference_node_names.append(node.name)
324 return contentlet_node_names, contentreference_node_specs
326 def __unicode__(self):
327 return self.get_path(u' › ', 'name')
330 @fattr(is_usable=True)
331 def loader(template_name, template_dirs=None): # load_template_source
333 template = Template.objects.get_with_path(template_name)
334 except Template.DoesNotExist:
335 raise TemplateDoesNotExist(template_name)
336 return (template.code, template.origin)
340 template = models.ForeignKey(Template, related_name='pages')
341 title = models.CharField(max_length=255)
343 def render_to_response(self, request, path=None, subpath=None):
344 return HttpResponse(self.template.django_template.render(RequestContext(request, {'page': self})), mimetype=self.template.mimetype)
346 def __unicode__(self):
347 return self.get_path(u' › ', 'title')
350 # the following line enables the selection of a node as the root for a given django.contrib.sites Site object
351 models.ForeignKey(Node, related_name='sites', null=True, blank=True).contribute_to_class(Site, 'root_node')
354 class Contentlet(models.Model):
355 page = models.ForeignKey(Page, related_name='contentlets')
356 name = models.CharField(max_length=255)
357 content = models.TextField()
358 dynamic = models.BooleanField(default=False)
361 class ContentReference(models.Model):
362 page = models.ForeignKey(Page, related_name='contentreferences')
363 name = models.CharField(max_length=255)
364 content_type = models.ForeignKey(ContentType, verbose_name='Content type')
365 content_id = models.PositiveIntegerField(verbose_name='Content ID')
366 content = generic.GenericForeignKey('content_type', 'content_id')
369 register_templatetags('philo.templatetags.containers')
372 register_value_model(User)
373 register_value_model(Group)
374 register_value_model(Site)
375 register_value_model(Collection)
376 register_value_model(Template)
377 register_value_model(Page)