Skip to content

Commit

Permalink
Merge pull request #57 from bckohan/54-support-django-50
Browse files Browse the repository at this point in the history
54 support django 50
  • Loading branch information
bckohan authored Dec 13, 2023
2 parents fa07a0b + f2b2e22 commit fb9bef7
Show file tree
Hide file tree
Showing 17 changed files with 668 additions and 33 deletions.
10 changes: 6 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,21 @@ jobs:
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']
django-version:
- 'Django~=3.2.0' # LTS April 2024
- 'Django~=4.1.0' # December 2023
- 'Django~=4.2.0' # LTS April 2026
- 'Django~=5.0.0' # April 2025
exclude:
- python-version: '3.7'
django-version: 'Django~=4.1.0'
django-version: 'Django~=5.0.0'
- python-version: '3.7'
django-version: 'Django~=4.2.0'
- python-version: '3.8'
django-version: 'Django~=5.0.0'
- python-version: '3.9'
django-version: 'Django~=5.0.0'
- python-version: '3.11'
django-version: 'Django~=3.2.0'
- python-version: '3.12'
django-version: 'Django~=3.2.0'
- python-version: '3.12'
django-version: 'Django~=4.1.0'

steps:
- uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion django_enum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
'EnumFilter'
]

VERSION = (1, 2, 2)
VERSION = (1, 3, 0)

__title__ = 'Django Enum'
__version__ = '.'.join(str(i) for i in VERSION)
Expand Down
17 changes: 12 additions & 5 deletions django_enum/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
from django.db.models import Choices
from django.db.models import IntegerChoices as DjangoIntegerChoices
from django.db.models import TextChoices as DjangoTextChoices
from django.db.models.enums import ChoicesMeta

try:
from django.db.models.enums import ChoicesType
except ImportError: # pragma: no cover
from django.db.models.enums import ChoicesMeta as ChoicesType


def choices(enum: Optional[Type[Enum]]) -> List[Tuple[Any, str]]:
Expand Down Expand Up @@ -87,7 +91,7 @@ def values(enum: Optional[Type[Enum]]) -> List[Any]:
from enum_properties import EnumPropertiesMeta, SymmetricMixin


class DjangoEnumPropertiesMeta(EnumPropertiesMeta, ChoicesMeta):
class DjangoEnumPropertiesMeta(EnumPropertiesMeta, ChoicesType):
"""
A composite meta class that combines Django's Choices metaclass with
enum-properties metaclass. This metaclass will add Django's expected
Expand All @@ -104,7 +108,7 @@ class DjangoSymmetricMixin(SymmetricMixin):
_symmetric_builtins_ = ['name', 'label']


class TextChoices(
class TextChoices( # pylint: disable=too-many-ancestors
DjangoSymmetricMixin,
DjangoTextChoices,
metaclass=DjangoEnumPropertiesMeta
Expand All @@ -118,7 +122,7 @@ def __hash__(self):
return DjangoTextChoices.__hash__(self)


class IntegerChoices(
class IntegerChoices( # pylint: disable=too-many-ancestors
DjangoSymmetricMixin,
DjangoIntegerChoices,
metaclass=DjangoEnumPropertiesMeta
Expand Down Expand Up @@ -146,6 +150,9 @@ class FloatChoices(
def __hash__(self):
return float.__hash__(self)

def __str__(self):
return str(self.value)


except (ImportError, ModuleNotFoundError):

Expand All @@ -162,7 +169,7 @@ def __init__(self, *args, **kwargs): # pylint: disable=W0231
DjangoSymmetricMixin = MissingEnumProperties # type: ignore


class DjangoEnumPropertiesMeta(ChoicesMeta): # type: ignore
class DjangoEnumPropertiesMeta(ChoicesType): # type: ignore
"""
Throw error if metaclass is used without enum-properties
Expand Down
65 changes: 47 additions & 18 deletions django_enum/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@
SmallIntegerField,
)
from django.db.models.query_utils import DeferredAttribute

try:
from django.db.models.expressions import DatabaseDefault
except ImportError: # pragma: no cover
class DatabaseDefault: # type: ignore
"""Spoof DatabaseDefault for Django < 5.0"""

from django_enum.choices import choices, values
from django_enum.forms import EnumChoiceField, NonStrictSelect

