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