Created JSON field, descriptor, and formfield to handle json storage on models unifor...
[philo.git] / models / fields.py
1 from django.db import models
2 from django import forms
3 from django.core.exceptions import FieldError, ValidationError
4 from django.utils import simplejson as json
5 from django.utils.text import capfirst
6 from philo.signals import entity_class_prepared
7 from philo.validators import TemplateValidator, json_validator
8
9
10 __all__ = ('AttributeField', 'RelationshipField')
11
12
13 class EntityProxyField(object):
14         descriptor_class = None
15         
16         def __init__(self, *args, **kwargs):
17                 if self.descriptor_class is None:
18                         raise NotImplementedError('EntityProxyField subclasses must specify a descriptor_class.')
19                 self.verbose_name = kwargs.get('verbose_name', None)
20                 self.help_text = kwargs.get('help_text', None)
21         
22         def actually_contribute_to_class(self, sender, **kwargs):
23                 sender._entity_meta.add_proxy_field(self)
24                 setattr(sender, self.attname, self.descriptor_class(self))
25         
26         def contribute_to_class(self, cls, name):
27                 from philo.models.base import Entity
28                 if issubclass(cls, Entity):
29                         self.name = name
30                         self.attname = name
31                         if self.verbose_name is None and name:
32                                 self.verbose_name = name.replace('_', ' ')
33                         entity_class_prepared.connect(self.actually_contribute_to_class, sender=cls)
34                 else:
35                         raise FieldError('%s instances can only be declared on Entity subclasses.' % self.__class__.__name__)
36         
37         def formfield(self, *args, **kwargs):
38                 raise NotImplementedError('EntityProxyField subclasses must implement a formfield method.')
39         
40         def value_from_object(self, obj):
41                 return getattr(obj, self.attname)
42
43
44 class AttributeFieldDescriptor(object):
45         def __init__(self, field):
46                 self.field = field
47         
48         def __get__(self, instance, owner):
49                 if instance:
50                         if self.field.key in instance._added_attribute_registry:
51                                 return instance._added_attribute_registry[self.field.key]
52                         if self.field.key in instance._removed_attribute_registry:
53                                 return None
54                         try:
55                                 return instance.attributes[self.field.key]
56                         except KeyError:
57                                 return None
58                 else:
59                         raise AttributeError('The \'%s\' attribute can only be accessed from %s instances.' % (self.field.name, owner.__name__))
60         
61         def __set__(self, instance, value):
62                 if self.field.key in instance._removed_attribute_registry:
63                         instance._removed_attribute_registry.remove(self.field.key)
64                 instance._added_attribute_registry[self.field.key] = value
65         
66         def __delete__(self, instance):
67                 if self.field.key in instance._added_attribute_registry:
68                         del instance._added_attribute_registry[self.field.key]
69                 instance._removed_attribute_registry.append(self.field.key)
70
71
72 class AttributeField(EntityProxyField):
73         descriptor_class = AttributeFieldDescriptor
74         
75         def __init__(self, field_template=None, key=None, **kwargs):
76                 super(AttributeField, self).__init__(**kwargs)
77                 self.key = key
78                 if field_template is None:
79                         field_template = models.CharField(max_length=255)
80                 self.field_template = field_template
81         
82         def contribute_to_class(self, cls, name):
83                 super(AttributeField, self).contribute_to_class(cls, name)
84                 if self.key is None:
85                         self.key = name
86         
87         def formfield(self, **kwargs):
88                 defaults = {'required': False, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
89                 defaults.update(kwargs)
90                 return self.field_template.formfield(**defaults)
91
92
93 class RelationshipFieldDescriptor(object):
94         def __init__(self, field):
95                 self.field = field
96         
97         def __get__(self, instance, owner):
98                 if instance:
99                         if self.field.key in instance._added_relationship_registry:
100                                 return instance._added_relationship_registry[self.field.key]
101                         if self.field.key in instance._removed_relationship_registry:
102                                 return None
103                         try:
104                                 return instance.relationships[self.field.key]
105                         except KeyError:
106                                 return None
107                 else:
108                         raise AttributeError('The \'%s\' attribute can only be accessed from %s instances.' % (self.field.name, owner.__name__))
109         
110         def __set__(self, instance, value):
111                 if isinstance(value, (models.Model, type(None))):
112                         if self.field.key in instance._removed_relationship_registry:
113                                 instance._removed_relationship_registry.remove(self.field.key)
114                         instance._added_relationship_registry[self.field.key] = value
115                 else:
116                         raise AttributeError('The \'%s\' attribute can only be set using existing Model objects.' % self.field.name)
117         
118         def __delete__(self, instance):
119                 if self.field.key in instance._added_relationship_registry:
120                         del instance._added_relationship_registry[self.field.key]
121                 instance._removed_relationship_registry.append(self.field.key)
122
123
124 class RelationshipField(EntityProxyField):
125         descriptor_class = RelationshipFieldDescriptor
126         
127         def __init__(self, model, limit_choices_to=None, key=None, **kwargs):
128                 super(RelationshipField, self).__init__(**kwargs)
129                 self.key = key
130                 self.model = model
131                 if limit_choices_to is None:
132                         limit_choices_to = {}
133                 self.limit_choices_to = limit_choices_to
134         
135         def contribute_to_class(self, cls, name):
136                 super(RelationshipField, self).contribute_to_class(cls, name)
137                 if self.key is None:
138                         self.key = name
139         
140         def formfield(self, form_class=forms.ModelChoiceField, **kwargs):
141                 defaults = {'required': False, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
142                 defaults.update(kwargs)
143                 return form_class(self.model._default_manager.complex_filter(self.limit_choices_to), **defaults)
144         
145         def value_from_object(self, obj):
146                 relobj = super(RelationshipField, self).value_from_object(obj)
147                 return getattr(relobj, 'pk', None)
148
149
150 class TemplateField(models.TextField):
151         def __init__(self, allow=None, disallow=None, secure=True, *args, **kwargs):
152                 super(TemplateField, self).__init__(*args, **kwargs)
153                 self.validators.append(TemplateValidator(allow, disallow, secure))
154
155
156 class JSONFormField(forms.Field):
157         def clean(self, value):
158                 try:
159                         return json.loads(value)
160                 except Exception, e:
161                         raise ValidationError(u'JSON decode error: %s' % e)
162
163
164 class JSONDescriptor(object):
165         def __init__(self, field):
166                 self.field = field
167         
168         def __get__(self, instance, owner):
169                 if instance is None:
170                         raise AttributeError # ?
171                 
172                 if self.field.name not in instance.__dict__:
173                         json_string = getattr(instance, self.field.attname)
174                         instance.__dict__[self.field.name] = json.loads(json_string)
175                 
176                 return instance.__dict__[self.field.name]
177         
178         def __set__(self, instance, value):
179                 instance.__dict__[self.field.name] = value
180                 setattr(instance, self.field.attname, json.dumps(value))
181         
182         def __delete__(self, instance):
183                 del(instance.__dict__[self.field.name])
184                 setattr(instance, self.field.attname, json.dumps(None))
185
186
187 class JSONField(models.TextField):
188         def __init__(self, *args, **kwargs):
189                 super(JSONField, self).__init__(*args, **kwargs)
190                 self.validators.append(json_validator)
191         
192         def get_attname(self):
193                 return "%s_json" % self.name
194         
195         def contribute_to_class(self, cls, name):
196                 super(JSONField, self).contribute_to_class(cls, name)
197                 setattr(cls, name, JSONDescriptor(self))
198         
199         def formfield(self, *args, **kwargs):
200                 kwargs["form_class"] = JSONFormField
201                 return super(JSONField, self).formfield(*args, **kwargs)
202
203
204 try:
205         from south.modelsinspector import add_introspection_rules
206 except ImportError:
207         pass
208 else:
209         add_introspection_rules([], ["^philo\.models\.fields\.TemplateField"])
210         add_introspection_rules([], ["^philo\.models\.fields\.JSONField"])