Paras
Tengo una clase modelo en la que quiero que dos campos sean campos de elección, por lo que para completar esas opciones estoy usando una enumeración como se indica a continuación:
#models.py
class Transaction(models.Model):
transaction_status = models.CharField(max_length=255, choices=TransactionStatus.choices())
transaction_type = models.CharField(max_length=255, choices=TransactionType.choices())
#enums.py
class TransactionType(Enum):
IN = "IN",
OUT = "OUT"
@classmethod
def choices(cls):
print(tuple((i.name, i.value) for i in cls))
return tuple((i.name, i.value) for i in cls)
class TransactionStatus(Enum):
INITIATED = "INITIATED",
PENDING = "PENDING",
COMPLETED = "COMPLETED",
FAILED = "FAILED"
ERROR = "ERROR"
@classmethod
def choices(cls):
print(tuple((i.name, i.value) for i in cls))
return tuple((i.name, i.value) for i in cls)
Sin embargo, cuando intento acceder a este modelo a través de Django Admin, aparece el siguiente error:
Django Version: 1.11
Exception Type: ValueError
Exception Value:
too many values to unpack (expected 2)
Seguí dos artículos que describían cómo usar enumeraciones:
César Canassa
Django 3.0 tiene soporte incorporado para Enums
Ejemplo:
from django.utils.translation import gettext_lazy as _ class Student(models.Model): class YearInSchool(models.TextChoices): FRESHMAN = 'FR', _('Freshman') SOPHOMORE = 'SO', _('Sophomore') JUNIOR = 'JR', _('Junior') SENIOR = 'SR', _('Senior') GRADUATE = 'GR', _('Graduate') year_in_school = models.CharField( max_length=2, choices=YearInSchool.choices, default=YearInSchool.FRESHMAN, )
Estos funcionan de manera similar a enumeración de la biblioteca estándar de Python, pero con algunas modificaciones:
- Los valores de los miembros de enumeración son una tupla de argumentos para usar al construir el tipo de datos concreto. Django admite agregar un valor de cadena adicional al final de esta tupla para usar como nombre legible por humanos, o
label
. Ellabel
puede ser una cadena traducible perezosa. Por lo tanto, en la mayoría de los casos, el valor del miembro será un(value, label)
dos tuplas. Si no se proporciona una tupla, o el último elemento no es una cadena (perezosa), la etiqueta es generada automáticamente del nombre del miembro.- A
.label
La propiedad se agrega a los valores, para devolver el nombre legible por humanos. Se agregan varias propiedades personalizadas a las clases de enumeración:.choices
,.labels
,.values
y.names
– para facilitar el acceso a las listas de esas partes separadas de la enumeración. Usar.choices
como un valor adecuado para pasar a opciones en una definición de campo.- El uso de
enum.unique()
se aplica para garantizar que los valores no se puedan definir varias veces. Es poco probable que esto se espere en las elecciones de un campo.
Para más información, revisa la documentación
Nota:
Como señaló @Danielle Madeley, si intenta acceder a la year_in_school
atributo directamente Django todavía devuelve la cadena sin procesar en lugar del objeto Enum:
>>> student.year_in_school
'FR'
Lo que suelo hacer es crear un método auxiliar que devuelva el objeto Enum:
class Student(models.Model):
...
def get_year_in_school(self) -> YearInSchool:
# Get value from choices enum
return self.YearInSchool[self.year_in_school]
-
Solo una advertencia aquí,
student.year_in_school
devuelve una cadena, no una enumeración. Tienes que devolver manualmente el resultado o usar ==/!=– Danielle Madeley
24 de diciembre de 2020 a las 2:47
-
@DavidPiao Me temo que no entiendo tu pregunta. Pero aquí hay una respuesta a la pregunta “a”: Django, lamentablemente, nunca devolverá un tipo de enumeración y siempre devolverá una cadena. Entonces
foo.state == State.DRAFT
funcionará en la comparación de cadenas, pero los documentos de Python prefierenfoo.state is State.DRAFT
no lo haré Tu puedes hacerState(foo.state) is State.DRAFT
.– Danielle Madeley
10 oct 2021 a las 21:45
-
@CesarCanassa Si se copia algo de la documentación textualmente, cítelo.
–Abdul Aziz Barkat
13 de noviembre de 2021 a las 7:34
-
return self.YearInSchool[self.year_in_school]
debe tener corchetes en lugar de corchetes:return self.YearInSchool(self.year_in_school)
de lo contrario obtendrá un KeyError.– código de salida
11 de marzo de 2022 a las 11:54
-
Gracias por esto. Nota: creo que al método auxiliar le falta ‘self’ en los parámetros del método. Debería decir: def get_year_in_school(self) -> YearInSchool:
– Levitybot
30 de julio de 2022 a las 8:43
dirkgroten
Para Django 2.x y versiones anteriores:
Tú defines un Enum
configurando las diversas opciones como se documenta aquí:
class TransactionStatus(Enum):
INITIATED = "INITIATED"
PENDING = "PENDING"
COMPLETED = "COMPLETED"
FAILED = "FAILED"
ERROR = "ERROR"
Tenga en cuenta que no hay comas! Esto le permite más adelante en su código referirse a TransactionStatus.ERROR
o TransactionStatus.PENDING
.
El resto de tu código es correcto. obtienes el choices
creando tuplas de option.name
, option.value
.
ACTUALIZACIÓN: para Django 3.x y superioruse los tipos incorporados TextChoices
, IntegerChoices
y Choices
como se describe aquí. De esa manera usted no tiene que construir el choices
tupla tú mismo.
anjaneyulubatta505
El problema en tu código es que INITIATED = "INITIATED",
una coma despues INITIATED
opción y otras opciones. cuando agregamos una coma después de cualquier cadena, se convertirá en una tupla. Vea un ejemplo a continuación
s="my str"
print(type(s))
# output: str
s="my str",
print(type(s))
# output: tuple
#modelos.py
class Transaction(models.Model):
trasaction_status = models.CharField(max_length=255, choices=TransactionStatus.choices())
transaction_type = models.CharField(max_length=255, choices=TransactionType.choices())
#enums.py
class TransactionType(Enum):
IN = "IN"
OUT = "OUT"
@classmethod
def choices(cls):
print(tuple((i.name, i.value) for i in cls))
return tuple((i.name, i.value) for i in cls)
class TransactionStatus(Enum):
INITIATED = "INITIATED"
PENDING = "PENDING"
COMPLETED = "COMPLETED"
FAILED = "FAILED"
ERROR = "ERROR"
@classmethod
def choices(cls):
print(tuple((i.name, i.value) for i in cls))
return tuple((i.name, i.value) for i in cls)
Para Django > 3.0 https://docs.djangoproject.com/en/4.0/ref/models/fields/#field-choices-enum-types
-
No es así como se define un
Enum
. Solo elimina las comas.– dirkgroten
21 de febrero de 2019 a las 11:24
laeleccióncorrecta
Si está recibiendo este error:
‘opciones’ debe ser un iterable que contiene (valor real, nombre legible por humanos) tuplas
Y está usando Django3, entonces probablemente se encuentre con el mismo problema que tuve: los “Enums” deben estar incrustados en el modelo donde está tratando de usarlos y no pueden declararse fuera del modelo. Por ejemplo, esto será no trabajar:
class YearInSchool(models.TextChoices):
FRESHMAN = 'FR', _('Freshman')
SOPHOMORE = 'SO', _('Sophomore')
JUNIOR = 'JR', _('Junior')
SENIOR = 'SR', _('Senior')
GRADUATE = 'GR', _('Graduate')
class Student(models.Model):
year_in_school = models.CharField(
max_length=2,
choices=YearInSchool.choices,
default=YearInSchool.FRESHMAN,
)
Donde como este ejemplo de los documentos:
class Student(models.Model):
class YearInSchool(models.TextChoices):
FRESHMAN = 'FR', _('Freshman')
SOPHOMORE = 'SO', _('Sophomore')
JUNIOR = 'JR', _('Junior')
SENIOR = 'SR', _('Senior')
GRADUATE = 'GR', _('Graduate')
year_in_school = models.CharField(
max_length=2,
choices=YearInSchool.choices,
default=YearInSchool.FRESHMAN,
)
carril.khayrullin
Un ejemplo de mi proyecto:
import enum
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.utils.translation import gettext_lazy as _
class NotificationTemplate(models.Model):
class Meta:
verbose_name = _('notification template')
verbose_name_plural = _('notification templates')
@enum.unique
class Name(str, enum.Enum):
ONBOARDING = 'onboarding'
TG_ERROR = 'tg_error'
FB_ERROR = 'fb_error'
@classmethod
def choices(cls):
return [(item.value, item.name) for item in cls]
@enum.unique
class Type(int, enum.Enum):
PUSH = 1
EMAIL = 2
TELEGRAM = 3
VK = 4
OTHER = 5
@classmethod
def choices(cls):
return [(item.value, item.name) for item in cls]
name = models.CharField(_('notification name'), max_length=64, unique=True, choices=Name.choices(), default=Name.ONBOARDING)
template_type = ArrayField(models.PositiveSmallIntegerField(_('type'), choices=Type.choices()))
max_count = models.PositiveSmallIntegerField(default=1)
def __str__(self):
return self.Name(self.name).name
Hardik Gajjar
class YearInSchool(models.TextChoices):
FRESHMAN = 'FR', _('Freshman')
SOPHOMORE = 'SO', _('Sophomore')
JUNIOR = 'JR', _('Junior')
SENIOR = 'SR', _('Senior')
GRADUATE = 'GR', _('Graduate')
year_in_school = models.CharField(
max_length=2,
choices=YearInSchool.choices,
default=YearInSchool.FRESHMAN,
)
Para Django 3.0 anterior, puede usar el ejemplo anterior.
Para opciones enteras, puede usar el siguiente código.
class Suit(models.IntegerChoices):
DIAMOND = 1
SPADE = 2
HEART = 3
CLUB = 4
suit = models.IntegerField(choices=Suit.choices)
Por cierto, Djanog también admite el auto() de Python 3 como el valor Enum. Puede usar la siguiente clase de ayuda para hacer su vida más fácil.
from django.db.models.enums import TextChoices
class AutoEnumChoices(TextChoices):
def _generate_next_value_(name, start, count, last_values): # @NoSelf
return name.lower()
@property
def choices(cls): # @NoSelf
empty = [(None, cls.__empty__)] if hasattr(cls, '__empty__') else []
return empty + [(member.value, member.label) for member in cls]
Luego utilícelo en su definición de opciones:
class TransferBasicStatus(AutoEnumChoices):
NONE = auto()
WAITING = auto()
PENDING = auto()
PROGRESS = auto()
SUCCESS = auto()
DECLINED = auto()
ENDED = 'ended', _('Ended - The transfer has ended with mixed states')
Tienes una coma después de “IN” y después de “INICIADO”…
– dirkgroten
21 de febrero de 2019 a las 8:42
Cuatro líneas de su código tienen comas no deseadas al final.
– khelwood
21 de febrero de 2019 a las 8:45