Author: russellm
Date: 2010-08-14 07:07:25 -0500 (Sat, 14 Aug 2010)
New Revision: 13578

Modified:
   django/branches/releases/1.2.X/django/forms/fields.py
   django/branches/releases/1.2.X/django/forms/forms.py
   django/branches/releases/1.2.X/django/forms/models.py
   django/branches/releases/1.2.X/django/forms/widgets.py
   django/branches/releases/1.2.X/tests/regressiontests/forms/models.py
Log:
[1.2.X] Fixed #13679, #13231, #7287 -- Ensured that models that have 
ForeignKeys/ManyToManyField can use a a callable default that returns a model 
instance/queryset. #13679 was a regression in behavior; the other two tickets 
are pleasant side effects. Thanks to 3point2 for the report.

Backport of r13577 from trunk.

Modified: django/branches/releases/1.2.X/django/forms/fields.py
===================================================================
--- django/branches/releases/1.2.X/django/forms/fields.py       2010-08-14 
12:05:41 UTC (rev 13577)
+++ django/branches/releases/1.2.X/django/forms/fields.py       2010-08-14 
12:07:25 UTC (rev 13578)
@@ -127,6 +127,9 @@
 
         self.validators = self.default_validators + validators
 
+    def prepare_value(self, value):
+        return value
+
     def to_python(self, value):
         return value
 

Modified: django/branches/releases/1.2.X/django/forms/forms.py
===================================================================
--- django/branches/releases/1.2.X/django/forms/forms.py        2010-08-14 
12:05:41 UTC (rev 13577)
+++ django/branches/releases/1.2.X/django/forms/forms.py        2010-08-14 
12:07:25 UTC (rev 13578)
@@ -18,10 +18,10 @@
 NON_FIELD_ERRORS = '__all__'
 
 def pretty_name(name):
-    """Converts 'first_name' to 'First name'""" 
-    if not name: 
-        return u'' 
-    return name.replace('_', ' ').capitalize() 
+    """Converts 'first_name' to 'First name'"""
+    if not name:
+        return u''
+    return name.replace('_', ' ').capitalize()
 
 def get_declared_fields(bases, attrs, with_base_fields=True):
     """
@@ -423,6 +423,7 @@
         """
         if not widget:
             widget = self.field.widget
+
         attrs = attrs or {}
         auto_id = self.auto_id
         if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
@@ -430,6 +431,7 @@
                 attrs['id'] = auto_id
             else:
                 attrs['id'] = self.html_initial_id
+
         if not self.form.is_bound:
             data = self.form.initial.get(self.name, self.field.initial)
             if callable(data):
@@ -439,6 +441,8 @@
                 data = self.form.initial.get(self.name, self.field.initial)
             else:
                 data = self.data
+        data = self.field.prepare_value(data)
+
         if not only_initial:
             name = self.html_name
         else:

Modified: django/branches/releases/1.2.X/django/forms/models.py
===================================================================
--- django/branches/releases/1.2.X/django/forms/models.py       2010-08-14 
12:05:41 UTC (rev 13577)
+++ django/branches/releases/1.2.X/django/forms/models.py       2010-08-14 
12:07:25 UTC (rev 13578)
@@ -906,13 +906,8 @@
         return len(self.queryset)
 
     def choice(self, obj):
-        if self.field.to_field_name:
-            key = obj.serializable_value(self.field.to_field_name)
-        else:
-            key = obj.pk
-        return (key, self.field.label_from_instance(obj))
+        return (self.field.prepare_value(obj), 
self.field.label_from_instance(obj))
 
-
 class ModelChoiceField(ChoiceField):
     """A ChoiceField whose choices are a model QuerySet."""
     # This class is a subclass of ChoiceField for purity, but it doesn't
@@ -971,8 +966,8 @@
             return self._choices
 
         # Otherwise, execute the QuerySet in self.queryset to determine the
-        # choices dynamically. Return a fresh QuerySetIterator that has not 
been
-        # consumed. Note that we're instantiating a new QuerySetIterator *each*
+        # choices dynamically. Return a fresh ModelChoiceIterator that has not 
been
+        # consumed. Note that we're instantiating a new ModelChoiceIterator 
*each*
         # time _get_choices() is called (and, thus, each time self.choices is
         # accessed) so that we can ensure the QuerySet has not been consumed. 
This
         # construct might look complicated but it allows for lazy evaluation of
@@ -981,6 +976,14 @@
 
     choices = property(_get_choices, ChoiceField._set_choices)
 
+    def prepare_value(self, value):
+        if hasattr(value, '_meta'):
+            if self.to_field_name:
+                return value.serializable_value(self.to_field_name)
+            else:
+                return value.pk
+        return super(ModelChoiceField, self).prepare_value(value)
+
     def to_python(self, value):
         if value in EMPTY_VALUES:
             return None
@@ -1030,3 +1033,8 @@
             if force_unicode(val) not in pks:
                 raise ValidationError(self.error_messages['invalid_choice'] % 
val)
         return qs
