Fixed #14567 -- Made ModelMultipleChoiceField return EmptyQuerySet as empty value
This commit is contained in:
parent
d25a599dca
commit
218abcc9e5
|
@ -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'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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 #########################
|
||||||
|
|
Loading…
Reference in New Issue