| 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
|
|---|