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 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, pathsep='/'):
166 slugs = path.split(pathsep)
169 if slug: # ignore blank slugs, handles for multiple consecutive pathseps
171 obj = self.get(slug__exact=slug, parent__exact=obj)
172 except self.model.DoesNotExist:
177 raise self.model.DoesNotExist('%s matching query does not exist.' % self.model._meta.object_name)
180 class TreeModel(models.Model):
181 objects = TreeManager()
182 parent = models.ForeignKey('self', related_name='children', null=True, blank=True)
183 slug = models.SlugField()
185 def get_path(self, pathsep='/', field='slug'):
186 path = getattr(self, field)
189 path = getattr(parent, field) + pathsep + path
190 parent = parent.parent
192 path = property(get_path)
194 def __unicode__(self):
201 class TreeEntity(TreeModel, Entity):
203 def attributes(self):
205 return QuerySetMapper(self.attribute_set, passthrough=self.parent.attributes)
206 return super(TreeEntity, self).attributes
209 def relationships(self):
211 return QuerySetMapper(self.relationship_set, passthrough=self.parent.relationships)
212 return super(TreeEntity, self).relationships
218 class Node(TreeEntity):
219 instance_type = models.ForeignKey(ContentType, editable=False)
221 def save(self, force_insert=False, force_update=False):
222 if not hasattr(self, 'instance_type_ptr'):
223 self.instance_type = ContentType.objects.get_for_model(self.__class__)
224 super(Node, self).save(force_insert, force_update)
228 return self.instance_type.get_object_for_this_type(id=self.id)
230 accepts_subpath = False
232 def render_to_response(self, request, path=None, subpath=None):
233 return HttpResponseServerError()
236 class Redirect(Node):
241 target = models.URLField()
242 status_code = models.IntegerField(choices=STATUS_CODES, default=302, verbose_name="redirect type")
244 def render_to_response(self, request, path=None, subpath=None):
245 response = HttpResponseRedirect(self.target)
246 response.status_code = self.status_code
251 """ For storing arbitrary files """
252 mimetype = models.CharField(max_length=255)
253 file = models.FileField(upload_to='philo/files/%Y/%m/%d')
255 def render_to_response(self, request, path=None, subpath=None):
256 wrapper = FileWrapper(self.file)
257 response = HttpResponse(wrapper, content_type=self.mimetype)
258 response['Content-Length'] = self.file.size
262 class Template(TreeModel):
263 name = models.CharField(max_length=255)
264 documentation = models.TextField(null=True, blank=True)
265 mimetype = models.CharField(max_length=255, null=True, blank=True)
266 code = models.TextField()
270 return 'philo.models.Template: ' + self.path
273 def django_template(self):
274 return DjangoTemplate(self.code)
277 def containers(self):
279 Returns a list of names of contentlets referenced by containers.
280 This will break if there is a recursive extends or includes in the template code.
281 Due to the use of an empty Context, any extends or include tags with dynamic arguments probably won't work.
283 def container_node_names(template):
284 def nodelist_container_node_names(nodelist):
286 for node in nodelist:
288 for nodelist_name in ('nodelist', 'nodelist_loop', 'nodelist_empty', 'nodelist_true', 'nodelist_false'):
289 if hasattr(node, nodelist_name):
290 names.extend(nodelist_container_node_names(getattr(node, nodelist_name)))
291 if isinstance(node, ContainerNode):
292 names.append(node.name)
293 elif isinstance(node, ExtendsNode):
294 extended_template = node.get_parent(Context())
295 if extended_template:
296 names.extend(container_node_names(extended_template))
297 elif isinstance(node, ConstantIncludeNode):
298 included_template = node.template
299 if included_template:
300 names.extend(container_node_names(included_template))
301 elif isinstance(node, IncludeNode):
302 included_template = get_template(node.template_name.resolve(Context()))
303 if included_template:
304 names.extend(container_node_names(included_template))
306 pass # fail for this node
308 return nodelist_container_node_names(template.nodelist)
309 return set(container_node_names(self.django_template))
311 def __unicode__(self):
312 return self.get_path(u' › ', 'name')
315 @fattr(is_usable=True)
316 def loader(template_name, template_dirs=None): # load_template_source
318 template = Template.objects.get_with_path(template_name)
319 except Template.DoesNotExist:
320 raise TemplateDoesNotExist(template_name)
321 return (template.code, template.origin)
325 template = models.ForeignKey(Template, related_name='pages')
326 title = models.CharField(max_length=255)
328 def render_to_response(self, request, path=None, subpath=None):
329 return HttpResponse(self.template.django_template.render(RequestContext(request, {'page': self})), mimetype=self.template.mimetype)
331 def __unicode__(self):
332 return self.get_path(u' › ', 'title')
335 # the following line enables the selection of a node as the root for a given django.contrib.sites Site object
336 models.ForeignKey(Node, related_name='sites', null=True, blank=True).contribute_to_class(Site, 'root_node')
339 class Contentlet(models.Model):
340 page = models.ForeignKey(Page, related_name='contentlets')
341 name = models.CharField(max_length=255)
342 content = models.TextField()
343 dynamic = models.BooleanField(default=False)
346 register_templatetags('philo.templatetags.containers')
349 register_value_model(User)
350 register_value_model(Group)
351 register_value_model(Site)
352 register_value_model(Collection)
353 register_value_model(Template)
354 register_value_model(Page)