Ticket #7975: callable_initial_fix_with_tests_r8776.diff
File callable_initial_fix_with_tests_r8776.diff, 11.6 KB (added by , 16 years ago) |
---|
-
django/db/models/fields/__init__.py
231 231 232 232 def get_default(self): 233 233 "Returns the default value for this field." 234 if self. default is not NOT_PROVIDED:234 if self.has_default(): 235 235 if callable(self.default): 236 236 return self.default() 237 237 return force_unicode(self.default, strings_only=True) … … 314 314 kwargs.pop('max_length', None) 315 315 if self.has_default(): 316 316 defaults['initial'] = self.get_default() 317 if callable(self.default): 318 defaults['show_hidden_initial'] = True 317 319 defaults.update(kwargs) 318 320 return form_class(**defaults) 319 321 -
django/forms/fields.py
28 28 from django.utils.encoding import smart_unicode, smart_str 29 29 30 30 from util import ErrorList, ValidationError 31 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput, TimeInput 31 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput, TimeInput, SplitHiddenDateTimeWidget 32 32 from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile 33 33 34 34 __all__ = ( … … 59 59 creation_counter = 0 60 60 61 61 def __init__(self, required=True, widget=None, label=None, initial=None, 62 help_text=None, error_messages=None ):62 help_text=None, error_messages=None, show_hidden_initial=False): 63 63 # required -- Boolean that specifies whether the field is required. 64 64 # True by default. 65 65 # widget -- A Widget class, or instance of a Widget class, that should … … 73 73 # initial -- A value to use in this Field's initial display. This value 74 74 # is *not* used as a fallback if data isn't given. 75 75 # help_text -- An optional string to use as "help text" for this Field. 76 # show_hidden_initial -- Boolean that specifies if is needed to render a 77 # hidden widget with initial value after widget. 76 78 if label is not None: 77 79 label = smart_unicode(label) 78 80 self.required, self.label, self.initial = required, label, initial 81 self.show_hidden_initial = show_hidden_initial 79 82 if help_text is None: 80 83 self.help_text = u'' 81 84 else: … … 840 843 self.widget.choices = self.choices 841 844 842 845 class SplitDateTimeField(MultiValueField): 846 hidden_widget = SplitHiddenDateTimeWidget 843 847 default_error_messages = { 844 848 'invalid_date': _(u'Enter a valid date.'), 845 849 'invalid_time': _(u'Enter a valid time.'), -
django/forms/forms.py
128 128 """ 129 129 return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name 130 130 131 def add_initial_prefix(self, field_name): 132 """ 133 Add a 'initial' prefix for checking dynamic initial values 134 """ 135 return u'initial-%s' % self.add_prefix(field_name) 136 131 137 def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): 132 138 "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." 133 139 top_errors = self.non_field_errors() # Errors that should be displayed above all fields. … … 245 251 Returns True if data differs from initial. 246 252 """ 247 253 return bool(self.changed_data) 248 254 249 255 def _get_changed_data(self): 250 256 if self._changed_data is None: 251 257 self._changed_data = [] … … 258 264 for name, field in self.fields.items(): 259 265 prefixed_name = self.add_prefix(name) 260 266 data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name) 261 initial_value = self.initial.get(name, field.initial) 267 if not field.show_hidden_initial: 268 initial_value = self.initial.get(name, field.initial) 269 else: 270 initial_prefixed_name = self.add_initial_prefix(name) 271 raw_initial_value = field.hidden_widget().value_from_datadict(self.data, 272 self.files, initial_prefixed_name) 273 initial_value = field.clean(raw_initial_value) 262 274 if field.widget._has_changed(initial_value, data_value): 263 275 self._changed_data.append(name) 264 276 return self._changed_data … … 300 312 self.field = field 301 313 self.name = name 302 314 self.html_name = form.add_prefix(name) 315 self.html_check_name = form.add_initial_prefix(name) 303 316 if self.field.label is None: 304 317 self.label = pretty_name(name) 305 318 else: … … 336 349 data = data() 337 350 else: 338 351 data = self.data 339 return widget.render(self.html_name, data, attrs=attrs) 352 field_html = widget.render(self.html_name, data, attrs=attrs) 353 if self.field.show_hidden_initial: 354 field_html += self.field.hidden_widget().render(self.html_check_name, data) 355 return field_html 340 356 341 357 def as_text(self, attrs=None): 342 358 """ -
django/forms/widgets.py
25 25 'HiddenInput', 'MultipleHiddenInput', 26 26 'FileInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput', 27 27 'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect', 28 'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget', 28 'CheckboxSelectMultiple', 'MultiWidget', 29 'SplitDateTimeWidget', 'SplitHiddenDateTimeWidget', 29 30 ) 30 31 31 32 MEDIA_TYPES = ('css','js') … … 662 663 return [value.date(), value.time().replace(microsecond=0)] 663 664 return [None, None] 664 665 666 class SplitHiddenDateTimeWidget(SplitDateTimeWidget): 667 """ 668 A Widget that splits datetime input into two <input type="hidden"> inputs. 669 """ 670 def __init__(self, attrs=None): 671 widgets = (HiddenInput(attrs=attrs), HiddenInput(attrs=attrs)) 672 super(SplitDateTimeWidget, self).__init__(widgets, attrs) 673 674 -
tests/modeltests/model_formsets/models.py
1 from django import forms 1 2 from django.db import models 3 import datetime 2 4 3 5 try: 4 6 sorted … … 76 78 class MexicanRestaurant(Restaurant): 77 79 serves_tacos = models.BooleanField() 78 80 81 # models for testing callable defaults (see bug #7975). If you define a model 82 # with a callable default value, you cannot rely on the initial value in a 83 # form. 84 class Person(models.Model): 85 name = models.CharField(max_length=128) 79 86 87 class Membership(models.Model): 88 person = models.ForeignKey(Person) 89 date_joined = models.DateTimeField(default=datetime.datetime.now) 90 karma = models.IntegerField() 91 80 92 __test__ = {'API_TESTS': """ 81 93 82 94 >>> from datetime import date … … 553 565 >>> type(_get_foreign_key(MexicanRestaurant, Owner)) 554 566 <class 'django.db.models.fields.related.ForeignKey'> 555 567 568 # Use of callable defaults (see bug #7975). 569 570 >>> person = Person.objects.create(name='Ringo') 571 >>> FormSet = inlineformset_factory(Person, Membership, can_delete=False, extra=1) 572 >>> formset = FormSet(instance=person) 573 574 # in forms rendering with a form field which model form was containing a 575 # callable default value, django puts hidden fields for initial data as well as 576 # normal field 577 578 >>> form = formset.forms[0] # this formset only has one form 579 >>> now = form.fields['date_joined'].initial 580 >>> '<input type="hidden" name="initial-membership_set-0-date_joined" value="' in unicode(form['date_joined']) 581 True 582 583 # test for validation with callable defaults. Validations rely on hidden fields 584 585 >>> data = { 586 ... 'membership_set-TOTAL_FORMS': '1', 587 ... 'membership_set-INITIAL_FORMS': '0', 588 ... 'membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), 589 ... 'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), 590 ... } 591 >>> formset = FormSet(data, instance=person) 592 >>> formset.is_valid() 593 True 594 >>> one_day_later = now + datetime.timedelta(days=1) 595 >>> filled_data = { 596 ... 'membership_set-TOTAL_FORMS': '1', 597 ... 'membership_set-INITIAL_FORMS': '0', 598 ... 'membership_set-0-date_joined': unicode(one_day_later.strftime('%Y-%m-%d %H:%M:%S')), 599 ... 'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), 600 ... } 601 >>> formset = FormSet(filled_data, instance=person) 602 >>> formset.is_valid() 603 False 604 605 # now test with split datetime fields 606 607 >>> class MembershipForm(forms.ModelForm): 608 ... date_joined = forms.SplitDateTimeField(initial=now) 609 ... class Meta: model = Membership 610 ... def __init__(self, **kwargs): 611 ... super(MembershipForm, self).__init__(**kwargs) 612 ... self.fields['date_joined'].widget = forms.SplitDateTimeWidget() 613 >>> FormSet = inlineformset_factory(Person, Membership, form=MembershipForm, can_delete=False, extra=1) 614 >>> data = { 615 ... 'membership_set-TOTAL_FORMS': '1', 616 ... 'membership_set-INITIAL_FORMS': '0', 617 ... 'membership_set-0-date_joined_0': unicode(now.strftime('%Y-%m-%d')), 618 ... 'membership_set-0-date_joined_1': unicode(now.strftime('%H:%M:%S')), 619 ... 'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), 620 ... } 621 >>> formset = FormSet(data, instance=person) 622 >>> formset.is_valid() 623 True 624 556 625 """} -
tests/regressiontests/forms/widgets.py
1093 1093 >>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51)) 1094 1094 u'<input type="text" name="date" value="2007-09-17 12:51:00" />' 1095 1095 1096 # TimeInput ############################################################### 1096 # TimeInput ################################################################### 1097 1097 1098 1098 >>> w = TimeInput() 1099 1099 >>> w.render('time', None) … … 1113 1113 We should be able to initialize from a unicode value. 1114 1114 >>> w.render('time', u'13:12:11') 1115 1115 u'<input type="text" name="time" value="13:12:11" />' 1116 1117 # SplitHiddenDateTimeWidget ################################################### 1118 1119 >>> w = SplitHiddenDateTimeWidget() 1120 >>> w.render('date', '') 1121 u'<input type="hidden" name="date_0" /><input type="hidden" name="date_1" />' 1122 >>> w.render('date', d) 1123 u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" name="date_1" value="12:51:34" />' 1124 >>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51, 34)) 1125 u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" name="date_1" value="12:51:34" />' 1126 >>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51)) 1127 u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" name="date_1" value="12:51:00" />' 1128 1116 1129 """ 1117 1130