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 _
9 from philo.forms.fields import JSONFormField
10 from philo.utils.registry import RegistryIterator
11 from philo.validators import TemplateValidator, json_validator
12 from philo.forms.widgets import EmbedWidget
13 #from philo.models.fields.entities import *
16 class TemplateField(models.Field):
17 """A :class:`TextField` which is validated with a :class:`.TemplateValidator`. ``allow``, ``disallow``, and ``secure`` will be passed into the validator's construction."""
18 def __init__(self, allow=None, disallow=None, secure=True, *args, **kwargs):
19 super(TemplateField, self).__init__(*args, **kwargs)
20 self.validators.append(TemplateValidator(allow, disallow, secure))
22 def formfield(self, **kwargs):
23 defaults = {'widget': EmbedWidget}
24 defaults.update(kwargs)
25 return super(TemplateField, self).formfield(**defaults)
28 class JSONDescriptor(object):
29 def __init__(self, field):
32 def __get__(self, instance, owner):
34 raise AttributeError # ?
36 if self.field.name not in instance.__dict__:
37 json_string = getattr(instance, self.field.attname)
38 instance.__dict__[self.field.name] = json.loads(json_string)
40 return instance.__dict__[self.field.name]
42 def __set__(self, instance, value):
43 instance.__dict__[self.field.name] = value
44 setattr(instance, self.field.attname, json.dumps(value))
46 def __delete__(self, instance):
47 del(instance.__dict__[self.field.name])
48 setattr(instance, self.field.attname, json.dumps(None))
51 class JSONField(models.TextField):
52 """A :class:`TextField` which stores its value on the model instance as a python object and stores its value in the database as JSON. Validated with :func:`.json_validator`."""
53 default_validators = [json_validator]
55 def get_attname(self):
56 return "%s_json" % self.name
58 def contribute_to_class(self, cls, name):
59 super(JSONField, self).contribute_to_class(cls, name)
60 setattr(cls, name, JSONDescriptor(self))
61 models.signals.pre_init.connect(self.fix_init_kwarg, sender=cls)
63 def fix_init_kwarg(self, sender, args, kwargs, **signal_kwargs):
64 # Anything passed in as self.name is assumed to come from a serializer and
65 # will be treated as a json string.
66 if self.name in kwargs:
67 value = kwargs.pop(self.name)
69 # Hack to handle the xml serializer's handling of "null"
73 kwargs[self.attname] = value
75 def formfield(self, *args, **kwargs):
76 kwargs["form_class"] = JSONFormField
77 return super(JSONField, self).formfield(*args, **kwargs)
80 class SlugMultipleChoiceField(models.Field):
81 """Stores a selection of multiple items with unique slugs in the form of a comma-separated list. Also knows how to correctly handle :class:`RegistryIterator`\ s passed in as choices."""
82 __metaclass__ = models.SubfieldBase
83 description = _("Comma-separated slug field")
85 def get_internal_type(self):
88 def to_python(self, value):
92 if isinstance(value, list):
95 return value.split(',')
97 def get_prep_value(self, value):
98 return ','.join(value)
100 def formfield(self, **kwargs):
101 # This is necessary because django hard-codes TypedChoiceField for things with choices.
103 'widget': forms.CheckboxSelectMultiple,
104 'choices': self.get_choices(include_blank=False),
105 'label': capfirst(self.verbose_name),
106 'required': not self.blank,
107 'help_text': self.help_text
109 if self.has_default():
110 if callable(self.default):
111 defaults['initial'] = self.default
112 defaults['show_hidden_initial'] = True
114 defaults['initial'] = self.get_default()
116 for k in kwargs.keys():
117 if k not in ('coerce', 'empty_value', 'choices', 'required',
118 'widget', 'label', 'initial', 'help_text',
119 'error_messages', 'show_hidden_initial'):
122 defaults.update(kwargs)
123 form_class = forms.TypedMultipleChoiceField
124 return form_class(**defaults)
126 def validate(self, value, model_instance):
131 except ValidationError:
132 invalid_values.append(val)
135 # should really make a custom message.
136 raise ValidationError(self.error_messages['invalid_choice'] % invalid_values)
138 def _get_choices(self):
139 if isinstance(self._choices, RegistryIterator):
140 return self._choices.copy()
141 elif hasattr(self._choices, 'next'):
142 choices, self._choices = itertools.tee(self._choices)
146 choices = property(_get_choices)
150 from south.modelsinspector import add_introspection_rules
154 add_introspection_rules([], ["^philo\.models\.fields\.SlugMultipleChoiceField"])
155 add_introspection_rules([], ["^philo\.models\.fields\.TemplateField"])
156 add_introspection_rules([], ["^philo\.models\.fields\.JSONField"])