Ticket #6632: 02-newforms-inlines.diff
File 02-newforms-inlines.diff, 38.9 KB (added by , 17 years ago) |
---|
-
django/newforms/formsets.py
=== added file 'django/newforms/formsets.py'
1 from django.core.exceptions import ImproperlyConfigured 2 from django.utils.datastructures import InheritableOptions 3 from django.utils.encoding import StrAndUnicode 4 from django.utils.translation import ugettext as _ 5 6 from fields import Field, BooleanField, IntegerField 7 from forms import Form, FormOptions 8 from widgets import HiddenInput 9 from util import ValidationError 10 11 __all__ = ('BaseFormSet', 'FormSet') 12 13 # Special field names. 14 CHANGE_FORMS_COUNT_FIELD_NAME = 'CHANGE-FORMS-COUNT' 15 ALL_FORMS_COUNT_FIELD_NAME = 'ALL-FORMS-COUNT' 16 ORDERING_FIELD_NAME = 'ORDER' 17 DELETION_FIELD_NAME = 'DELETE' 18 19 class ManagementForm(Form): 20 """ 21 ``ManagementForm`` is used to keep track of how many form instances 22 are displayed on the page. If adding new forms via javascript, you should 23 increment the count field of this form as well. 24 """ 25 def __init__(self, *args, **kwargs): 26 self.base_fields[CHANGE_FORMS_COUNT_FIELD_NAME] = IntegerField(widget=HiddenInput) 27 self.base_fields[ALL_FORMS_COUNT_FIELD_NAME] = IntegerField(widget=HiddenInput) 28 super(ManagementForm, self).__init__(*args, **kwargs) 29 30 class FormSetOptions(InheritableOptions): 31 _formset_options = { 32 # form 33 'form': None, 34 'base_form': Form, 35 # other options 36 'deletable': False, 37 'emptiness_test_fields': None, 38 'emptiness_test_exclude': None, 39 'fieldset_attrs': {}, 40 'fieldset_html_output_method': None, 41 'fieldset_legend': None, 42 'num_extra': 1, 43 'orderable': False, 44 'output_type': 'tr', 45 } 46 _default_options = FormOptions._default_options.copy() 47 _default_options.update(_formset_options) 48 49 class FormSetMetaclass(type): 50 51 def create_options(cls, new_cls): 52 new_cls._meta = new_cls.options(new_cls) 53 try: 54 delattr(new_cls, 'Meta') 55 except AttributeError: 56 pass 57 create_options = classmethod(create_options) 58 59 def create_form_if_not_exists(cls, new_cls): 60 if not new_cls._meta.form: 61 form_attrs = { 62 'Meta': type('Meta', (), new_cls._meta.__dict__), 63 } 64 for name, attr in new_cls.__dict__.items(): 65 if isinstance(attr, Field): 66 form_attrs[name] = attr 67 delattr(new_cls, name) 68 new_cls._meta.form = type('%sForm' % new_cls.__name__, (new_cls._meta.base_form,), form_attrs) 69 create_form_if_not_exists = classmethod(create_form_if_not_exists) 70 71 def check_no_fieldsets_in_form(cls, new_cls): 72 if new_cls._meta.form._meta.fieldsets is not None: 73 raise ImproperlyConfigured("%s cannot have fieldsets." % new_cls.__name__) 74 check_no_fieldsets_in_form = classmethod(check_no_fieldsets_in_form) 75 76 def __new__(cls, name, bases, attrs): 77 new_cls = type.__new__(cls, name, bases, attrs) 78 cls.create_options(new_cls) 79 cls.create_form_if_not_exists(new_cls) 80 cls.check_no_fieldsets_in_form(new_cls) 81 return new_cls 82 83 class BaseFormSet(StrAndUnicode): 84 """A collection of instances of the same Form class.""" 85 86 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None): 87 self.is_bound = data is not None or files is not None 88 self.data = data 89 self.files = files 90 self.auto_id = auto_id 91 self.prefix = prefix or 'form' 92 self.initial = initial 93 self._is_valid = None # Stores validation state after full_clean() has been called. 94 self._create_forms() 95 96 def _create_forms(self): 97 self._create_management_forms() 98 self._create_change_forms() 99 self._create_add_forms() 100 self.forms = self.change_forms + self.add_forms 101 102 def _create_management_forms(self): 103 # Initialization is different depending on whether we recieved data, initial, or nothing. 104 self.management_form = None 105 if self.data or self.files: 106 self.management_form = ManagementForm(self.data, self.files, auto_id=self.auto_id, prefix=self.prefix) 107 if self.management_form.is_valid(): 108 self.change_forms_count = self.management_form.cleaned_data[CHANGE_FORMS_COUNT_FIELD_NAME] 109 self.all_forms_count = self.management_form.cleaned_data[ALL_FORMS_COUNT_FIELD_NAME] 110 else: 111 # ManagementForm data is missing or has been tampered with." 112 self.management_form = None 113 if not self.management_form: 114 self.change_forms_count = self.initial and len(self.initial) or 0 115 self.all_forms_count = self.change_forms_count + self._meta.num_extra 116 management_form_initial = { 117 CHANGE_FORMS_COUNT_FIELD_NAME: self.change_forms_count, 118 ALL_FORMS_COUNT_FIELD_NAME: self.all_forms_count, 119 } 120 self.management_form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial=management_form_initial) 121 122 def _create_change_forms(self): 123 self.change_forms = [] 124 for i in range(0, self.change_forms_count): 125 kwargs = {'auto_id': self.auto_id, 'prefix': self.add_prefix(i)} 126 if self.data: 127 kwargs['data'] = self.data 128 if self.files: 129 kwargs['files'] = self.files 130 if self.initial: 131 kwargs['initial'] = self.initial[i] 132 change_form = self._meta.form(**kwargs) 133 self.add_fields(change_form, i) 134 self.change_forms.append(change_form) 135 136 def _create_add_forms(self): 137 self.add_forms = [] 138 for i in range(self.change_forms_count, self.all_forms_count): 139 kwargs = {'auto_id': self.auto_id, 'prefix': self.add_prefix(i)} 140 if self.data: 141 kwargs['data'] = self.data 142 if self.files: 143 kwargs['files'] = self.files 144 add_form = self._meta.form(**kwargs) 145 self.add_fields(add_form, i) 146 self.add_forms.append(add_form) 147 148 def __unicode__(self): 149 return getattr(self, 'as_%s' % self._meta.output_type)() 150 151 def add_prefix(self, index): 152 return '%s-%s' % (self.prefix, index) 153 154 def add_fields(self, form, index): 155 """A hook for adding extra fields on to each form instance.""" 156 if self._meta.orderable: 157 form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_('Order'), initial=index+1) 158 if self._meta.deletable: 159 form.fields[DELETION_FIELD_NAME] = BooleanField(label=_('Delete'), required=False) 160 161 def as_table(self): 162 "Returns this form rendered as HTML <tr>s." 163 return u'\n'.join(u'<table>\n%s\n</table>' % form.as_table() for form in [self.management_form] + self.forms) 164 165 def as_ul(self): 166 "Returns this form rendered as HTML <li>s." 167 return u'\n'.join(u'<ul>\n%s\n</ul>' % form.as_ul() for form in [self.management_form] + self.forms) 168 169 def as_p(self): 170 "Returns this form rendered as HTML <p>s." 171 return u'\n'.join(u'<div>\n%s\n</div>' % form.as_p() for form in [self.management_form] + self.forms) 172 173 def as_tr(self): 174 "Returns this form rendered as HTML <td>s." 175 output = [self.management_form.as_tr()] 176 if self.forms: 177 output.append(u'<tr>') 178 output.extend(u'<th>%s</th>' % bf.label for bf in self.forms[0] if not bf.is_hidden) 179 output.append(u'</tr>') 180 output.extend(form.as_tr() for form in [self.management_form] + self.forms) 181 return '\n'.join(output) 182 183 def is_valid(self): 184 """ 185 Returns True if the formset (and its forms) have no errors. 186 """ 187 if self._is_valid is None: 188 self.full_clean() 189 return self._is_valid 190 191 def _get_errors(self): 192 """ 193 Returns list of ErrorDict for all forms. 194 """ 195 if self._is_valid is None: 196 self.full_clean() 197 return self._errors 198 errors = property(_get_errors) 199 200 def _get_non_form_errors(self): 201 """ 202 Returns an ErrorList of errors that aren't associated with a particular 203 form -- i.e., from formset.clean(). Returns an empty ErrorList if there 204 are none. 205 """ 206 if self._is_valid is None: 207 self.full_clean() 208 return self._non_form_errors 209 non_form_errors = property(_get_non_form_errors) 210 211 def full_clean(self): 212 """ 213 Cleans all of self.data and populates self._is_valid, self._errors, 214 self._no_form_errors, self.cleaned_data and self.deleted_data. 215 """ 216 self._is_valid = True # Assume the formset is valid until proven otherwise. 217 self._errors = [] 218 self._non_form_errors = self._meta.error_class() 219 if not self.is_bound: # Stop further processing. 220 return 221 self.cleaned_data = [] 222 self.deleted_data = [] 223 # Process change forms. 224 for form in self.change_forms: 225 if form.is_valid(): 226 if self._meta.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 227 self.deleted_data.append(form.cleaned_data) 228 else: 229 self.cleaned_data.append(form.cleaned_data) 230 else: 231 self._is_valid = False 232 self._errors.append(form.errors) 233 # Process add forms in reverse so we can easily tell when the remaining 234 # ones should be required. 235 remaining_forms_required = False 236 add_errors = [] 237 for i in range(len(self.add_forms)-1, -1, -1): 238 form = self.add_forms[i] 239 # If an add form is empty, reset it so it won't have any errors. 240 if not remaining_forms_required and self.is_empty(form): 241 form.reset() 242 continue 243 else: 244 remaining_forms_required = True 245 if form.is_valid(): 246 self.cleaned_data.append(form.cleaned_data) 247 else: 248 self._is_valid = False 249 add_errors.append(form.errors) 250 add_errors.reverse() 251 self._errors.extend(add_errors) 252 # Sort cleaned_data if the formset is orderable. 253 if self._meta.orderable: 254 self.cleaned_data.sort(lambda x, y: x[ORDERING_FIELD_NAME] - y[ORDERING_FIELD_NAME]) 255 # Give self.clean() a chance to do validation. 256 try: 257 self.cleaned_data = self.clean() 258 except ValidationError, e: 259 self._non_form_errors = self._meta.error_class(e.messages) 260 self._is_valid = False 261 # If there were errors, remove the cleaned_data and deleted_data attributes. 262 if not self._is_valid: 263 delattr(self, 'cleaned_data') 264 delattr(self, 'deleted_data') 265 266 def clean(self): 267 """ 268 Hook for doing any extra formset-wide cleaning after Form.clean() has 269 been called on every form. Any ValidationError raised by this method 270 will not be associated with a particular form; it will be accesible 271 via formset.non_form_errors() 272 """ 273 return self.cleaned_data 274 275 def is_empty(self, form=None): 276 """ 277 Returns True if the formset (including forms) or selected form 278 is empty. Otherwise, False. 279 """ 280 fields = list(self._meta.emptiness_test_fields) or [] 281 exclude = list(self._meta.emptiness_test_exclude) or [] 282 if self._meta.orderable: 283 exclude.append(ORDERING_FIELD_NAME) 284 if form is None: 285 for form in self: 286 if not form.is_empty(fields, exclude): 287 return False 288 return True 289 else: 290 return form.is_empty(fields, exclude) 291 292 def reset(self): 293 """ 294 Resets the formset (including forms) to the state it was in 295 before data was passed to it. 296 """ 297 self.is_bound = False 298 self.data = {} 299 self.files = {} 300 self._is_valid = None 301 self._create_forms() 302 303 def is_multipart(self): 304 """ 305 Returns True if the formset needs to be multipart-encrypted, i.e. its 306 form has FileInput. Otherwise, False. 307 """ 308 if self.forms: 309 return self.forms[0].is_multipart() 310 else: 311 return False 312 313 class FormSet(BaseFormSet): 314 __metaclass__ = FormSetMetaclass 315 options = FormSetOptions -
django/newforms/__init__.py
=== modified file 'django/newforms/__init__.py'
15 15 from fields import * 16 16 from forms import * 17 17 from models import * 18 from formsets import * -
django/newforms/forms.py
=== modified file 'django/newforms/forms.py'
4 4 5 5 from copy import deepcopy 6 6 7 from django.core.exceptions import ImproperlyConfigured 7 8 from django.utils.datastructures import SortedDict, InheritableOptions 8 9 from django.utils.html import conditional_escape 9 10 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode … … 29 30 'fieldsets': None, 30 31 'fields': None, 31 32 'exclude': None, 33 'inlines': None, 32 34 # other options 33 35 'error_class': ErrorList, 34 36 'formfield_for_formfield': lambda self, formfield: formfield, … … 37 39 'html_class_for_required_fields': 'required', 38 40 'label_capitalization': True, 39 41 'label_suffix': ':', 42 'output_type': 'table', 40 43 'validation_order': None, 41 44 } 42 45 … … 76 79 def create_base_fields_from_base_fields_pool(cls, new_cls): 77 80 if new_cls._meta.fieldsets: 78 81 names = [] 79 for fieldset in new_cls._meta.fieldsets: 80 names.extend(fieldset['fields']) 82 for fieldset_or_inline in new_cls._meta.fieldsets: 83 if isinstance(fieldset_or_inline, dict): 84 names.extend(fieldset_or_inline['fields']) 81 85 elif new_cls._meta.fields: 82 86 names = new_cls._meta.fields 83 87 elif new_cls._meta.exclude: … … 87 91 new_cls.base_fields = SortedDict([(name, new_cls._meta.formfield_for_formfield(new_cls._meta, new_cls._base_fields_pool[name])) for name in names]) 88 92 create_base_fields_from_base_fields_pool = classmethod(create_base_fields_from_base_fields_pool) 89 93 94 def create_fieldsets_if_inlines_exist(cls, new_cls): 95 if new_cls._meta.inlines is not None: 96 if new_cls._meta.fieldsets is not None: 97 raise ImproperlyConfigured("Options fieldsets and inlines cannot be used together.") 98 new_cls._meta.fieldsets = [{'fields': new_cls.base_fields.keys()}] + list(new_cls._meta.inlines) 99 create_fieldsets_if_inlines_exist = classmethod(create_fieldsets_if_inlines_exist) 100 90 101 def __new__(cls, name, bases, attrs): 91 102 new_cls = type.__new__(cls, name, bases, attrs) 92 103 cls.create_options(new_cls) 93 104 cls.create_declared_fields(new_cls) 94 105 cls.create_base_fields_pool_from_declared_fields(new_cls) 95 106 cls.create_base_fields_from_base_fields_pool(new_cls) 107 cls.create_fieldsets_if_inlines_exist(new_cls) 96 108 return new_cls 97 109 98 110 class BaseForm(StrAndUnicode): … … 100 112 # class is different than Form. See the comments by the Form class for more 101 113 # information. Any improvements to the form API should be made to *this* 102 114 # class, not to the Form class. 103 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None ):115 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None, inlines=None): 104 116 self.is_bound = data is not None or files is not None 105 117 self.data = data or {} 106 118 self.files = files or {} 107 119 self.auto_id = auto_id 108 120 self.prefix = prefix 109 121 self.initial = initial or {} 110 self._errors = None # Stores the errors after clean() has been called. 122 self.inlines = inlines or [] # It is not responsibility of this class to create inlines. 123 self._is_valid = None # Stores validation state after full_clean() has been called. 111 124 112 125 # The base_fields class attribute is the *class-wide* definition of 113 126 # fields. Because a particular *instance* of the class might want to … … 117 130 self.fields = deepcopy(self.base_fields) 118 131 119 132 def __unicode__(self): 120 return self.as_table()133 return getattr(self, 'as_%s' % self._meta.output_type)() 121 134 122 135 def __iter__(self): 123 136 for name, field in self.fields.items(): … … 131 144 raise KeyError('Key %r not found in Form' % name) 132 145 return BoundField(self, field, name) 133 146 134 def _get_errors(self):135 "Returns an ErrorDict for the data provided for the form."136 if self._errors is None:137 self.full_clean()138 return self._errors139 errors = property(_get_errors)140 141 def is_valid(self):142 """143 Returns True if the form has no errors. Otherwise, False. If errors are144 being ignored, returns False.145 """146 return self.is_bound and not bool(self.errors)147 148 147 def has_fieldsets(self): 149 148 "Returns True if this form has fieldsets." 150 149 return bool(self._meta.fieldsets) … … 192 191 output.append(fieldset_end % u'</fieldset>') 193 192 return u'\n'.join(output) 194 193 194 def inline_html_output(self, inline, fieldset_start, fieldset_end, is_first, is_last): 195 "Helper function for outputting HTML from a inline. Used by _html_output." 196 output = [] 197 if fieldset_start and not is_first: 198 fieldset_attrs = flatatt(inline._meta.fieldset_attrs) 199 if inline._meta.fieldset_legend: 200 legend_tag = u'\n<legend>%s</legend>' % conditional_escape(force_unicode(inline._meta.fieldset_legend)) 201 else: 202 legend_tag = u'' 203 output.append(fieldset_start % (u'<fieldset%s>%s' % (fieldset_attrs, legend_tag))) 204 output.append(unicode(inline)) 205 if fieldset_end and not is_last: 206 output.append(fieldset_end % u'</fieldset>') 207 return u'\n'.join(output) 208 195 209 def hidden_fields_html_output(self, hidden_fields, hidden_fields_row): 196 210 "Helper function for outputting HTML from a hidden fields row. Used by _html_output." 197 211 if self._meta.html_class_for_hidden_fields_row: … … 217 231 if top_errors: 218 232 output.append(self.top_errors_html_output(top_errors, top_errors_row)) 219 233 if self.has_fieldsets(): 220 for i, fieldset in enumerate(self._meta.fieldsets): 221 output_method = fieldset.get('html_output_method', self.__class__.fieldset_html_output) 222 fields = dict((name, visible_fields[name]) for name in fieldset['fields'] if name in visible_fields) 234 inlines = list(self.inlines) # Copy it - method pop should not changed self.inlines. 235 for i, fieldset_or_inline in enumerate(self._meta.fieldsets): 223 236 is_first = (i == 0) 224 237 is_last = (i + 1 == len(self._meta.fieldsets)) 225 output.append(output_method(self, fieldset, fields, fieldset_start, fieldset_end, is_first, is_last)) 238 if isinstance(fieldset_or_inline, dict): 239 output_method = fieldset_or_inline.get('html_output_method', self.__class__.fieldset_html_output) 240 fields = dict((name, visible_fields[name]) for name in fieldset_or_inline['fields'] if name in visible_fields) 241 output.append(output_method(self, fieldset_or_inline, fields, fieldset_start, fieldset_end, is_first, is_last)) 242 else: 243 output_method = fieldset_or_inline._meta.fieldset_html_output_method or self.__class__.inline_html_output 244 output.append(output_method(self, inlines.pop(0), fieldset_start, fieldset_end, is_first, is_last)) 226 245 else: 227 246 for name in self.fields: 228 247 if name in visible_fields: … … 243 262 "Returns this form rendered as HTML <p>s." 244 263 return self._html_output('p', u'%s', u'%s', u'%s', u'<p%s>%s</p>') 245 264 265 def as_tr(self): 266 "Returns this form rendered as HTML <td>s." 267 if self.has_fieldsets(): 268 raise ValueError("%s has fieldsets so its method as_tr cannot be used." % self.__class__) 269 colspan = len([bf for bf in self if not bf.is_hidden]) 270 html_output = self._html_output('tr', u'<tr><td colspan="%s">%%s</td></tr>\n<tr>' % colspan, u'%s', u'%s', u'</tr>\n<tr%%s><td colspan="%s">%%s</td></tr>' % colspan) 271 if not html_output.startswith('<tr>'): 272 html_output = u'<tr>\n%s' % html_output 273 if not html_output.endswith('<tr>'): 274 html_output = u'%s\n</tr>' % html_output 275 return html_output 276 277 def is_valid(self): 278 """ 279 Returns True if the form and its inlines have no errors. 280 """ 281 if self._is_valid is None: 282 self.full_clean() 283 return self._is_valid 284 285 def _get_errors(self): 286 "Returns an ErrorDict for the data provided for the form." 287 if self._is_valid is None: 288 self.full_clean() 289 return self._errors 290 errors = property(_get_errors) 291 246 292 def non_field_errors(self): 247 293 """ 248 294 Returns an ErrorList of errors that aren't associated with a particular … … 253 299 254 300 def full_clean(self): 255 301 """ 256 Cleans all of self.data and populates self._ errors and302 Cleans all of self.data and populates self._is_valid, self._errors and 257 303 self.cleaned_data. 258 304 """ 305 self._is_valid = True # Assume the form is valid until proven otherwise. 259 306 self._errors = ErrorDict() 260 307 if not self.is_bound: # Stop further processing. 261 308 return 262 309 self.cleaned_data = {} 310 # Process fields. 263 311 if self._meta.validation_order: 264 312 items = [(name, self.fields[name]) for name in self._meta.validation_order] 265 313 else: 266 314 items = self.fields.items() 267 for name, field in items: 268 # value_from_datadict() gets the data from the data dictionaries. 269 # Each widget type knows how to retrieve its own data, because some 270 # widgets split data over several HTML fields. 271 value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) 315 for bf in self: 272 316 try: 273 if isinstance( field, FileField):274 initial = self.initial.get(name, field.initial)275 value = field.clean(value, initial)317 if isinstance(bf.field, FileField): 318 initial = self.initial.get(name, bf.field.initial) 319 value = bf.field.clean(bf.data, initial) 276 320 else: 277 value = field.clean(value) 278 self.cleaned_data[name] = value 279 if hasattr(self, 'clean_%s' % name): 280 value = getattr(self, 'clean_%s' % name)() 281 self.cleaned_data[name] = value 321 value = bf.field.clean(bf.data) 322 self.cleaned_data[bf.name] = value 323 if hasattr(self, 'clean_%s' % bf.name): 324 self.cleaned_data[bf.name] = getattr(self, 'clean_%s' % bf.name)() 282 325 except ValidationError, e: 283 self._errors[name] = self._meta.error_class(e.messages) 284 if name in self.cleaned_data: 285 del self.cleaned_data[name] 326 self._errors[bf.name] = self._meta.error_class(e.messages) 327 self._is_valid = False 328 if bf.name in self.cleaned_data: 329 del self.cleaned_data[bf.name] 330 # Process inlines. 331 for inline in self.inlines: 332 inline.full_clean() 333 if not inline.is_valid(): 334 self._is_valid = False 335 # Give self.clean() a chance to do validation. 286 336 try: 287 337 self.cleaned_data = self.clean() 288 338 except ValidationError, e: 289 339 self._errors[NON_FIELD_ERRORS] = self._meta.error_class(e.messages) 290 if self._errors: 340 self._is_valid = False 341 # If there were errors, remove the cleaned_data attribute. 342 if not self._is_valid: 291 343 delattr(self, 'cleaned_data') 292 344 293 345 def clean(self): … … 299 351 """ 300 352 return self.cleaned_data 301 353 354 def is_empty(self, fields=None, exclude=None): 355 """ 356 Returns True if the form (including inlines) is empty. Otherwise, False. 357 """ 358 for bf in self: 359 if fields and bf.name not in fields: 360 continue 361 if exclude and bf.name in exclude: 362 continue 363 if not bf.widget.is_empty(bf.data): 364 return False 365 for inline in self.inlines: 366 if not inline.is_empty(): 367 return False 368 return True 369 370 def reset(self): 371 """ 372 Resets the form (including inlines) to the state it was in 373 before data was passed to it. 374 """ 375 self.is_bound = False 376 self.data = {} 377 self.files = {} 378 self._is_valid = None 379 for inline in self.inlines: 380 inline.reset() 381 302 382 def is_multipart(self): 303 383 """ 304 Returns True if the form needs to be multipart-encrypted, i.e. it has305 FileInput. Otherwise, False.384 Returns True if the form (including inlines) needs to be 385 multipart-encrypted, i.e. it has FileInput. Otherwise, False. 306 386 """ 307 387 for field in self.fields.values(): 308 388 if field.widget.needs_multipart_form: 309 389 return True 390 for inline in self.inlines: 391 if inline.is_multipart(): 392 return True 310 393 return False 311 394 312 395 class Form(BaseForm): -
django/newforms/models.py
=== modified file 'django/newforms/models.py'
5 5 6 6 from warnings import warn 7 7 8 from django.core.exceptions import ImproperlyConfigured 8 9 from django.utils.translation import ugettext_lazy as _ 9 10 from django.utils.encoding import smart_unicode 10 11 from django.utils.datastructures import SortedDict, InheritableOptions 11 12 12 13 from util import ValidationError 13 14 from forms import FormOptions, FormMetaclass, BaseForm 14 from fields import Field, ChoiceField, EMPTY_VALUES 15 from formsets import FormSetOptions, FormSetMetaclass, BaseFormSet, DELETION_FIELD_NAME 16 from fields import Field, ChoiceField, EMPTY_VALUES, IntegerField, HiddenInput 15 17 from widgets import Select, SelectMultiple, MultipleHiddenInput 16 18 17 19 __all__ = ( 18 20 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model', 19 21 'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields', 20 'ModelChoiceField', 'ModelMultipleChoiceField' 22 'ModelChoiceField', 'ModelMultipleChoiceField', 'BaseModelFormSet', 23 'ModelFormSet', 'BaseInlineFormSet', 'InlineFormSet', 21 24 ) 22 25 23 26 def save_instance(form, instance, fields=None, fail_message='saved', … … 249 252 cls.create_declared_fields(new_cls) 250 253 cls.create_base_fields_pool_from_model_fields_and_declared_fields(new_cls) 251 254 cls.create_base_fields_from_base_fields_pool(new_cls) 255 cls.create_fieldsets_if_inlines_exist(new_cls) 252 256 return new_cls 253 257 254 258 class BaseModelForm(BaseForm): 255 259 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None, instance=None): 256 opts = self._meta257 260 if instance is None: 258 # if we didn't get an instance, instantiate a new one259 self.instance = opts.model()261 # If we didn't get an instance, instantiate a new one. 262 self.instance = self._meta.model() 260 263 object_data = {} 261 264 else: 262 265 self.instance = instance 263 266 object_data = model_to_dict(instance, self.base_fields.keys()) 264 # if initial was provided, it should override the values from instance267 # If initial was provided, it should override the values from instance. 265 268 if initial is not None: 266 269 object_data.update(initial) 267 BaseForm.__init__(self, data, files, auto_id, prefix, object_data) 270 # Create inlines. 271 inlines = [] 272 if self.has_fieldsets(): 273 for fieldset_or_inline in self._meta.fieldsets: 274 if not isinstance(fieldset_or_inline, dict): 275 inline_prefix = '%s-%s' % (prefix or 'inline', len(inlines) + 1) 276 inlines.append(fieldset_or_inline(data, files, auto_id, inline_prefix, instance)) 277 BaseForm.__init__(self, data, files, auto_id, prefix, object_data, inlines) 268 278 269 279 def save(self, commit=True): 270 280 """ … … 278 288 fail_message = 'created' 279 289 else: 280 290 fail_message = 'changed' 281 return save_instance(self, self.instance, self.base_fields.keys(), fail_message, commit) 291 instance = save_instance(self, self.instance, self.base_fields.keys(), fail_message, commit) 292 for inline in self.inlines: 293 inline.save(commit) 294 return instance 282 295 283 296 class ModelForm(BaseModelForm): 284 297 __metaclass__ = ModelFormMetaclass 285 298 options = ModelFormOptions 286 299 287 288 300 # Fields ##################################################################### 289 301 290 302 class QuerySetIterator(object): … … 395 407 else: 396 408 final_values.append(obj) 397 409 return final_values 410 411 # Model-FormSet integration ################################################### 412 413 class ModelFormSetOptions(InheritableOptions): 414 _default_options = ModelFormOptions._default_options.copy() 415 _default_options.update(FormSetOptions._formset_options) 416 _default_options.update({ 417 'base_form': ModelForm, 418 }) 419 420 class BaseModelFormSet(BaseFormSet): 421 """ 422 A ``FormSet`` for editing a queryset and/or adding new objects to it. 423 """ 424 425 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, queryset=None, **kwargs): 426 if queryset is None: 427 self.queryset = self.get_queryset(**kwargs) 428 else: 429 self.queryset = queryset 430 initial = [model_to_dict(obj, self._meta.form._meta.base_fields.keys()) for obj in self.queryset] 431 super(BaseModelFormSet, self).__init__(data, files, auto_id, prefix, initial) 432 433 def get_queryset(self, **kwargs): 434 """ 435 Hook to returning a queryset for this model. 436 """ 437 return self._meta.form._meta.model._default_manager.all() 438 439 def add_fields(self, form, index): 440 """Add a hidden field for the object's primary key.""" 441 self._pk_field_name = self._meta.form._meta.model._meta.pk.attname 442 form.fields[self._pk_field_name] = IntegerField(required=False, widget=HiddenInput) 443 super(BaseModelFormSet, self).add_fields(form, index) 444 445 def save_new(self, form, commit=True): 446 """Saves and returns a new model instance for the given form.""" 447 return save_instance(form, self._meta.form._meta.model(), commit=commit) 448 449 def save_instance(self, form, instance, commit=True): 450 """Saves and returns an existing model instance for the given form.""" 451 return save_instance(form, instance, commit=commit) 452 453 def save(self, commit=True): 454 """Saves model instances for every form, adding and changing instances 455 as necessary, and returns the list of instances. 456 """ 457 return self.save_existing_objects(commit) + self.save_new_objects(commit) 458 459 def save_existing_objects(self, commit=True): 460 if not self.queryset: 461 return [] 462 # Put the objects from self.queryset into a dict so they are easy to lookup by pk. 463 existing_objects = {} 464 for obj in self.queryset: 465 existing_objects[obj.pk] = obj 466 saved_instances = [] 467 for form in self.change_forms: 468 obj = existing_objects[form.cleaned_data[self._pk_field_name]] 469 if self._meta.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 470 obj.delete() 471 else: 472 saved_instances.append(self.save_instance(form, obj, commit=commit)) 473 return saved_instances 474 475 def save_new_objects(self, commit=True): 476 new_objects = [] 477 for form in self.add_forms: 478 if form.is_empty(): 479 continue 480 # If someone has marked an add form for deletion, don't save the 481 # object. At some point it would be nice if we didn't display 482 # the deletion widget for add forms. 483 if self._meta.deletable and form.cleaned_data[DELETION_FIELD_NAME]: 484 continue 485 new_objects.append(self.save_new(form, commit=commit)) 486 return new_objects 487 488 class ModelFormSet(BaseModelFormSet): 489 __metaclass__ = FormSetMetaclass 490 options = ModelFormSetOptions 491 492 class InlineFormSetOptions(InheritableOptions): 493 _model_formset_options = { 494 'parent_model': None, 495 'fk_name': None, 496 } 497 _default_options = ModelFormSetOptions._default_options.copy() 498 _default_options.update(_model_formset_options) 499 _default_options.update({ 500 'deletable': True, 501 'num_extra': 3, 502 }) 503 504 class InlineFormSetMetaclass(FormSetMetaclass): 505 506 def add_fk_attribute_and_remove_fk_from_base_fields(cls, new_cls): 507 # Get options - if models are not set, this class wouldn't be used directly. 508 parent_model, model, fk_name = new_cls._meta.parent_model, new_cls._meta.model, new_cls._meta.fk_name 509 if not (parent_model and model): 510 return 511 # Try to discover what the foreign key from model to parent_model is. 512 fks_to_parent = [] 513 for field in model._meta.fields: 514 # Exceptions are neccessary here - ForeignKey cannot be imported for circular dependancy. 515 try: 516 if field.rel.to == parent_model: 517 fks_to_parent.append(field) 518 except AttributeError: 519 pass 520 if len(fks_to_parent) == 0: 521 raise ImproperlyConfigured("%s has no ForeignKey to %s." % (model, parent_model)) 522 if fk_name: 523 fks_to_parent = [fk for fk in fks_to_parent if fk.name == fk_name] 524 if len(fks_to_parent) > 1: 525 raise ImproperlyConfigured("%s has more than one ForeignKey to %s." % (model, parent_model)) 526 new_cls.fk = fks_to_parent[0] 527 # Try to remove the foreign key from base_fields to keep it transparent to the form. 528 try: 529 del new_cls._meta.form.base_fields[new_cls.fk.name] 530 except KeyError: 531 pass 532 add_fk_attribute_and_remove_fk_from_base_fields = classmethod(add_fk_attribute_and_remove_fk_from_base_fields) 533 534 def __new__(cls, name, bases, attrs): 535 new_cls = type.__new__(cls, name, bases, attrs) 536 cls.create_options(new_cls) 537 cls.create_form_if_not_exists(new_cls) 538 cls.check_no_fieldsets_in_form(new_cls) 539 cls.add_fk_attribute_and_remove_fk_from_base_fields(new_cls) 540 return new_cls 541 542 class BaseInlineFormSet(BaseModelFormSet): 543 """A formset for child objects related to a parent.""" 544 545 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, instance=None, **kwargs): 546 self.instance = instance 547 super(BaseInlineFormSet, self).__init__(data, files, auto_id, prefix, **kwargs) 548 549 def get_queryset(self, **kwargs): 550 """ 551 Returns this FormSet's queryset, but restricted to children of 552 self.instance 553 """ 554 if self.instance is None: 555 return self._meta.form._meta.model._default_manager.none() 556 queryset = super(BaseInlineFormSet, self).get_queryset(**kwargs) 557 return queryset.filter(**{self.fk.name: self.instance}) 558 559 def save_new(self, form, commit=True): 560 kwargs = {self.fk.get_attname(): self.instance.pk} 561 new_obj = self._meta.form._meta.model(**kwargs) 562 return save_instance(form, new_obj, commit=commit) 563 564 class InlineFormSet(BaseInlineFormSet): 565 __metaclass__ = InlineFormSetMetaclass 566 options = InlineFormSetOptions -
django/newforms/widgets.py
=== modified file 'django/newforms/widgets.py'
69 69 """ 70 70 return data.get(name, None) 71 71 72 def is_empty(self, value): 73 """ 74 Returns True if this form is empty. 75 """ 76 if value not in (None, ''): 77 return False 78 return True 79 72 80 def id_for_label(self, id_): 73 81 """ 74 82 Returns the HTML ID attribute of this Widget for use by a <label>, … … 104 112 help_text = u' %s' % help_text 105 113 return u'%(rendered_errors)s<p%(row_attrs)s>%(label_tag)s %(rendered_widget)s%(help_text)s</p>' % locals() 106 114 115 def for_tr(self, rendered_widget, rendered_errors, label_tag, help_text, row_attrs): 116 "Returns this widget rendered as HTML <td>." 117 if help_text: 118 help_text = u' %s' % help_text 119 return u'<td%(row_attrs)s>%(rendered_errors)s%(rendered_widget)s%(help_text)s</td>' % locals() 120 107 121 class Input(Widget): 108 122 """ 109 123 Base class for all <input> widgets (except type='checkbox' and … … 228 242 return False 229 243 return super(CheckboxInput, self).value_from_datadict(data, files, name) 230 244 245 def is_empty(self, value): 246 # This widget will always either be True or False, so always return the 247 # opposite value so False values will make the form empty. 248 return not value 249 231 250 class Select(Widget): 232 251 def __init__(self, attrs=None, row_attrs=None, choices=()): 233 252 super(Select, self).__init__(attrs, row_attrs) … … 270 289 value = data.get(name, None) 271 290 return {u'2': True, u'3': False, True: True, False: False}.get(value, None) 272 291 292 def is_empty(self, value): 293 # This widget will always either be True or False, so always return the 294 # opposite value so False values will make the form empty. 295 return not value 296 273 297 class SelectMultiple(Select): 274 298 def render(self, name, value, attrs=None, choices=()): 275 299 if value is None: value = [] … … 461 485 def value_from_datadict(self, data, files, name): 462 486 return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)] 463 487 488 def is_empty(self, value): 489 for widget, val in zip(self.widgets, value): 490 if not widget.is_empty(val): 491 return False 492 return True 493 464 494 def format_output(self, rendered_widgets): 465 495 """ 466 496 Given a list of rendered widgets (as strings), returns a Unicode string