Django

Code

Changeset 8816

Show
Ignore:
Timestamp:
09/01/08 16:28:32 (3 months ago)
Author:
brosner
Message:

Fixed #7975 -- Callable defaults in inline model formsets now work correctly. Based on patch from msaelices. Thanks for your hard work msaelices.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/db/models/fields/__init__.py

    r8806 r8816  
    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() 
     
    307307        if self.has_default(): 
    308308            defaults['initial'] = self.get_default() 
    309  
     309            if callable(self.default): 
     310                defaults['show_hidden_initial'] = True 
    310311        if self.choices: 
    311312            # Fields with choices get special treatment.  
     
    315316            if self.null: 
    316317                defaults['empty_value'] = None 
    317              
    318318            form_class = forms.TypedChoiceField 
    319              
    320319            # Many of the subclass-specific formfield arguments (min_value, 
    321320            # max_value) don't apply for choice fields, so be sure to only pass 
     
    326325                             'error_messages'): 
    327326                    del kwargs[k] 
    328          
    329327        defaults.update(kwargs) 
    330328        return form_class(**defaults) 
  • django/trunk/django/forms/fields.py

    r8771 r8816  
    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 
     
    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. 
     
    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 it 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'' 
     
    841844 
    842845class SplitDateTimeField(MultiValueField): 
     846    hidden_widget = SplitHiddenDateTimeWidget 
    843847    default_error_messages = { 
    844848        'invalid_date': _(u'Enter a valid date.'), 
  • django/trunk/django/forms/forms.py

    r8761 r8816  
    128128        """ 
    129129        return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name 
     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) 
    130136 
    131137    def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): 
     
    246252        """ 
    247253        return bool(self.changed_data) 
    248      
     254 
    249255    def _get_changed_data(self): 
    250256        if self._changed_data is None: 
     
    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                    hidden_widget = field.hidden_widget() 
     272                    initial_value = hidden_widget.value_from_datadict( 
     273                        self.data, self.files, initial_prefixed_name) 
    262274                if field.widget._has_changed(initial_value, data_value): 
    263275                    self._changed_data.append(name) 
     
    301313        self.name = name 
    302314        self.html_name = form.add_prefix(name) 
     315        self.html_initial_name = form.add_initial_prefix(name) 
    303316        if self.field.label is None: 
    304317            self.label = pretty_name(name) 
     
    309322    def __unicode__(self): 
    310323        """Renders this field as an HTML widget.""" 
     324        if self.field.show_hidden_initial: 
     325            return self.as_widget() + self.as_hidden(only_initial=True) 
    311326        return self.as_widget() 
    312327 
     
    319334    errors = property(_errors) 
    320335 
    321     def as_widget(self, widget=None, attrs=None): 
     336    def as_widget(self, widget=None, attrs=None, only_initial=False): 
    322337        """ 
    323338        Renders the field by rendering the passed widget, adding any HTML 
     
    331346        if auto_id and 'id' not in attrs and 'id' not in widget.attrs: 
    332347            attrs['id'] = auto_id 
    333         if not self.form.is_bound
     348        if not self.form.is_bound or only_initial
    334349            data = self.form.initial.get(self.name, self.field.initial) 
    335350            if callable(data): 
     
    337352        else: 
    338353            data = self.data 
    339         return widget.render(self.html_name, data, attrs=attrs) 
    340  
    341     def as_text(self, attrs=None): 
     354        if not only_initial: 
     355            name = self.html_name 
     356        else: 
     357            name = self.html_initial_name 
     358        return widget.render(name, data, attrs=attrs) 
     359         
     360    def as_text(self, attrs=None, **kwargs): 
    342361        """ 
    343362        Returns a string of HTML for representing this as an <input type="text">. 
    344363        """ 
    345         return self.as_widget(TextInput(), attrs
    346  
    347     def as_textarea(self, attrs=None): 
     364        return self.as_widget(TextInput(), attrs, **kwargs
     365 
     366    def as_textarea(self, attrs=None, **kwargs): 
    348367        "Returns a string of HTML for representing this as a <textarea>." 
    349         return self.as_widget(Textarea(), attrs
    350  
    351     def as_hidden(self, attrs=None): 
     368        return self.as_widget(Textarea(), attrs, **kwargs
     369 
     370    def as_hidden(self, attrs=None, **kwargs): 
    352371        """ 
    353372        Returns a string of HTML for representing this as an <input type="hidden">. 
    354373        """ 
    355         return self.as_widget(self.field.hidden_widget(), attrs
     374        return self.as_widget(self.field.hidden_widget(), attrs, **kwargs
    356375 
    357376    def _data(self): 
  • django/trunk/django/forms/widgets.py

    r8549 r8816  
    2626    'FileInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput', 
    2727    'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect', 
    28     'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget', 
     28    'CheckboxSelectMultiple', 'MultiWidget', 
     29    'SplitDateTimeWidget', 
    2930) 
    3031 
     
    618619            initial = [u'' for x in range(0, len(data))] 
    619620        else: 
    620             initial = self.decompress(initial) 
     621            if not isinstance(initial, list): 
     622                initial = self.decompress(initial) 
    621623        for widget, initial, data in zip(self.widgets, initial, data): 
    622624            if widget._has_changed(initial, data): 
     
    663665        return [None, None] 
    664666 
     667class SplitHiddenDateTimeWidget(SplitDateTimeWidget): 
     668    """ 
     669    A Widget that splits datetime input into two <input type="hidden"> inputs. 
     670    """ 
     671    def __init__(self, attrs=None): 
     672        widgets = (HiddenInput(attrs=attrs), HiddenInput(attrs=attrs)) 
     673        super(SplitDateTimeWidget, self).__init__(widgets, attrs) 
     674         
  • django/trunk/tests/modeltests/model_formsets/models.py

    r8805 r8816  
     1 
     2import datetime 
     3 
     4from django import forms 
    15from django.db import models 
    26 
     
    9397    serves_tacos = models.BooleanField() 
    9498 
     99# models for testing callable defaults (see bug #7975). If you define a model 
     100# with a callable default value, you cannot rely on the initial value in a 
     101# form. 
     102class Person(models.Model): 
     103    name = models.CharField(max_length=128) 
     104 
     105class Membership(models.Model): 
     106    person = models.ForeignKey(Person) 
     107    date_joined = models.DateTimeField(default=datetime.datetime.now) 
     108    karma = models.IntegerField() 
    95109 
    96110__test__ = {'API_TESTS': """ 
     
    622636[{'__all__': [u'Price with this Price and Quantity already exists.']}] 
    623637 
     638# Use of callable defaults (see bug #7975). 
     639 
     640>>> person = Person.objects.create(name='Ringo') 
     641>>> FormSet = inlineformset_factory(Person, Membership, can_delete=False, extra=1) 
     642>>> formset = FormSet(instance=person) 
     643 
     644# Django will render a hidden field for model fields that have a callable 
     645# default. This is required to ensure the value is tested for change correctly 
     646# when determine what extra forms have changed to save. 
     647 
     648>>> form = formset.forms[0] # this formset only has one form 
     649>>> now = form.fields['date_joined'].initial 
     650>>> print form.as_p() 
     651<p><label for="id_membership_set-0-date_joined">Date joined:</label> <input type="text" name="membership_set-0-date_joined" value="..." id="id_membership_set-0-date_joined" /><input type="hidden" name="initial-membership_set-0-date_joined" value="..." id="id_membership_set-0-date_joined" /></p> 
     652<p><label for="id_membership_set-0-karma">Karma:</label> <input type="text" name="membership_set-0-karma" id="id_membership_set-0-karma" /><input type="hidden" name="membership_set-0-id" id="id_membership_set-0-id" /></p> 
     653 
     654# test for validation with callable defaults. Validations rely on hidden fields 
     655 
     656>>> data = { 
     657...     'membership_set-TOTAL_FORMS': '1', 
     658...     'membership_set-INITIAL_FORMS': '0', 
     659...     'membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), 
     660...     'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), 
     661...     'membership_set-0-karma': '', 
     662... } 
     663>>> formset = FormSet(data, instance=person) 
     664>>> formset.is_valid() 
     665True 
     666 
     667# now test for when the data changes 
     668 
     669>>> one_day_later = now + datetime.timedelta(days=1) 
     670>>> filled_data = { 
     671...     'membership_set-TOTAL_FORMS': '1', 
     672...     'membership_set-INITIAL_FORMS': '0', 
     673...     'membership_set-0-date_joined': unicode(one_day_later.strftime('%Y-%m-%d %H:%M:%S')), 
     674...     'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), 
     675...     'membership_set-0-karma': '', 
     676... } 
     677>>> formset = FormSet(filled_data, instance=person) 
     678>>> formset.is_valid() 
     679False 
     680 
     681# now test with split datetime fields 
     682 
     683>>> class MembershipForm(forms.ModelForm): 
     684...     date_joined = forms.SplitDateTimeField(initial=now) 
     685...     class Meta: 
     686...         model = Membership 
     687...     def __init__(self, **kwargs): 
     688...         super(MembershipForm, self).__init__(**kwargs) 
     689...         self.fields['date_joined'].widget = forms.SplitDateTimeWidget() 
     690 
     691>>> FormSet = inlineformset_factory(Person, Membership, form=MembershipForm, can_delete=False, extra=1) 
     692>>> data = { 
     693...     'membership_set-TOTAL_FORMS': '1', 
     694...     'membership_set-INITIAL_FORMS': '0', 
     695...     'membership_set-0-date_joined_0': unicode(now.strftime('%Y-%m-%d')), 
     696...     'membership_set-0-date_joined_1': unicode(now.strftime('%H:%M:%S')), 
     697...     'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d %H:%M:%S')), 
     698...     'membership_set-0-karma': '', 
     699... } 
     700>>> formset = FormSet(data, instance=person) 
     701>>> formset.is_valid() 
     702True 
     703 
     704 
    624705"""} 
  • django/trunk/tests/regressiontests/forms/widgets.py

    r8601 r8816  
    10941094u'<input type="text" name="date" value="2007-09-17 12:51:00" />' 
    10951095 
    1096 # TimeInput ############################################################### 
     1096# TimeInput ################################################################### 
    10971097 
    10981098>>> w = TimeInput() 
     
    11141114>>> w.render('time', u'13:12:11') 
    11151115u'<input type="text" name="time" value="13:12:11" />' 
     1116 
     1117# SplitHiddenDateTimeWidget ################################################### 
     1118 
     1119>>> from django.forms.widgets import SplitHiddenDateTimeWidget 
     1120 
     1121>>> w = SplitHiddenDateTimeWidget() 
     1122>>> w.render('date', '') 
     1123u'<input type="hidden" name="date_0" /><input type="hidden" name="date_1" />' 
     1124>>> w.render('date', d) 
     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, 34)) 
     1127u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" name="date_1" value="12:51:34" />' 
     1128>>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51)) 
     1129u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" name="date_1" value="12:51:00" />' 
     1130 
    11161131""" 
    11171132