Skip to content

54 support django 50 #57

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

Merged
merged 9 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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