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 InheritableTreeEntity(TreeEntity):
199 instance_type = models.ForeignKey(ContentType, editable=False)
201 def save(self, force_insert=False, force_update=False):
202 if not hasattr(self, 'instance_type_ptr'):
203 self.instance_type = ContentType.objects.get_for_model(self.__class__)
204 super(InheritableTreeEntity, self).save(force_insert, force_update)
208 return self.instance_type.get_object_for_this_type(id=self.id)
210 def get_path(self, pathsep='/', field='slug'):
211 path = getattr(self.instance, field, '?')
214 path = getattr(parent.instance, field, '?') + pathsep + path
215 parent = parent.parent
217 path = property(get_path)
220 def attributes(self):
222 return QuerySetMapper(self.instance.attribute_set, passthrough=self.parent.instance.attributes)
223 return QuerySetMapper(self.instance.attribute_set)
226 def relationships(self):
228 return QuerySetMapper(self.instance.relationship_set, passthrough=self.parent.instance.relationships)
229 return QuerySetMapper(self.instance.relationship_set)
235 class Node(InheritableTreeEntity):
236 accepts_subpath = False
238 def render_to_response(self, request, path=None, subpath=None):
239 return HttpResponseServerError()
242 class MultiNode(Node):
243 accepts_subpath = True
247 def render_to_response(self, request, path=None, subpath=None):
250 subpath = "/" + subpath
251 from django.core.urlresolvers import resolve
252 view, args, kwargs = resolve(subpath, urlconf=self)
253 return view(request, *args, **kwargs)
259 class Redirect(Node):
264 target = models.URLField()
265 status_code = models.IntegerField(choices=STATUS_CODES, default=302, verbose_name='redirect type')
267 def render_to_response(self, request, path=None, subpath=None):
268 response = HttpResponseRedirect(self.target)
269 response.status_code = self.status_code
274 """ For storing arbitrary files """
275 mimetype = models.CharField(max_length=255)
276 file = models.FileField(upload_to='philo/files/%Y/%m/%d')
278 def render_to_response(self, request, path=None, subpath=None):
279 wrapper = FileWrapper(self.file)
280 response = HttpResponse(wrapper, content_type=self.mimetype)
281 response['Content-Length'] = self.file.size
285 class Template(TreeModel):
286 name = models.CharField(max_length=255)
287 documentation = models.TextField(null=True, blank=True)
288 mimetype = models.CharField(max_length=255, null=True, blank=True)
289 code = models.TextField()
293 return 'philo.models.Template: ' + self.path
296 def django_template(self):
297 return DjangoTemplate(self.code)
300 def containers(self):
302 Returns a tuple where the first item is a list of names of contentlets referenced by containers,
303 and the second item is a list of tuples of names and contenttypes of contentreferences referenced by containers.
304 This will break if there is a recursive extends or includes in the template code.
305 Due to the use of an empty Context, any extends or include tags with dynamic arguments probably won't work.
307 def container_nodes(template):
308 def nodelist_container_nodes(nodelist):
310 for node in nodelist:
312 for nodelist_name in ('nodelist', 'nodelist_loop', 'nodelist_empty', 'nodelist_true', 'nodelist_false'):
313 if hasattr(node, nodelist_name):
314 nodes.extend(nodelist_container_nodes(getattr(node, nodelist_name)))
315 if isinstance(node, ContainerNode):
317 elif isinstance(node, ExtendsNode):
318 extended_template = node.get_parent(Context())
319 if extended_template:
320 nodes.extend(container_nodes(extended_template))
321 elif isinstance(node, ConstantIncludeNode):
322 included_template = node.template
323 if included_template:
324 nodes.extend(container_nodes(included_template))
325 elif isinstance(node, IncludeNode):
326 included_template = get_template(node.template_name.resolve(Context()))
327 if included_template:
328 nodes.extend(container_nodes(included_template))
330 pass # fail for this node
332 return nodelist_container_nodes(template.nodelist)
333 all_nodes = container_nodes(self.django_template)
334 contentlet_node_names = set([node.name for node in all_nodes if not node.references])
335 contentreference_node_names = []
336 contentreference_node_specs = []
337 for node in all_nodes:
338 if node.references and node.name not in contentreference_node_names:
339 contentreference_node_specs.append((node.name, node.references))
340 contentreference_node_names.append(node.name)
341 return contentlet_node_names, contentreference_node_specs
343 def __unicode__(self):
344 return self.get_path(u' › ', 'name')
347 @fattr(is_usable=True)
348 def loader(template_name, template_dirs=None): # load_template_source
350 template = Template.objects.get_with_path(template_name)
351 except Template.DoesNotExist:
352 raise TemplateDoesNotExist(template_name)
353 return (template.code, template.origin)
357 template = models.ForeignKey(Template, related_name='pages')
358 title = models.CharField(max_length=255)
360 def render_to_response(self, request, path=None, subpath=None):
361 return HttpResponse(self.template.django_template.render(RequestContext(request, {'page': self})), mimetype=self.template.mimetype)
363 def __unicode__(self):
364 return self.get_path(u' › ', 'title')
367 # the following line enables the selection of a node as the root for a given django.contrib.sites Site object
368 models.ForeignKey(Node, related_name='sites', null=True, blank=True).contribute_to_class(Site, 'root_node')
371 class Contentlet(models.Model):
372 page = models.ForeignKey(Page, related_name='contentlets')
373 name = models.CharField(max_length=255)
374 content = models.TextField()
375 dynamic = models.BooleanField(default=False)
378 class ContentReference(models.Model):
379 page = models.ForeignKey(Page, related_name='contentreferences')
380 name = models.CharField(max_length=255)
381 content_type = models.ForeignKey(ContentType, verbose_name='Content type')
382 content_id = models.PositiveIntegerField(verbose_name='Content ID')
383 content = generic.GenericForeignKey('content_type', 'content_id')
386 register_templatetags('philo.templatetags.containers')
389 register_value_model(User)
390 register_value_model(Group)
391 register_value_model(Site)
392 register_value_model(Collection)
393 register_value_model(Template)
394 register_value_model(Page)