Ticket #18830: form_container.py

File form_container.py, 4.8 KB (added by Russell Keith-Magee, 11 years ago)

Example of a FormContainer that works with FormWizard

Line 
1from django import forms
2from django.db.models.fields import BLANK_CHOICE_DASH
3from django.forms import models
4from django.forms.formsets import BaseFormSet
5from django.utils.datastructures import SortedDict, MultiValueDict
6from django.utils.encoding import StrAndUnicode
7from django.utils.safestring import mark_safe
8
9
10class FormContainerMetaclass(type):
11 def __new__(cls, name, bases, attrs):
12 form_classes = SortedDict(
13 (prefix, attrs.pop(prefix))
14 for prefix, form_class in attrs.items()
15 if isinstance(form_class, type) and issubclass(form_class, (forms.BaseForm, BaseFormSet))
16 )
17
18 new_class = super(FormContainerMetaclass, cls).__new__(cls, name, bases, attrs)
19
20 new_class.form_classes = form_classes
21
22 # Making the form container look like a form, for the
23 # sake of the FormWizard.
24 new_class.base_fields = {}
25 for prefix, form_class in new_class.form_classes.items():
26 if issubclass(form_class, BaseFormSet):
27 new_class.base_fields.update(form_class.form.base_fields)
28 else:
29 new_class.base_fields.update(form_class.base_fields)
30
31 return new_class
32
33
34class FormContainer(StrAndUnicode):
35 __metaclass__ = FormContainerMetaclass
36
37 def __init__(self, **kwargs):
38 self._errors = {}
39 self.forms = SortedDict()
40 container_prefix = kwargs.pop('prefix', '')
41
42 # Instantiate all the forms in the container
43 for form_prefix, form_class in self.form_classes.items():
44 self.forms[form_prefix] = form_class(
45 prefix='-'.join(p for p in [container_prefix, form_prefix] if p),
46 **self.get_form_kwargs(form_prefix, **kwargs)
47 )
48
49 def get_form_kwargs(self, prefix, **kwargs):
50 """Return per-form kwargs for instantiating a specific form
51
52 By default, just returns the kwargs provided. prefix is the
53 label for the form in the container, allowing you to specify
54 extra (or less) kwargs for each form in the container.
55 """
56 return kwargs
57
58 def __unicode__(self):
59 "Render all the forms in the container"
60 return mark_safe(u''.join([f.as_table() for f in self.forms.values()]))
61
62 def __iter__(self):
63 "Return each of the forms in the container"
64 for prefix in self.forms:
65 yield self[prefix]
66
67 def __getitem__(self, prefix):
68 "Return a specific form in the container"
69 try:
70 form = self.forms[prefix]
71 except KeyError:
72 raise KeyError('Prefix %r not found in Form container' % prefix)
73 return form
74
75 def is_valid(self):
76 return all(f.is_valid() for f in self.forms.values())
77
78 @property
79 def data(self):
80 "Return a compressed dictionary of all data from all subforms"
81 all_data = MultiValueDict()
82 for prefix, form in self.forms.items():
83 for key in form.data:
84 all_data.setlist(key, form.data.getlist(key))
85 return all_data
86
87 @property
88 def files(self):
89 "Return a compressed dictionary of all files from all subforms"
90 all_files = MultiValueDict()
91 for prefix, form in self.forms.items():
92 for key in form.files:
93 all_files.setlist(key, form.files.getlist(key))
94 return all_files
95
96 @property
97 def errors(self):
98 "Return a compressed dictionary of all errors form all subforms"
99 return dict((prefix, form.errors) for prefix, form in self.forms.items())
100
101 def save(self, *args, **kwargs):
102 "Save each of the subforms"
103 return [f.save(*args, **kwargs) for f in self.forms.values()]
104
105 def save_m2m(self):
106 """Save any related objects -- e.g., m2m entries or inline formsets
107
108 This is needed if the original form collection was saved with commit=False
109 """
110 for prefix, form in self.forms.items():
111 try:
112 for subform in form.saved_forms:
113 # Because the related instance wasn't saved at the time the
114 # form was created, the new PK value hasn't propegated to
115 # the inline object on the formset. We need to re-set the
116 # instance to update the _id attribute, which will allow the
117 # inline form instance to save.
118 setattr(subform.instance, form.fk.name, form.instance)
119 subform.instance.save()
120 except AttributeError:
121 pass
122
123 try:
124 form.save_m2m()
125 except AttributeError:
126 pass
127
128
129# And now, an example of usage:
130# Assuming you have a UserDetailsForm, and an AddressesFormSet...
131
132class UserFormsContainer(FormContainer):
133 user = UserDetailForm
134 addresses = AddressesFormSet
Back to Top