Author: brosner
Date: 2008-09-01 16:28:32 -0500 (Mon, 01 Sep 2008)
New Revision: 8816

Modified:
   django/trunk/django/db/models/fields/__init__.py
   django/trunk/django/forms/fields.py
   django/trunk/django/forms/forms.py
   django/trunk/django/forms/widgets.py
   django/trunk/tests/modeltests/model_formsets/models.py
   django/trunk/tests/regressiontests/forms/widgets.py
Log:
Fixed #7975 -- Callable defaults in inline model formsets now work correctly. 
Based on patch from msaelices. Thanks for your hard work msaelices.

Modified: django/trunk/django/db/models/fields/__init__.py
===================================================================
--- django/trunk/django/db/models/fields/__init__.py    2008-09-01 21:16:17 UTC 
(rev 8815)
+++ django/trunk/django/db/models/fields/__init__.py    2008-09-01 21:28:32 UTC 
(rev 8816)
@@ -231,7 +231,7 @@
 
     def get_default(self):
         "Returns the default value for this field."
-        if self.default is not NOT_PROVIDED:
+        if self.has_default():
             if callable(self.default):
                 return self.default()
             return force_unicode(self.default, strings_only=True)
@@ -306,7 +306,8 @@
         defaults = {'required': not self.blank, 'label': 
capfirst(self.verbose_name), 'help_text': self.help_text}
         if self.has_default():
             defaults['initial'] = self.get_default()
-
+            if callable(self.default):
+                defaults['show_hidden_initial'] = True
         if self.choices:
             # Fields with choices get special treatment. 
             include_blank = self.blank or not (self.has_default() or 'initial' 
in kwargs)
@@ -314,9 +315,7 @@
             defaults['coerce'] = self.to_python
             if self.null:
                 defaults['empty_value'] = None
-            
             form_class = forms.TypedChoiceField
-            
             # Many of the subclass-specific formfield arguments (min_value,
             # max_value) don't apply for choice fields, so be sure to only pass
             # the values that TypedChoiceField will understand.
@@ -325,7 +324,6 @@
                              'widget', 'label', 'initial', 'help_text',
                              'error_messages'):
                     del kwargs[k]
-        
         defaults.update(kwargs)
         return form_class(**defaults)
 

Modified: django/trunk/django/forms/fields.py
===================================================================
--- django/trunk/django/forms/fields.py 2008-09-01 21:16:17 UTC (rev 8815)
+++ django/trunk/django/forms/fields.py 2008-09-01 21:28:32 UTC (rev 8816)
@@ -28,7 +28,7 @@
 from django.utils.encoding import smart_unicode, smart_str
 
 from util import ErrorList, ValidationError
-from widgets import TextInput, PasswordInput, HiddenInput, 
MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, 
SelectMultiple, DateTimeInput, TimeInput
+from widgets import TextInput, PasswordInput, HiddenInput, 
MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, 
SelectMultiple, DateTimeInput, TimeInput, SplitHiddenDateTimeWidget
 from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile
 
 __all__ = (
@@ -59,7 +59,7 @@
     creation_counter = 0
 
     def __init__(self, required=True, widget=None, label=None, initial=None,
-                 help_text=None, error_messages=None):
+                 help_text=None, error_messages=None, 
show_hidden_initial=False):
         # required -- Boolean that specifies whether the field is required.
         #             True by default.
         # widget -- A Widget class, or instance of a Widget class, that should
@@ -73,9 +73,12 @@
         # initial -- A value to use in this Field's initial display. This value
         #            is *not* used as a fallback if data isn't given.
         # help_text -- An optional string to use as "help text" for this Field.
+        # show_hidden_initial -- Boolean that specifies if it is needed to 
render a
+        #                        hidden widget with initial value after widget.
         if label is not None:
             label = smart_unicode(label)
         self.required, self.label, self.initial = required, label, initial
