[python] 선택 항목 =… 이름으로 Django IntegerField 설정

선택 옵션이있는 모델 필드가 있으면 사람이 읽을 수있는 이름과 관련된 마법 값이있는 경향이 있습니다. Django에 값 대신 사람이 읽을 수있는 이름으로 이러한 필드를 설정하는 편리한 방법이 있습니까?

이 모델을 고려하십시오.

class Thing(models.Model):
  PRIORITIES = (
    (0, 'Low'),
    (1, 'Normal'),
    (2, 'High'),
  )

  priority = models.IntegerField(default=0, choices=PRIORITIES)

어느 시점에서 Thing 인스턴스가 있고 우선 순위를 설정하려고합니다. 분명히 할 수 있습니다.

thing.priority = 1

그러나 그것은 당신이 PRIORITIES의 Value-Name 매핑을 기억하도록 강요합니다. 작동하지 않습니다.

thing.priority = 'Normal' # Throws ValueError on .save()

현재 다음과 같은 어리석은 해결 방법이 있습니다.

thing.priority = dict((key,value) for (value,key) in Thing.PRIORITIES)['Normal']

그러나 그것은 투박합니다. 이 시나리오가 얼마나 흔한지를 감안할 때 누구에게 더 나은 솔루션이 있는지 궁금합니다. 내가 완전히 간과 한 선택 이름으로 필드를 설정하는 필드 방법이 있습니까?



답변

여기 에서 본대로하십시오 . 그런 다음 적절한 정수를 나타내는 단어를 사용할 수 있습니다.

이렇게 :

LOW = 0
NORMAL = 1
HIGH = 2
STATUS_CHOICES = (
    (LOW, 'Low'),
    (NORMAL, 'Normal'),
    (HIGH, 'High'),
)

그런 다음 그들은 여전히 ​​DB의 정수입니다.

사용법은 thing.priority = Thing.NORMAL


답변

나는 아마도 역방향 조회 딕셔너리를 한 번에 설정했을 것입니다.하지만 그렇지 않았다면 그냥 사용했을 것입니다.

thing.priority = next(value for value, name in Thing.PRIORITIES
                      if name=='Normal')

즉석에서 딕셔너리를 구축하는 것보다 더 간단 해 보입니다.


답변

여기에 내가 몇 분 전에 작성한 필드 유형이 있으며 원하는 것을 수행한다고 생각합니다. 생성자는 IntegerField에 대한 선택 옵션과 동일한 형식의 2- 튜플 튜플이거나 대신 간단한 이름 목록 (예 : ChoiceField (( ‘Low’, ‘Normal’, ‘High’), 기본값 = ‘Low’)). 클래스는 문자열에서 int 로의 매핑을 처리하므로 int를 볼 수 없습니다.

  class ChoiceField(models.IntegerField):
    def __init__(self, choices, **kwargs):
        if not hasattr(choices[0],'__iter__'):
            choices = zip(range(len(choices)), choices)

        self.val2choice = dict(choices)
        self.choice2val = dict((v,k) for k,v in choices)

        kwargs['choices'] = choices
        super(models.IntegerField, self).__init__(**kwargs)

    def to_python(self, value):
        return self.val2choice[value]

    def get_db_prep_value(self, choice):
        return self.choice2val[choice]


답변

지속적인 정의 방법에 감사하지만 Enum 유형 이이 작업에 가장 적합 하다고 생각 합니다. 코드를 더 읽기 쉽게 유지하면서 동시에 항목의 정수와 문자열을 나타낼 수 있습니다.

열거 형은 버전 3.4에서 Python에 도입되었습니다. 당신이 어떤 낮은 (버전 2.x 등)를 사용하는 경우 여전히 설치하여 수 백 포트 패키지 : pip install enum34.

# myapp/fields.py
from enum import Enum


class ChoiceEnum(Enum):

    @classmethod
    def choices(cls):
        choices = list()

        # Loop thru defined enums
        for item in cls:
            choices.append((item.value, item.name))

        # return as tuple
        return tuple(choices)

    def __str__(self):
        return self.name

    def __int__(self):
        return self.value


class Language(ChoiceEnum):
    Python = 1
    Ruby = 2
    Java = 3
    PHP = 4
    Cpp = 5

# Uh oh
Language.Cpp._name_ = 'C++'

이것은 거의 모든 것입니다. 를 상속 ChoiceEnum하여 고유 한 정의를 만들고 다음과 같은 모델 정의에서 사용할 수 있습니다.

from django.db import models
from myapp.fields import Language

class MyModel(models.Model):
    language = models.IntegerField(choices=Language.choices(), default=int(Language.Python))
    # ...

추측 할 수 있듯이 쿼리는 케이크를 장식합니다.

MyModel.objects.filter(language=int(Language.Ruby))
# or if you don't prefer `__int__` method..
MyModel.objects.filter(language=Language.Ruby.value)

문자열로 표현하는 것도 쉽습니다.

# Get the enum item
lang = Language(some_instance.language)

print(str(lang))
# or if you don't prefer `__str__` method..
print(lang.name)

# Same as get_FOO_display
lang.name == some_instance.get_language_display()


답변

class Sequence(object):
    def __init__(self, func, *opts):
        keys = func(len(opts))
        self.attrs = dict(zip([t[0] for t in opts], keys))
        self.choices = zip(keys, [t[1] for t in opts])
        self.labels = dict(self.choices)
    def __getattr__(self, a):
        return self.attrs[a]
    def __getitem__(self, k):
        return self.labels[k]
    def __len__(self):
        return len(self.choices)
    def __iter__(self):
        return iter(self.choices)
    def __deepcopy__(self, memo):
        return self

class Enum(Sequence):
    def __init__(self, *opts):
        return super(Enum, self).__init__(range, *opts)

class Flags(Sequence):
    def __init__(self, *opts):
        return super(Flags, self).__init__(lambda l: [1<<i for i in xrange(l)], *opts)

다음과 같이 사용하십시오.

Priorities = Enum(
    ('LOW', 'Low'),
    ('NORMAL', 'Normal'),
    ('HIGH', 'High')
)

priority = models.IntegerField(default=Priorities.LOW, choices=Priorities)


답변

Django 3.0부터 다음을 사용할 수 있습니다.

class ThingPriority(models.IntegerChoices):
    LOW = 0, 'Low'
    NORMAL = 1, 'Normal'
    HIGH = 2, 'High'


class Thing(models.Model):
    priority = models.IntegerField(default=ThingPriority.LOW, choices=ThingPriority.choices)

# then in your code
thing = get_my_thing()
thing.priority = ThingPriority.HIGH


답변

원하는 사람이 읽을 수있는 값으로 숫자를 바꾸면됩니다. 이와 같이 :

PRIORITIES = (
('LOW', 'Low'),
('NORMAL', 'Normal'),
('HIGH', 'High'),
)

이렇게하면 사람이 읽을 수 있지만 자신 만의 순서를 정의해야합니다.