1 | from django import forms
|
---|
2 | from django.db.models.fields import BLANK_CHOICE_DASH
|
---|
3 | from django.forms import models
|
---|
4 | from django.forms.formsets import BaseFormSet
|
---|
5 | from django.utils.datastructures import SortedDict, MultiValueDict
|
---|
6 | from django.utils.encoding import StrAndUnicode
|
---|
7 | from django.utils.safestring import mark_safe
|
---|
8 |
|
---|
9 |
|
---|
10 | class 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 |
|
---|
34 | class 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 |
|
---|
132 | class UserFormsContainer(FormContainer):
|
---|
133 | user = UserDetailForm
|
---|
134 | addresses = AddressesFormSet
|
---|