Expand Down Expand Up @@ -56,7 +63,11 @@ class ToPythonDeferredAttribute(DeferredAttribute):

def __set__(self, instance: Model, value: Any):
try:
instance.__dict__[self.field.name] = self.field.to_python(value)
instance.__dict__[self.field.name] = (
value
if isinstance(value, DatabaseDefault) else
self.field.to_python(value)
)
except (ValidationError, ValueError):
# Django core fields allow assignment of any value, we do the same
instance.__dict__[self.field.name] = value
Expand All @@ -83,19 +94,10 @@ class EnumMixin(
enum: Optional[Type[Enum]] = None
strict: bool = True
coerce: bool = True
primitive: Type[Any]

descriptor_class = ToPythonDeferredAttribute

def _coerce_to_value_type(self, value: Any) -> Enum:
"""Coerce the value to the enumerations value type"""
# note if enum type is int and a floating point is passed we could get
# situations like X.xxx == X - this is acceptable
if self.enum:
return type(values(self.enum)[0])(value)
# can't ever reach this - just here to make type checker happy
return value # pragma: no cover


def __init__(
self,
*args,
Expand All @@ -121,30 +123,42 @@ def _try_coerce(
and non-strict, coercion to enum's primitive type will be done,
otherwise a ValueError is raised.
"""
if (
(self.coerce or force)
and self.enum is not None
and not isinstance(value, self.enum)
):
if self.enum is None:
return value

if (self.coerce or force) and not isinstance(value, self.enum):
try:
value = self.enum(value)
except (TypeError, ValueError):
try:
value = self._coerce_to_value_type(value)
value = self.primitive(value)
value = self.enum(value)
except (TypeError, ValueError):
try:
value = self.enum[value]
except KeyError as err:
if self.strict or not isinstance(
value,
type(values(self.enum)[0])
self.primitive
):
raise ValueError(
f"'{value}' is not a valid "
f"{self.enum.__name__} "
f"required by field {self.name}."
) from err
elif (
not self.coerce and
not isinstance(value, self.primitive) and
not isinstance(value, self.enum)
):
try:
return self.primitive(value)
except (TypeError, ValueError) as err:
raise ValueError(
f"'{value}' is not coercible to {self.primitive.__name__} "
f"required by field {self.name}."
) from err

return value

def deconstruct(self) -> Tuple[str, str, List, dict]:
Expand Down Expand Up @@ -306,6 +320,8 @@ class EnumCharField(EnumMixin, CharField):
A database field supporting enumerations with character values.
"""

primitive = str

def __init__(self, *args, enum=None, **kwargs):
kwargs.setdefault(
'max_length',
Expand All @@ -320,48 +336,61 @@ def __init__(self, *args, enum=None, **kwargs):
class EnumFloatField(EnumMixin, FloatField):
"""A database field supporting enumerations with floating point values"""

primitive = float


class EnumSmallIntegerField(EnumMixin, SmallIntegerField):
"""
A database field supporting enumerations with integer values that fit into
2 bytes or fewer
"""

primitive = int


class EnumPositiveSmallIntegerField(EnumMixin, PositiveSmallIntegerField):
"""
A database field supporting enumerations with positive (but signed) integer
values that fit into 2 bytes or fewer
"""

primitive = int

class EnumIntegerField(EnumMixin, IntegerField):
"""
A database field supporting enumerations with integer values that fit into
32 bytes or fewer
"""

primitive = int


class EnumPositiveIntegerField(EnumMixin, PositiveIntegerField):
"""
A database field supporting enumerations with positive (but signed) integer
values that fit into 32 bytes or fewer
"""

primitive = int


class EnumBigIntegerField(EnumMixin, BigIntegerField):
"""
A database field supporting enumerations with integer values that fit into
64 bytes or fewer
"""

primitive = int


class EnumPositiveBigIntegerField(EnumMixin, PositiveBigIntegerField):
"""
A database field supporting enumerations with positive (but signed) integer
values that fit into 64 bytes or fewer
"""

primitive = int


class _EnumFieldMetaClass(type):

Expand Down
Empty file.
6 changes: 6 additions & 0 deletions django_enum/tests/db_default/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class DBDefaultConfig(AppConfig):
name = 'django_enum.tests.db_default'
label = name.replace('.', '_')
Loading

0 comments on commit fb9bef7

Please sign in to comment.