Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions django/db/models/fields/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,11 +316,13 @@ def _check_choices(self):
if not self.choices:
return []

if not isinstance(self.choices, Iterable) or isinstance(self.choices, str):
if not isinstance(self.choices, Iterable) or isinstance(
self.choices, (str, set, frozenset)
):
return [
checks.Error(
"'choices' must be a mapping (e.g. a dictionary) or an iterable "
"(e.g. a list or tuple).",
"'choices' must be a mapping (e.g. a dictionary) or an "
"ordered iterable (e.g. a list or tuple, but not a set).",
obj=self,
id="fields.E004",
)
Expand Down
2 changes: 1 addition & 1 deletion docs/ref/checks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ Model fields
* **fields.E003**: ``pk`` is a reserved word that cannot be used as a field
name.
* **fields.E004**: ``choices`` must be a mapping (e.g. a dictionary) or an
iterable (e.g. a list or tuple).
ordered iterable (e.g. a list or tuple, but not a set).
* **fields.E005**: ``choices`` must be a mapping of actual values to human
readable names or an iterable containing ``(actual value, human readable
name)`` tuples.
Expand Down
42 changes: 38 additions & 4 deletions tests/invalid_models_tests/test_ordinary_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,8 @@ class Model(models.Model):
field.check(),
[
Error(
"'choices' must be a mapping (e.g. a dictionary) or an iterable "
"(e.g. a list or tuple).",
"'choices' must be a mapping (e.g. a dictionary) or an "
"ordered iterable (e.g. a list or tuple, but not a set).",
obj=field,
id="fields.E004",
),
Expand Down Expand Up @@ -906,8 +906,42 @@ class Model(models.Model):
field.check(),
[
Error(
"'choices' must be a mapping (e.g. a dictionary) or an iterable "
"(e.g. a list or tuple).",
"'choices' must be a mapping (e.g. a dictionary) or an "
"ordered iterable (e.g. a list or tuple, but not a set).",
obj=field,
id="fields.E004",
),
],
)

def test_unordered_choices_set(self):
class Model(models.Model):
field = models.IntegerField(choices={1, 2, 3})

field = Model._meta.get_field("field")
self.assertEqual(
field.check(),
[
Error(
"'choices' must be a mapping (e.g. a dictionary) or an "
"ordered iterable (e.g. a list or tuple, but not a set).",
obj=field,
id="fields.E004",
),
],
)

def test_unordered_choices_frozenset(self):
class Model(models.Model):
field = models.IntegerField(choices=frozenset({1, 2, 3}))

field = Model._meta.get_field("field")
self.assertEqual(
field.check(),
[
Error(
"'choices' must be a mapping (e.g. a dictionary) or an "
"ordered iterable (e.g. a list or tuple, but not a set).",
obj=field,
id="fields.E004",
),
Expand Down