Поле модели Django Choices с возможностью выбора классов
Следующий код работал в python 3.10, но не работал в 3.11 из-за изменения в модуле enum
.
Теперь приложение не запускается со следующим сообщением об ошибке :
File "/home/runner/work/e/e/certification/models.py", line 3, in <module>
from .certifications.models import Certification, QuizzCertification # noqa: F401
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/e/e/certification/certifications/models.py", line 17, in <module>
class CertificationType(models.TextChoices):
File "/opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/site-packages/django/db/models/enums.py", line 49, in __new__
cls = super().__new__(metacls, classname, bases, classdict, **kwds)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/enum.py", line 560, in __new__
raise exc
File "/opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/enum.py", line 259, in __set_name__
enum_member = enum_class._new_member_(enum_class, *args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.2/x64/lib/python3.11/enum.py", line 1278, in __new__
raise TypeError('%r is not a string' % (values[0], ))
TypeError: <class 'certification.certifications.models.QuizzCertification'> is not a string
Как бы вы реализовали поле модели с вариантами выбора, где первым элементом является класс Model?
class QuizzCertification(models.Model):
...
class OtherCertification(models.Model):
...
class CertificationType(models.TextChoices):
QUIZZ = QuizzCertification, "Quizz"
OTHER = OtherCertification, "Other"
class Certification(models.Model):
name = models.CharField(max_length=100, unique=True)
description = models.TextField(_("Description"), blank=True, null=True)
type_of = models.CharField(
_("Type of certification"),
max_length=100,
choices=CertificationType,
default=CertificationType.QUIZZ,
)
В итоге я создал новое поле Model. Мне нужно было переопределить метод _check_choices, чтобы избежать ошибки при запуске из-за выбора, который не является строкой, и я добавил проверку, чтобы убедиться, что все варианты являются действительными именами классов.
class ModelField(models.CharField):
"""Same as CharField but python object is a django.db.models.Model class."""
_choice_not_found_error = (
"Some model %s.%s choices saved in DB do not exist in the code. "
"Did you rename or delete a model? Incorrect choices: %s"
)
def from_db_value(self, value, *args):
"""Converts the value as it's loaded from the database."""
if value is None:
return value
return self._get_value_from_choices(value)
def to_python(self, value):
"""Converts the value into the correct Python object."""
if not isinstance(value, str):
return value
if value is None:
return value
return self._get_value_from_choices(value)
def get_prep_value(self, value):
"""Convert class to string value to be saved in the DB."""
return str(value)
def _check_choices(self):
"""Checks if all choices saved in the database exist in the code."""
model_names_from_choices = [str(choice[0][0]) for choice in self.choices]
wrong_models = self.model.objects.exclude(type_of__in=model_names_from_choices)
if wrong_models:
wrong_choices = [
choice
for model in wrong_models
for choice in getattr(model.choices)
if choice[0][0] in model_names_from_choices
]
formatted_wrong_choices = ", ".join(wrong_choices)
return [
checks.Error(
self._choice_not_found_error
% (self.model, self.attname, formatted_wrong_choices),
)
]
return []
def _get_value_from_choices(self, value: str):
"""
Retrieves the corresponding choice for the given value.
If choice is invalid, it raises a ValueError.
"""
try:
return next(choice for choice in self.choices if str(choice[0][0]) == value)
except StopIteration as error:
raise ValueError(
self._choice_not_found_error % (self.model, self.attname, value)
) from error
И я могу использовать его следующим образом :
class CertificationType(models.Choices):
QUIZZ = QuizzCertification, _("Quizz")
class Certification(models.Model):
name = models.CharField(max_length=100, unique=True)
type_of = ModelField(
_("Type of certification"),
max_length=100,
choices=CertificationType,
default=CertificationType.QUIZZ,
)