Skip to content
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

Added support for Django 5.0. #1607

Merged

Conversation

ngnpope
Copy link
Contributor

@ngnpope ngnpope commented Sep 1, 2023

Refs #1605.

Alternative to #1606.

@ngnpope ngnpope force-pushed the fixes-for-django-5.0-alternate branch from b24047d to b62e99b Compare September 1, 2023 11:57
@@ -210,7 +219,7 @@ def clean(self, value):
return value


class ChoiceIterator:
class ChoiceIterator(BaseChoiceIterator):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Django 5.0 I added django.utils.choices.ChoiceIterator as a base class for the following:

  • django.forms.models.ModelChoiceIterator
  • django.utils.choices.CallableChoiceIterator (or django.forms.fields.CallableChoiceIterator in Django < 5.0)

It is expected that a subclass of this will handle normalization via use of django.utils.choices.normalize_choices() or manually (as is the case for ModelChoiceIterator which yields 2-tuples).

Comment on lines +281 to +280
super()._set_choices(value)
value = self.iterator(self, self._choices)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This double setting case is required in Django < 5.0 because callables are handled very specifically whereas it's all handled in django.utils.choices.normalize_choices() for Django 5.0.

# Simple `super()` syntax for calling a parent property setter is
# unsupported. See https://github.com/python/cpython/issues/59170
super(ChoiceIteratorMixin, self.__class__).choices.__set__(self, value)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the most unfortunate bit, but works well nonetheless.

Ideally this whole override ought to be as simple as super().choices = self.iterator(self, value).

But currently there is no support for calling the setter of a parent property, see python/cpython#59170, although there does seem to be indication that a fix for this would be acceptable, see python/cpython#29950 (comment).

There is this workaround which is even used in Python itself but there were challenges observed regarding mixins. I have managed to make this work for this mixin by using super(ChoiceIteratorMixin, self.__class__) instead of super(self.__class__, self.__class__) which ensures that when calling e.g. ChoiceField().choices = (for a subclass of the mixin), the super() call will not be passed ChoiceField as the value for self.__class__ as the first argument. Passing ChoiceIteratorMixin means that the next lookup in the MRO will be for forms.ChoiceField.choices.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I should also mention that this only works if we're using @choices.setter here and the property is defined here, not in the parent otherwise we end up with infinite recursion. Another reason why the getter must be redefined here.)

@ngnpope ngnpope mentioned this pull request Sep 1, 2023
Copy link

@laymonage laymonage left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I have enough expertise to review this (especially with the nuances around the property). What I had in mind was something like #1606, but Nick's solution here seems very nice. Looking at the CI it seems to work, so I suppose that's all there is for now? Happy to help with anything else though @carltongibson 🙌

ngnpope added a commit to ngnpope/django that referenced this pull request Sep 4, 2023
Some third-party applications, e.g. `django-filter`, already define
their own `ChoiceIterator`, so renaming this `BaseChoiceIterator` will
be a better fit and avoid any potential confusion.

See carltongibson/django-filter#1607.
@@ -17,6 +17,15 @@
RangeWidget,
)

try:
from django.utils.choices import ChoiceIterator as BaseChoiceIterator
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@carltongibson A heads up that I've opened django/django#17223 to rename ChoiceIterator to BaseChoiceIterator. Will update this PR to simplify this once that's been merged.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great. Thanks Nick. I'll hold off here for that.

@carltongibson carltongibson linked an issue Sep 4, 2023 that may be closed by this pull request
felixxm pushed a commit to django/django that referenced this pull request Sep 4, 2023
Some third-party applications, e.g. `django-filter`, already define
their own `ChoiceIterator`, so renaming this `BaseChoiceIterator` will
be a better fit and avoid any potential confusion.

See carltongibson/django-filter#1607.
@ngnpope ngnpope force-pushed the fixes-for-django-5.0-alternate branch from b62e99b to 8306c8f Compare September 4, 2023 13:15
@codecov-commenter
Copy link

codecov-commenter commented Sep 4, 2023

Codecov Report

Merging #1607 (b62e99b) into main (e5fc05d) will increase coverage by 0.01%.
The diff coverage is 100.00%.

❗ Current head b62e99b differs from pull request most recent head 8306c8f. Consider uploading reports for the commit 8306c8f to get more accurate results

@@            Coverage Diff             @@
##             main    #1607      +/-   ##
==========================================
+ Coverage   98.58%   98.59%   +0.01%     
==========================================
  Files          15       15              
  Lines        1271     1283      +12     
==========================================
+ Hits         1253     1265      +12     
  Misses         18       18              
Files Changed Coverage Δ
django_filters/fields.py 100.00% <100.00%> (ø)

Co-authored-by: Carlton Gibson <carlton.gibson@noumenal.es>
@ngnpope ngnpope force-pushed the fixes-for-django-5.0-alternate branch from 8306c8f to 117f364 Compare September 4, 2023 13:16
@ngnpope
Copy link
Contributor Author

ngnpope commented Sep 4, 2023

Ok. Updated now the change for BaseChoiceIterator upstream has been merged. Should be ready now.

Copy link
Owner

@carltongibson carltongibson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome. Thanks @ngnpope 🥇

@carltongibson carltongibson merged commit 6119d45 into carltongibson:main Sep 4, 2023
@ngnpope ngnpope deleted the fixes-for-django-5.0-alternate branch September 4, 2023 16:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Choice iterator breakage against Django pre-5.0
4 participants