11"""
22Support for Django model fields built from enumeration types.
33"""
4+ from enum import Enum
45from typing import (
56 TYPE_CHECKING ,
67 Any ,
1617from django .db .models import (
1718 BigIntegerField ,
1819 CharField ,
19- Choices ,
2020 Field ,
2121 FloatField ,
2222 IntegerField ,
2727 SmallIntegerField ,
2828)
2929from django .db .models .query_utils import DeferredAttribute
30+ from django_enum .choices import choices , values
3031from django_enum .forms import EnumChoiceField , NonStrictSelect
3132
3233T = TypeVar ('T' ) # pylint: disable=C0103
@@ -79,25 +80,25 @@ class EnumMixin(
7980 field type.
8081 """
8182
82- enum : Optional [Type [Choices ]] = None
83+ enum : Optional [Type [Enum ]] = None
8384 strict : bool = True
8485 coerce : bool = True
8586
8687 descriptor_class = ToPythonDeferredAttribute
8788
88- def _coerce_to_value_type (self , value : Any ) -> Choices :
89+ def _coerce_to_value_type (self , value : Any ) -> Enum :
8990 """Coerce the value to the enumerations value type"""
9091 # note if enum type is int and a floating point is passed we could get
9192 # situations like X.xxx == X - this is acceptable
9293 if self .enum :
93- return type (self .enum . values [0 ])(value )
94+ return type (values ( self .enum ) [0 ])(value )
9495 # can't ever reach this - just here to make type checker happy
9596 return value # pragma: no cover
9697
9798 def __init__ (
9899 self ,
99100 * args ,
100- enum : Optional [Type [Choices ]] = None ,
101+ enum : Optional [Type [Enum ]] = None ,
101102 strict : bool = strict ,
102103 coerce : bool = coerce ,
103104 ** kwargs
@@ -106,14 +107,14 @@ def __init__(
106107 self .strict = strict if enum else False
107108 self .coerce = coerce if enum else False
108109 if self .enum is not None :
109- kwargs .setdefault ('choices' , enum . choices if enum else [] )
110+ kwargs .setdefault ('choices' , choices ( enum ) )
110111 super ().__init__ (* args , ** kwargs )
111112
112113 def _try_coerce (
113114 self ,
114115 value : Any ,
115116 force : bool = False
116- ) -> Union [Choices , Any ]:
117+ ) -> Union [Enum , Any ]:
117118 """
118119 Attempt coercion of value to enumeration type instance, if unsuccessful
119120 and non-strict, coercion to enum's primitive type will be done,
@@ -130,15 +131,18 @@ def _try_coerce(
130131 try :
131132 value = self ._coerce_to_value_type (value )
132133 value = self .enum (value )
133- except (TypeError , ValueError ) as err :
134- if self .strict or not isinstance (
135- value ,
136- type (self .enum .values [0 ])
137- ):
138- raise ValueError (
139- f"'{ value } ' is not a valid { self .enum .__name__ } "
140- f"required by field { self .name } ."
141- ) from err
134+ except (TypeError , ValueError ):
135+ try :
136+ value = self .enum [value ]
137+ except KeyError as err :
138+ if self .strict or not isinstance (
139+ value ,
140+ type (values (self .enum )[0 ])
141+ ):
142+ raise ValueError (
143+ f"'{ value } ' is not a valid { self .enum .__name__ } "
144+ f"required by field { self .name } ."
145+ ) from err
142146 return value
143147
144148 def deconstruct (self ) -> Tuple [str , str , List , dict ]:
@@ -159,7 +163,7 @@ def deconstruct(self) -> Tuple[str, str, List, dict]:
159163 """
160164 name , path , args , kwargs = super ().deconstruct ()
161165 if self .enum is not None :
162- kwargs ['choices' ] = self .enum . choices
166+ kwargs ['choices' ] = choices ( self .enum )
163167
164168 if 'default' in kwargs :
165169 # ensure default in deconstructed fields is always the primitive
@@ -216,7 +220,7 @@ def from_db_value(
216220 return value
217221 return self ._try_coerce (value )
218222
219- def to_python (self , value : Any ) -> Union [Choices , Any ]:
223+ def to_python (self , value : Any ) -> Union [Enum , Any ]:
220224 """
221225 Converts the value in the enumeration type.
222226
@@ -301,10 +305,12 @@ class EnumCharField(EnumMixin, CharField):
301305 """
302306
303307 def __init__ (self , * args , enum = None , ** kwargs ):
304- choices = kwargs .get ('choices' , enum .choices if enum else [])
305308 kwargs .setdefault (
306309 'max_length' ,
307- max ((len (choice [0 ]) for choice in choices ))
310+ max ((
311+ len (choice [0 ])
312+ for choice in kwargs .get ('choices' , choices (enum ))
313+ ))
308314 )
309315 super ().__init__ (* args , enum = enum , ** kwargs )
310316
@@ -361,7 +367,7 @@ class _EnumFieldMetaClass(type):
361367
362368 def __new__ ( # pylint: disable=R0911
363369 mcs ,
364- enum : Type [Choices ]
370+ enum : Type [Enum ]
365371 ) -> Type [EnumMixin ]:
366372 """
367373 Construct a new Django Field class given the Enumeration class. The
@@ -370,23 +376,21 @@ def __new__( # pylint: disable=R0911
370376
371377 :param enum: The class of the Enumeration to build a field class for
372378 """
373- assert issubclass (enum , Choices ), \
374- f'{ enum } must inherit from { Choices } !'
375379 primitives = mcs .SUPPORTED_PRIMITIVES .intersection (set (enum .__mro__ ))
376- assert len ( primitives ) == 1 , f' { enum } must inherit from exactly one ' \
377- f'supported primitive type ' \
378- f' { mcs . SUPPORTED_PRIMITIVES } , ' \
379- f'encountered: { primitives } .'
380-
381- primitive = list ( primitives )[ 0 ]
380+ primitive = (
381+ list ( primitives )[ 0 ] if primitives else type ( values ( enum )[ 0 ])
382+ )
383+ assert primitive in mcs . SUPPORTED_PRIMITIVES , \
384+ f'Enum { enum } has values of an unnsupported primitive type: ' \
385+ f' { primitive } '
382386
383387 if primitive is float :
384388 return EnumFloatField
385389
386390 if primitive is int :
387- values = [define .value for define in enum ]
388- min_value = min (values )
389- max_value = max (values )
391+ vals = [define .value for define in enum ]
392+ min_value = min (vals )
393+ max_value = max (vals )
390394 if min_value < 0 :
391395 if min_value < - 2147483648 or max_value > 2147483647 :
392396 return EnumBigIntegerField
@@ -404,7 +408,7 @@ def __new__( # pylint: disable=R0911
404408
405409
406410def EnumField ( # pylint: disable=C0103
407- enum : Type [Choices ],
411+ enum : Type [Enum ],
408412 * field_args ,
409413 ** field_kwargs
410414) -> EnumMixin :
0 commit comments