Поле модели 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,
    )
Вернуться на верх