Установите Django IntegerField по выбору =... name
Когда у вас есть поле модели с опцией выбора, вы, как правило, имеете некоторые магические значения, связанные с читаемыми людьми именами. Есть ли в Django удобный способ установить эти поля с помощью читаемого пользователем имени вместо значения?
Рассмотрим эту модель:
class Thing(models.Model):
PRIORITIES = (
(0, 'Low'),
(1, 'Normal'),
(2, 'High'),
)
priority = models.IntegerField(default=0, choices=PRIORITIES)
В какой-то момент у нас есть экземпляр Thing, и мы хотим установить его приоритет. Очевидно, вы могли бы сделать,
thing.priority = 1
Но это заставляет вас запоминать сопоставление Value-Name PRIORITIES. Это не работает:
thing.priority = 'Normal' # Throws ValueError on .save()
В настоящее время у меня есть это глупое обходное решение:
thing.priority = dict((key,value) for (value,key) in Thing.PRIORITIES)['Normal']
но это неуклюже. Учитывая, насколько распространен этот сценарий, мне было интересно, есть ли у кого-то лучшее решение. Есть ли какой-либо полевой метод для установки полей по выбору, которые я полностью игнорировал?
Ответы
Ответ 1
Сделайте как здесь. Затем вы можете использовать слово, представляющее правильное целое число.
Так же:
LOW = 0
NORMAL = 1
HIGH = 2
STATUS_CHOICES = (
(LOW, 'Low'),
(NORMAL, 'Normal'),
(HIGH, 'High'),
)
Затем они все еще целые числа в БД.
Использование будет thing.priority = Thing.NORMAL
Ответ 2
Я бы, наверное, раз и навсегда установил диктовку обратного поиска, но если бы я этого не сделал, я бы просто использовал:
thing.priority = next(value for value, name in Thing.PRIORITIES
if name=='Normal')
который кажется более простым, чем создание dict на лету, чтобы просто отбросить его; -).
Ответ 3
Здесь тип поля, который я написал несколько минут назад, что я думаю, делает то, что вы хотите. Его конструктор требует аргумента "выбор", который может быть либо кортежем из 2-х кортежей в том же формате, что и опция выбора IntegerField, либо просто простой список имен (например, ChoiceField (( "Low", "Normal", "Высокий" ), по умолчанию = "Низкий" )). Класс заботится о отображении из строки в 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]
Ответ 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)
Ответ 5
Я ценю постоянный определяющий способ, но считаю, что Enum тип намного лучше подходит для этой задачи. Они могут представлять целое число и строку для элемента в одно и то же время, сохраняя ваш код более удобочитаемым.
Перечисления были представлены Python в версии 3.4. Если вы используете какой-либо более низкий (например, v2.x), вы все равно можете его установить, установив backported package: 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()
Ответ 6
Просто замените свои номера на удобочитаемые вами значения. Таким образом:
PRIORITIES = (
('LOW', 'Low'),
('NORMAL', 'Normal'),
('HIGH', 'High'),
)
Это делает его общедоступным для чтения, однако вам нужно будет определить свой собственный порядок.
Ответ 7
Мой ответ очень поздний и может показаться очевидным для сегодняшних специалистов - экспертов Django, но для тех, кто приземляется здесь, я недавно обнаружил очень элегантное решение, принесенное django-model-utils: https://django-model-utils.readthedocs.io/en/latest/utilities.html#choices
Этот пакет позволяет вам определять варианты с тремя кортежами, где:
- Первым элементом является значение базы данных
- Второй элемент - это код для чтения
- Третий элемент - удобочитаемое значение
Итак, что вы можете сделать:
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)
Таким образом:
- Вы можете использовать свое удобочитаемое значение для фактического выбора значения вашего поля (в моем случае это полезно, потому что я очищаю дикий контент и сохраняю его нормализованным образом)
- Чистая величина сохраняется в вашей базе данных
- У вас нет ничего, кроме СУХОЙ;)
Наслаждайтесь:)