-
-
Notifications
You must be signed in to change notification settings - Fork 31.1k
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 #27910 -- Added enumeration helpers for use in Field.choices #11540
Conversation
c7b411b
to
9b24e48
Compare
@shaib: Could you explain the use case for the @felixxm: This is largely done. The main issue remaining is handling of blank values using
It would be a shame if 3 were the only option here. |
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.
@pope1ni Thanks 👍 I will review docs and tests in the next week.
It was motivated by https://groups.google.com/d/msgid/django-developers/CAL13Cg_KEtgmNzRo%3DC8cnCiNsukr5YOCTv3MF8AybV%3D%3DiUXP0g%40mail.gmail.com
No, there's no need for these. Take a look at the loop starting at line 14 of enums.py -- it clearly shows that the values assigned to enum members in the source don't need to be valid members of the type (tuples are not I think an even better variation of this idea is, instead of using the |
0e61d5e
to
55964fb
Compare
I've pushed a >>> class Suit(models.ChoiceIntEnum):
... DIAMOND = 1, _('Diamond')
... SPADE = 2, _('Spade')
... HEART = 3, _('Heart')
... CLUB = 4, _('Club')
...
>>> Suit.DIAMOND in Suit
True
>>> 1 in Suit
True
>>> 0 in Suit
False I will look into the label for the empty value soon. |
caec4cb
to
98f0856
Compare
37053ab
to
33da978
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.
Main code and docs look good to me. Haven't gone through tests in detail, but they seem pretty extensive too.
Have rebased this again. Is there anything else to do @carltongibson / @felixxm? |
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.
Perhaps add tests and/or docs for usage with other types of fields, besides str
and int
? Because choices
is defined on Field
after all, available to all field types.
I am trying to get it working with a DateField
but meeting little success. Maybe I'm missing something obvious?
class ChoiceDateEnum(datetime.date, enum.Enum, metaclass=ChoiceEnumMeta):
"""Class for creating an enum date choices."""
def _generate_next_value_(name, start, count, last_values):
return name # what goes here?
class StartChoices(ChoiceDateEnum):
mid2019 = datetime.date(2019, 6, 1), 'Middle of 2019'
start2019 = datetime.date(2019, 1, 1), 'Start of 2019'
mid2018 = datetime.date(2018, 6, 1), 'Middle of 2018'
# ...
class MyHalfYearModel(models.Model):
start = models.DateField(choices=StartChoices.choices, null=True, blank=True)
# ...
This produces TypeError: an integer is required (got type datetime.date)
at import time. Full traceback: https://dpaste.de/EU1V
Original (old-style) choices:
START_CHOICES = (
(datetime.date(2019, 6, 1), 'Middle of 2019'),
(datetime.date(2019, 1, 1), 'Start of 2019'),
(datetime.date(2018, 6, 1), 'Middle of 2018'),
# ...
)
@frnhr I'm getting the same error when trying to define a date-based enum with no relation to ChoiceEnum: >>> from enum import Enum
>>> from datetime import date
>>> class MyEnum(date, Enum):
... A = date(2014, 1, 1)
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.7/enum.py", line 218, in __new__
enum_member = __new__(enum_class, *args)
TypeError: an integer is required (got type datetime.date) So I'm guessing there's little we can do here. I would try something like using the In my taste, this use-case falls a little out of what Django should actively support. |
Fair enough, but we should at least mention this in the docs. I doubt that I will be the only one attempting to use it with a field that is not Especially because it looks like it should work, and it is documented right next to the old-style choices which do work for all field types. |
Just to clarify my PG comment: This was all about the database engines. The fact that Django forces you to specify a On Postgres, the internal representation of different text types is the same (that's why the performance is the same). So when you change from Oracle, for example (at least in the versions of 6-7 years ago when I was using it) was actually keeping rows together, and allocating the right number of characters in each row for each field. Thus, a similar change implies a change in the layout of the table's storage, which can be very expensive -- and could mean a long downtime for the migration. SQLite isn't usually discussed much in terms of effects on production systems, but just for good measure -- last I checked, every such change implied a complete recreation of the table. |
ab7734b
to
376a20f
Compare
Ok. Changes made. Thanks Shai. |
Created PR #11742. |
e22bb1c
to
19e5842
Compare
@pope1ni I pushed minor edits, rebased from master, simplified tests, and moved choices tests to a separate directory. I noticed that these lines are not covered, I don't think that we still need this catch or if we really want to catch super().__new__(metacls, classname, bases, classdict) I'm working on docs. |
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 the polishing Felix.
I just noticed a couple of little things:
So originally when we only covered |
3d4ee53
to
bda6bb7
Compare
@pope1ni OK, we are almost there, I pushed all edits, but I also noticed one issue with migrations that I would try to fix, i.e. field=models.CharField(choices=[('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior'), ('GR', 'Graduate'), ('XX', 'Xxx')], default=test_one.models.YearInSchool('FR'), max_length=2),
AttributeError: module 'test_one.models' has no attribute 'YearInSchool' the old-fashioned choices evaluates properly to the |
That's not an issue with default per se, it's an issue with defining the choices enum within the model class, and the auto-migration-composer not handling nested classes. |
I think that could be handled by overriding |
@shaib Yes, I was able to fix this quite easily with: diff --git a/django/db/migrations/serializer.py b/django/db/migrations/serializer.py
index 1f1b3f4f20..c6bfd90621 100644
--- a/django/db/migrations/serializer.py
+++ b/django/db/migrations/serializer.py
@@ -45,6 +45,9 @@ class BaseSimpleSerializer(BaseSerializer):
def serialize(self):
return repr(self.value), set()
+class ChoicesSerializer(BaseSerializer):
+ def serialize(self):
+ return repr(self.value.value), set()
class DateTimeSerializer(BaseSerializer):
"""For datetime.*, except datetime.datetime."""
@@ -279,6 +282,7 @@ class Serializer:
set: SetSerializer,
tuple: TupleSerializer,
dict: DictionarySerializer,
+ models.Choices: ChoicesSerializer,
enum.Enum: EnumSerializer,
datetime.datetime: DatetimeDatetimeSerializer,
(datetime.date, datetime.timedelta, datetime.time): DateTimeSerializer, after that it evaluates to |
bda6bb7
to
46138c4
Compare
Good idea. Should seamlessly work for custom choices then too. Can you try with one that subclasses |
Yes, I'm preparing tests. |
These classes can serve as a base class for user enums, supporting translatable human-readable names, or names automatically inferred from the enum member name. Additional properties make it easy to access the list of names, values and display labels. Thanks to the following for ideas and reviews: Carlton Gibson, Fran Hrženjak, Ian Foote, Mariusz Felisiak, Shai Berger. Co-authored-by: Shai Berger <shai@platonix.com> Co-authored-by: Nick Pope <nick.pope@flightdataservices.com> Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
46138c4
to
72ebe85
Compare
I pushed fix. |
Thanks y'all 🚀 Many thanks to Nick and Shai 🙇♂️ Now we can celebrate 🍾 💃 🚀 🎊 |
Thanks both of you! |
Ticket #27910
Supersedes PR #11223
Improvements over the previous pull request:
ChoiceEnum
intoChoiceEnumMeta
.labels
,values
, andnames
.enum.unique()
to prevent duplicate values.django.db.models.enums
intodocs/ref/models/fields.txt
.docs/ref/models/fields.txt
.tests/model_fields/test_choiceenum.py
.Things left to do:
docs/topics/db/models.txt
.Add documentation for the.validate()
method.''
orNone
.None
is potentially problematic as were restricted toint
orstr
.