Initial implementation of AttributeField and RelationshipField.
[philo.git] / models / fields.py
1 from django.db import models
2 from django.db.models import signals
3 from django.core.exceptions import ObjectDoesNotExist, FieldError
4 from django.contrib.contenttypes.models import ContentType
5 from functools import partial
6 from philo.models.base import Attribute, Relationship, Entity
7
8
9 __all__ = ('AttributeField', 'RelationshipField')
10
11
12 class AttributeFieldDescriptor(object):
13         def __init__(self, field):
14                 self.field = field
15         
16         def __get__(self, instance, owner):
17                 if instance:
18                         try:
19                                 return instance.attribute_set.get(key__exact=self.field.key).value
20                         except ObjectDoesNotExist:
21                                 return None
22                 else:
23                         raise AttributeError('The \'%s\' attribute can only be accessed from %s instances.' % (self.field.name, owner.__name__))
24         
25         def __set__(self, instance, value):
26                 try:
27                         attribute = instance.attribute_set.get(key__exact=self.field.key)
28                 except ObjectDoesNotExist:
29                         attribute = Attribute()
30                         attribute.entity = instance
31                         attribute.key = self.field.key
32                 attribute.value = value
33                 attribute.save()
34         
35         def __delete__(self, instance):
36                 instance.attribute_set.filter(key__exact=self.field.key).delete()
37
38
39 class AttributeField(object):
40         def __init__(self, key):
41                 self.key = key
42         
43         def actually_contribute_to_class(self, sender, **kwargs):
44                 setattr(sender, self.name, AttributeFieldDescriptor(self))
45         
46         def contribute_to_class(self, cls, name):
47                 if issubclass(cls, Entity):
48                         self.name = name
49                         signals.class_prepared.connect(self.actually_contribute_to_class, sender=cls)
50                 else:
51                         raise FieldError('AttributeFields can only be declared on Entity subclasses.')
52
53
54 class RelationshipFieldDescriptor(object):
55         def __init__(self, field):
56                 self.field = field
57         
58         def __get__(self, instance, owner):
59                 if instance:
60                         try:
61                                 return instance.relationship_set.get(key__exact=self.field.key).value
62                         except ObjectDoesNotExist:
63                                 return None
64                 else:
65                         raise AttributeError('The \'%s\' attribute can only be accessed from %s instances.' % (self.field.name, owner.__name__))
66         
67         def __set__(self, instance, value):
68                 if isinstance(value, (models.Model, type(None))):
69                         try:
70                                 relationship = instance.relationship_set.get(key__exact=self.field.key)
71                         except ObjectDoesNotExist:
72                                 relationship = Relationship()
73                                 relationship.entity = instance
74                                 relationship.key = self.field.key
75                         relationship.value = value
76                         relationship.save()
77                 else:
78                         raise AttributeError('The \'%\' attribute can only be set using existing Model objects.' % self.field.name)
79         
80         def __delete__(self, instance):
81                 instance.relationship_set.filter(key__exact=self.field.key).delete()
82
83
84 class RelationshipField(object):
85         def __init__(self, key):
86                 self.key = key
87         
88         def actually_contribute_to_class(self, sender, **kwargs):
89                 setattr(sender, self.name, RelationshipFieldDescriptor(self))
90         
91         def contribute_to_class(self, cls, name):
92                 if issubclass(cls, Entity):
93                         self.name = name
94                         signals.class_prepared.connect(self.actually_contribute_to_class, sender=cls)
95                 else:
96                         raise FieldError('RelationshipFields can only be declared on Entity subclasses.')