together integerfield example choices python django django-models

python - integerfield - django unique together



Establecer Django IntegerField por opciones=... nombre (7)

Cuando tiene un campo modelo con una opción de opciones, tiende a tener algunos valores mágicos asociados con nombres legibles por humanos. ¿Existe en Django una forma conveniente de establecer estos campos por el nombre legible por el ser humano en lugar del valor?

Considera este modelo:

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

En algún momento tenemos una instancia Thing y queremos establecer su prioridad. Obviamente podrías hacer,

thing.priority = 1

Pero eso te obliga a memorizar el mapeo Value-Name de PRIORIDADES. Esto no funciona:

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

Actualmente tengo esta solución tonta:

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

pero eso es torpe. Dado lo común que podría ser este escenario, me preguntaba si alguien tenía una mejor solución. ¿Hay algún método de campo para establecer campos por nombre de elección que he pasado por alto por completo?


Aprecio la constante forma de definir, pero creo que el tipo Enum es mucho mejor para esta tarea. Pueden representar un entero y una cadena para un artículo al mismo tiempo, mientras mantienen su código más legible.

Los enums se introdujeron en Python en la versión 3.4. Si está utilizando un valor inferior (como v2.x), puede tenerlo instalando el paquete backported : 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++''

Esto es más o menos todo. Puede heredar ChoiceEnum para crear sus propias definiciones y usarlas en una definición de modelo como:

from django.db import models from myapp.fields import Language class MyModel(models.Model): language = models.IntegerField(choices=Language.choices(), default=int(Language.Python)) # ...

Las consultas son la guinda del pastel, como puedes adivinar:

MyModel.objects.filter(language=int(Language.Ruby)) # or if you don''t prefer `__int__` method.. MyModel.objects.filter(language=Language.Ruby.value)

Representarlos en una cuerda también es fácil:

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


Este es un tipo de campo que escribí hace unos minutos y creo que hace lo que quiere. Su constructor requiere un argumento ''elecciones'', que puede ser una tupla de 2 tuplas en el mismo formato que la opción de opciones para IntegerField, o en su lugar una lista simple de nombres (es decir, ChoiceField ((''Bajo'', ''Normal'', ''Alto''), predeterminado = ''Bajo'')). La clase se encarga de mapear de cadena a int para ti, nunca se ve el 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]


Haz lo que se ve aquí . Entonces puedes usar una palabra que represente el entero apropiado.

Al igual que:

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

Entonces todavía son enteros en el DB.

El uso sería thing.priority = Thing.NORMAL


Mi respuesta es muy tarde y puede parecer obvio para los expertos actuales de Django, pero para quien aterrice aquí, descubrí recientemente una solución muy elegante presentada por django-model-utils: https://django-model-utils.readthedocs.io/en/latest/utilities.html#choices

Este paquete le permite definir Elecciones con tres tuplas donde:

  • El primer elemento es el valor de la base de datos
  • El segundo elemento es un valor legible por código
  • El tercer artículo es un valor humano legible

Entonces, esto es lo que puedes hacer:

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)

De esta manera:

  • Puedes usar tu valor legible para ser humano para elegir realmente el valor de tu campo (en mi caso, es útil porque estoy raspando contenido salvaje y almacenándolo de forma normalizada)
  • Un valor limpio se almacena en su base de datos
  • No tiene nada que hacer, no SECO;)

Disfruta :)


Probablemente configuré el dict de búsqueda inversa de una vez por todas, pero si no lo hubiera hecho, solo usaría:

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

que parece más simple que construir el dict sobre la marcha simplemente para tirarlo de nuevo ;-).


Simplemente reemplace sus números con los valores humanos legibles que le gustaría. Como tal:

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

Esto hace que sea legible por humanos, sin embargo, tendrías que definir tu propio orden.


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)

Úselo así:

Priorities = Enum( (''LOW'', ''Low''), (''NORMAL'', ''Normal''), (''HIGH'', ''High'') ) priority = models.IntegerField(default=Priorities.LOW, choices=Priorities)