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 _ct_model_name(model):
29 model = opts.proxy_for_model
31 return opts.object_name.lower()
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.')
43 return json.loads(self.json_value)
45 def __unicode__(self):
46 return u'"%s": %s' % (self.key, self.value)
49 class Relationship(models.Model):
53 def register_value_model(model):
54 if issubclass(model, models.Model):
55 model_name = _ct_model_name(model)
56 if model_name not in Relationship._value_models:
57 Relationship._value_models.append(model_name)
59 raise TypeError('Relationship.register_value_model only accepts subclasses of django.db.models.Model')
62 def unregister_value_model(model):
63 if issubclass(model, models.Model):
64 model_name = _ct_model_name(model)
65 if model_name in Relationship._value_models:
66 Relationship._value_models.remove(model_name)
68 raise TypeError('Relationship.unregister_value_model only accepts subclasses of django.db.models.Model')
70 entity_content_type = models.ForeignKey(ContentType, related_name='relationship_entity_set', verbose_name='Entity type')
71 entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID')
72 entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
73 key = models.CharField(max_length=255)
74 value_content_type = models.ForeignKey(ContentType, related_name='relationship_value_set', limit_choices_to={'model__in':_value_models}, verbose_name='Value type')
75 value_object_id = models.PositiveIntegerField(verbose_name='Value ID')
76 value = generic.GenericForeignKey('value_content_type', 'value_object_id')
78 def __unicode__(self):
79 return u'"%s": %s' % (self.key, self.value)
82 class QuerySetMapper(object, DictMixin):
83 def __init__(self, queryset, passthrough=None):
84 self.queryset = queryset
85 self.passthrough = passthrough
86 def __getitem__(self, key):
88 return self.queryset.get(key__exact=key).value
89 except ObjectDoesNotExist:
91 return self.passthrough.__getitem__(key)
94 keys = set(self.queryset.values_list('key', flat=True).distinct())
96 keys += set(self.passthrough.keys())
100 class Entity(models.Model):
101 attribute_set = generic.GenericRelation(Attribute, content_type_field='entity_content_type', object_id_field='entity_object_id')
102 relationship_set = generic.GenericRelation(Relationship, content_type_field='entity_content_type', object_id_field='entity_object_id')
105 def attributes(self):
106 return QuerySetMapper(self.attribute_set)
109 def relationships(self):
110 return QuerySetMapper(self.relationship_set)
116 class Collection(models.Model):
117 name = models.CharField(max_length=255)
118 description = models.TextField(blank=True, null=True)
121 class CollectionMember(models.Model):
125 def register_value_model(model):
126 if issubclass(model, models.Model):
127 model_name = _ct_model_name(model)
128 if model_name not in CollectionMember._value_models:
129 CollectionMember._value_models.append(model_name)
131 raise TypeError('CollectionMember.register_value_model only accepts subclasses of django.db.models.Model')
134 def unregister_value_model(model):
135 if issubclass(model, models.Model):
136 model_name = _ct_model_name(model)
137 if model_name in CollectionMember._value_models:
138 CollectionMember._value_models.remove(model_name)
140 raise TypeError('CollectionMember.unregister_value_model only accepts subclasses of django.db.models.Model')
142 collection = models.ForeignKey(Collection, related_name='members')
143 index = models.PositiveIntegerField(verbose_name='Index', help_text='This will determine the ordering of the item within the collection. (Optional)', null=True, blank=True)
144 member_content_type = models.ForeignKey(ContentType, limit_choices_to={'model__in':_value_models}, verbose_name='Member type')
145 member_object_id = models.PositiveIntegerField(verbose_name='Member ID')
146 member = generic.GenericForeignKey('member_content_type', 'member_object_id')
149 def register_value_model(model):
150 Relationship.register_value_model(model)
151 CollectionMember.register_value_model(model)
154 def unregister_value_model(model):
155 Relationship.unregister_value_model(model)
156 CollectionMember.unregister_value_model(model)
159 class TreeManager(models.Manager):
160 use_for_related_fields = True
163 return self.filter(parent__isnull=True)
165 def get_with_path(self, path, root=None, absolute_result=True, pathsep='/'):
167 Returns the object with the path, or None if there is no object with that path,
168 unless absolute_result is set to False, in which case it returns a tuple containing
169 the deepest object found along the path, and the remainder of the path after that
170 object as a string (or None in the case that there is no remaining path).
172 slugs = path.split(pathsep)
174 remaining_slugs = list(slugs)
177 remaining_slugs.remove(slug)
178 if slug: # ignore blank slugs, handles for multiple consecutive pathseps
180 obj = self.get(slug__exact=slug, parent__exact=obj)
181 except self.model.DoesNotExist:
184 remaining_slugs.insert(0, slug)
185 remainder = pathsep.join(remaining_slugs)
191 return (obj, remainder)
192 raise self.model.DoesNotExist('%s matching query does not exist.' % self.model._meta.object_name)
195 class TreeModel(models.Model):
196 objects = TreeManager()
197 parent = models.ForeignKey('self', related_name='children', null=True, blank=True)
198 slug = models.SlugField()
200 def get_path(self, pathsep='/', field='slug'):
201 path = getattr(self, field)
204 path = getattr(parent, field) + pathsep + path
205 parent = parent.parent
207 path = property(get_path)
209 def __unicode__(self):
216 class TreeEntity(TreeModel, Entity):
218 def attributes(self):
220 return QuerySetMapper(self.attribute_set, passthrough=self.parent.attributes)
221 return super(TreeEntity, self).attributes
224 def relationships(self):
226 return QuerySetMapper(self.relationship_set, passthrough=self.parent.relationships)
227 return super(TreeEntity, self).relationships
233 class Node(TreeEntity):
234 instance_type = models.ForeignKey(ContentType, editable=False)
236 def save(self, force_insert=False, force_update=False):
237 if not hasattr(self, 'instance_type_ptr'):
238 self.instance_type = ContentType.objects.get_for_model(self.__class__)
239 super(Node, self).save(force_insert, force_update)
243 return self.instance_type.get_object_for_this_type(id=self.id)
245 accepts_subpath = False
247 def render_to_response(self, request, path=None, subpath=None):
248 return HttpResponseServerError()
251 class MultiNode(Node):
252 accepts_subpath = True
255 def urlpatterns(self):
258 def render_to_response(self, request, path=None, subpath=None):
261 subpath = "/" + subpath
262 from django.core.urlresolvers import resolve
263 view, args, kwargs = resolve(subpath, urlconf=self)
264 return view(request, *args, **kwargs)
270 class Redirect(Node):
275 target = models.URLField()
276 status_code = models.IntegerField(choices=STATUS_CODES, default=302, verbose_name='redirect type')
278 def render_to_response(self, request, path=None, subpath=None):
279 response = HttpResponseRedirect(self.target)
280 response.status_code = self.status_code
285 """ For storing arbitrary files """
286 mimetype = models.CharField(max_length=255)
287 file = models.FileField(upload_to='philo/files/%Y/%m/%d')
289 def render_to_response(self, request, path=None, subpath=None):
290 wrapper = FileWrapper(self.file)
291 response = HttpResponse(wrapper, content_type=self.mimetype)
292 response['Content-Length'] = self.file.size
296 class Template(TreeModel):
297 name = models.CharField(max_length=255)
298 documentation = models.TextField(null=True, blank=True)
299 mimetype = models.CharField(max_length=255, null=True, blank=True)
300 code = models.TextField()
304 return 'philo.models.Template: ' + self.path
307 def django_template(self):
308 return DjangoTemplate(self.code)
311 def containers(self):
313 Returns a list of names of contentlets referenced by containers.
314 This will break if there is a recursive extends or includes in the template code.
315 Due to the use of an empty Context, any extends or include tags with dynamic arguments probably won't work.
317 def container_node_names(template):
318 def nodelist_container_node_names(nodelist):
320 for node in nodelist:
322 for nodelist_name in ('nodelist', 'nodelist_loop', 'nodelist_empty', 'nodelist_true', 'nodelist_false'):
323 if hasattr(node, nodelist_name):
324 names.extend(nodelist_container_node_names(getattr(node, nodelist_name)))
325 if isinstance(node, ContainerNode):
326 names.append(node.name)
327 elif isinstance(node, ExtendsNode):
328 extended_template = node.get_parent(Context())
329 if extended_template:
330 names.extend(container_node_names(extended_template))
331 elif isinstance(node, ConstantIncludeNode):
332 included_template = node.template
333 if included_template:
334 names.extend(container_node_names(included_template))
335 elif isinstance(node, IncludeNode):
336 included_template = get_template(node.template_name.resolve(Context()))
337 if included_template:
338 names.extend(container_node_names(included_template))
340 pass # fail for this node
342 return nodelist_container_node_names(template.nodelist)
343 return set(container_node_names(self.django_template))
345 def __unicode__(self):
346 return self.get_path(u' › ', 'name')
349 @fattr(is_usable=True)
350 def loader(template_name, template_dirs=None): # load_template_source
352 template = Template.objects.get_with_path(template_name)
353 except Template.DoesNotExist:
354 raise TemplateDoesNotExist(template_name)
355 return (template.code, template.origin)
359 template = models.ForeignKey(Template, related_name='pages')
360 title = models.CharField(max_length=255)
362 def render_to_response(self, request, path=None, subpath=None):
363 return HttpResponse(self.template.django_template.render(RequestContext(request, {'page': self})), mimetype=self.template.mimetype)
365 def __unicode__(self):
366 return self.get_path(u' › ', 'title')
369 # the following line enables the selection of a node as the root for a given django.contrib.sites Site object
370 models.ForeignKey(Node, related_name='sites', null=True, blank=True).contribute_to_class(Site, 'root_node')
373 class Contentlet(models.Model):
374 page = models.ForeignKey(Page, related_name='contentlets')
375 name = models.CharField(max_length=255)
376 content = models.TextField()
377 dynamic = models.BooleanField(default=False)
380 register_templatetags('philo.templatetags.containers')
383 register_value_model(User)
384 register_value_model(Group)
385 register_value_model(Site)
386 register_value_model(Collection)
387 register_value_model(Template)
388 register_value_model(Page)