+        self.show_hidden_initial = show_hidden_initial
         if help_text is None:
             self.help_text = u''
         else:
@@ -840,6 +843,7 @@
         self.widget.choices = self.choices
 
 class SplitDateTimeField(MultiValueField):
+    hidden_widget = SplitHiddenDateTimeWidget
     default_error_messages = {
         'invalid_date': _(u'Enter a valid date.'),
         'invalid_time': _(u'Enter a valid time.'),

Modified: django/trunk/django/forms/forms.py
===================================================================
--- django/trunk/django/forms/forms.py  2008-09-01 21:16:17 UTC (rev 8815)
+++ django/trunk/django/forms/forms.py  2008-09-01 21:28:32 UTC (rev 8816)
@@ -128,6 +128,12 @@
         """
         return self.prefix and ('%s-%s' % (self.prefix, field_name)) or 
field_name
 
+    def add_initial_prefix(self, field_name):
+        """
+        Add a 'initial' prefix for checking dynamic initial values
+        """
+        return u'initial-%s' % self.add_prefix(field_name)
+
     def _html_output(self, normal_row, error_row, row_ender, help_text_html, 
errors_on_separate_row):
         "Helper function for outputting HTML. Used by as_table(), as_ul(), 
as_p()."
         top_errors = self.non_field_errors() # Errors that should be displayed 
above all fields.
@@ -245,7 +251,7 @@
         Returns True if data differs from initial.
         """
         return bool(self.changed_data)
-    
+
     def _get_changed_data(self):
         if self._changed_data is None:
             self._changed_data = []
@@ -258,7 +264,13 @@
             for name, field in self.fields.items():
                 prefixed_name = self.add_prefix(name)
                 data_value = field.widget.value_from_datadict(self.data, 
self.files, prefixed_name)
-                initial_value = self.initial.get(name, field.initial)
+                if not field.show_hidden_initial:
+                    initial_value = self.initial.get(name, field.initial)
+                else:
+                    initial_prefixed_name = self.add_initial_prefix(name)
+                    hidden_widget = field.hidden_widget()
+                    initial_value = hidden_widget.value_from_datadict(
+                        self.data, self.files, initial_prefixed_name)
                 if field.widget._has_changed(initial_value, data_value):
                     self._changed_data.append(name)
         return self._changed_data
@@ -300,6 +312,7 @@
         self.field = field
         self.name = name
         self.html_name = form.add_prefix(name)
+        self.html_initial_name = form.add_initial_prefix(name)
         if self.field.label is None:
             self.label = pretty_name(name)
         else:
@@ -308,6 +321,8 @@
 
     def __unicode__(self):
         """Renders this field as an HTML widget."""
+        if self.field.show_hidden_initial:
+            return self.as_widget() + self.as_hidden(only_initial=True)
         return self.as_widget()
 
     def _errors(self):
@@ -318,7 +333,7 @@
         return self.form.errors.get(self.name, self.form.error_class())
     errors = property(_errors)
 
-    def as_widget(self, widget=None, attrs=None):
+    def as_widget(self, widget=None, attrs=None, only_initial=False):
         """
         Renders the field by rendering the passed widget, adding any HTML
         attributes passed as attrs.  If no widget is specified, then the
@@ -330,29 +345,33 @@
         auto_id = self.auto_id
         if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
             attrs['id'] = auto_id
-        if not self.form.is_bound:
+        if not self.form.is_bound or only_initial:
             data = self.form.initial.get(self.name, self.field.initial)
             if callable(data):
                 data = data()
         else:
             data = self.data
-        return widget.render(self.html_name, data, attrs=attrs)
-
-    def as_text(self, attrs=None):
+        if not only_initial:
+            name = self.html_name
+        else:
+            name = self.html_initial_name
+        return widget.render(name, data, attrs=attrs)
+        
+    def as_text(self, attrs=None, **kwargs):
         """
         Returns a string of HTML for representing this as an <input 
type="text">.
         """
-        return self.as_widget(TextInput(), attrs)
+        return self.as_widget(TextInput(), attrs, **kwargs)
 
-    def as_textarea(self, attrs=None):
+    def as_textarea(self, attrs=None, **kwargs):
         "Returns a string of HTML for representing this as a <textarea>."
-        return self.as_widget(Textarea(), attrs)
+        return self.as_widget(Textarea(), attrs, **kwargs)
 
-    def as_hidden(self, attrs=None):
+    def as_hidden(self, attrs=None, **kwargs):
         """
         Returns a string of HTML for representing this as an <input 
type="hidden">.
         """
-        return self.as_widget(self.field.hidden_widget(), attrs)
+        return self.as_widget(self.field.hidden_widget(), attrs, **kwargs)
 
     def _data(self):
         """

Modified: django/trunk/django/forms/widgets.py
===================================================================
--- django/trunk/django/forms/widgets.py        2008-09-01 21:16:17 UTC (rev 
8815)
+++ django/trunk/django/forms/widgets.py        2008-09-01 21:28:32 UTC (rev 
8816)
@@ -25,7 +25,8 @@
     'HiddenInput', 'MultipleHiddenInput',
     'FileInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput',
     'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
-    'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
+    'CheckboxSelectMultiple', 'MultiWidget',
+    'SplitDateTimeWidget',
 )
 
 MEDIA_TYPES = ('css','js')
