5ca9d93fc8a32072cd20b680056d7ce2079edc61
[philo.git] / models / base.py
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 UserDict import DictMixin
7
8
9 def register_value_model(model):
10         pass
11
12
13 def unregister_value_model(model):
14         pass
15
16
17 class Attribute(models.Model):
18         entity_content_type = models.ForeignKey(ContentType, verbose_name='Entity type')
19         entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID')
20         entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
21         key = models.CharField(max_length=255)
22         json_value = models.TextField(verbose_name='Value (JSON)', help_text='This value must be valid JSON.')
23         
24         def get_value(self):
25                 return json.loads(self.json_value)
26         
27         def set_value(self, value):
28                 self.json_value = json.dumps(value)
29         
30         def delete_value(self):
31                 self.json_value = json.dumps(None)
32         
33         value = property(get_value, set_value, delete_value)
34         
35         def __unicode__(self):
36                 return u'"%s": %s' % (self.key, self.value)
37         
38         class Meta:
39                 app_label = 'philo'
40
41
42 class Relationship(models.Model):
43         entity_content_type = models.ForeignKey(ContentType, related_name='relationship_entity_set', verbose_name='Entity type')
44         entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID')
45         entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
46         key = models.CharField(max_length=255)
47         value_content_type = models.ForeignKey(ContentType, related_name='relationship_value_set', verbose_name='Value type')
48         value_object_id = models.PositiveIntegerField(verbose_name='Value ID')
49         value = generic.GenericForeignKey('value_content_type', 'value_object_id')
50         
51         def __unicode__(self):
52                 return u'"%s": %s' % (self.key, self.value)
53         
54         class Meta:
55                 app_label = 'philo'
56
57
58 class QuerySetMapper(object, DictMixin):
59         def __init__(self, queryset, passthrough=None):
60                 self.queryset = queryset
61                 self.passthrough = passthrough
62         def __getitem__(self, key):
63                 try:
64                         return self.queryset.get(key__exact=key).value
65                 except ObjectDoesNotExist:
66                         if self.passthrough:
67                                 return self.passthrough.__getitem__(key)
68                         raise KeyError
69         def keys(self):
70                 keys = set(self.queryset.values_list('key', flat=True).distinct())
71                 if self.passthrough:
72                         keys += set(self.passthrough.keys())
73                 return list(keys)
74
75
76 class Entity(models.Model):
77         attribute_set = generic.GenericRelation(Attribute, content_type_field='entity_content_type', object_id_field='entity_object_id')
78         relationship_set = generic.GenericRelation(Relationship, content_type_field='entity_content_type', object_id_field='entity_object_id')
79         
80         @property
81         def attributes(self):
82                 return QuerySetMapper(self.attribute_set)
83         
84         @property
85         def relationships(self):
86                 return QuerySetMapper(self.relationship_set)
87         
88         class Meta:
89                 abstract = True
90                 app_label = 'philo'
91
92
93 class TreeManager(models.Manager):
94         use_for_related_fields = True
95         
96         def roots(self):
97                 return self.filter(parent__isnull=True)
98         
99         def get_with_path(self, path, root=None, absolute_result=True, pathsep='/'):
100                 """
101                 Returns the object with the path, or None if there is no object with that path,
102                 unless absolute_result is set to False, in which case it returns a tuple containing
103                 the deepest object found along the path, and the remainder of the path after that
104                 object as a string (or None in the case that there is no remaining path).
105                 """
106                 slugs = path.split(pathsep)
107                 obj = root
108                 remaining_slugs = list(slugs)
109                 remainder = None
110                 for slug in slugs:
111                         remaining_slugs.remove(slug)
112                         if slug: # ignore blank slugs, handles for multiple consecutive pathseps
113                                 try:
114                                         obj = self.get(slug__exact=slug, parent__exact=obj)
115                                 except self.model.DoesNotExist:
116                                         if absolute_result:
117                                                 obj = None
118                                         remaining_slugs.insert(0, slug)
119                                         remainder = pathsep.join(remaining_slugs)
120                                         break
121                 if obj:
122                         if absolute_result:
123                                 return obj
124                         else:
125                                 return (obj, remainder)
126                 raise self.model.DoesNotExist('%s matching query does not exist.' % self.model._meta.object_name)
127
128
129 class TreeModel(models.Model):
130         objects = TreeManager()
131         parent = models.ForeignKey('self', related_name='children', null=True, blank=True)
132         slug = models.SlugField()
133         
134         def get_path(self, pathsep='/', field='slug'):
135                 path = getattr(self, field, '?')
136                 parent = self.parent
137                 while parent:
138                         path = getattr(parent, field, '?') + pathsep + path
139                         parent = parent.parent
140                 return path
141         path = property(get_path)
142         
143         def __unicode__(self):
144                 return self.path
145         
146         class Meta:
147                 abstract = True
148                 app_label = 'philo'
149
150
151 class TreeEntity(TreeModel, Entity):
152         @property
153         def attributes(self):
154                 if self.parent:
155                         return QuerySetMapper(self.attribute_set, passthrough=self.parent.attributes)
156                 return super(TreeEntity, self).attributes
157         
158         @property
159         def relationships(self):
160                 if self.parent:
161                         return QuerySetMapper(self.relationship_set, passthrough=self.parent.relationships)
162                 return super(TreeEntity, self).relationships
163         
164         class Meta:
165                 abstract = True
166                 app_label = 'philo'
167
168
169 class InheritableTreeEntity(TreeEntity):
170         instance_type = models.ForeignKey(ContentType, editable=False)
171         
172         def save(self, force_insert=False, force_update=False):
173                 if not hasattr(self, 'instance_type_ptr'):
174                         self.instance_type = ContentType.objects.get_for_model(self.__class__)
175                 super(InheritableTreeEntity, self).save(force_insert, force_update)
176         
177         @property
178         def instance(self):
179                 try:
180                         return self.instance_type.get_object_for_this_type(id=self.id)
181                 except:
182                         return None
183         
184         def get_path(self, pathsep='/', field='slug'):
185                 path = getattr(self.instance, field, getattr(self.instance, 'slug', '?'))
186                 parent = self.parent
187                 while parent:
188                         path = getattr(parent.instance, field, getattr(parent.instance, 'slug', '?')) + pathsep + path
189                         parent = parent.parent
190                 return path
191         path = property(get_path)
192         
193         @property
194         def attributes(self):
195                 if self.parent:
196                         return QuerySetMapper(self.instance.attribute_set, passthrough=self.parent.instance.attributes)
197                 return QuerySetMapper(self.instance.attribute_set)
198
199         @property
200         def relationships(self):
201                 if self.parent:
202                         return QuerySetMapper(self.instance.relationship_set, passthrough=self.parent.instance.relationships)
203                 return QuerySetMapper(self.instance.relationship_set)
204         
205         class Meta:
206                 abstract = True
207                 app_label = 'philo'