+
+    def prepare_value(self, value):
+        if hasattr(value, '__iter__'):
+            return [super(ModelMultipleChoiceField, self).prepare_value(v) for 
v in value]
+        return super(ModelMultipleChoiceField, self).prepare_value(value)

Modified: django/branches/releases/1.2.X/django/forms/widgets.py
===================================================================
--- django/branches/releases/1.2.X/django/forms/widgets.py      2010-08-14 
12:05:41 UTC (rev 13577)
+++ django/branches/releases/1.2.X/django/forms/widgets.py      2010-08-14 
12:07:25 UTC (rev 13578)
@@ -450,13 +450,14 @@
         output.append(u'</select>')
         return mark_safe(u'\n'.join(output))
 
+    def render_option(self, selected_choices, option_value, option_label):
+        option_value = force_unicode(option_value)
+        selected_html = (option_value in selected_choices) and u' 
selected="selected"' or ''
+        return u'<option value="%s"%s>%s</option>' % (
+            escape(option_value), selected_html,
+            conditional_escape(force_unicode(option_label)))
+
     def render_options(self, choices, selected_choices):
-        def render_option(option_value, option_label):
-            option_value = force_unicode(option_value)
-            selected_html = (option_value in selected_choices) and u' 
selected="selected"' or ''
-            return u'<option value="%s"%s>%s</option>' % (
-                escape(option_value), selected_html,
-                conditional_escape(force_unicode(option_label)))
         # Normalize to strings.
         selected_choices = set([force_unicode(v) for v in selected_choices])
         output = []
@@ -464,10 +465,10 @@
             if isinstance(option_label, (list, tuple)):
                 output.append(u'<optgroup label="%s">' % 
escape(force_unicode(option_value)))
                 for option in option_label:
-                    output.append(render_option(*option))
+                    output.append(self.render_option(selected_choices, 
*option))
                 output.append(u'</optgroup>')
             else:
-                output.append(render_option(option_value, option_label))
+                output.append(self.render_option(selected_choices, 
option_value, option_label))
         return u'\n'.join(output)
 
 class NullBooleanSelect(Select):

Modified: django/branches/releases/1.2.X/tests/regressiontests/forms/models.py
===================================================================
--- django/branches/releases/1.2.X/tests/regressiontests/forms/models.py        
2010-08-14 12:05:41 UTC (rev 13577)
+++ django/branches/releases/1.2.X/tests/regressiontests/forms/models.py        
2010-08-14 12:07:25 UTC (rev 13578)
@@ -38,12 +38,29 @@
     Can't reuse ChoiceModel because error_message tests require that it have 
