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