Merge branch 'julian'
[philo.git] / models / fields / __init__.py
1 from django import forms
2 from django.core.exceptions import ValidationError
3 from django.core.validators import validate_slug
4 from django.db import models
5 from django.utils import simplejson as json
6 from django.utils.text import capfirst
7 from django.utils.translation import ugettext_lazy as _
8 from philo.forms.fields import JSONFormField
9 from philo.validators import TemplateValidator, json_validator
10 #from philo.models.fields.entities import *
11
12
13 class TemplateField(models.TextField):
14         def __init__(self, allow=None, disallow=None, secure=True, *args, **kwargs):
15                 super(TemplateField, self).__init__(*args, **kwargs)
16                 self.validators.append(TemplateValidator(allow, disallow, secure))
17
18
19 class JSONDescriptor(object):
20         def __init__(self, field):
21                 self.field = field
22         
23         def __get__(self, instance, owner):
24                 if instance is None:
25                         raise AttributeError # ?
26                 
27                 if self.field.name not in instance.__dict__:
28                         json_string = getattr(instance, self.field.attname)
29                         instance.__dict__[self.field.name] = json.loads(json_string)
30                 
31                 return instance.__dict__[self.field.name]
32         
33         def __set__(self, instance, value):
34                 instance.__dict__[self.field.name] = value
35                 setattr(instance, self.field.attname, json.dumps(value))
36         
37         def __delete__(self, instance):
38                 del(instance.__dict__[self.field.name])
39                 setattr(instance, self.field.attname, json.dumps(None))
40
41
42 class JSONField(models.TextField):
43         default_validators = [json_validator]
44         
45         def get_attname(self):
46                 return "%s_json" % self.name
47         
48         def contribute_to_class(self, cls, name):
49                 super(JSONField, self).contribute_to_class(cls, name)
50                 setattr(cls, name, JSONDescriptor(self))
51                 models.signals.pre_init.connect(self.fix_init_kwarg, sender=cls)
52         
53         def fix_init_kwarg(self, sender, args, kwargs, **signal_kwargs):
54                 # Anything passed in as self.name is assumed to come from a serializer and
55                 # will be treated as a json string.
56                 if self.name in kwargs:
57                         value = kwargs.pop(self.name)
58                         
59                         # Hack to handle the xml serializer's handling of "null"
60                         if value is None:
61                                 value = 'null'
62                         
63                         kwargs[self.attname] = value
64         
65         def formfield(self, *args, **kwargs):
66                 kwargs["form_class"] = JSONFormField
67                 return super(JSONField, self).formfield(*args, **kwargs)
68
69
70 class SlugMultipleChoiceField(models.Field):
71         __metaclass__ = models.SubfieldBase
72         description = _("Comma-separated slug field")
73         
74         def get_internal_type(self):
75                 return "TextField"
76         
77         def to_python(self, value):
78                 if not value:
79                         return []
80                 
81                 if isinstance(value, list):
82                         return value
83                 
84                 return value.split(',')
85         
86         def get_prep_value(self, value):
87                 return ','.join(value)
88         
89         def formfield(self, **kwargs):
90                 # This is necessary because django hard-codes TypedChoiceField for things with choices.
91                 defaults = {
92                         'widget': forms.CheckboxSelectMultiple,
93                         'choices': self.get_choices(include_blank=False),
94                         'label': capfirst(self.verbose_name),
95                         'required': not self.blank,
96                         'help_text': self.help_text
97                 }
98                 if self.has_default():
99                         if callable(self.default):
100                                 defaults['initial'] = self.default
101                                 defaults['show_hidden_initial'] = True
102                         else:
103                                 defaults['initial'] = self.get_default()
104                 
105                 for k in kwargs.keys():
106                         if k not in ('coerce', 'empty_value', 'choices', 'required',
107                                                  'widget', 'label', 'initial', 'help_text',
108                                                  'error_messages', 'show_hidden_initial'):
109                                 del kwargs[k]
110                 
111                 defaults.update(kwargs)
112                 form_class = forms.TypedMultipleChoiceField
113                 return form_class(**defaults)
114         
115         def validate(self, value, model_instance):
116                 invalid_values = []
117                 for val in value:
118                         try:
119                                 validate_slug(val)
120                         except ValidationError:
121                                 invalid_values.append(val)
122                 
123                 if invalid_values:
124                         # should really make a custom message.
125                         raise ValidationError(self.error_messages['invalid_choice'] % invalid_values)
126
127
128 try:
129         from south.modelsinspector import add_introspection_rules
130 except ImportError:
131         pass
132 else:
133         add_introspection_rules([], ["^philo\.models\.fields\.SlugMultipleChoiceField"])
134         add_introspection_rules([], ["^philo\.models\.fields\.TemplateField"])
135         add_introspection_rules([], ["^philo\.models\.fields\.JSONField"])