Skip to content

Commit

Permalink
Clean old code
Browse files Browse the repository at this point in the history
  • Loading branch information
christophehenry committed Sep 9, 2024
1 parent 8e8ea1f commit 90262f2
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 280 deletions.
44 changes: 36 additions & 8 deletions dsfr/enums.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django import VERSION
from django.db import models
from django.utils.safestring import mark_safe
from django.utils.version import PY311
Expand All @@ -7,6 +8,10 @@
else:
from types import DynamicClassAttribute as enum_property

if VERSION >= (5, 0):
from django.db.models.enums import ChoicesType
else:
from django.db.models.enums import ChoicesMeta as ChoicesType

__all__ = ["ExtendedChoices", "RichRadioButtonChoices"]

Expand All @@ -31,7 +36,7 @@ def _is_sunder(name):
)


class _ExtendedChoicesType(models.enums.ChoicesType):
class _ExtendedChoicesType(ChoicesType):
@classmethod
def __prepare__(metacls, cls, bases, **kwds):
classdict = super().__prepare__(cls, bases, **kwds)
Expand All @@ -49,8 +54,21 @@ def __init__(self, old_classdict):

def __setitem__(self, member, value):
"""Allows to handle declaring enum members as dicts"""
# _additional_attributes is also a dict, but we don't want it to be
# processed like an enum member
if not PY311 and isinstance(value, (list, tuple)):
# Prior to Python 3.11, EnumDict does not interpret auto() when
# wrapped in a tuple so we need to set the value alone a first time
# before wrapping it in a tuple.
if len(value) == 1:
return super().__setitem__(member, value[0])
elif len(value) == 2:
value, label = value
else:
*value, label = value

super().__setitem__(member, value)
dict.__setitem__(self, member, (self[member], label))
return

if not isinstance(value, dict):
return super().__setitem__(member, value)

Expand All @@ -62,12 +80,22 @@ def __setitem__(self, member, value):
)
)

if "label" in value:
super().__setitem__(
member, (value.pop("value"), value.pop("label"))
)
if PY311:
if "label" in value:
super().__setitem__(
member, (value.pop("value"), value.pop("label"))
)
else:
super().__setitem__(member, value.pop("value"))
else:
# Prior to Python 3.11, EnumDict does not interpret auto() when
# wrapped in a tuple so we need to set the value alone a first time
# before wrapping it in a tuple.
super().__setitem__(member, value.pop("value"))
if "label" in value:
dict.__setitem__(
self, member, (self[member], value.pop("label"))
)

for attr_name, attr_value in value.items():
if _is_sunder(attr_name) or _is_dunder(attr_name):
Expand Down Expand Up @@ -158,7 +186,7 @@ def pictogram_alt(self):
@enum_property
def html_label(self):
return (
mark_safe(getattr(self, self.private_variable_name("html_label")))
mark_safe(getattr(self, self.private_variable_name("html_label"))) # nosec
if hasattr(self, self.private_variable_name("html_label"))
else self.label
)
133 changes: 1 addition & 132 deletions dsfr/models.py
Original file line number Diff line number Diff line change
@@ -1,144 +1,13 @@
import functools
import os
from typing import Any


from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import TextChoices
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from django.utils.version import PY311

from dsfr.constants import DJANGO_DSFR_LANGUAGES, NOTICE_TYPE_CHOICES


if PY311:
from enum import property as enum_property
else:
from types import DynamicClassAttribute as enum_property


class _ExtendedChoicesType(models.enums.ChoicesType):
def __new__(metacls, classname, bases, classdict, **kwds):
dynamic_attributes = {}
for member in classdict._member_names:
value = classdict[member]
if isinstance(value, dict):
value = value.copy()
if "value" not in value:
raise ValueError(
"enum value for {member} should contain member 'value' "
"when using a dict as value; got {member} = {value}".format(
member=member, value=repr(value)
)
)

dict.__setitem__(classdict, member, metacls.get_value_from_dict(value))
value.pop("value")
value.pop("label", None)

