1 from django.db import models
2 from django.contrib.contenttypes.models import ContentType
3 from django.contrib.contenttypes import generic
4 from django.utils import simplejson as json
5 from django.core.exceptions import ObjectDoesNotExist
6 from philo.exceptions import AncestorDoesNotExist
7 from philo.utils import ContentTypeRegistryLimiter
8 from philo.signals import entity_class_prepared
9 from UserDict import DictMixin
12 class Tag(models.Model):
13 name = models.CharField(max_length=250)
14 slug = models.SlugField(unique=True)
16 def __unicode__(self):
23 class Titled(models.Model):
24 title = models.CharField(max_length=255)
25 slug = models.SlugField(max_length=255)
27 def __unicode__(self):
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)
57 unique_together = ('key', 'entity_content_type', 'entity_object_id')
60 value_content_type_limiter = ContentTypeRegistryLimiter()
63 def register_value_model(model):
64 value_content_type_limiter.register_class(model)
67 def unregister_value_model(model):
68 value_content_type_limiter.unregister_class(model)
72 class Relationship(models.Model):
73 entity_content_type = models.ForeignKey(ContentType, related_name='relationship_entity_set', verbose_name='Entity type')
74 entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID')
75 entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
76 key = models.CharField(max_length=255)
77 value_content_type = models.ForeignKey(ContentType, related_name='relationship_value_set', limit_choices_to=value_content_type_limiter, verbose_name='Value type', null=True, blank=True)
78 value_object_id = models.PositiveIntegerField(verbose_name='Value ID', null=True, blank=True)
79 value = generic.GenericForeignKey('value_content_type', 'value_object_id')
81 def __unicode__(self):
82 return u'"%s": %s' % (self.key, self.value)
86 unique_together = ('key', 'entity_content_type', 'entity_object_id')
89 class QuerySetMapper(object, DictMixin):
90 def __init__(self, queryset, passthrough=None):
91 self.queryset = queryset
92 self.passthrough = passthrough
94 def __getitem__(self, key):
96 return self.queryset.get(key__exact=key).value
97 except ObjectDoesNotExist:
98 if self.passthrough is not None:
99 return self.passthrough.__getitem__(key)
103 keys = set(self.queryset.values_list('key', flat=True).distinct())
104 if self.passthrough is not None:
105 keys |= set(self.passthrough.keys())
109 class EntityOptions(object):
110 def __init__(self, options):
111 if options is not None:
112 for key, value in options.__dict__.items():
113 setattr(self, key, value)
114 if not hasattr(self, 'proxy_fields'):
115 self.proxy_fields = []
117 def add_proxy_field(self, proxy_field):
118 self.proxy_fields.append(proxy_field)
121 class EntityBase(models.base.ModelBase):
122 def __new__(cls, name, bases, attrs):
123 new = super(EntityBase, cls).__new__(cls, name, bases, attrs)
124 entity_options = attrs.pop('EntityMeta', None)
125 setattr(new, '_entity_meta', EntityOptions(entity_options))
126 entity_class_prepared.send(sender=new)
130 class Entity(models.Model):
131 __metaclass__ = EntityBase
133 attribute_set = generic.GenericRelation(Attribute, content_type_field='entity_content_type', object_id_field='entity_object_id')
134 relationship_set = generic.GenericRelation(Relationship, content_type_field='entity_content_type', object_id_field='entity_object_id')
137 def attributes(self):
138 return QuerySetMapper(self.attribute_set)
141 def relationships(self):
142 return QuerySetMapper(self.relationship_set)
145 def _added_attribute_registry(self):
146 if not hasattr(self, '_real_added_attribute_registry'):
147 self._real_added_attribute_registry = {}
148 return self._real_added_attribute_registry
151 def _removed_attribute_registry(self):
152 if not hasattr(self, '_real_removed_attribute_registry'):
153 self._real_removed_attribute_registry = []
154 return self._real_removed_attribute_registry
157 def _added_relationship_registry(self):
158 if not hasattr(self, '_real_added_relationship_registry'):
159 self._real_added_relationship_registry = {}
160 return self._real_added_relationship_registry
163 def _removed_relationship_registry(self):
164 if not hasattr(self, '_real_removed_relationship_registry'):
165 self._real_removed_relationship_registry = []
166 return self._real_removed_relationship_registry
168 def save(self, *args, **kwargs):
169 super(Entity, self).save(*args, **kwargs)
171 for key in self._removed_attribute_registry:
172 self.attribute_set.filter(key__exact=key).delete()
173 del self._removed_attribute_registry[:]
175 for key, value in self._added_attribute_registry.items():
177 attribute = self.attribute_set.get(key__exact=key)
178 except Attribute.DoesNotExist:
179 attribute = Attribute()
180 attribute.entity = self
182 attribute.value = value
184 self._added_attribute_registry.clear()
186 for key in self._removed_relationship_registry:
187 self.relationship_set.filter(key__exact=key).delete()
188 del self._removed_relationship_registry[:]
190 for key, value in self._added_relationship_registry.items():
192 relationship = self.relationship_set.get(key__exact=key)
193 except Relationship.DoesNotExist:
194 relationship = Relationship()
195 relationship.entity = self
196 relationship.key = key
197 relationship.value = value
199 self._added_relationship_registry.clear()
205 class TreeManager(models.Manager):
206 use_for_related_fields = True
209 return self.filter(parent__isnull=True)
211 def get_with_path(self, path, root=None, absolute_result=True, pathsep='/'):
213 Returns the object with the path, or None if there is no object with that path,
214 unless absolute_result is set to False, in which case it returns a tuple containing
215 the deepest object found along the path, and the remainder of the path after that
216 object as a string (or None in the case that there is no remaining path).
218 slugs = path.split(pathsep)
220 remaining_slugs = list(slugs)
223 remaining_slugs.remove(slug)
224 if slug: # ignore blank slugs, handles for multiple consecutive pathseps
226 obj = self.get(slug__exact=slug, parent__exact=obj)
227 except self.model.DoesNotExist:
230 remaining_slugs.insert(0, slug)
231 remainder = pathsep.join(remaining_slugs)
237 return (obj, remainder)
238 raise self.model.DoesNotExist('%s matching query does not exist.' % self.model._meta.object_name)
241 class TreeModel(models.Model):
242 objects = TreeManager()
243 parent = models.ForeignKey('self', related_name='children', null=True, blank=True)
244 slug = models.SlugField(max_length=255)
246 def has_ancestor(self, ancestor):
249 if parent == ancestor:
251 parent = parent.parent
254 def get_path(self, root=None, pathsep='/', field='slug'):
256 if not self.has_ancestor(root):
257 raise AncestorDoesNotExist(root)
260 while parent and parent != root:
261 path = getattr(parent, field, '?') + pathsep + path
262 parent = parent.parent
265 path = getattr(self, field, '?')
267 while parent and parent != root:
268 path = getattr(parent, field, '?') + pathsep + path
269 parent = parent.parent
271 path = property(get_path)
273 def __unicode__(self):
277 unique_together = (('parent', 'slug'),)
281 class TreeEntity(Entity, TreeModel):
283 def attributes(self):
285 return QuerySetMapper(self.attribute_set, passthrough=self.parent.attributes)
286 return super(TreeEntity, self).attributes
289 def relationships(self):
291 return QuerySetMapper(self.relationship_set, passthrough=self.parent.relationships)
292 return super(TreeEntity, self).relationships