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
9 from utils import fattr
10 from django.template import add_to_builtins as register_templatetags
11 from django.template import Template as DjangoTemplate
12 from django.template import TemplateDoesNotExist
13 from django.template import Context
14 from django.core.exceptions import ObjectDoesNotExist
18 import simplejson as json
19 from UserDict import DictMixin
20 from templatetags.containers import ContainerNode
21 from django.template.loader_tags import ExtendsNode, ConstantIncludeNode, IncludeNode
22 from django.template.loader import get_template
25 def _ct_model_name(model):
28 model = opts.proxy_for_model
30 return opts.object_name.lower()
33 class Attribute(models.Model):
34 entity_content_type = models.ForeignKey(ContentType, verbose_name='Entity type')
35 entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID')
36 entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
37 key = models.CharField(max_length=255)
38 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 __unicode__(self):
45 return u'"%s": %s' % (self.key, self.value)
48 class Relationship(models.Model):
52 def register_value_model(model):
53 if issubclass(model, models.Model):
54 model_name = _ct_model_name(model)
55 if model_name not in Relationship._value_models:
56 Relationship._value_models.append(model_name)
58 raise TypeError('Relationship.register_value_model only accepts subclasses of django.db.models.Model')
61 def unregister_value_model(model):
62 if issubclass(model, models.Model):
63 model_name = _ct_model_name(model)
64 if model_name in Relationship._value_models:
65 Relationship._value_models.remove(model_name)
67 raise TypeError('Relationship.unregister_value_model only accepts subclasses of django.db.models.Model')
69 entity_content_type = models.ForeignKey(ContentType, related_name='relationship_entity_set', verbose_name='Entity type')
70 entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID')
71 entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
72 key = models.CharField(max_length=255)
73 value_content_type = models.ForeignKey(ContentType, related_name='relationship_value_set', limit_choices_to={'model__in':_value_models}, verbose_name='Value type')
74 value_object_id = models.PositiveIntegerField(verbose_name='Value ID')
75 value = generic.GenericForeignKey('value_content_type', 'value_object_id')
77 def __unicode__(self):
78 return u'"%s": %s' % (self.key, self.value)
81 class QuerySetMapper(object, DictMixin):
82 def __init__(self, queryset, passthrough=None):
83 self.queryset = queryset
84 self.passthrough = passthrough
85 def __getitem__(self, key):
87 return self.queryset.get(key__exact=key).value
88 except ObjectDoesNotExist:
90 return self.passthrough.__getitem__(key)
93 keys = set(self.queryset.values_list('key', flat=True).distinct())
95 keys += set(self.passthrough.keys())
99 class Entity(models.Model):
100 attribute_set = generic.GenericRelation(Attribute, content_type_field='entity_content_type', object_id_field='entity_object_id')
101 relationship_set = generic.GenericRelation(Relationship, content_type_field='entity_content_type', object_id_field='entity_object_id')
104 def attributes(self):
105 return QuerySetMapper(self.attribute_set)
108 def relationships(self):
109 return QuerySetMapper(self.relationship_set)
115 class Collection(models.Model):
116 name = models.CharField(max_length=255)
117 description = models.TextField(blank=True, null=True)
120 class CollectionMember(models.Model):
124 def register_value_model(model):
125 if issubclass(model, models.Model):
126 model_name = _ct_model_name(model)
127 if model_name not in CollectionMember._value_models:
128 CollectionMember._value_models.append(model_name)
130 raise TypeError('CollectionMember.register_value_model only accepts subclasses of django.db.models.Model')
133 def unregister_value_model(model):
134 if issubclass(model, models.Model):
135 model_name = _ct_model_name(model)
136 if model_name in CollectionMember._value_models:
137 CollectionMember._value_models.remove(model_name)
139 raise TypeError('CollectionMember.unregister_value_model only accepts subclasses of django.db.models.Model')
141 collection = models.ForeignKey(Collection, related_name='members')
142 index = models.PositiveIntegerField(verbose_name='Index', help_text='This will determine the ordering of the item within the collection. (Optional)', null=True, blank=True)
143 member_content_type = models.ForeignKey(ContentType, limit_choices_to={'model__in':_value_models}, verbose_name='Member type')
144 member_object_id = models.PositiveIntegerField(verbose_name='Member ID')
145 member = generic.GenericForeignKey('member_content_type', 'member_object_id')
148 def register_value_model(model):
149 Relationship.register_value_model(model)
150 CollectionMember.register_value_model(model)
153 def unregister_value_model(model):
154 Relationship.unregister_value_model(model)
155 CollectionMember.unregister_value_model(model)
158 class TreeManager(models.Manager):
159 use_for_related_fields = True
162 return self.filter(parent__isnull=True)
164 def get_with_path(self, path, root=None, pathsep='/'):
165 slugs = path.split(pathsep)
168 if slug: # ignore blank slugs, handles for multiple consecutive pathseps
170 obj = self.get(slug__exact=slug, parent__exact=obj)
171 except self.model.DoesNotExist:
176 raise self.model.DoesNotExist('%s matching query does not exist.' % self.model._meta.object_name)
179 class TreeModel(models.Model):
180 objects = TreeManager()
181 parent = models.ForeignKey('self', related_name='children', null=True, blank=True)
182 slug = models.SlugField()
184 def get_path(self, pathsep='/', field='slug'):
185 path = getattr(self, field)
188 path = getattr(parent, field) + pathsep + path
189 parent = parent.parent
191 path = property(get_path)
193 def __unicode__(self):
200 class TreeEntity(TreeModel, Entity):
202 def attributes(self):
204 return QuerySetMapper(self.attribute_set, passthrough=self.parent.attributes)
205 return super(TreeEntity, self).attributes
208 def relationships(self):
210 return QuerySetMapper(self.relationship_set, passthrough=self.parent.relationships)
211 return super(TreeEntity, self).relationships
217 class Template(TreeModel):
218 name = models.CharField(max_length=255)
219 documentation = models.TextField(null=True, blank=True)
220 mimetype = models.CharField(max_length=255, null=True, blank=True)
221 code = models.TextField()
225 return 'philo.models.Template: ' + self.path
228 def django_template(self):
229 return DjangoTemplate(self.code)
232 def containers(self):
234 Returns a list of names of contentlets referenced by containers.
235 This will break if there is a recursive extends or includes in the template code.
236 Due to the use of an empty Context, any extends or include tags with dynamic arguments probably won't work.
238 def container_node_names(template):
239 def nodelist_container_node_names(nodelist):
241 for node in nodelist:
243 if hasattr(node, 'nodelist'):
244 names.extend(nodelist_container_node_names(node.nodelist))
245 if isinstance(node, ContainerNode):
246 names.append(node.name)
247 elif isinstance(node, ExtendsNode):
248 extended_template = node.get_parent(Context())
249 if extended_template:
250 names.extend(container_node_names(extended_template))
251 elif isinstance(node, ConstantIncludeNode):
252 included_template = node.template
253 if included_template:
254 names.extend(container_node_names(included_template))
255 elif isinstance(node, IncludeNode):
256 included_template = get_template(node.template_name.resolve(Context()))
257 if included_template:
258 names.extend(container_node_names(included_template))
260 pass # fail for this node
262 return nodelist_container_node_names(template.nodelist)
263 return set(container_node_names(self.django_template))
265 def __unicode__(self):
266 return self.get_path(u' › ', 'name')
269 @fattr(is_usable=True)
270 def loader(template_name, template_dirs=None): # load_template_source
272 template = Template.objects.get_with_path(template_name)
273 except Template.DoesNotExist:
274 raise TemplateDoesNotExist(template_name)
275 return (template.code, template.origin)
276 mptt.register(Template)
279 class Page(TreeEntity):
280 template = models.ForeignKey(Template, related_name='pages')
281 title = models.CharField(max_length=255)
283 def __unicode__(self):
284 return self.get_path(u' › ', 'title')
288 # the following line enables the selection of a page as the root for a given django.contrib.sites Site object
289 models.ForeignKey(Page, related_name='sites', null=True, blank=True).contribute_to_class(Site, 'root_page')
292 class Contentlet(models.Model):
293 page = models.ForeignKey(Page, related_name='contentlets')
294 name = models.CharField(max_length=255)
295 content = models.TextField()
296 dynamic = models.BooleanField(default=False)
299 register_templatetags('philo.templatetags.containers')
302 register_value_model(User)
303 register_value_model(Group)
304 register_value_model(Site)
305 register_value_model(Collection)
306 register_value_model(Template)
307 register_value_model(Page)