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