Вопрос по django-models, django, python – Установите Django IntegerField с помощью выбора =… имя

88

Когда у вас есть поле модели с опцией выбора, вы склонны иметь магические значения, связанные с понятными для человека именами. Есть ли в Django удобный способ установить эти поля по понятному человеку имени вместо значения?

Рассмотрим эту модель:

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

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

В какой-то момент у нас есть экземпляр Thing, и мы хотим установить его приоритет. Очевидно, вы могли бы сделать,

thing.priority = 1

Но это заставляет вас запоминать отображение Приоритетов. Это не работает:

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

В настоящее время у меня есть этот глупый обходной путь:

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

но это неуклюже. Учитывая, насколько распространенным может быть этот сценарий, мне было интересно, есть ли у кого-нибудь лучшее решение. Есть какой-то метод поля для установки полей по имени выбора, который я полностью пропустил?

Ваш Ответ

7   ответов
3
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)
146

Делай каквидел здесь, Затем вы можете использовать слово, которое представляет правильное целое число.

Вот так:

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

Тогда они все еще целые числа в БД.

Использование будетthing.priority = Thing.NORMAL

FWIW, если вам нужно установить его из буквальной строки (возможно, из формы, ввода пользователя или подобного), вы можете просто сделать: thing.priority = getattr (thing, strvalue.upper ()).
Очень нравится раздел Инкапсуляция в блоге.
Это путь, но будьте осторожны: если вы добавите или удалите варианты в будущем, ваши номера не будут последовательными. Вы могли бы закомментировать устаревшие варианты, чтобы не было путаницы в будущем, и вы не столкнетесь с коллизиями БД.
Это очень подробное сообщение в блоге на эту тему. Трудно найти с Google тоже, так что спасибо. Alexander Ljungberg
У меня проблема: я всегда вижу значение по умолчанию для администратора! Я проверил, что значение действительно меняется! Что мне теперь делать?
1

экспертов Django, но для тех, кто сюда приземлится, я недавно обнаружил очень элегантное решение, предложенное django-model-utils:https://django-model-utils.readthedocs.io/en/latest/utilities.html#choices

Этот пакет позволяет вам определять варианты с тремя кортежами, где:

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

Вот что вы можете сделать:

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)

Сюда:

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

Наслаждаться :)

Вполне допустимо, чтобы вызвать django-model-utils. Одно небольшое предложение для повышения читабельности; используйте точечную нотацию и для параметра по умолчанию:priority = models.IntegerField(default=PRIORITIES.Low, choices=PRIORITIES) (Само собой разумеется, что назначение приоритета должно быть сделано с отступом, чтобы быть внутри класса Thing, чтобы сделать это). Также рассмотрите возможность использования строчных слов / символов для идентификатора Python, поскольку вы ссылаетесь не на класс, а на параметр (тогда ваш выбор станет:(0, 'low', 'Low'), и так далее).
Спасибо @Kim, ответ отредактирован;)
5

который я написал несколько минут назад и который, я думаю, делает то, что вы хотите. Его конструктор требует аргумента «выборы», который может быть либо кортежем из 2 кортежей в том же формате, что и опция выбора для IntegerField, либо вместо этого простым списком имен (то есть ChoiceField ((«Низкий», «apos;»); Нормальный »,« Высокий »), по умолчанию =« Низкий »)). Класс заботится о преобразовании строки в 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]
Это неплохо, Аллан. Спасибо! Alexander Ljungberg
7

вероятно, настроил бы диктовку обратного просмотра раз и навсегда, но если бы я не использовал, я бы просто использовал:

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

что кажется проще, чем строить диктат на лету, чтобы просто отбросить его ;-).

Да, бросать диктат немного глупо, теперь, когда ты это говоришь. :) Alexander Ljungberg
1

Просто замените ваши цифры на удобочитаемые значения, которые вы хотели бы. В качестве таких:

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

Это делает его читаемым человеком, однако вам необходимо определить свой собственный порядок.

2

Enum Тип гораздо лучше для этой задачи. Они могут представлять целое число и строку для элемента одновременно, сохраняя при этом ваш код более читабельным.

Перечисления были введены в Python в версии 3.4. Если вы используете какой-либо более низкий (например, v2.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()

Похожие вопросы