Пользовательское поле Django не возвращает экземпляры Enum из запроса

У меня есть простое настраиваемое поле, реализованное для использования экземпляров Python 3 Enum. Назначение экземпляров enum для моего атрибута модели и сохранение в базе данных работает правильно. Однако выборка экземпляров модели с использованием QuerySet приводит к тому, что атрибут enum является строкой, а не соответствующим экземпляром Enum.

Как я могу получить ниже EnumField вернуть действительный Enum экземпляры, а не строки?

fields.py:

from enum import Enum

from django.core.exceptions import ValidationError
from django.db import models


class EnumField(models.CharField):
    description = 'Enum with strictly typed choices'

    def __init__(self, enum_class, *args, **kwargs):
        self._enum_class = enum_class
        choices = []
        for enum in self._enum_class:
            title_case = enum.name.replace('_', ' ').title()
            entry = (enum, title_case)
            choices.append(entry)
        kwargs['choices'] = choices
        kwargs['blank'] = False  # blank doesn't make sense for enum's
        super().__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        args.insert(0, self._enum_class)
        del kwargs['choices']
        return name, path, args, kwargs

    def from_db_values(self, value, expression, connection, context):
        return self.to_python(value)

    def to_python(self, value):
        if value is None or isinstance(value, self._enum_class):
            return value
        else:
            return self._parse_enum(value)

    def _parse_enum(self, value):
        try:
            enum = self._enum_class[value]
        except KeyError:
            raise ValidationError("Invalid type '{}' for {}".format(
                value, self._enum_class))
        else:
            return enum

    def get_prep_value(self, value):
        if value is None:
            return None
        elif isinstance(value, Enum):
            return value.name
        else:
            msg = "'{}' must have type {}".format(
                value, self._enum_class.__name__)
            if self.null:
                msg += ', or `None`'
            raise TypeError(msg)

    def get_choices(self, **kwargs):
        kwargs['include_blank'] = False  # Blank is not a valid option
        choices = super().get_choices(**kwargs)
        return choices

2 ответа

Решение

После долгих раскопок я смог ответить на свой вопрос:

SubfieldBase устарела и будет удалена в Django 1.10; вот почему я оставил это вне реализации выше. Тем не менее, кажется, что то, что он делает, все еще важно. Добавление следующего метода для замены функциональности, которая SubfieldBase добавил бы.

def contribute_to_class(self, cls, name, **kwargs):
    super(EnumField, self).contribute_to_class(cls, name, **kwargs)
    setattr(cls, self.name, Creator(self))

Creator дескриптор это то, что вызывает to_python по атрибутам. Если этого не произойдет, запросы на модели приведут к EnumField Поля в экземплярах модели являются просто строками, а не экземплярами Enum, как я хотел.

Не человек Джанго, но я вижу две возможности:

  • from_db_values не вызывается, поэтому ваша строка не восстанавливается в enum
  • self._enum_class это {str: str} словарь а не актуальный Enum

Попробуйте посыпать в некоторых print() или же log() звонки, чтобы получить лучшее представление о том, что (не) происходит.

О, еще одна возможность заключается в том, что вы работаете на Python до 3.4 и не используете бэкпорт enum34.

Другие вопросы по тегам