1 from django.forms.models import ModelFormMetaclass, ModelForm, ModelFormOptions
2 from django.utils.datastructures import SortedDict
4 from philo.utils import fattr
7 __all__ = ('EntityForm',)
10 def proxy_fields_for_entity_model(entity_model, fields=None, exclude=None, widgets=None, formfield_callback=None):
13 opts = entity_model._entity_meta
14 for f in opts.proxy_fields:
17 if fields and not f.name in fields:
19 if exclude and f.name in exclude:
21 if widgets and f.name in widgets:
22 kwargs = {'widget': widgets[f.name]}
26 if formfield_callback is None:
27 formfield = f.formfield(**kwargs)
28 elif not callable(formfield_callback):
29 raise TypeError('formfield_callback must be a function or callable')
31 formfield = formfield_callback(f, **kwargs)
34 field_list.append((f.name, formfield))
36 ignored.append(f.name)
37 field_dict = SortedDict(field_list)
39 field_dict = SortedDict(
40 [(f, field_dict.get(f)) for f in fields
41 if ((not exclude) or (exclude and f not in exclude)) and (f not in ignored) and (f in field_dict)]
46 # HACK until http://code.djangoproject.com/ticket/14082 is resolved.
47 _old = ModelFormMetaclass.__new__
48 def _new(cls, name, bases, attrs):
49 if cls == ModelFormMetaclass:
50 m = attrs.get('__metaclass__', None)
52 parents = [b for b in bases if issubclass(b, ModelForm)]
54 if c.__metaclass__ != ModelFormMetaclass:
59 return m(name, bases, attrs)
61 return _old(cls, name, bases, attrs)
62 ModelFormMetaclass.__new__ = staticmethod(_new)
66 class EntityFormMetaclass(ModelFormMetaclass):
67 def __new__(cls, name, bases, attrs):
69 parents = [b for b in bases if issubclass(b, EntityForm)]
71 # We are defining EntityForm itself
73 sup = super(EntityFormMetaclass, cls)
76 # Then there's no business trying to use proxy fields.
77 return sup.__new__(cls, name, bases, attrs)
79 # Fake a declaration of all proxy fields so they'll be handled correctly.
80 opts = ModelFormOptions(attrs.get('Meta', None))
83 formfield_callback = attrs.get('formfield_callback', None)
84 proxy_fields = proxy_fields_for_entity_model(opts.model, opts.fields, opts.exclude, opts.widgets, formfield_callback)
88 new_attrs = proxy_fields.copy()
89 new_attrs.update(attrs)
91 new_class = sup.__new__(cls, name, bases, new_attrs)
92 new_class.proxy_fields = proxy_fields
96 class EntityForm(ModelForm):
98 :class:`EntityForm` knows how to handle :class:`.Entity` instances - specifically, how to set initial values for :class:`.AttributeProxyField`\ s and save cleaned values to an instance on save.
101 __metaclass__ = EntityFormMetaclass
103 def __init__(self, *args, **kwargs):
104 initial = kwargs.pop('initial', None)
105 instance = kwargs.get('instance', None)
106 if instance is not None:
108 for f in instance._entity_meta.proxy_fields:
109 if self._meta.fields and not f.name in self._meta.fields:
111 if self._meta.exclude and f.name in self._meta.exclude:
113 new_initial[f.name] = f.value_from_object(instance)
116 if initial is not None:
117 new_initial.update(initial)
118 kwargs['initial'] = new_initial
119 super(EntityForm, self).__init__(*args, **kwargs)
121 @fattr(alters_data=True)
122 def save(self, commit=True):
123 cleaned_data = self.cleaned_data
124 instance = super(EntityForm, self).save(commit=False)
126 for f in instance._entity_meta.proxy_fields:
127 if not f.editable or not f.name in cleaned_data:
129 if self._meta.fields and f.name not in self._meta.fields:
131 if self._meta.exclude and f.name in self._meta.exclude:
133 setattr(instance, f.attname, f.get_storage_value(cleaned_data[f.name]))