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