mirror of
https://github.com/django/django.git
synced 2025-02-14 19:37:29 +00:00
Consolidated ModelChoiceField tests.
This commit is contained in:
parent
94a180402c
commit
06172d7bc2
@ -130,12 +130,5 @@ class FileModel(models.Model):
|
|||||||
file = models.FileField(storage=temp_storage, upload_to='tests')
|
file = models.FileField(storage=temp_storage, upload_to='tests')
|
||||||
|
|
||||||
|
|
||||||
class Group(models.Model):
|
|
||||||
name = models.CharField(max_length=10)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '%s' % self.name
|
|
||||||
|
|
||||||
|
|
||||||
class Article(models.Model):
|
class Article(models.Model):
|
||||||
content = models.TextField()
|
content = models.TextField()
|
||||||
|
@ -2,15 +2,13 @@ import datetime
|
|||||||
|
|
||||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.forms import (
|
from django.forms import CharField, FileField, Form, ModelForm
|
||||||
CharField, FileField, Form, ModelChoiceField, ModelForm,
|
|
||||||
)
|
|
||||||
from django.forms.models import ModelFormMetaclass
|
from django.forms.models import ModelFormMetaclass
|
||||||
from django.test import SimpleTestCase, TestCase
|
from django.test import SimpleTestCase, TestCase
|
||||||
|
|
||||||
from ..models import (
|
from ..models import (
|
||||||
BoundaryModel, ChoiceFieldModel, ChoiceModel, ChoiceOptionModel, Defaults,
|
BoundaryModel, ChoiceFieldModel, ChoiceModel, ChoiceOptionModel, Defaults,
|
||||||
FileModel, Group, OptionalMultiChoiceModel,
|
FileModel, OptionalMultiChoiceModel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -56,24 +54,6 @@ class FileForm(Form):
|
|||||||
file1 = FileField()
|
file1 = FileField()
|
||||||
|
|
||||||
|
|
||||||
class TestModelChoiceField(TestCase):
|
|
||||||
|
|
||||||
def test_choices_not_fetched_when_not_rendering(self):
|
|
||||||
"""
|
|
||||||
Generating choices for ModelChoiceField should require 1 query (#12510).
|
|
||||||
"""
|
|
||||||
self.groups = [Group.objects.create(name=name) for name in 'abc']
|
|
||||||
# only one query is required to pull the model from DB
|
|
||||||
with self.assertNumQueries(1):
|
|
||||||
field = ModelChoiceField(Group.objects.order_by('-name'))
|
|
||||||
self.assertEqual('a', field.clean(self.groups[0].pk).name)
|
|
||||||
|
|
||||||
def test_queryset_manager(self):
|
|
||||||
f = ModelChoiceField(ChoiceOptionModel.objects)
|
|
||||||
choice = ChoiceOptionModel.objects.create(name="choice 1")
|
|
||||||
self.assertEqual(list(f.choices), [('', '---------'), (choice.pk, str(choice))])
|
|
||||||
|
|
||||||
|
|
||||||
class TestTicket14567(TestCase):
|
class TestTicket14567(TestCase):
|
||||||
"""
|
"""
|
||||||
The return values of ModelMultipleChoiceFields are QuerySets
|
The return values of ModelMultipleChoiceFields are QuerySets
|
||||||
|
275
tests/model_forms/test_modelchoicefield.py
Normal file
275
tests/model_forms/test_modelchoicefield.py
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.core.validators import ValidationError
|
||||||
|
from django.forms.models import ModelChoiceIterator
|
||||||
|
from django.forms.widgets import CheckboxSelectMultiple
|
||||||
|
from django.template import Context, Template
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from .models import Article, Author, Book, Category, Writer
|
||||||
|
|
||||||
|
|
||||||
|
class ModelChoiceFieldTests(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.c1 = Category.objects.create(name='Entertainment', slug='entertainment', url='entertainment')
|
||||||
|
cls.c2 = Category.objects.create(name='A test', slug='test', url='test')
|
||||||
|
cls.c3 = Category.objects.create(name='Third', slug='third-test', url='third')
|
||||||
|
|
||||||
|
def test_basics(self):
|
||||||
|
f = forms.ModelChoiceField(Category.objects.all())
|
||||||
|
self.assertEqual(list(f.choices), [
|
||||||
|
('', '---------'),
|
||||||
|
(self.c1.pk, 'Entertainment'),
|
||||||
|
(self.c2.pk, '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.
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
f.clean([['fail']])
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
f.clean([{'foo': 'bar'}])
|
||||||
|
|
||||||
|
self.assertEqual(f.clean(self.c2.id).name, '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 instantiation time.
|
||||||
|
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 instantiation time.
|
||||||
|
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_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, 'A test'),
|
||||||
|
])
|
||||||
|
self.assertEqual(f.clean(self.c2.id).name, 'A test')
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
f.clean(self.c3.id)
|
||||||
|
|
||||||
|
# Choices can be iterated repeatedly.
|
||||||
|
gen_one = list(f.choices)
|
||||||
|
gen_two = f.choices
|
||||||
|
self.assertEqual(gen_one[2], (self.c2.pk, 'A test'))
|
||||||
|
self.assertEqual(list(gen_two), [
|
||||||
|
('', '---------'),
|
||||||
|
(self.c1.pk, 'Entertainment'),
|
||||||
|
(self.c2.pk, 'A test'),
|
||||||
|
])
|
||||||
|
|
||||||
|
# Overriding label_from_instance() to print custom labels.
|
||||||
|
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 A test'),
|
||||||
|
(self.c3.pk, 'category Third'),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_deepcopies_widget(self):
|
||||||
|
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
|
||||||
|
# without affecting other forms, the following must hold (#11183):
|
||||||
|
self.assertIsNot(field1, ModelChoiceForm.base_fields['category'])
|
||||||
|
self.assertIs(field1.widget.choices.field, field1)
|
||||||
|
|
||||||
|
def test_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_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_no_extra_query_when_accessing_attrs(self):
|
||||||
|
"""
|
||||||
|
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_overridable_choice_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_choice_iterator_passes_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="test">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_choices_not_fetched_when_not_rendering(self):
|
||||||
|
with self.assertNumQueries(1):
|
||||||
|
field = forms.ModelChoiceField(Category.objects.order_by('-name'))
|
||||||
|
self.assertEqual('Entertainment', field.clean(self.c1.pk).name)
|
||||||
|
|
||||||
|
def test_queryset_manager(self):
|
||||||
|
f = forms.ModelChoiceField(Category.objects)
|
||||||
|
self.assertEqual(list(f.choices), [
|
||||||
|
('', '---------'),
|
||||||
|
(self.c1.pk, 'Entertainment'),
|
||||||
|
(self.c2.pk, 'A test'),
|
||||||
|
(self.c3.pk, 'Third'),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_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()}))
|
@ -12,10 +12,9 @@ from django.core.validators import ValidationError
|
|||||||
from django.db import connection, models
|
from django.db import connection, models
|
||||||
from django.db.models.query import EmptyQuerySet
|
from django.db.models.query import EmptyQuerySet
|
||||||
from django.forms.models import (
|
from django.forms.models import (
|
||||||
ModelChoiceIterator, ModelFormMetaclass, construct_instance,
|
ModelFormMetaclass, construct_instance, fields_for_model, model_to_dict,
|
||||||
fields_for_model, model_to_dict, modelform_factory,
|
modelform_factory,
|
||||||
)
|
)
|
||||||
from django.forms.widgets import CheckboxSelectMultiple
|
|
||||||
from django.template import Context, Template
|
from django.template import Context, Template
|
||||||
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
|
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
|
||||||
|
|
||||||
@ -1557,261 +1556,6 @@ class ModelFormBasicTests(TestCase):
|
|||||||
obj.full_clean()
|
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'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):
|
class ModelMultipleChoiceFieldTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.c1 = Category.objects.create(
|
self.c1 = Category.objects.create(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user