@@ -617,7 +618,8 @@
         if initial is None:
             initial = [u'' for x in range(0, len(data))]
         else:
-            initial = self.decompress(initial)
+            if not isinstance(initial, list):
+                initial = self.decompress(initial)
         for widget, initial, data in zip(self.widgets, initial, data):
             if widget._has_changed(initial, data):
                 return True
@@ -662,3 +664,11 @@
             return [value.date(), value.time().replace(microsecond=0)]
         return [None, None]
 
+class SplitHiddenDateTimeWidget(SplitDateTimeWidget):
+    """
+    A Widget that splits datetime input into two <input type="hidden"> inputs.
+    """
+    def __init__(self, attrs=None):
+        widgets = (HiddenInput(attrs=attrs), HiddenInput(attrs=attrs))
+        super(SplitDateTimeWidget, self).__init__(widgets, attrs)
+        
\ No newline at end of file

Modified: django/trunk/tests/modeltests/model_formsets/models.py
===================================================================
--- django/trunk/tests/modeltests/model_formsets/models.py      2008-09-01 
21:16:17 UTC (rev 8815)
+++ django/trunk/tests/modeltests/model_formsets/models.py      2008-09-01 
21:28:32 UTC (rev 8816)
@@ -1,3 +1,7 @@
+
+import datetime
+
+from django import forms
 from django.db import models
 
 try:
@@ -92,7 +96,17 @@
 class MexicanRestaurant(Restaurant):
     serves_tacos = models.BooleanField()
 
+# models for testing callable defaults (see bug #7975). If you define a model
+# with a callable default value, you cannot rely on the initial value in a
+# form.
+class Person(models.Model):
+    name = models.CharField(max_length=128)
 
