New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixed #31262 -- Added support for mappings on model fields and ChoiceField's choices. #16943
Conversation
Hi @ngnpope, thanks for this work! I'm a little busy this week with some GSoC duties but I'll get to this review either this or next week. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this work!
I'm adding an initial set of comments, mostly focused on docs.
4d74546
to
41d47bd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great! I really like the direction of the proposed changes. I added what I would consider a final set of tweaks, which include minimal doc changes and some extra, dedicated tests for the new normalize_field_choices
method.
Hi @ngnpope! I've been on vacation but I'll re-review next week. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there! I think we are just missing tests for the new utils file (see comment) and a few minor doc extensions.
Let me know if you have time to work on this, otherwise I can push this to the final line. Thank you!
Hey @nessita. Am just looking into a few more tweaks, so will get back to you on this soon. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @nessita 👋🏻
So I've updated this to add a bunch of explicit tests for normalize_field_choices()
. In the process I fixed a few other things which I've detailed below. The whole world of choices
stuff has grown additional use cases rather organically over the years and hopefully this will ensure that things are more robust and maintainable for the long term.
Great news! So just to clarify, is this ready for another review @ngnpope? |
Yup! Thanks. |
I'm working on this review now. Do you think that, given the advanced stage of the review, we could squash the many commits into one at this point? EDIT: I just saw your inline comment about this, so ignore this comment please :-) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are so so close! I added some comments about a few missing tests, not out of pedantry, but because I think those would help me/readers fully grasp the goal of some of the guards in the match-case block.
I have also re-checked the performance thing and we still are at 11 queries 💃
Then, once the above is completed, I could help in rebasing onto main since I'm about to land a few improvements to the radioselect and select tests (see PR #16977), and that will cause some conflicts.
Thank you!!!
Thanks for the latest round of reviews @nessita. I've added a few fixups (not squashed in yet so you can see) and responses to your comments above. |
Excellent, I will re-review today. I've just landed the PR I mentioned earlier, I can rebase and fix conflicts if you are busy. Let me know! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I replied to the comments, I'll work on rebasing onto main and resolving conflicts.
Hey @ngnpope, I rebased onto main, fixed conflicts, and made a new commit mimicking the new/extended tests for nested widgets in the test_radioselect file. Please pull before making your changes and a new push! Thanks :-) |
Oh no. I rebased too 😂 I'll have a look and attempt to resolve the differences. (Good thing I'm a rebasing guru.) |
Sorry, I was trying to help! |
I have rebased fixing conflicts and suggested yet a minor rewording of the release notes. @smithdc1 would you fancy taking a look at that (the promotion to the What's New section)? On the other hand, I want to look at this branch with a different mindset. I have just noticed that this work allows for passing a callable to model field's choices, which would solve ticket #24561 (but a duplicate of that ticket has some notes that I want to re-read and analyze before merging this branch). And, if we indeed fixed that ticket, we should mention that in the added release notes and link to it and etc. |
docs/releases/5.0.txt
Outdated
expected form:: | ||
*(for form fields)* allow for more flexibility when declaring their values. In | ||
previous versions of Django, ``choices`` should either be a list of 2-tuples, | ||
or an instance of :ref:`field-choices-enum-types`, but the latter required |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"... an instance of ..." isn't quite correct as an enum is not instantiated. You pass the enum class itself - an instance is an enum member.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @ngnpope, will tweak that.
On (un)related news, I found out this branch now allows for field choices to be callables (yey) but if they are, every time makemigrations
is run, a new migration is created, even without making any chance at all. I'll investigate more tomorrow.
I've been doing extensive testing. Everything looks good except that:
but with this branch there is no error. One would think this may be a good news and that we are solving ticket #24561 along the way, but see below.
Specifically, suppose a model like this: from django.db import models
def get_choices():
return ((i, i) for i in range(3))
class TestModel(models.Model):
field1 = models.PositiveSmallIntegerField(choices=get_choices, default=1) The migrations autodetector builds these representations:
Given the above, and considering that I don't think it's wise to try to solve ticket #24561 in this same PR, I think we should change A naïve change is to remove the @ngnpope Do you have opinions? Ideas? (I will have to change my vote until we sort this out, just to match the current state of this PR landeability). I will also (try to) craft a test for the migration autodetector to catch this issue. @felixxm any ideas or suggestions are welcome! |
I think it's ok to consolidate this behaviour for normalizing choices as we have done to ensure consistency, but not do anything more to address full support for callables in model fields as per ticket-24561. Callable support (as well as incomplete) is undocumented so I don't think we need to worry too much. (Sorry, but I can't dig more into this for a while.) |
I agree with @ngnpope and @carltongibson that we shouldn't do anything else in this branch to address full support for callables in model fields. But, I don't think that we can land this as is because the following items are, IMHO, regressions (despite callable support being undocumented for model field
I agree with Carlton in that we should go for the minimum viable to maintain the behavior on main while landing this work. In order to do that, and considering that I've narrowed the issue to be caused by the removals of: # in Field.__init__
if isinstance(choices, collections.abc.Iterator):
choices = list(choices)
# in Field.deconstruct
if name == "choices" and isinstance(value, collections.abc.Iterable):
value = list(value) EDIT: I have a newer (and better?) proposal in the comment next to this one ⏬ I'm proposing two things: PR #17167 to add a few more related tests in diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index fbbb6e59c7..9cc4d2fe2c 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -545,7 +545,7 @@ class Field(RegisterLookupMixin):
@choices.setter
def choices(self, value):
- self._choices = normalize_choices(value)
+ self._choices = normalize_choices(value, unroll=True)
@cached_property
def cached_col(self):
diff --git a/django/utils/choices.py b/django/utils/choices.py
index 7732bc3697..498c506be5 100644
--- a/django/utils/choices.py
+++ b/django/utils/choices.py
@@ -18,7 +18,7 @@ class CallableChoiceIterator(ChoiceIterator):
yield from normalize_choices(self.choices_func())
-def normalize_choices(value, *, depth=0):
+def normalize_choices(value, *, depth=0, unroll=False):
"""Normalize choices values consistently for fields and widgets."""
match value:
@@ -44,11 +44,11 @@ def normalize_choices(value, *, depth=0):
# String-like types are iterable, so the guard above ensures that
# they're handled by the default case below.
pass
+ case Callable() if unroll or depth == 1:
+ value = value()
case Callable() if depth == 0:
# If at the top level, wrap callables to be evaluated lazily.
return CallableChoiceIterator(value)
- case Callable() if depth < 2:
- value = value()
case _:
return value I believe that the ultimate fix to avoid the unrolling of callable |
ef9a964
to
60896c9
Compare
@ngnpope @carltongibson I've been thinking and trying different approaches to solve my latest concern (callable choices for model fields). Today I put together a solution (that I like better) which does not involve the unrolling of iterable/callable choices for model fields. Instead, I'm proposing changes to a field's I'm also ensuring that the model field's checks for callable choices are still being raised to be consistent with My plan would be to, ideally, land this by the end of next week (feature freeze is approaching fast!). |
…hoices. Co-authored-by: Tom Forbes <tom@tomforb.es> Co-authored-by: Carlton Gibson <carlton@noumenal.es> Co-authored-by: Natalia Bidart <124304+nessita@users.noreply.github.com> Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
…al function and not as its wrapper.
I'm landing this now, if any of you have further concerns, please reach out and we'll create follow up PRs. Thanks! |
Thanks @nessita for getting this across the line and all of the reviews! Sorry, I meant to reply at some point this week, but life… Small heads-up for next time - it looks like this was squashed down and lost all of the |
Yeah I realized this last night after inspecting the git log. Sorry about that! |
ticket-31262
Supersedes PR #12449.
I've updated this with a commit to fix the performance issue raised by @carltongibson based on my comments in the previous PR. I've tested it with the reproducer that he provided and it reduces the queries from 41 to 11 as expected:
I've also added a couple of
!fixup
commits to roll into the main commit, one has a few further example tweaks in the documentation and the other handles pushing down the normalization ofChoicesMeta
subclasses into the newnormalize_field_choices()
helper since the support for passing these direct aschoices
was added recently in a2eaea8.