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