Fixed #14567 -- Made ModelMultipleChoiceField return EmptyQuerySet as empty value

This commit is contained in:
Stephen Burrows 2012-10-03 19:50:12 +03:00 committed by Anssi Kääriäinen
parent d25a599dca
commit 218abcc9e5
6 changed files with 40 additions and 7 deletions

View File

@ -1013,7 +1013,7 @@ class ModelMultipleChoiceField(ModelChoiceField):
if self.required and not value: if self.required and not value:
raise ValidationError(self.error_messages['required']) raise ValidationError(self.error_messages['required'])
elif not self.required and not value: elif not self.required and not value:
return [] return self.queryset.none()
if not isinstance(value, (list, tuple)): if not isinstance(value, (list, tuple)):
raise ValidationError(self.error_messages['list']) raise ValidationError(self.error_messages['list'])
key = self.to_field_name or 'pk' key = self.to_field_name or 'pk'

View File

@ -997,13 +997,17 @@ objects (in the case of ``ModelMultipleChoiceField``) into the
.. class:: ModelMultipleChoiceField(**kwargs) .. class:: ModelMultipleChoiceField(**kwargs)
* Default widget: ``SelectMultiple`` * Default widget: ``SelectMultiple``
* Empty value: ``[]`` (an empty list) * Empty value: An empty ``QuerySet`` (self.queryset.none())
* Normalizes to: A list of model instances. * Normalizes to: A ``QuerySet`` of model instances.
* Validates that every id in the given list of values exists in the * Validates that every id in the given list of values exists in the
queryset. queryset.
* Error message keys: ``required``, ``list``, ``invalid_choice``, * Error message keys: ``required``, ``list``, ``invalid_choice``,
``invalid_pk_value`` ``invalid_pk_value``
.. versionchanged:: 1.5
The empty and normalized values were changed to be consistently
``QuerySets`` instead of ``[]`` and ``QuerySet`` respectively.
Allows the selection of one or more model objects, suitable for Allows the selection of one or more model objects, suitable for
representing a many-to-many relation. As with :class:`ModelChoiceField`, representing a many-to-many relation. As with :class:`ModelChoiceField`,
you can use ``label_from_instance`` to customize the object you can use ``label_from_instance`` to customize the object

View File

@ -422,6 +422,9 @@ on the form.
Miscellaneous Miscellaneous
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
* :class:`django.forms.ModelMultipleChoiceField` now returns an empty
``QuerySet`` as the empty value instead of an empty list.
* :func:`~django.utils.http.int_to_base36` properly raises a :exc:`TypeError` * :func:`~django.utils.http.int_to_base36` properly raises a :exc:`TypeError`
instead of :exc:`ValueError` for non-integer inputs. instead of :exc:`ValueError` for non-integer inputs.

View File

@ -8,6 +8,7 @@ from django import forms
from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.validators import ValidationError from django.core.validators import ValidationError
from django.db import connection from django.db import connection
from django.db.models.query import EmptyQuerySet
from django.forms.models import model_to_dict from django.forms.models import model_to_dict
from django.utils.unittest import skipUnless from django.utils.unittest import skipUnless
from django.test import TestCase from django.test import TestCase
@ -1035,8 +1036,8 @@ class OldFormForXTests(TestCase):
f.clean([c6.id]) f.clean([c6.id])
f = forms.ModelMultipleChoiceField(Category.objects.all(), required=False) f = forms.ModelMultipleChoiceField(Category.objects.all(), required=False)
self.assertEqual(f.clean([]), []) self.assertIsInstance(f.clean([]), EmptyQuerySet)
self.assertEqual(f.clean(()), []) self.assertIsInstance(f.clean(()), EmptyQuerySet)
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
f.clean(['10']) f.clean(['10'])
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):

View File

@ -63,6 +63,12 @@ class ChoiceFieldModel(models.Model):
multi_choice_int = models.ManyToManyField(ChoiceOptionModel, blank=False, related_name='multi_choice_int', multi_choice_int = models.ManyToManyField(ChoiceOptionModel, blank=False, related_name='multi_choice_int',
default=lambda: [1]) default=lambda: [1])
class OptionalMultiChoiceModel(models.Model):
multi_choice = models.ManyToManyField(ChoiceOptionModel, blank=False, related_name='not_relevant',
default=lambda: ChoiceOptionModel.objects.filter(name='default'))
multi_choice_optional = models.ManyToManyField(ChoiceOptionModel, blank=True, null=True,
related_name='not_relevant2')
class FileModel(models.Model): class FileModel(models.Model):
file = models.FileField(storage=temp_storage, upload_to='tests') file = models.FileField(storage=temp_storage, upload_to='tests')

View File

@ -11,7 +11,7 @@ from django.test import TestCase
from django.utils import six from django.utils import six
from ..models import (ChoiceOptionModel, ChoiceFieldModel, FileModel, Group, from ..models import (ChoiceOptionModel, ChoiceFieldModel, FileModel, Group,
BoundaryModel, Defaults) BoundaryModel, Defaults, OptionalMultiChoiceModel)
class ChoiceFieldForm(ModelForm): class ChoiceFieldForm(ModelForm):
@ -19,6 +19,11 @@ class ChoiceFieldForm(ModelForm):
model = ChoiceFieldModel model = ChoiceFieldModel
class OptionalMultiChoiceModelForm(ModelForm):
class Meta:
model = OptionalMultiChoiceModel
class FileForm(Form): class FileForm(Form):
file1 = FileField() file1 = FileField()
@ -34,6 +39,21 @@ class TestTicket12510(TestCase):
field = ModelChoiceField(Group.objects.order_by('-name')) field = ModelChoiceField(Group.objects.order_by('-name'))
self.assertEqual('a', field.clean(self.groups[0].pk).name) self.assertEqual('a', field.clean(self.groups[0].pk).name)
class TestTicket14567(TestCase):
"""
Check that the return values of ModelMultipleChoiceFields are QuerySets
"""
def test_empty_queryset_return(self):
"If a model's ManyToManyField has blank=True and is saved with no data, a queryset is returned."
form = OptionalMultiChoiceModelForm({'multi_choice_optional': '', 'multi_choice': ['1']})
self.assertTrue(form.is_valid())
# Check that the empty value is a QuerySet
self.assertTrue(isinstance(form.cleaned_data['multi_choice_optional'], models.query.QuerySet))
# While we're at it, test whether a QuerySet is returned if there *is* a value.
self.assertTrue(isinstance(form.cleaned_data['multi_choice'], models.query.QuerySet))
class ModelFormCallableModelDefault(TestCase): class ModelFormCallableModelDefault(TestCase):
def test_no_empty_option(self): def test_no_empty_option(self):
"If a model's ForeignKey has blank=False and a default, no empty option is created (Refs #10792)." "If a model's ForeignKey has blank=False and a default, no empty option is created (Refs #10792)."
@ -103,7 +123,6 @@ class ModelFormCallableModelDefault(TestCase):
<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>""") <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>""")
class FormsModelTestCase(TestCase): class FormsModelTestCase(TestCase):
def test_unicode_filename(self): def test_unicode_filename(self):
# FileModel with unicode filename and data ######################### # FileModel with unicode filename and data #########################