1
0
mirror of https://github.com/django/django.git synced 2025-10-23 21:59:11 +00:00

Consolidated ModelChoiceField tests.

This commit is contained in:
François Freitag
2018-02-24 19:38:23 -08:00
committed by Tim Graham
parent 94a180402c
commit 06172d7bc2
4 changed files with 279 additions and 287 deletions

View File

@@ -12,10 +12,9 @@ from django.core.validators import ValidationError
from django.db import connection, models
from django.db.models.query import EmptyQuerySet
from django.forms.models import (
ModelChoiceIterator, ModelFormMetaclass, construct_instance,
fields_for_model, model_to_dict, modelform_factory,
ModelFormMetaclass, construct_instance, fields_for_model, model_to_dict,
modelform_factory,
)
from django.forms.widgets import CheckboxSelectMultiple
from django.template import Context, Template
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
@@ -1557,261 +1556,6 @@ class ModelFormBasicTests(TestCase):
obj.full_clean()
class ModelChoiceFieldTests(TestCase):
def setUp(self):
self.c1 = Category.objects.create(
name="Entertainment", slug="entertainment", url="entertainment")
self.c2 = Category.objects.create(
name="It's a test", slug="its-test", url="test")
self.c3 = Category.objects.create(
name="Third", slug="third-test", url="third")
# ModelChoiceField ############################################################
def test_modelchoicefield(self):
f = forms.ModelChoiceField(Category.objects.all())
self.assertEqual(list(f.choices), [
('', '---------'),
(self.c1.pk, 'Entertainment'),
(self.c2.pk, "It's a test"),
(self.c3.pk, 'Third')])
with self.assertRaises(ValidationError):
f.clean('')
with self.assertRaises(ValidationError):
f.clean(None)
with self.assertRaises(ValidationError):
f.clean(0)
# Invalid types that require TypeError to be caught (#22808).
with self.assertRaises(ValidationError):
f.clean([['fail']])
with self.assertRaises(ValidationError):
f.clean([{'foo': 'bar'}])
self.assertEqual(f.clean(self.c2.id).name, "It's a test")
self.assertEqual(f.clean(self.c3.id).name, 'Third')
# Add a Category object *after* the ModelChoiceField has already been
# instantiated. This proves clean() checks the database during clean() rather
# than caching it at time of instantiation.
c4 = Category.objects.create(name='Fourth', url='4th')
self.assertEqual(f.clean(c4.id).name, 'Fourth')
# Delete a Category object *after* the ModelChoiceField has already been
# instantiated. This proves clean() checks the database during clean() rather
# than caching it at time of instantiation.
Category.objects.get(url='4th').delete()
msg = "['Select a valid choice. That choice is not one of the available choices.']"
with self.assertRaisesMessage(ValidationError, msg):
f.clean(c4.id)
def test_modelchoicefield_choices(self):
f = forms.ModelChoiceField(Category.objects.filter(pk=self.c1.id), required=False)
self.assertIsNone(f.clean(''))
self.assertEqual(f.clean(str(self.c1.id)).name, "Entertainment")
with self.assertRaises(ValidationError):
f.clean('100')
# len can be called on choices
self.assertEqual(len(f.choices), 2)
# queryset can be changed after the field is created.
f.queryset = Category.objects.exclude(name='Third')
self.assertEqual(list(f.choices), [
('', '---------'),
(self.c1.pk, 'Entertainment'),
(self.c2.pk, "It's a test")])
self.assertEqual(f.clean(self.c2.id).name, "It's a test")
with self.assertRaises(ValidationError):
f.clean(self.c3.id)
# check that we can safely iterate choices repeatedly
gen_one = list(f.choices)
gen_two = f.choices
self.assertEqual(gen_one[2], (self.c2.pk, "It's a test"))
self.assertEqual(list(gen_two), [
('', '---------'),
(self.c1.pk, 'Entertainment'),
(self.c2.pk, "It's a test")])
# check that we can override the label_from_instance method to print custom labels (#4620)
f.queryset = Category.objects.all()
f.label_from_instance = lambda obj: "category " + str(obj)
self.assertEqual(list(f.choices), [
('', '---------'),
(self.c1.pk, 'category Entertainment'),
(self.c2.pk, "category It's a test"),
(self.c3.pk, 'category Third')])
def test_modelchoicefield_11183(self):
"""
Regression test for ticket #11183.
"""
class ModelChoiceForm(forms.Form):
category = forms.ModelChoiceField(Category.objects.all())
form1 = ModelChoiceForm()
field1 = form1.fields['category']
# To allow the widget to change the queryset of field1.widget.choices correctly,
# without affecting other forms, the following must hold:
self.assertIsNot(field1, ModelChoiceForm.base_fields['category'])
self.assertIs(field1.widget.choices.field, field1)
def test_modelchoicefield_result_cache_not_shared(self):
class ModelChoiceForm(forms.Form):
category = forms.ModelChoiceField(Category.objects.all())
form1 = ModelChoiceForm()
self.assertCountEqual(form1.fields['category'].queryset, [self.c1, self.c2, self.c3])
form2 = ModelChoiceForm()
self.assertIsNone(form2.fields['category'].queryset._result_cache)
def test_modelchoicefield_queryset_none(self):
class ModelChoiceForm(forms.Form):
category = forms.ModelChoiceField(queryset=None)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['category'].queryset = Category.objects.filter(slug__contains='test')
form = ModelChoiceForm()
self.assertCountEqual(form.fields['category'].queryset, [self.c2, self.c3])
def test_modelchoicefield_22745(self):
"""
#22745 -- Make sure that ModelChoiceField with RadioSelect widget
doesn't produce unnecessary db queries when accessing its BoundField's
attrs.
"""
class ModelChoiceForm(forms.Form):
category = forms.ModelChoiceField(Category.objects.all(), widget=forms.RadioSelect)
form = ModelChoiceForm()
field = form['category'] # BoundField
template = Template('{{ field.name }}{{ field }}{{ field.help_text }}')
with self.assertNumQueries(1):
template.render(Context({'field': field}))
def test_disabled_modelchoicefield(self):
class ModelChoiceForm(forms.ModelForm):
author = forms.ModelChoiceField(Author.objects.all(), disabled=True)
class Meta:
model = Book
fields = ['author']
book = Book.objects.create(author=Writer.objects.create(name='Test writer'))
form = ModelChoiceForm({}, instance=book)
self.assertEqual(
form.errors['author'],
['Select a valid choice. That choice is not one of the available choices.']
)
def test_disabled_modelchoicefield_has_changed(self):
field = forms.ModelChoiceField(Author.objects.all(), disabled=True)
self.assertIs(field.has_changed('x', 'y'), False)
def test_disabled_multiplemodelchoicefield(self):
class ArticleForm(forms.ModelForm):
categories = forms.ModelMultipleChoiceField(Category.objects.all(), required=False)
class Meta:
model = Article
fields = ['categories']
category1 = Category.objects.create(name='cat1')
category2 = Category.objects.create(name='cat2')
article = Article.objects.create(
pub_date=datetime.date(1988, 1, 4),
writer=Writer.objects.create(name='Test writer'),
)
article.categories.set([category1.pk])
form = ArticleForm(data={'categories': [category2.pk]}, instance=article)
self.assertEqual(form.errors, {})
self.assertEqual([x.pk for x in form.cleaned_data['categories']], [category2.pk])
# Disabled fields use the value from `instance` rather than `data`.
form = ArticleForm(data={'categories': [category2.pk]}, instance=article)
form.fields['categories'].disabled = True
self.assertEqual(form.errors, {})
self.assertEqual([x.pk for x in form.cleaned_data['categories']], [category1.pk])
def test_disabled_modelmultiplechoicefield_has_changed(self):
field = forms.ModelMultipleChoiceField(Author.objects.all(), disabled=True)
self.assertIs(field.has_changed('x', 'y'), False)
def test_modelchoicefield_iterator(self):
"""
Iterator defaults to ModelChoiceIterator and can be overridden with
the iterator attribute on a ModelChoiceField subclass.
"""
field = forms.ModelChoiceField(Category.objects.all())
self.assertIsInstance(field.choices, ModelChoiceIterator)
class CustomModelChoiceIterator(ModelChoiceIterator):
pass
class CustomModelChoiceField(forms.ModelChoiceField):
iterator = CustomModelChoiceIterator
field = CustomModelChoiceField(Category.objects.all())
self.assertIsInstance(field.choices, CustomModelChoiceIterator)
def test_modelchoicefield_iterator_pass_model_to_widget(self):
class CustomModelChoiceValue:
def __init__(self, value, obj):
self.value = value
self.obj = obj
def __str__(self):
return str(self.value)
class CustomModelChoiceIterator(ModelChoiceIterator):
def choice(self, obj):
value, label = super().choice(obj)
return CustomModelChoiceValue(value, obj), label
class CustomCheckboxSelectMultiple(CheckboxSelectMultiple):
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
option = super().create_option(name, value, label, selected, index, subindex=None, attrs=None)
# Modify the HTML based on the object being rendered.
c = value.obj
option['attrs']['data-slug'] = c.slug
return option
class CustomModelMultipleChoiceField(forms.ModelMultipleChoiceField):
iterator = CustomModelChoiceIterator
widget = CustomCheckboxSelectMultiple
field = CustomModelMultipleChoiceField(Category.objects.all())
self.assertHTMLEqual(
field.widget.render('name', []),
'''<ul>
<li><label><input type="checkbox" name="name" value="%d" data-slug="entertainment">Entertainment</label></li>
<li><label><input type="checkbox" name="name" value="%d" data-slug="its-test">It&#39;s a test</label></li>
<li><label><input type="checkbox" name="name" value="%d" data-slug="third-test">Third</label></li>
</ul>''' % (self.c1.pk, self.c2.pk, self.c3.pk),
)
def test_modelchoicefield_num_queries(self):
"""
Widgets that render multiple subwidgets shouldn't make more than one
database query.
"""
categories = Category.objects.all()
class CategoriesForm(forms.Form):
radio = forms.ModelChoiceField(queryset=categories, widget=forms.RadioSelect)
checkbox = forms.ModelMultipleChoiceField(queryset=categories, widget=forms.CheckboxSelectMultiple)
template = Template("""
{% for widget in form.checkbox %}{{ widget }}{% endfor %}
{% for widget in form.radio %}{{ widget }}{% endfor %}
""")
with self.assertNumQueries(2):
template.render(Context({'form': CategoriesForm()}))
class ModelMultipleChoiceFieldTests(TestCase):
def setUp(self):
self.c1 = Category.objects.create(