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


109

선택 옵션이있는 모델 필드가 있으면 사람이 읽을 수있는 이름과 관련된 마법 값이있는 경향이 있습니다. 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']

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

답변:


164

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

이렇게 :

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

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

사용법은 thing.priority = Thing.NORMAL


2
주제에 대한 매우 상세한 블로그 게시물입니다. Google에서도 찾기가 어렵 기 때문에 감사합니다.
Alexander Ljungberg

1
FWIW, 리터럴 문자열 (예 : 양식, 사용자 입력 또는 이와 유사한 것)에서 설정해야하는 경우 다음과 같이 할 수 있습니다. thing.priority = getattr (thing, strvalue.upper ()).
mrooney 2013

1
블로그의 캡슐화 섹션을 정말 좋아합니다.
Nathan Keller

문제가 있습니다 : 관리자에게 항상 기본값이 표시됩니다! 값이 실제로 변하는 지 테스트했습니다! 이제 어떻게해야합니까?
Mahdi

이것이 갈 길이지만 조심하십시오. 나중에 선택 사항을 추가하거나 제거하면 번호가 순차적이지 않습니다. 더 이상 사용되지 않는 선택 항목을 주석 처리하여 향후 혼란이없고 db 충돌이 발생하지 않도록 할 수 있습니다.
grokpot 2015

7

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

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

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


예, dict를 던지는 것은 조금 어리석은 일입니다. :)
Alexander Ljungberg

7

여기에 내가 몇 분 전에 작성한 필드 유형이 있으며 원하는 것을 수행한다고 생각합니다. 생성자는 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]

1
그건 나쁘지 않은 앨런. 감사!
Alexander Ljungberg

5

지속적인 정의 방법에 감사하지만 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()

1
당신이 기본 클래스처럼 소개하지 않으려면 ChoiceEnum, 당신은 사용할 수 있습니다 .value.name@kirpit 설명으로하고의 사용 대신 choices()tuple([(x.value, x.name) for x in cls])필드의 생성자에서 직접 기능 (DRY)에 --either 또는.
Seth

4
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)

3

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

1

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

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

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


1

내 대답은 매우 늦었고 요즘 Django 전문가에게는 분명해 보일 수 있지만 여기에 착륙하는 사람에게는 최근 django-model-utils가 가져온 매우 우아한 솔루션을 발견했습니다 .https : //django-model-utils.readthedocs.io/ ko / latest / utilities.html # choices

이 패키지를 사용하면 3 개 튜플로 선택 사항을 정의 할 수 있습니다.

  • 첫 번째 항목은 데이터베이스 값입니다.
  • 두 번째 항목은 코드 판독 가능 값입니다.
  • 세 번째 항목은 사람이 읽을 수있는 값입니다.

따라서 수행 할 수있는 작업은 다음과 같습니다.

from model_utils import Choices

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

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

thing.priority = getattr(Thing.PRIORITIES.Normal)

이 방법:

  • 사람이 읽을 수있는 값을 사용하여 실제로 필드의 값을 선택할 수 있습니다 (제 경우에는 와일드 콘텐츠를 스크랩하고 정규화 된 방식으로 저장하기 때문에 유용합니다)
  • 깨끗한 값이 데이터베이스에 저장됩니다.
  • 비 -DRY 할 일이 없습니다;)

즐겨 :)


1
django-model-utils를 가져 오는 데 완전히 유효합니다. 가독성을 높이기위한 작은 제안; 기본 매개 변수에도 점 표기법을 사용합니다. priority = models.IntegerField(default=PRIORITIES.Low, choices=PRIORITIES)(말할 필요도없이 우선 순위 할당이 Thing 클래스 내부에 있도록 들여 쓰기되어야합니다). 또한 파이썬 식별자에 소문자 단어 / 문자를 사용하는 것을 고려하십시오. 클래스를 참조하는 것이 아니라 매개 변수를 대신 사용하기 때문입니다 (선택 사항은 다음 (0, 'low', 'Low'),과 같이됩니다).
Kim

0

원래 @Allan의 답변 수정 버전을 사용했습니다.

from enum import IntEnum, EnumMeta

class IntegerChoiceField(models.IntegerField):
    def __init__(self, choices, **kwargs):
        if hasattr(choices, '__iter__') and isinstance(choices, EnumMeta):
            choices = list(zip(range(1, len(choices) + 1), [member.name for member in list(choices)]))

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

    def to_python(self, value):
        return self.choices(value)

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

models.IntegerChoiceField = IntegerChoiceField

GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')

class Gear(Item, models.Model):
    # Safe to assume last element is largest value member of an enum?
    #type = models.IntegerChoiceField(GEAR, default=list(GEAR)[-1].name)
    largest_member = GEAR(max([member.value for member in list(GEAR)]))
    type = models.IntegerChoiceField(GEAR, default=largest_member)

    def __init__(self, *args, **kwargs):
        super(Gear, self).__init__(*args, **kwargs)

        for member in GEAR:
            setattr(self, member.name, member.value)

print(Gear().HEAD, (Gear().HEAD == GEAR.HEAD.value))

django-enumfields지금 사용 하는 패키지 패키지로 단순화 :

from enumfields import EnumIntegerField, IntEnum

GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')

class Gear(Item, models.Model):
    # Safe to assume last element is largest value member of an enum?
    type = EnumIntegerField(GEAR, default=list(GEAR)[-1])
    #largest_member = GEAR(max([member.value for member in list(GEAR)]))
    #type = EnumIntegerField(GEAR, default=largest_member)

    def __init__(self, *args, **kwargs):
        super(Gear, self).__init__(*args, **kwargs)

        for member in GEAR:
            setattr(self, member.name, member.value)

0

모델의 선택 옵션은이 필드에 대한 선택으로 사용할 정확히 두 항목 (예 : [(A, B), (A, B) ...])의 반복 가능으로 구성된 시퀀스를 허용합니다.

또한 Django는 선택 항목을 간결하게 정의하기 위해 하위 클래스로 분류 할 수있는 열거 형 유형 을 제공합니다 .

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

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

Django는 사람이 읽을 수있는 이름 또는 레이블로 사용할 추가 문자열 값을이 튜플 끝에 추가하는 것을 지원합니다. 레이블은 지연 번역 가능한 문자열 일 수 있습니다.

   # in your code 
   thing = get_thing() # instance of Thing
   thing.priority = ThingPriority.LOW

참고 : 당신이 사용할 수있는 사용이 ThingPriority.HIGH, ThingPriority.['HIGH']또는 ThingPriority(0)액세스 또는 조회 열거 회원.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.