2012-10-10 20 views
31

Tengo un modelo de Django que se basa en una tupla. Me pregunto cuál es la mejor práctica para referirme a constantes dentro de esa tupla para mi programa Django. Aquí, por ejemplo, me gustaría especificar "default=0" como algo que es más legible y no requiere comentarios. ¿Alguna sugerencia?Mejor práctica para las constantes de Python y Django

Status = (
    (-1, 'Cancelled'), 
    (0, 'Requires attention'), 
    (1, 'Work in progress'), 
    (2, 'Complete'), 
) 

class Task(models.Model): 
    status = models.IntegerField(choices=Status, default=0) # Status is 'Requires attention' (0) by default. 

EDIT:

Si es posible me gustaría evitar el uso de un número completo. De alguna manera, usar la cadena 'Requiere atención' sería más legible.

+0

¿Has leído este interesante enfoque que usa '__metaclass__'? http://tomforb.es/using-python-metaclasses-to-make-awesome-django-model-field-choices –

Respuesta

51

Es bastante común para definir constantes para los valores enteros de la siguiente manera:

class Task(models.Model): 
    CANCELLED = -1 
    REQUIRES_ATTENTION = 0 
    WORK_IN_PROGRESS = 1 
    COMPLETE = 2 

    Status = (
     (CANCELLED, 'Cancelled'), 
     (REQUIRES_ATTENTION, 'Requires attention'), 
     (WORK_IN_PROGRESS, 'Work in progress'), 
     (COMPLETE, 'Complete'), 
    ) 

    status = models.IntegerField(choices=Status, default=REQUIRES_ATTENTION) 

Moviendo las constantes y Status dentro de la clase, a mantener limpio el espacio del módulo, y como un bono puede hacer referencia a Tasks.COMPLETE donde importe el modelo Tasks.

+0

También da acceso a las representaciones de cadena con: 'def status_str (self): return self.Status [self.status] [1]' – Alveoli

+3

@Alveoli El modelo base de django proporciona una función 'get_fieldname_display()' para cada campo dentro un modelo. Por lo tanto, get_status_display() ya está disponible – quin

+0

¿No crees que requerirá memoria adicional para los objetos de esa clase? Una mejor idea sería mantenerlos separados y si desea acceder a ellos desde un objeto, entonces puede definir una función que pueda devolver esto. – Anuj

1

Se puede usar un diccionario para una pequeña mejora en la claridad:

Status = { 
    -1: 'Cancelled', 
    0: 'Requires attention', 
    1: 'Work in progress', 
    2: 'Complete', 
} 

class Task(models.Model): 
    status = models.IntegerField(choices=Status.items(), default=Status[0]) 
+0

Creo que esto es muy similar a simplemente tener la constante numérica.¿Hay alguna manera de agregar información contextual legible por humanos a lo que es el significado de '1' en este caso? ¿Cómo puede alguien decir que 1 significa 'Trabajo en progreso'? –

2

Mi enfoque:

class Task(models.Model): 
    STATUSES = { 'cancelled': 'Cancelled', 
       'requires attention': 'Requires attention', 
       'work in progress': 'Work in progress', 
       'complete': 'Complete' } 

    status = models.CharField(choices=STATUSES.items(), default='cancelled') 

Esto le permite escribir expresiones convenientes:

tasks = Task.objects.filter(status='complete') 

Además, te permite no crear variables globales innecesarias.

Si realmente desea utilizar campo entero:

class Task(models.Model): 

    class STATUS: 
     CANCELED, ATTENTION, WIP, COMPLETE = range(-1, 3) 
     choices = { 
     CANCELED: 'Cancelled', 
     ATTENTION: 'Requires attention', 
     WIP: 'Work in progress', 
     COMPLETE: 'Complete' 
     } 


    status = models.CharField(choices=STATUSES.choices.items(), default=STATUSES.CANCELED) 

Y:

tasks = Task.objects.filter(status=Task.STATUSES.COMPLETE) 
+0

Creo que esto es muy similar a simplemente tener la constante numérica. ¿Hay alguna manera de agregar información contextual legible por humanos a lo que es el significado de '1' en este caso? ¿Cómo puede alguien decir que 1 significa 'Trabajo en progreso'? –

16
CANCELED, ATTENTION, WIP, COMPLETE = range(-1, 3) 
Status = (
    (CANCELED, 'Cancelled'), 
    (ATTENTION, 'Requires attention'), 
    (WIP, 'Work in progress'), 
    (COMPLETE, 'Complete'), 
) 

class Task(models.Model): 
    status = models.IntegerField(choices=Status, default=CANCELED) 


Tenga en cuenta que, como otros han dicho, la forma correcta es poner estas variables dentro de su clase de modelo. Así es como lo hace el oficial django example.

Solo hay un motivo por el que desea colocarlo fuera del espacio de nombres de clase y solo si estos semánticos son compartidos por igual por otros modelos de su aplicación. es decir, , no puede decidir en qué modelo específico pertenece.

Aunque no parece que este sea el caso en su ejemplo particular.

+1

Upvoted, pero me gusta más cuando las constantes se mueven a la clase como en la respuesta de Alasdair. –

+0

@ChrisWesseling Totalmente de acuerdo, simplemente no quería desviarse del código de ejemplo del OP. – rantanplan

+0