+class Membership(models.Model):
+    person = models.ForeignKey(Person)
+    date_joined = models.DateTimeField(default=datetime.datetime.now)
+    karma = models.IntegerField()
+
 __test__ = {'API_TESTS': """
 
 >>> from datetime import date
@@ -621,4 +635,71 @@
 >>> formset.errors
 [{'__all__': [u'Price with this Price and Quantity already exists.']}]
 
+# Use of callable defaults (see bug #7975).
+
+>>> person = Person.objects.create(name='Ringo')
+>>> FormSet = inlineformset_factory(Person, Membership, can_delete=False, 
extra=1)
+>>> formset = FormSet(instance=person)
+
+# Django will render a hidden field for model fields that have a callable
+# default. This is required to ensure the value is tested for change correctly
+# when determine what extra forms have changed to save.
+
+>>> form = formset.forms[0] # this formset only has one form
+>>> now = form.fields['date_joined'].initial
+>>> print form.as_p()
+<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>
+<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>
+
+# test for validation with callable defaults. Validations rely on hidden fields
+
+>>> data = {
+...     'membership_set-TOTAL_FORMS': '1',
+...     'membership_set-INITIAL_FORMS': '0',
+...     'membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d 
%H:%M:%S')),
+...     'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d 
%H:%M:%S')),
+...     'membership_set-0-karma': '',
+... }
+>>> formset = FormSet(data, instance=person)
+>>> formset.is_valid()
+True
+
+# now test for when the data changes
+
+>>> one_day_later = now + datetime.timedelta(days=1)
+>>> filled_data = {
+...     'membership_set-TOTAL_FORMS': '1',
+...     'membership_set-INITIAL_FORMS': '0',
+...     'membership_set-0-date_joined': 
unicode(one_day_later.strftime('%Y-%m-%d %H:%M:%S')),
+...     'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d 
%H:%M:%S')),
+...     'membership_set-0-karma': '',
+... }
+>>> formset = FormSet(filled_data, instance=person)
+>>> formset.is_valid()
+False
+
+# now test with split datetime fields
+
+>>> class MembershipForm(forms.ModelForm):
+...     date_joined = forms.SplitDateTimeField(initial=now)
+...     class Meta:
+...         model = Membership
+...     def __init__(self, **kwargs):
+...         super(MembershipForm, self).__init__(**kwargs)
+...         self.fields['date_joined'].widget = forms.SplitDateTimeWidget()
+
+>>> FormSet = inlineformset_factory(Person, Membership, form=MembershipForm, 
can_delete=False, extra=1)
+>>> data = {
+...     'membership_set-TOTAL_FORMS': '1',
+...     'membership_set-INITIAL_FORMS': '0',
+...     'membership_set-0-date_joined_0': unicode(now.strftime('%Y-%m-%d')),
+...     'membership_set-0-date_joined_1': unicode(now.strftime('%H:%M:%S')),
+...     'initial-membership_set-0-date_joined': unicode(now.strftime('%Y-%m-%d 
%H:%M:%S')),
+...     'membership_set-0-karma': '',
+... }
+>>> formset = FormSet(data, instance=person)
+>>> formset.is_valid()
+True
+
+
 """}

Modified: django/trunk/tests/regressiontests/forms/widgets.py
===================================================================
--- django/trunk/tests/regressiontests/forms/widgets.py 2008-09-01 21:16:17 UTC 
(rev 8815)
+++ django/trunk/tests/regressiontests/forms/widgets.py 2008-09-01 21:28:32 UTC 
(rev 8816)
@@ -1093,7 +1093,7 @@
 >>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51))
 u'<input type="text" name="date" value="2007-09-17 12:51:00" />'
 
-# TimeInput ###############################################################
+# TimeInput ###################################################################
 
 >>> w = TimeInput()
 >>> w.render('time', None)
@@ -1113,5 +1113,20 @@
 We should be able to initialize from a unicode value.
 >>> w.render('time', u'13:12:11')
 u'<input type="text" name="time" value="13:12:11" />'
+
+# SplitHiddenDateTimeWidget ###################################################
+
+>>> from django.forms.widgets import SplitHiddenDateTimeWidget
+
+>>> w = SplitHiddenDateTimeWidget()
+>>> w.render('date', '')
+u'<input type="hidden" name="date_0" /><input type="hidden" name="date_1" />'
+>>> w.render('date', d)
+u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" 
name="date_1" value="12:51:34" />'
+>>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51, 34))
+u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" 
name="date_1" value="12:51:34" />'
+>>> w.render('date', datetime.datetime(2007, 9, 17, 12, 51))
+u'<input type="hidden" name="date_0" value="2007-09-17" /><input type="hidden" 
name="date_1" value="12:51:00" />'
+
 """
 


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to