for k, v in value.items():
if metacls.is_sunder(k) or metacls.is_dunder(k):
raise ValueError(
(
"enum value for {member} contains key {key}. "
"Names surrounded with single or double underscores are "
"not authorized as dict values"
).format(member=member, key=k)
)
dynamic_attributes.setdefault(k, {})
dynamic_attributes[k][member] = v

classdict._last_values = [
metacls.get_value_from_dict(item) for item in classdict._last_values
]

cls = super().__new__(metacls, classname, bases, classdict, **kwds)

metacls.set_dynamic_attributes(cls, dynamic_attributes)

return cls

@staticmethod
def set_dynamic_attributes(cls, dynamic_attributes: dict[str, dict[str, Any]]):
cls.NO_VALUE = object()

for k, v in dynamic_attributes.items():
variable = "_{}_".format(k)
for instance in cls:
if hasattr(instance, variable):
raise ValueError(
(
"Can't set {} on {} members; please choose a different name "
"or remove from the member value"
).format(variable, cls.__name__)
)
setattr(instance, variable, v.get(instance.name, cls.NO_VALUE))

def _getter(name, self):
result = getattr(self, name, cls.NO_VALUE)
if result is cls.NO_VALUE:
raise AttributeError(
"{} not present in {}.{}".format(
variable, cls.__name__, self.name
)
)
return result

setattr(cls, k, enum_property(functools.partial(_getter, variable)))

@staticmethod
def get_value_from_dict(value):
if not isinstance(value, dict):
return value
elif "label" in value:
return value["value"], value["label"]
else:
return value["value"]

@staticmethod
def is_dunder(name):
"""
Returns True if a __dunder__ name, False otherwise.
"""
return (
len(name) > 4
and name[:2] == name[-2:] == "__"
and name[2] != "_"
and name[-3] != "_"
)

@staticmethod
def is_sunder(name):
"""
Returns True if a _sunder_ name, False otherwise.
"""
return (
len(name) > 2
and name[0] == name[-1] == "_"
and name[1:2] != "_"
and name[-2:-1] != "_"
)


class ExtendedChoices(models.Choices, metaclass=_ExtendedChoicesType):
...


class RichRadioButton(ExtendedChoices, TextChoices):
@enum_property
def pictogram(self):
return self._pictogram_ if hasattr(self, "_pictogram_") else ""

@enum_property
def html_label(self):
return (
mark_safe(self._html_label_)
if hasattr(self, "_html_label_")
else self.label
)


def validate_image_extension(value):
ext = os.path.splitext(value.name)[1] # [0] returns path+filename
valid_extensions = [".jpg", ".jpeg", ".png", ".svg"]
Expand Down
15 changes: 8 additions & 7 deletions dsfr/test/test_enums.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from enum import auto, nonmember
from enum import auto
from unittest import skipIf

from django.db.models import IntegerChoices
from django.test import SimpleTestCase
Expand All @@ -8,7 +9,7 @@
from dsfr.enums import ExtendedChoices

if PY311:
from enum import property as enum_property
from enum import property as enum_property, nonmember
else:
from types import DynamicClassAttribute as enum_property

Expand Down Expand Up @@ -62,6 +63,7 @@ class TestExtendedChoices(ExtendedChoices, IntegerChoices):
{it.additionnal_attribute_2 for it in TestExtendedChoices},
)

@skipIf(not PY311, "'enum.nonmember' was added to Python 3.11")
def test_nonmember_attributes(self):
class TestExtendedChoices(ExtendedChoices, IntegerChoices):
TEST_1 = {"value": auto()}
Expand Down Expand Up @@ -102,11 +104,10 @@ class TestExtendedChoices(ExtendedChoices, IntegerChoices):
TEST_2 = auto()

def dynamic_attribute_value(self, name):
match name:
case "additionnal_attribute":
return {"lorem": "ipsum {}".format(self.value)}
case _:
return {"lorem": "ipsum x"}
if name == "additionnal_attribute":
return {"lorem": "ipsum {}".format(self.value)}
else:
return {"lorem": "ipsum x"}

self.assertEqual(
[{"lorem": "ipsum 1"}, {"lorem": "ipsum 2"}],
Expand Down
126 changes: 0 additions & 126 deletions dsfr/test/test_models.py

This file was deleted.

Loading

0 comments on commit 90262f2

Please sign in to comment.