Best practice for Python & Django constants
It is quite common to define constants for the integer values as follows:
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)
By moving the constants and Status
inside the model class, you keep the module's namespace cleaner, and as a bonus you can refer to Task.COMPLETE
wherever you import the Task
model.
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)
Keep in mind that as others noted, the proper way is to put these variables inside your Model class. That's also how the official django example does it.
There is only one reason where you'd want to put it outside the class namespace and that is only if these semantics are equally shared by other models of your app. i.e. you can't decide in which specific model they belong.
Though it doesn't seem like this is the case in your particular example.
Python 3.4+: Enum
You write "If possible I'd like to avoid using a number altogether." and indeed a named representation is clearly more pythonic. A bare string, however, is susceptible to typos.
Python 3.4 introduces a module called
enum
providing Enum
and IntEnum
pseudoclasses
that help with this situation.
With it, your example could work as follows:
# 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)
and once the Django team picks up Enum
, the
choiceadapter
will even be built into Django.
EDIT 2021-06: After some work with Enum
, I must say I am not enthused.
In my style of work (in Django; your mileage may vary), the abstraction tends to get in the way and I find myself preferring a loose list of constants (often embedded in a different class that exists anyway).
You could use a namedtuple
, using an Immutable for a constant seems fitting. ;-)
>>> 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
Using attributes on Task
as constants like in Alasdair's answer makes more sense in this case, but namedtuples are very cheap substitutes for dicts and objects that don't change. Especially very handy if you want to have lots of them in memory. They are like regular tuples with a bonus of a descriptive __repr__
and attribute access.