1 from django.forms.models import ModelFormMetaclass, ModelForm, ModelFormOptions
2 from django.utils.datastructures import SortedDict
3 from philo.utils import fattr
6 __all__ = ('EntityForm',)
9 def proxy_fields_for_entity_model(entity_model, fields=None, exclude=None, widgets=None, formfield_callback=None):
12 opts = entity_model._entity_meta
13 for f in opts.proxy_fields:
16 if fields and not f.name in fields:
18 if exclude and f.name in exclude:
20 if widgets and f.name in widgets:
21 kwargs = {'widget': widgets[f.name]}
25 if formfield_callback is None:
26 formfield = f.formfield(**kwargs)
27 elif not callable(formfield_callback):
28 raise TypeError('formfield_callback must be a function or callable')
30 formfield = formfield_callback(f, **kwargs)
33 field_list.append((f.name, formfield))
35 ignored.append(f.name)
36 field_dict = SortedDict(field_list)
38 field_dict = SortedDict(
39 [(f, field_dict.get(f)) for f in fields
40 if ((not exclude) or (exclude and f not in exclude)) and (f not in ignored) and (f in field_dict)]
45 class EntityFormMetaclass(ModelFormMetaclass):
46 def __new__(cls, name, bases, attrs):
48 parents = [b for b in bases if issubclass(b, EntityForm)]
50 # We are defining EntityForm itself
52 sup = super(EntityFormMetaclass, cls)
55 # Then there's no business trying to use proxy fields.
56 return sup.__new__(cls, name, bases, attrs)
58 # Preemptively make a meta so we can screen out any proxy fields.
59 # After http://code.djangoproject.com/ticket/14082 has been resolved,
60 # perhaps switch to setting proxy fields as "declared"?
61 _opts = ModelFormOptions(attrs.get('Meta', None))
63 # Make another copy of opts to spoof the proxy fields not being there.
64 opts = ModelFormOptions(attrs.get('Meta', None))
66 formfield_callback = attrs.get('formfield_callback', None)
67 proxy_fields = proxy_fields_for_entity_model(opts.model, opts.fields, opts.exclude, opts.widgets, formfield_callback)
71 # Screen out all proxy fields from the meta
72 opts.fields = list(opts.fields or [])
73 opts.exclude = list(opts.exclude or [])
75 for field in proxy_fields.keys():
77 opts.fields.remove(field)
81 opts.exclude.remove(field)
84 opts.fields = opts.fields or None
85 opts.exclude = opts.exclude or None
87 # Put in the new Meta.
90 new_class = sup.__new__(cls, name, bases, attrs)
92 intersections = set(new_class.declared_fields.keys()) & set(proxy_fields.keys())
93 for key in intersections:
96 new_class.proxy_fields = proxy_fields
97 new_class._meta = _opts
98 new_class.base_fields.update(proxy_fields)
102 class EntityForm(ModelForm):
103 __metaclass__ = EntityFormMetaclass
105 def __init__(self, *args, **kwargs):
106 initial = kwargs.pop('initial', None)
107 instance = kwargs.get('instance', None)
108 if instance is not None:
110 for f in instance._entity_meta.proxy_fields:
111 if self._meta.fields and not f.name in self._meta.fields:
113 if self._meta.exclude and f.name in self._meta.exclude:
115 new_initial[f.name] = f.value_from_object(instance)
118 if initial is not None:
119 new_initial.update(initial)
120 kwargs['initial'] = new_initial
121 super(EntityForm, self).__init__(*args, **kwargs)
123 @fattr(alters_data=True)
124 def save(self, commit=True):
125 cleaned_data = self.cleaned_data
126 instance = super(EntityForm, self).save(commit=False)
128 for f in instance._entity_meta.proxy_fields:
129 if not f.editable or not f.name in cleaned_data:
131 if self._meta.fields and f.name not in self._meta.fields:
133 if self._meta.exclude and f.name in self._meta.exclude:
135 setattr(instance, f.attname, f.get_storage_value(cleaned_data[f.name]))