Automatically add constants for each of the choices in a Django model
I'm constantly doing the following pattern in Django:
class MyModel(models.Model):
FOO = 1
BAR = 2
GOO = 3
BLAH_TYPES = (
(FOO, 'Foodally boogaly'),
(BAR, 'Bar bar bar bar'),
(GOO, 'Goo goo gaa gaa')开发者_Go百科,
)
TYPE_FOR_ID = dict(BLAH_TYPES)
ID_FOR_TYPE = dict(zip(TYPE_FOR_ID.values(), TYPE_FOR_ID.keys()))
blah = models.IntegerField(choices=BLAH_TYPES)
Is there a good pattern that other people follow that achieves the same effect (i.e. I have access to constants with names and dictionaries that go both ways) without so much code?
Carl Meyer's django-model-utils library has an excellent solution for this - the Choices class, which allows you to declare a list of choices with access via human-readable attributes.
This is about the most compact way to initialize your constants:
# foobar choices constants
FOO, BAR, BAZ = range(3)
# bebop choices constants (if you prefer this to readability)
(BUU,
BAA,
DII,
DUU) = range(4)
# then, in a galaxy far away, much to your expectations:
>> FOO
0
>> DII
2
In any case, I usually whip up a const.py
and keep all the insanity in there (just looking at one that's 98 lines long), but definitely move them out of the model class and make them a part of the module data to conserve time and memory, unless your doing something dynamic.
Otherwise, your doing it about as good as it can possibly get. I also do a BLAH_TYPES
, to conform to the Django choices structure every developer is accustomed to reading. That's to part you can't avoid and can't make any shortcuts. And, if I need those transformations, such as ID_FOR_TYPE
, I just define them right beneath the choices. Readable, clean and compact.
I had the same itch, and that's what I wrote:
from django.db import models
from django.db.models.base import ModelBase
import re
class ModelBaseWithChoices(ModelBase):
def __new__(cls, name, bases, attrs):
def format_label(label):
return re.sub('[^A-Z]+', '_', label.upper()).strip('_')
def get_choices(attrs):
for attr, value in attrs.items():
if attr.endswith('_CHOICES') and isinstance(value, tuple):
base = '_'.join(attr.split('_')[:-1])
for val, label in value:
yield '_'.join((base, format_label(label))), val
attrs.update(list(get_choices(attrs)))
return super(ModelBaseWithChoices, cls).__new__(cls, name, bases, attrs)
class ModelWithChoices(models.Model):
__metaclass__ = ModelBaseWithChoices
class Meta:
abstract = True
From there, you can rewrite MyModel
as:
class MyModel(ModelWithChoices):
BLAH_CHOICES = ((1, 'Foodally boogaly'),
(2, 'Bar bar bar bar'),
(3, 'Goo goo gaa gaa'))
blah = models.IntegerField(choices = BLAH_CHOICES)
And have all the constants automatically created for you in the model. From the Django shell:
>>> MyModel.BLAH_FOODALLY_BOOGALY
1
>>> MyModel.BLAH_GOO_GOO_GAA_GAA
3
精彩评论