Files
drf-rack/net/xeaf/rack/models/fields/enum_choice_field.py

147 lines
5.6 KiB
Python

# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса EnumChoiceField
"""
from typing import Any
from typing import Sequence
from typing import Type
from django.core.exceptions import ValidationError
from django.db import models
from net.xeaf.rack.core.core_enum import CoreEnum
class EnumChoiceField(models.CharField):
"""
Поле выбора перечисления со строгой рантайм-валидацией типов
"""
MAX_DEFAULT_LENGTH = 64
""" Максимальный размер поля """
_enum_cls: Type[CoreEnum]
""" Класс перечисления """
def __init__(self, choices: Type[CoreEnum], *args, **kwargs):
"""
Инициализация
:param choices: Класс перечисления CoreEnum
:param args: Дополнительные аргументы
:param kwargs: Дополнительные параметры
"""
if not isinstance(choices, type) or not issubclass(choices, CoreEnum):
raise TypeError("The 'choices' parameter must be a subclass of CoreEnum.")
self._enum_cls = choices
kwargs.setdefault('max_length', self.MAX_DEFAULT_LENGTH)
kwargs['choices'] = choices.choices()
super().__init__(*args, **kwargs)
# noinspection unused-parameter
def from_db_value[E: CoreEnum](self, value: Any, expression: Any, connection: Any) -> E | str | None:
"""
Преобразует сырое значение из базы данных в объект CoreEnum
:param value: Входящее сырое значение
:param expression: Выражение для преобразования значения
:param connection: Соединение с базой данных
:return: Представление объекта CoreEnum или строка, или None
"""
return self._normalize_and_validate(value, action_context="read")
def to_python[E: CoreEnum](self, value: Any) -> E | str | None:
"""
Строго преобразует входящее значение в объект CoreEnum при очистке форм или сериализации
:param value: Входящее значение
:return: Объект CoreEnum или строка, или None
"""
return self._normalize_and_validate(value, action_context="clean")
def validate(self, value: Any, model_instance: Any) -> None:
"""
Валидация значения поля
"""
if value is None or value == "":
return super().validate(value, model_instance)
lookup_value = value.value if isinstance(value, self._enum_cls) else value
if lookup_value not in self._enum_cls.values():
raise ValidationError(
self.error_messages['invalid_choice'],
code='invalid_choice',
params={'value': value},
)
return super(models.CharField, self).validate(value, model_instance)
def get_prep_value(self, value: Any) -> str | None:
"""
Преобразует объект CoreEnum обратно в строку перед сохранением в БД
"""
# Стандартная проверка
value = super().get_prep_value(value)
# Проверяем строгость типов и ограничений через наш метод
resolved = self._normalize_and_validate(value, action_context="save")
if isinstance(resolved, self._enum_cls):
return resolved.value
return resolved
def deconstruct(self) -> tuple[str, str, Sequence[Any], dict[str, Any]]:
"""
Возвращает параметры для сериализации поля
:return: Кортеж с параметрами для сериализации поля
"""
name, path, args, kwargs = super().deconstruct()
kwargs['choices'] = self._enum_cls
return name, path, args, kwargs
def _normalize_and_validate(self, value: Any, action_context: str) -> CoreEnum | str | None:
"""
Внутренний метод для проверки null/blank ограничений и приведения значения к CoreEnum.
Реализует паттерн Fail-Fast для предотвращения работы с некорректными данными.
:param value: Входящее сырое значение
:param action_context: Контекст выполнения для текста ошибки (e.g., 'read', 'save')
:return: Объект CoreEnum, пустая строка или None
:raises ValueError: Если значение нарушает правила null/blank или отсутствует в перечислении
"""
if value is None:
if not self.null:
raise ValueError(f"Cannot {action_context} null; the field '{self.name}' is not nullable.")
return None
if value == "":
if not self.blank:
raise ValueError(f"Cannot {action_context} blank string; the field '{self.name}' does not allow blanks.")
return ""
if isinstance(value, self._enum_cls):
return value
return self._enum_cls.from_string(str(value))