Skip to content

Commit 97acd4d

Browse files
felixxmKarl Wooster
andauthored
Fixed #26609 -- Extended fields.E004 system check for unordered iterables.
Co-authored-by: Karl Wooster <karl.wooster@alleima.com>
1 parent e05f2a7 commit 97acd4d

3 files changed

Lines changed: 44 additions & 8 deletions

File tree

django/db/models/fields/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,11 +316,13 @@ def _check_choices(self):
316316
if not self.choices:
317317
return []
318318

319-
if not isinstance(self.choices, Iterable) or isinstance(self.choices, str):
319+
if not isinstance(self.choices, Iterable) or isinstance(
320+
self.choices, (str, set, frozenset)
321+
):
320322
return [
321323
checks.Error(
322-
"'choices' must be a mapping (e.g. a dictionary) or an iterable "
323-
"(e.g. a list or tuple).",
324+
"'choices' must be a mapping (e.g. a dictionary) or an "
325+
"ordered iterable (e.g. a list or tuple, but not a set).",
324326
obj=self,
325327
id="fields.E004",
326328
)

docs/ref/checks.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ Model fields
167167
* **fields.E003**: ``pk`` is a reserved word that cannot be used as a field
168168
name.
169169
* **fields.E004**: ``choices`` must be a mapping (e.g. a dictionary) or an
170-
iterable (e.g. a list or tuple).
170+
ordered iterable (e.g. a list or tuple, but not a set).
171171
* **fields.E005**: ``choices`` must be a mapping of actual values to human
172172
readable names or an iterable containing ``(actual value, human readable
173173
name)`` tuples.

tests/invalid_models_tests/test_ordinary_fields.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,8 @@ class Model(models.Model):
199199
field.check(),
200200
[
201201
Error(
202-
"'choices' must be a mapping (e.g. a dictionary) or an iterable "
203-
"(e.g. a list or tuple).",
202+
"'choices' must be a mapping (e.g. a dictionary) or an "
203+
"ordered iterable (e.g. a list or tuple, but not a set).",
204204
obj=field,
205205
id="fields.E004",
206206
),
@@ -906,8 +906,42 @@ class Model(models.Model):
906906
field.check(),
907907
[
908908
Error(
909-
"'choices' must be a mapping (e.g. a dictionary) or an iterable "
910-
"(e.g. a list or tuple).",
909+
"'choices' must be a mapping (e.g. a dictionary) or an "
910+
"ordered iterable (e.g. a list or tuple, but not a set).",
911+
obj=field,
912+
id="fields.E004",
913+
),
914+
],
915+
)
916+
917+
def test_unordered_choices_set(self):
918+
class Model(models.Model):
919+
field = models.IntegerField(choices={1, 2, 3})
920+
921+
field = Model._meta.get_field("field")
922+
self.assertEqual(
923+
field.check(),
924+
[
925+
Error(
926+
"'choices' must be a mapping (e.g. a dictionary) or an "
927+
"ordered iterable (e.g. a list or tuple, but not a set).",
928+
obj=field,
929+
id="fields.E004",
930+
),
931+
],
932+
)
933+
934+
def test_unordered_choices_frozenset(self):
935+
class Model(models.Model):
936+
field = models.IntegerField(choices=frozenset({1, 2, 3}))
937+
938+
field = Model._meta.get_field("field")
939+
self.assertEqual(
940+
field.check(),
941+
[
942+
Error(
943+
"'choices' must be a mapping (e.g. a dictionary) or an "
944+
"ordered iterable (e.g. a list or tuple, but not a set).",
911945
obj=field,
912946
id="fields.E004",
913947
),

0 commit comments

Comments
 (0)