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
10 __all__ = ('JSONAttribute', 'ForeignKeyAttribute', 'ManyToManyAttribute')
13 class EntityProxyField(object):
14 descriptor_class = None
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)
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))
26 def contribute_to_class(self, cls, name):
27 from philo.models.base import Entity
28 if issubclass(cls, Entity):
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)
35 raise FieldError('%s instances can only be declared on Entity subclasses.' % self.__class__.__name__)
37 def formfield(self, *args, **kwargs):
38 raise NotImplementedError('EntityProxyField subclasses must implement a formfield method.')
40 def value_from_object(self, obj):
41 return getattr(obj, self.attname)
44 class AttributeFieldDescriptor(object):
45 def __init__(self, field):
48 def __get__(self, instance, owner):
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:
55 return instance.attributes[self.field.key]
59 raise AttributeError('The \'%s\' attribute can only be accessed from %s instances.' % (self.field.name, owner.__name__))
61 def __set__(self, instance, value):
62 raise NotImplementedError('AttributeFieldDescriptor subclasses must implement a __set__ method.')
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)
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
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
84 raise AttributeError('The \'%s\' attribute can only be set using existing Model objects.' % self.field.name)
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
94 raise AttributeError('The \'%s\' attribute can only be set to a QuerySet.' % self.field.name)
97 class AttributeField(EntityProxyField):
98 def contribute_to_class(self, cls, name):
99 super(AttributeField, self).contribute_to_class(cls, name)
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()
109 new_value = attribute.value
110 new_value.value = value
112 attribute.value = new_value
115 class JSONAttribute(AttributeField):
116 descriptor_class = JSONAttributeDescriptor
118 def __init__(self, field_template=None, key=None, **kwargs):
119 super(AttributeField, self).__init__(**kwargs)
121 if field_template is None:
122 field_template = models.CharField(max_length=255)
123 self.field_template = field_template
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)
130 def value_from_object(self, obj):
132 return getattr(obj, self.attname).value
133 except AttributeError:
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)
143 class ForeignKeyAttribute(AttributeField):
144 descriptor_class = ForeignKeyAttributeDescriptor
146 def __init__(self, model, limit_choices_to=None, key=None, **kwargs):
147 super(ForeignKeyAttribute, self).__init__(**kwargs)
150 if limit_choices_to is None:
151 limit_choices_to = {}
152 self.limit_choices_to = limit_choices_to
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)
159 def value_from_object(self, obj):
161 relobj = super(ForeignKeyAttribute, self).value_from_object(obj).value
162 except AttributeError:
164 return getattr(relobj, 'pk', None)
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)
173 class ManyToManyAttribute(ForeignKeyAttribute):
174 descriptor_class = ManyToManyAttributeDescriptor
176 def formfield(self, form_class=forms.ModelMultipleChoiceField, **kwargs):
177 return super(ManyToManyAttribute, self).formfield(form_class, **kwargs)
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)
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))
192 class JSONFormField(forms.Field):
193 def clean(self, value):
195 return json.loads(value)
197 raise ValidationError(u'JSON decode error: %s' % e)
200 class JSONDescriptor(object):
201 def __init__(self, field):
204 def __get__(self, instance, owner):
206 raise AttributeError # ?
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)
212 return instance.__dict__[self.field.name]
214 def __set__(self, instance, value):
215 instance.__dict__[self.field.name] = value
216 setattr(instance, self.field.attname, json.dumps(value))
218 def __delete__(self, instance):
219 del(instance.__dict__[self.field.name])
220 setattr(instance, self.field.attname, json.dumps(None))
223 class JSONField(models.TextField):
224 def __init__(self, *args, **kwargs):
225 super(JSONField, self).__init__(*args, **kwargs)
226 self.validators.append(json_validator)
228 def get_attname(self):
229 return "%s_json" % self.name
231 def contribute_to_class(self, cls, name):
232 super(JSONField, self).contribute_to_class(cls, name)
233 setattr(cls, name, JSONDescriptor(self))
235 def formfield(self, *args, **kwargs):
236 kwargs["form_class"] = JSONFormField
237 return super(JSONField, self).formfield(*args, **kwargs)
241 from south.modelsinspector import add_introspection_rules
245 add_introspection_rules([], ["^philo\.models\.fields\.TemplateField"])
246 add_introspection_rules([], ["^philo\.models\.fields\.JSONField"])