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 list of names of contentlets referenced by containers.
286 This will break if there is a recursive extends or includes in the template code.
287 Due to the use of an empty Context, any extends or include tags with dynamic arguments probably won't work.
289 def container_node_names(template):
290 def nodelist_container_node_names(nodelist):
292 for node in nodelist:
294 for nodelist_name in ('nodelist', 'nodelist_loop', 'nodelist_empty', 'nodelist_true', 'nodelist_false'):
295 if hasattr(node, nodelist_name):
296 names.extend(nodelist_container_node_names(getattr(node, nodelist_name)))
297 if isinstance(node, ContainerNode):
298 names.append(node.name)
299 elif isinstance(node, ExtendsNode):
300 extended_template = node.get_parent(Context())
301 if extended_template:
302 names.extend(container_node_names(extended_template))
303 elif isinstance(node, ConstantIncludeNode):
304 included_template = node.template
305 if included_template:
306 names.extend(container_node_names(included_template))
307 elif isinstance(node, IncludeNode):
308 included_template = get_template(node.template_name.resolve(Context()))
309 if included_template:
310 names.extend(container_node_names(included_template))
312 pass # fail for this node
314 return nodelist_container_node_names(template.nodelist)
315 return set(container_node_names(self.django_template))
317 def __unicode__(self):
318 return self.get_path(u' › ', 'name')
321 @fattr(is_usable=True)
322 def loader(template_name, template_dirs=None): # load_template_source
324 template = Template.objects.get_with_path(template_name)
325 except Template.DoesNotExist:
326 raise TemplateDoesNotExist(template_name)
327 return (template.code, template.origin)
331 template = models.ForeignKey(Template, related_name='pages')
332 title = models.CharField(max_length=255)
334 def render_to_response(self, request, path=None, subpath=None):
335 return HttpResponse(self.template.django_template.render(RequestContext(request, {'page': self})), mimetype=self.template.mimetype)
337 def __unicode__(self):
338 return self.get_path(u' › ', 'title')
341 # the following line enables the selection of a node as the root for a given django.contrib.sites Site object
342 models.ForeignKey(Node, related_name='sites', null=True, blank=True).contribute_to_class(Site, 'root_node')
345 class Contentlet(models.Model):
346 page = models.ForeignKey(Page, related_name='contentlets')
347 name = models.CharField(max_length=255)
348 content = models.TextField()
349 dynamic = models.BooleanField(default=False)
352 register_templatetags('philo.templatetags.containers')
355 register_value_model(User)
356 register_value_model(Group)
357 register_value_model(Site)
358 register_value_model(Collection)
359 register_value_model(Template)
360 register_value_model(Page)