=== modified file 'django/forms/forms.py'
|
|
|
|
| 28 | 28 | self.fieldsets = getattr(options, 'fieldsets', None) |
| 29 | 29 | self.fields = getattr(options, 'fields', None) |
| 30 | 30 | self.exclude = getattr(options, 'exclude', None) |
| | 31 | self.inlines = getattr(options, 'inlines', None) |
| 31 | 32 | |
| 32 | 33 | def create_declared_fields(cls, attrs): |
| 33 | 34 | """ |
| … |
… |
|
| 72 | 73 | if cls._meta.fieldsets: |
| 73 | 74 | names = [] |
| 74 | 75 | for fieldset in cls._meta.fieldsets: |
| 75 | | names.extend(fieldset['fields']) |
| | 76 | if isinstance(fieldset, dict): |
| | 77 | names.extend(fieldset['fields']) |
| 76 | 78 | elif cls._meta.fields: |
| 77 | 79 | names = cls._meta.fields |
| 78 | 80 | elif cls._meta.exclude: |
| … |
… |
|
| 81 | 83 | names = cls.base_fields_pool.keys() |
| 82 | 84 | cls.base_fields = SortedDict([(name, cls.base_fields_pool[name]) for name in names]) |
| 83 | 85 | |
| | 86 | def create_fieldsets_if_inlines_exist(cls, attrs): |
| | 87 | if cls._meta.inlines is not None: |
| | 88 | if cls._meta.fieldsets is not None: |
| | 89 | raise ImproperlyConfigured("%s cannot have more than one option from fieldsets and inlines." % cls.__name__) |
| | 90 | cls._meta.fieldsets = [{'fields': cls.base_fields.keys()}] + list(cls._meta.inlines) |
| | 91 | |
| 84 | 92 | class FormMetaclass(type): |
| 85 | 93 | """ |
| 86 | 94 | Metaclass that converts Field attributes to a dictionary called |
| … |
… |
|
| 94 | 102 | create_declared_fields(new_class, attrs) |
| 95 | 103 | create_base_fields_pool_from_declared_fields(new_class, attrs) |
| 96 | 104 | create_base_fields_from_base_fields_pool(new_class, attrs) |
| | 105 | create_fieldsets_if_inlines_exist(new_class, attrs) |
| 97 | 106 | if 'media' not in attrs: |
| 98 | 107 | new_class.media = media_property(new_class) |
| 99 | 108 | return new_class |
| … |
… |
|
| 115 | 124 | self.error_class = error_class |
| 116 | 125 | self.label_suffix = label_suffix |
| 117 | 126 | self.empty_permitted = empty_permitted |
| 118 | | self._errors = None # Stores the errors after clean() has been called. |
| | 127 | self._is_valid = None # Stores validation state after full_clean() has been called. |
| 119 | 128 | self._changed_data = None |
| 120 | 129 | |
| 121 | 130 | # The base_fields class attribute is the *class-wide* definition of |
| … |
… |
|
| 124 | 133 | # Instances should always modify self.fields; they should not modify |
| 125 | 134 | # self.base_fields. |
| 126 | 135 | self.fields = deepcopy(self.base_fields) |
| | 136 | self._construct_inlines() |
| | 137 | |
| | 138 | def _construct_inlines(self): |
| | 139 | # This class cannot create any inlines. |
| | 140 | self.inlines = [] |
| | 141 | if self.has_fieldsets(): |
| | 142 | for fieldset in self._meta.fieldsets: |
| | 143 | if not isinstance(fieldset, dict): |
| | 144 | raise ValueError('%s cannot create instance of %s.' % (self.__class__.__name__, fieldset.__name__)) |
| 127 | 145 | |
| 128 | 146 | def __unicode__(self): |
| 129 | 147 | return self.as_table() |
| … |
… |
|
| 145 | 163 | |
| 146 | 164 | def fieldsets(self): |
| 147 | 165 | if self.has_fieldsets(): |
| | 166 | fieldset_counter, formset_counter = 0, 0 |
| 148 | 167 | for fieldset in self._meta.fieldsets: |
| 149 | | yield { |
| 150 | | 'attrs': fieldset.get('attrs', {}), |
| 151 | | 'legend': fieldset.get('legend', u''), |
| 152 | | 'fields': [self[name] for name in fieldset['fields']], |
| 153 | | } |
| 154 | | |
| | 168 | if isinstance(fieldset, dict): |
| | 169 | yield { |
| | 170 | 'order': fieldset_counter, |
| | 171 | 'attrs': fieldset.get('attrs', {}), |
| | 172 | 'legend': fieldset.get('legend', u''), |
| | 173 | 'fields': [self[name] for name in fieldset['fields']], |
| | 174 | } |
| | 175 | fieldset_counter += 1 |
| | 176 | else: |
| | 177 | yield { |
| | 178 | 'order': formset_counter, |
| | 179 | 'formset': self.inlines[formset_counter] |
| | 180 | } |
| | 181 | formset_counter += 1 |
| | 182 | |
| 155 | 183 | def _get_errors(self): |
| 156 | 184 | "Returns an ErrorDict for the data provided for the form" |
| 157 | | if self._errors is None: |
| | 185 | if self._is_valid is None: |
| 158 | 186 | self.full_clean() |
| 159 | 187 | return self._errors |
| 160 | 188 | errors = property(_get_errors) |
| … |
… |
|
| 164 | 192 | Returns True if the form has no errors. Otherwise, False. If errors are |
| 165 | 193 | being ignored, returns False. |
| 166 | 194 | """ |
| 167 | | return self.is_bound and not bool(self.errors) |
| | 195 | if self._is_valid is None: |
| | 196 | self.full_clean() |
| | 197 | return self._is_valid |
| 168 | 198 | |
| 169 | 199 | def add_prefix(self, field_name): |
| 170 | 200 | """ |
| … |
… |
|
| 254 | 284 | |
| 255 | 285 | def full_clean(self): |
| 256 | 286 | """ |
| 257 | | Cleans all of self.data and populates self._errors and |
| | 287 | Cleans all of self.data and populates self._is_valid, self._errors and |
| 258 | 288 | self.cleaned_data. |
| 259 | 289 | """ |
| | 290 | self._is_valid = True # Assume the form is valid until proven otherwise. |
| 260 | 291 | self._errors = ErrorDict() |
| 261 | 292 | if not self.is_bound: # Stop further processing. |
| | 293 | self._is_valid = False |
| 262 | 294 | return |
| 263 | 295 | self.cleaned_data = {} |
| 264 | 296 | # If the form is permitted to be empty, and none of the form data has |
| … |
… |
|
| 284 | 316 | self._errors[name] = e.messages |
| 285 | 317 | if name in self.cleaned_data: |
| 286 | 318 | del self.cleaned_data[name] |
| | 319 | self._is_valid = False |
| 287 | 320 | try: |
| 288 | 321 | self.cleaned_data = self.clean() |
| 289 | 322 | except ValidationError, e: |
| 290 | 323 | self._errors[NON_FIELD_ERRORS] = e.messages |
| 291 | | if self._errors: |
| | 324 | self._is_valid = False |
| | 325 | for inline in self.inlines: |
| | 326 | inline.full_clean() |
| | 327 | if not inline.is_valid(): |
| | 328 | self._is_valid = False |
| | 329 | if not self._is_valid: |
| 292 | 330 | delattr(self, 'cleaned_data') |
| | 331 | for inline in self.inlines: |
| | 332 | inline._is_valid = False |
| 293 | 333 | |
| 294 | 334 | def clean(self): |
| 295 | 335 | """ |
| … |
… |
|
| 337 | 377 | media = Media() |
| 338 | 378 | for field in self.fields.values(): |
| 339 | 379 | media = media + field.widget.media |
| | 380 | for inline in self.inlines: |
| | 381 | media = media + inline.media |
| 340 | 382 | return media |
| 341 | 383 | media = property(_get_media) |
| 342 | 384 | |
| … |
… |
|
| 348 | 390 | for field in self.fields.values(): |
| 349 | 391 | if field.widget.needs_multipart_form: |
| 350 | 392 | return True |
| | 393 | for inline in self.inlines: |
| | 394 | if inline.is_multipart(): |
| | 395 | return True |
| 351 | 396 | return False |
| 352 | 397 | |
| 353 | 398 | class Form(BaseForm): |
=== modified file 'django/forms/formsets.py'
|
|
|
|
| 38 | 38 | self.files = files |
| 39 | 39 | self.initial = initial |
| 40 | 40 | self.error_class = error_class |
| 41 | | self._errors = None |
| 42 | | self._non_form_errors = None |
| | 41 | self._is_valid = None # Stores validation state after full_clean() has been called. |
| 43 | 42 | # initialization is different depending on whether we recieved data, initial, or nothing |
| 44 | 43 | if data or files: |
| 45 | 44 | self.management_form = ManagementForm(data, auto_id=self.auto_id, prefix=self.prefix) |
| … |
… |
|
| 182 | 181 | form -- i.e., from formset.clean(). Returns an empty ErrorList if there |
| 183 | 182 | are none. |
| 184 | 183 | """ |
| 185 | | if self._non_form_errors is not None: |
| 186 | | return self._non_form_errors |
| 187 | | return self.error_class() |
| | 184 | if self._is_valid is None: |
| | 185 | self.full_clean() |
| | 186 | return self._non_form_errors |
| 188 | 187 | |
| 189 | 188 | def _get_errors(self): |
| 190 | 189 | """ |
| 191 | 190 | Returns a list of form.errors for every form in self.forms. |
| 192 | 191 | """ |
| 193 | | if self._errors is None: |
| | 192 | if self._is_valid is None: |
| 194 | 193 | self.full_clean() |
| 195 | 194 | return self._errors |
| 196 | 195 | errors = property(_get_errors) |
| … |
… |
|
| 199 | 198 | """ |
| 200 | 199 | Returns True if form.errors is empty for every form in self.forms. |
| 201 | 200 | """ |
| 202 | | if not self.is_bound: |
| 203 | | return False |
| 204 | | # We loop over every form.errors here rather than short circuiting on the |
| 205 | | # first failure to make sure validation gets triggered for every form. |
| 206 | | forms_valid = True |
| 207 | | for errors in self.errors: |
| 208 | | if bool(errors): |
| 209 | | forms_valid = False |
| 210 | | return forms_valid and not bool(self.non_form_errors()) |
| | 201 | if self._is_valid is None: |
| | 202 | self.full_clean() |
| | 203 | return self._is_valid |
| 211 | 204 | |
| 212 | 205 | def full_clean(self): |
| 213 | 206 | """ |
| 214 | 207 | Cleans all of self.data and populates self._errors. |
| 215 | 208 | """ |
| | 209 | self._is_valid = True # Assume the form is valid until proven otherwise. |
| 216 | 210 | self._errors = [] |
| | 211 | self._non_form_errors = self.error_class() |
| 217 | 212 | if not self.is_bound: # Stop further processing. |
| | 213 | self._is_valid = False |
| 218 | 214 | return |
| 219 | 215 | for i in range(0, self._total_form_count): |
| 220 | 216 | form = self.forms[i] |
| 221 | 217 | self._errors.append(form.errors) |
| | 218 | if form.errors: |
| | 219 | self._is_valid = False |
| 222 | 220 | # Give self.clean() a chance to do cross-form validation. |
| 223 | 221 | try: |
| 224 | 222 | self.clean() |
| 225 | 223 | except ValidationError, e: |
| 226 | | self._non_form_errors = e.messages |
| | 224 | self._non_form_errors = self.error_class(e.messages) |
| | 225 | self._is_valid = False |
| 227 | 226 | |
| 228 | 227 | def clean(self): |
| 229 | 228 | """ |
=== modified file 'django/forms/models.py'
|
|
|
|
| 11 | 11 | from util import ValidationError, ErrorList |
| 12 | 12 | from forms import FormOptions, BaseForm, create_declared_fields |
| 13 | 13 | from forms import create_base_fields_from_base_fields_pool |
| | 14 | from forms import create_fieldsets_if_inlines_exist |
| 14 | 15 | from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES |
| 15 | 16 | from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput |
| 16 | 17 | from widgets import media_property |
| … |
… |
|
| 60 | 61 | continue |
| 61 | 62 | if f.name in cleaned_data: |
| 62 | 63 | f.save_form_data(instance, cleaned_data[f.name]) |
| | 64 | if not commit: |
| | 65 | for inline in form.inlines: |
| | 66 | inline.save_m2m() |
| 63 | 67 | if commit: |
| 64 | 68 | # If we are committing, save the instance and the m2m data immediately. |
| 65 | 69 | instance.save() |
| … |
… |
|
| 209 | 213 | create_declared_fields(new_class, attrs) |
| 210 | 214 | create_base_fields_pool_from_model_fields_and_declared_fields(new_class, attrs) |
| 211 | 215 | create_base_fields_from_base_fields_pool(new_class, attrs) |
| | 216 | create_fieldsets_if_inlines_exist(new_class, attrs) |
| 212 | 217 | if 'media' not in attrs: |
| 213 | 218 | new_class.media = media_property(new_class) |
| 214 | 219 | return new_class |
| … |
… |
|
| 233 | 238 | self.validate_unique() |
| 234 | 239 | return self.cleaned_data |
| 235 | 240 | |
| | 241 | def _construct_inlines(self): |
| | 242 | # This class can create inlines which are subclass of BaseInlineFormSet. |
| | 243 | self.inlines = [] |
| | 244 | if self.has_fieldsets(): |
| | 245 | for fieldset in self._meta.fieldsets: |
| | 246 | if not isinstance(fieldset, dict): |
| | 247 | if not issubclass(fieldset, BaseInlineFormSet): |
| | 248 | raise ValueError('%s cannot create instance of %s.' % (self.__class__.__name__, fieldset.__name__)) |
| | 249 | self.inlines.append(fieldset(self.data, self.files, self.instance)) |
| | 250 | |
| 236 | 251 | def validate_unique(self): |
| 237 | 252 | from django.db.models.fields import FieldDoesNotExist |
| 238 | 253 | |
| … |
… |
|
| 295 | 310 | {'model_name': unicode(model_name), |
| 296 | 311 | 'field_label': unicode(field_label)} |
| 297 | 312 | ]) |
| | 313 | self._is_valid = False |
| 298 | 314 | # unique_together |
| 299 | 315 | else: |
| 300 | 316 | field_labels = [self.fields[field_name].label for field_name in unique_check] |
| … |
… |
|
| 304 | 320 | {'model_name': unicode(model_name), |
| 305 | 321 | 'field_label': unicode(field_labels)} |
| 306 | 322 | ) |
| 307 | | |
| | 323 | self._is_valid = False |
| | 324 | |
| 308 | 325 | # Mark these fields as needing to be removed from cleaned data |
| 309 | 326 | # later. |
| 310 | 327 | for field_name in unique_check: |
| … |
… |
|
| 329 | 346 | fail_message = 'created' |
| 330 | 347 | else: |
| 331 | 348 | fail_message = 'changed' |
| 332 | | return save_instance(self, self.instance, self.fields.keys(), fail_message, commit) |
| | 349 | self.saved_instance = save_instance(self, self.instance, self.fields.keys(), fail_message, commit) |
| | 350 | self.saved_inline_instances = [inline.save(commit) for inline in self.inlines] |
| | 351 | return self.saved_instance |
| 333 | 352 | |
| 334 | 353 | class ModelForm(BaseModelForm): |
| 335 | 354 | __metaclass__ = ModelFormMetaclass |
| 336 | 355 | |
| 337 | 356 | def modelform_factory(model, form=ModelForm, fields=None, exclude=None, fieldsets=None, |
| 338 | | formfield_callback=lambda f: f.formfield()): |
| | 357 | inlines=None, formfield_callback=lambda f: f.formfield()): |
| 339 | 358 | # HACK: we should be able to construct a ModelForm without creating |
| 340 | 359 | # and passing in a temporary inner class |
| 341 | 360 | class Meta: |
| … |
… |
|
| 344 | 363 | setattr(Meta, 'fieldsets', fieldsets) |
| 345 | 364 | setattr(Meta, 'fields', fields) |
| 346 | 365 | setattr(Meta, 'exclude', exclude) |
| | 366 | setattr(Meta, 'inlines', inlines) |
| 347 | 367 | class_name = model.__name__ + 'Form' |
| 348 | 368 | return ModelFormMetaclass(class_name, (form,), {'Meta': Meta, |
| 349 | 369 | 'formfield_callback': formfield_callback}) |
| … |
… |
|
| 451 | 471 | def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(), |
| 452 | 472 | formset=BaseModelFormSet, |
| 453 | 473 | extra=1, can_delete=False, can_order=False, |
| 454 | | max_num=0, fields=None, exclude=None, fieldsets=None): |
| | 474 | max_num=0, fields=None, exclude=None, fieldsets=None, inlines=None): |
| 455 | 475 | """ |
| 456 | 476 | Returns a FormSet class for the given Django model class. |
| 457 | 477 | """ |
| 458 | 478 | form = modelform_factory(model, form=form, fields=fields, exclude=exclude, |
| 459 | | fieldsets=fieldsets, |
| | 479 | fieldsets=fieldsets, inlines=inlines, |
| 460 | 480 | formfield_callback=formfield_callback) |
| 461 | 481 | FormSet = formset_factory(form, formset, extra=extra, max_num=max_num, |
| 462 | 482 | can_order=can_order, can_delete=can_delete) |
| … |
… |
|
| 546 | 566 | |
| 547 | 567 | def inlineformset_factory(parent_model, model, form=ModelForm, |
| 548 | 568 | formset=BaseInlineFormSet, fk_name=None, |
| 549 | | fields=None, exclude=None, fieldsets=None, |
| | 569 | fields=None, exclude=None, fieldsets=None, inlines=None, |
| 550 | 570 | extra=3, can_order=False, can_delete=True, max_num=0, |
| 551 | 571 | formfield_callback=lambda f: f.formfield()): |
| 552 | 572 | """ |
| … |
… |
|
| 574 | 594 | 'fieldsets': fieldsets, |
| 575 | 595 | 'fields': fields, |
| 576 | 596 | 'exclude': exclude, |
| | 597 | 'inlines': inlines, |
| 577 | 598 | 'max_num': max_num, |
| 578 | 599 | } |
| 579 | 600 | FormSet = modelformset_factory(model, **kwargs) |