Merge branch 'julian'
[philo.git] / forms / entities.py
1 from django.forms.models import ModelFormMetaclass, ModelForm, ModelFormOptions
2 from django.utils.datastructures import SortedDict
3 from philo.utils import fattr
4
5
6 __all__ = ('EntityForm',)
7
8
9 def proxy_fields_for_entity_model(entity_model, fields=None, exclude=None, widgets=None, formfield_callback=None):
10         field_list = []
11         ignored = []
12         opts = entity_model._entity_meta
13         for f in opts.proxy_fields:
14                 if not f.editable:
15                         continue
16                 if fields and not f.name in fields:
17                         continue
18                 if exclude and f.name in exclude:
19                         continue
20                 if widgets and f.name in widgets:
21                         kwargs = {'widget': widgets[f.name]}
22                 else:
23                         kwargs = {}
24                 
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')
29                 else:
30                         formfield = formfield_callback(f, **kwargs)
31                 
32                 if formfield:
33                         field_list.append((f.name, formfield))
34                 else:
35                         ignored.append(f.name)
36         field_dict = SortedDict(field_list)
37         if fields:
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)]
41                 )
42         return field_dict
43
44
45 # HACK until http://code.djangoproject.com/ticket/14082 is resolved.
46 _old = ModelFormMetaclass.__new__
47 def _new(cls, name, bases, attrs):
48         if cls == ModelFormMetaclass:
49                 m = attrs.get('__metaclass__', None)
50                 if m is None:
51                         parents = [b for b in bases if issubclass(b, ModelForm)]
52                         for c in parents:
53                                 if c.__metaclass__ != ModelFormMetaclass:
54                                         m = c.__metaclass__
55                                         break
56         
57                 if m is not None:
58                         return m(name, bases, attrs)
59         
60         return _old(cls, name, bases, attrs)
61 ModelFormMetaclass.__new__ = staticmethod(_new)
62 # END HACK
63
64
65 class EntityFormMetaclass(ModelFormMetaclass):
66         def __new__(cls, name, bases, attrs):
67                 try:
68                         parents = [b for b in bases if issubclass(b, EntityForm)]
69                 except NameError:
70                         # We are defining EntityForm itself
71                         parents = None
72                 sup = super(EntityFormMetaclass, cls)
73                 
74                 if not parents:
75                         # Then there's no business trying to use proxy fields.
76                         return sup.__new__(cls, name, bases, attrs)
77                 
78                 # Fake a declaration of all proxy fields so they'll be handled correctly.
79                 opts = ModelFormOptions(attrs.get('Meta', None))
80                 
81                 if opts.model:
82                         formfield_callback = attrs.get('formfield_callback', None)
83                         proxy_fields = proxy_fields_for_entity_model(opts.model, opts.fields, opts.exclude, opts.widgets, formfield_callback)
84                 else:
85                         proxy_fields = {}
86                 
87                 new_attrs = proxy_fields.copy()
88                 new_attrs.update(attrs)
89                 
90                 new_class = sup.__new__(cls, name, bases, new_attrs)
91                 new_class.proxy_fields = proxy_fields
92                 return new_class
93
94
95 class EntityForm(ModelForm):
96         __metaclass__ = EntityFormMetaclass
97         
98         def __init__(self, *args, **kwargs):
99                 initial = kwargs.pop('initial', None)
100                 instance = kwargs.get('instance', None)
101                 if instance is not None:
102                         new_initial = {}
103                         for f in instance._entity_meta.proxy_fields:
104                                 if self._meta.fields and not f.name in self._meta.fields:
105                                         continue
106                                 if self._meta.exclude and f.name in self._meta.exclude:
107                                         continue
108                                 new_initial[f.name] = f.value_from_object(instance)
109                 else:
110                         new_initial = {}
111                 if initial is not None:
112                         new_initial.update(initial)
113                 kwargs['initial'] = new_initial
114                 super(EntityForm, self).__init__(*args, **kwargs)
115         
116         @fattr(alters_data=True)
117         def save(self, commit=True):
118                 cleaned_data = self.cleaned_data
119                 instance = super(EntityForm, self).save(commit=False)
120                 
121                 for f in instance._entity_meta.proxy_fields:
122                         if not f.editable or not f.name in cleaned_data:
123                                 continue
124                         if self._meta.fields and f.name not in self._meta.fields:
125                                 continue
126                         if self._meta.exclude and f.name in self._meta.exclude:
127                                 continue
128                         setattr(instance, f.attname, f.get_storage_value(cleaned_data[f.name]))
129                 
130                 if commit:
131                         instance.save()
132                         self.save_m2m()
133                 
134                 return instance