Programing

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

lottogame 2020. 8. 28. 07:50
반응형

선택 항목 =… 이름으로 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]

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)

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

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

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

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


My answer is very late and might seem obvious to nowadays-Django experts, but to whoever lands here, i recently discovered a very elegant solution brought by django-model-utils: https://django-model-utils.readthedocs.io/en/latest/utilities.html#choices

This package allows you to define Choices with three-tuples where:

  • The first item is the database value
  • The second item is a code-readable value
  • The third item is a human-readable value

So here's what you can do:

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)

This way:

  • You can use your human-readable value to actually choose the value of your field (in my case, it's useful because i'm scraping wild content and storing it in a normalized way)
  • A clean value is stored in your database
  • You have nothing non-DRY to do ;)

Enjoy :)


Originally I used a modified version of @Allan's answer:

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

Simplified with the django-enumfields package package which I now use:

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)

참고URL : https://stackoverflow.com/questions/1117564/set-django-integerfield-by-choices-name

반응형