Opened 14 years ago
Closed 13 years ago
#16663 closed Bug (needsinfo)
Model field choices should accept empty iterator
| Reported by: | Daniel Naab | Owned by: | nobody |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | 1.3 |
| Severity: | Normal | Keywords: | |
| Cc: | Daniel Naab | Triage Stage: | Design decision needed |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
The base Field class constructor uses the following shorthand to initialize the choices for a field (line 96 of django/db/model/fields/__init__.py):
self._choices = choices or []
In Python, an empty iterator evaluates to False, so if we did:
class MyModel(models.Model):
my_field = models.IntegerField(choices=my_list_like_object)
_choices will default to an empty list if the my_list_like_object is permanently or temporarilly empty at time of class definition. However, the documentation clearly states the intent of allowing any iterable as a choices kwarg:
Finally, note that choices can be any iterable object -- not necessarily a list or tuple. This lets you construct choices dynamically. But if you find yourself hacking choices to be dynamic, you're probably better off using a proper database table with a ForeignKey. choices is meant for static data that doesn't change much, if ever.
So I propose making a small change to the above line:
if choices == None:
self._choices = []
else:
self._choices = choices
Attachments (2)
Change History (8)
comment:1 by , 14 years ago
| Cc: | added |
|---|
by , 14 years ago
| Attachment: | 16663-failing-test.patch added |
|---|
by , 14 years ago
| Attachment: | 16663-docs.patch added |
|---|
comment:3 by , 14 years ago
| Easy pickings: | unset |
|---|---|
| Has patch: | set |
| Triage Stage: | Unreviewed → Design decision needed |
comment:4 by , 14 years ago
I suspected there would be side effects of making the default self._choices == None instead of the empty list, which is why I proposed the simpler solution. A list-like object with length of zero is functionally identical to an empty list, so I don't see what possible side effects there could be to my suggestion (although setting choices to None is more sensible in an absolute sense).
comment:5 by , 14 years ago
danielnaab, can you clarify the real-world usecase here? I'm having trouble figuring out if this is actually a bug or something more abstract. Thanks!
comment:6 by , 13 years ago
| Resolution: | → needsinfo |
|---|---|
| Status: | new → closed |
I respectfully disagree with this sentence:
To the best of my knowledge, an iterator always evaluate to
True, no matter if it's empty:However, at first sight, the intent here is to use
Noneinstead of[]as the default value forchoices, to avoid the usual mutable-default-value issue. Hence, a strict check againstNonelooks more appropriate.I began working on a patch (first patch attached)... and failed horribly.
In fact, if we want to handle properly the case when an empty list is passed in the
choicesargument, we mustn't setself._choices = []when thechoicesargument isn't passed. Otherwise we can no longer tell if (a) no choices were given or (b) a empty list was given, that will be populated later. This becomes ugly very quickly because Django usesif <field>.choicesin many places. I'm afraid a lot of third-party code (like custom fields) also uses this idiom.In particular, Django only creates
get_FOO_displaywhen<field>.choicesevaluates toTruewhen the field is initialized, which is why the test in my patch fails.So, even if we implement a strict check (
self._choices = [] if choices is None else choices), it still won't be possible to use an empty list as initial value ofchoices. Fixing this properly is backwards-incompatible. So I recommend not changing anything :)Since iterators always evaluate to
True, this shouldn't affect too many people. We could put a note in the docs (second patch attached, it just adds the word "non-empty" and rewraps the paragraph).