Ticket #7975: callable_initial_fix_with_tests_r8776.diff

File callable_initial_fix_with_tests_r8776.diff, 11.6 KB (added by Manuel Saelices, 16 years ago)

Patch with tests

  • django/db/models/fields/__init__.py

     
    231231
    232232    def get_default(self):
    233233        "Returns the default value for this field."
    234         if self.default is not NOT_PROVIDED:
     234        if self.has_default():
    235235            if callable(self.default):
    236236                return self.default()
    237237            return force_unicode(self.default, strings_only=True)
     
    314314            kwargs.pop('max_length', None)
    315315        if self.has_default():
    316316            defaults['initial'] = self.get_default()
     317            if callable(self.default):
     318                defaults['show_hidden_initial'] = True
    317319        defaults.update(kwargs)
    318320        return form_class(**defaults)
    319321
  • django/forms/fields.py

     
    2828from django.utils.encoding import smart_unicode, smart_str
    2929
    3030from util import ErrorList, ValidationError
    31 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput, TimeInput
     31from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput, TimeInput, SplitHiddenDateTimeWidget
    3232from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile
    3333
    3434__all__ = (
     
    5959    creation_counter = 0
    6060
    6161    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):
    6363        # required -- Boolean that specifies whether the field is required.
    6464        #             True by default.
    6565        # widget -- A Widget class, or instance of a Widget class, that should
     
    7373        # initial -- A value to use in this Field's initial display. This value
    7474        #            is *not* used as a fallback if data isn't given.
    7575        # 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.
    7678        if label is not None:
    7779            label = smart_unicode(label)
    7880        self.required, self.label, self.initial = required, label, initial
     81        self.show_hidden_initial = show_hidden_initial
    7982        if help_text is None:
    8083            self.help_text = u''
    8184        else:
     
    840843        self.widget.choices = self.choices
    841844
    842845class SplitDateTimeField(MultiValueField):
     846    hidden_widget = SplitHiddenDateTimeWidget
    843847    default_error_messages = {
    844848        'invalid_date': _(u'Enter a valid date.'),
    845849        'invalid_time': _(u'Enter a valid time.'),
  • django/forms/forms.py

     
    128128        """
    129129        return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
    130130
     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
    131137    def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
    132138        "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
    133139        top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
     
    245251        Returns True if data differs from initial.
    246252        """
    247253        return bool(self.changed_data)
    248    
     254
    249255    def _get_changed_data(self):
    250256        if self._changed_data is None:
    251257            self._changed_data = []
     
    258264            for name, field in self.fields.items():
    259265                prefixed_name = self.add_prefix(name)
    260266                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)
    262274                if field.widget._has_changed(initial_value, data_value):
    263275                    self._changed_data.append(name)
    264276        return self._changed_data
     
    300312        self.field = field
    301313        self.name = name
    302314        self.html_name = form.add_prefix(name)
     315        self.html_check_name = form.add_initial_prefix(name)
    303316        if self.field.label is None:
    304317            self.label = pretty_name(name)
    305318        else:
     
    336349                data = data()
    337350        else:
    338351            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
    340356
    341357    def as_text(self, attrs=None):
    342358        """
  • django/forms/widgets.py

     
    2525    'HiddenInput', 'MultipleHiddenInput',
    2626    'FileInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput',
    2727    'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
    28     'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
     28    'CheckboxSelectMultiple', 'MultiWidget',
     29    'SplitDateTimeWidget', 'SplitHiddenDateTimeWidget',
    2930)
    3031
    3132MEDIA_TYPES = ('css','js')
     
    662663            return [value.date(), value.time().replace(microsecond=0)]
    663664        return [None, None]
    664665
     666class 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

     
     1from django import forms
    12from django.db import models
     3import datetime
    24
    35try:
    46    sorted
     
    7678class MexicanRestaurant(Restaurant):
    7779    serves_tacos = models.BooleanField()
    7880
     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.
     84class Person(models.Model):
     85    name = models.CharField(max_length=128)
    7986
     87class Membership(models.Model):
     88    person = models.ForeignKey(Person)
     89    date_joined = models.DateTimeField(default=datetime.datetime.now)
     90    karma = models.IntegerField()
     91
    8092__test__ = {'API_TESTS': """
    8193
    8294>>> from datetime import date
     
    553565>>> type(_get_foreign_key(MexicanRestaurant, Owner))
    554566<class 'django.db.models.fields.related.ForeignKey'>
    555567
     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'])
     581True
     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()
     593True
     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()
     603False
     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()
     623True
     624
    556625"""}
  • tests/regressiontests/forms/widgets.py

     
    10931093>>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51))
    10941094u'<input type="text" name="date" value="2007-09-17 12:51:00" />'
    10951095
    1096 # TimeInput ###############################################################
     1096# TimeInput ###################################################################
    10971097
    10981098>>> w = TimeInput()
    10991099>>> w.render('time', None)
     
    11131113We should be able to initialize from a unicode value.
    11141114>>> w.render('time', u'13:12:11')
    11151115u'<input type="text" name="time" value="13:12:11" />'
     1116
     1117# SplitHiddenDateTimeWidget ###################################################
     1118
     1119>>> w = SplitHiddenDateTimeWidget()
     1120>>> w.render('date', '')
     1121u'<input type="hidden" name="date_0" /><input type="hidden" name="date_1" />'
     1122>>> w.render('date', d)
     1123u'<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))
     1125u'<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))
     1127u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" name="date_1" value="12:51:00" />'
     1128
    11161129"""
    11171130
Back to Top