Aunque solo se usa en el modelo, a veces es útil colocarlos fuera de la clase. Tal vez en un archivo de constantes. Tenía que hacerlo para evitar las importaciones circulares. Para llamar a Task.CANCELED, tengo que importarlo. Como se llama en muchos lugares y los modelos cambian muy rápido existe la posibilidad de importaciones circulares si importamos Tarea en todos los archivos necesarios. –

7

Puede usar un namedtuple, usando un Inmutable para una constante parece apropiado. ;-)

>>> from collections import namedtuple 
>>> Status = namedtuple('Status', ['CANCELLED', 'REQUIRES_ATTENTION', 'WORK_IN_PROGRESS', 'COMPLETE'])(*range(-1, 3)) 
>>> Status 
Status(CANCELLED=-1, REQUIRES_ATTENTION=0, WORK_IN_PROGRESS=1, COMPLETE=2) 
>>> Status.CANCELLED 
-1 
>>> Status[0] 
-1 

Uso de atributos en Task tan constantes como en Alasdair's answer tiene más sentido en este caso, pero namedtuples son sustitutos muy baratos para dicts y objetos que no cambian. Especialmente muy útil si desea tener lotes de ellos en la memoria.Son como tuplas regulares con un bonus de __repr__ descriptivo y acceso a atributos.

+3

Si necesita más [convincente] (http://pyvideo.org/video/367/pycon-2011--fun-with-python--39-s-newer-tools) '[11:35 - 26:00 ] ' – kreativitea

0

No consumo Django, pero sí algo parecido a la siguiente un poco debajo de la Pirámide y torció ...

def setup_mapping(pairs): 
    mapping = {'id':{},'name':{}} 
    for (k,v) in pairs: 
     mapping['id'][k]= v 
     mapping['name'][v]= k 
    return mapping 

class ConstantsObject(object): 
    _pairs= None 
    mapping= None 

    @classmethod 
    def lookup_id(cls , id): 
     pass 

    @classmethod 
    def lookup_name(cls , name): 
     pass 

class StatusConstants(ConstantsObject): 
    CANCELLED = -1 
    REQUIRES_ATTENTION = 0 
    WORK_IN_PROGRESS = 1 
    COMPLETE = 2 

    _pairs= (
     (-1, 'Cancelled'), 
     (0, 'Requires attention'), 
     (1, 'Work in progress'), 
     (2, 'Complete'), 
    ) 
    mapping= setup_mapping(_pairs) 

Así que la esencia es la siguiente:

  • Hay una base clase "constantes" y otra clase para cada tipo. la clase define las palabras clave a un valor en ALLCAPS
  • Echo el texto sin formato _pairs en la clase también. ¿por qué? porque podría necesitar construir algunas tablas DB con ellas, o podría quererlas para mensajes de error/estado. Utilizo los números y no el nombre de la variable ALLCAPS como preferencia personal.
  • i inicializar una variable mapping clase que básicamente monkeypatches la clase Precompilando un montón de variables dentro de un diccionario porque ...
  • la clase se deriva de la clase base, que ofrece funcionalidad classmethod para buscar un valor o hacer otras cosas estándar que a menudo necesita hacer con constantes.

No es un enfoque único para todos, pero en general me gusta mucho. Puede usar fácilmente un dict para definir los pares, tiene la función 'mapeo' configurando otros atributos, tales como darle tuplas de los valores de par como k, v o v, k o cualquier formato extraño que pueda necesitar.

mi código puede entonces se parece a esto:

status_id = sa.Column(sa.Integer, sa.ForeignKey("_status.id") , nullable=False , default=constants.StatusConstants.CANCELLED) 

status_name = constants.StatusConstants.lookup_id(status_id)  
status_name = constants.StatusConstants.mapping['id'][status_id] 

cada vez que necesite utilizar las constantes de otra manera, que acaba de añadir o alterar las classmethods de la base.

0

A veces tengo que crear una gran lista de opciones. No me gusta escribir como un mono, así que en vez de crear una funcion de esta manera:

def choices(labels): 
    labels = labels.strip().split('\n') 
    ids = range(1, len(labels)+1) 
    return zip(ids, labels) 

y utilizar así:

my_choices = """ 
choice1 
choice2 
choice3 
""" 
MY_CHOICES = choices(my_choices) 
print(MY_CHOICES) # ((1, choice1), (2, choice2), (3, choice3)) 
6

Python 3.4+: Enum

Escribes "Si es posible, me gustaría evitar usar un número completo". y, de hecho, una representación con nombre es claramente más pitónico. Una cadena al descubierto, sin embargo, es susceptible a errores tipográficos.

Python 3.4 introduce un módulo llamado enum proporcionar Enum y IntEnum pseudoclasses que ayudan a esta situación. Con él, el ejemplo podría funcionar de la siguiente manera:

# in Python 3.4 or later: 
import enum 

class Status(enum.IntEnum): 
    Cancelled = -1, 
    Requires_attention = 0, 
    Work_in_progress = 1, 
    Complete = 2 

def choiceadapter(enumtype): 
    return ((item.value, item.name.replace('_', ' ')) for item in enumtype) 

class Task(models.Model): 
    status = models.IntegerField(choices=choiceadapter(Status), 
           default=Status.Requires_attention.value) 

y una vez que el equipo de Django recoge Enum, la choiceadapter incluso se construirá en Django.

Cuestiones relacionadas