no instances."""
     name = models.CharField(max_length=10)
 
+    class Meta:
+        ordering = ('name',)
+
+    def __unicode__(self):
+        return u'ChoiceOption %d' % self.pk
+
 class ChoiceFieldModel(models.Model):
     """Model with ForeignKey to another model, for testing ModelForm
     generation with ModelChoiceField."""
     choice = models.ForeignKey(ChoiceOptionModel, blank=False,
-                               default=lambda: 
ChoiceOptionModel.objects.all()[0])
+                               default=lambda: 
ChoiceOptionModel.objects.get(name='default'))
+    choice_int = models.ForeignKey(ChoiceOptionModel, blank=False, 
related_name='choice_int',
+                                   default=lambda: 1)
 
+    multi_choice = models.ManyToManyField(ChoiceOptionModel, blank=False, 
related_name='multi_choice',
+                                          default=lambda: 
ChoiceOptionModel.objects.filter(name='default'))
+    multi_choice_int = models.ManyToManyField(ChoiceOptionModel, blank=False, 
related_name='multi_choice_int',
+                                              default=lambda: [1])
+
+class ChoiceFieldForm(django_forms.ModelForm):
+    class Meta:
+        model = ChoiceFieldModel
+
 class FileModel(models.Model):
     file = models.FileField(storage=temp_storage, upload_to='tests')
 
@@ -74,7 +91,75 @@
         # only one query is required to pull the model from DB
         self.assertEqual(initial_queries+1, len(connection.queries))
 
+class ModelFormCallableModelDefault(TestCase):
+    def test_no_empty_option(self):
+        "If a model's ForeignKey has blank=False and a default, no empty 
option is created (Refs #10792)."
+        option = ChoiceOptionModel.objects.create(name='default')
 
+        choices = list(ChoiceFieldForm().fields['choice'].choices)
+        self.assertEquals(len(choices), 1)
+        self.assertEquals(choices[0], (option.pk, unicode(option)))
+
+    def test_callable_initial_value(self):
+        "The initial value for a callable default returning a queryset is the 
pk (refs #13769)"
+        obj1 = ChoiceOptionModel.objects.create(id=1, name='default')
+        obj2 = ChoiceOptionModel.objects.create(id=2, name='option 2')
+        obj3 = ChoiceOptionModel.objects.create(id=3, name='option 3')
+        self.assertEquals(ChoiceFieldForm().as_p(), """<p><label 
for="id_choice">Choice:</label> <select name="choice" id="id_choice">
+<option value="1" selected="selected">ChoiceOption 1</option>
+<option value="2">ChoiceOption 2</option>
+<option value="3">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-choice" value="1" 
id="initial-id_choice" /></p>
+<p><label for="id_choice_int">Choice int:</label> <select name="choice_int" 
id="id_choice_int">
+<option value="1" selected="selected">ChoiceOption 1</option>
+<option value="2">ChoiceOption 2</option>
+<option value="3">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-choice_int" value="1" 
id="initial-id_choice_int" /></p>
+<p><label for="id_multi_choice">Multi choice:</label> <select 
multiple="multiple" name="multi_choice" id="id_multi_choice">
+<option value="1" selected="selected">ChoiceOption 1</option>
+<option value="2">ChoiceOption 2</option>
+<option value="3">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-multi_choice" value="1" 
id="initial-id_multi_choice_0" /> <span class="helptext"> Hold down "Control", 
or "Command" on a Mac, to select more than one.</span></p>
+<p><label for="id_multi_choice_int">Multi choice int:</label> <select 
multiple="multiple" name="multi_choice_int" id="id_multi_choice_int">
+<option value="1" selected="selected">ChoiceOption 1</option>
+<option value="2">ChoiceOption 2</option>
+<option value="3">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-multi_choice_int" value="1" 
id="initial-id_multi_choice_int_0" /> <span class="helptext"> Hold down 
"Control", or "Command" on a Mac, to select more than one.</span></p>""")
+
+    def test_initial_instance_value(self):
+        "Initial instances for model fields may also be instances (refs #7287)"
+        obj1 = ChoiceOptionModel.objects.create(id=1, name='default')
+        obj2 = ChoiceOptionModel.objects.create(id=2, name='option 2')
+        obj3 = ChoiceOptionModel.objects.create(id=3, name='option 3')
+        self.assertEquals(ChoiceFieldForm(initial={
+                'choice': obj2,
+                'choice_int': obj2,
+                'multi_choice': [obj2,obj3],
+                'multi_choice_int': 
ChoiceOptionModel.objects.exclude(name="default"),
+            }).as_p(), """<p><label for="id_choice">Choice:</label> <select 
name="choice" id="id_choice">
+<option value="1">ChoiceOption 1</option>
+<option value="2" selected="selected">ChoiceOption 2</option>
+<option value="3">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-choice" value="2" 
id="initial-id_choice" /></p>
+<p><label for="id_choice_int">Choice int:</label> <select name="choice_int" 
id="id_choice_int">
+<option value="1">ChoiceOption 1</option>
+<option value="2" selected="selected">ChoiceOption 2</option>
+<option value="3">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-choice_int" value="2" 
id="initial-id_choice_int" /></p>
+<p><label for="id_multi_choice">Multi choice:</label> <select 
multiple="multiple" name="multi_choice" id="id_multi_choice">
+<option value="1">ChoiceOption 1</option>
+<option value="2" selected="selected">ChoiceOption 2</option>
+<option value="3" selected="selected">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-multi_choice" value="2" 
id="initial-id_multi_choice_0" />
+<input type="hidden" name="initial-multi_choice" value="3" 
id="initial-id_multi_choice_1" /> <span class="helptext"> Hold down "Control", 
or "Command" on a Mac, to select more than one.</span></p>
+<p><label for="id_multi_choice_int">Multi choice int:</label> <select 
multiple="multiple" name="multi_choice_int" id="id_multi_choice_int">
+<option value="1">ChoiceOption 1</option>
+<option value="2" selected="selected">ChoiceOption 2</option>
+<option value="3" selected="selected">ChoiceOption 3</option>
+</select><input type="hidden" name="initial-multi_choice_int" value="2" 
id="initial-id_multi_choice_int_0" />
+<input type="hidden" name="initial-multi_choice_int" value="3" 
id="initial-id_multi_choice_int_1" /> <span class="helptext"> Hold down 
"Control", or "Command" on a Mac, to select more than one.</span></p>""")
+
+
 __test__ = {'API_TESTS': """
 >>> from django.forms.models import ModelForm
 >>> from django.core.files.uploadedfile import SimpleUploadedFile
@@ -155,18 +240,5 @@
 datetime.date(1999, 3, 2)
 >>> shutil.rmtree(temp_storage_location)
 
-In a ModelForm with a ModelChoiceField, if the model's ForeignKey has 
blank=False and a default,
-no empty option is created (regression test for #10792).
 
-First we need at least one instance of ChoiceOptionModel:
-
->>> ChoiceOptionModel.objects.create(name='default')
-<ChoiceOptionModel: ChoiceOptionModel object>
-
->>> class ChoiceFieldForm(ModelForm):
-...     class Meta:
-...         model = ChoiceFieldModel
->>> list(ChoiceFieldForm().fields['choice'].choices)
-[(1, u'ChoiceOptionModel object')]
-
 """}

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to django-upda...@googlegroups.com.
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to