Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django - Handling "enum models"

Is there any best practice in handling "support tables" in Django?

I dislike Field.choices, as it doesn't really enforce integrity (it doesn't even create check constraints), so I prefer creating a full-blown model (and often, I find myself adding additional fields in the support table).

Now, if I use a full model, I suppose the right way to go is to create an initial data fixture for the table content, but is there a "right way" to have named instances of the row, say...

class State(models.Model):
    name = model.TextField()

STATES = dict(
    NEW=State.objects.get(pk=0),
    IN_PROGRESS=State.objects.get(pk=1),
)

... or something like that.

What do you use?

like image 555
alex Avatar asked Mar 09 '12 10:03

alex


2 Answers

Django ORM checks integrity if you specify choices attribute (when you insert/update data via user forms).

You also can set validation logic to database level and use database ENUM field if you db support this.

UPD:

class EnumField(models.Field):

    def __init__(self, *args, **kwargs):
        super(EnumField, self).__init__(*args, **kwargs)
        if not self.choices:
            raise AttributeError('EnumField requires `choices` attribute.')

    def db_type(self):
        return "enum(%s)" % ','.join("'%s'" % k for (k, _) in self.choices)

GENDER_MALE = 'm'
GENDER_FEMALE = 'f'
GENDER_CHOICES = (
    (GENDER_MALE, 'Male'),
    (GENDER_FEMALE, 'Female'),
)


class Person(models.Model):
    name = models.CharField(max_length=50)
    gender = EnumField(choices=GENDER_CHOICES)
like image 69
San4ez Avatar answered Nov 11 '22 01:11

San4ez


I have also a use case for database-backed Enum models. I came up with a solution that uses Python descriptors to implement django choices-like interface to the enum values:

from django.db import models

class ModelInstance:
    """Descriptor that uses predefined value fields to access model instances."""
    def __init__(self, **kwargs):
        self._instance_attributes = kwargs
        self._instance = None
    def __get__(self, instance, instance_type):
        if not self._instance:
            self._instance = instance_type.objects.get(**self._instance_attributes)
        return self._instance

class EnumModel(models.Model):
    name = models.CharField(max_length=100)
    JOHN = ModelInstance(name='John')
    FRED = ModelInstance(name='Fred')

JOHN and FRED are lazily evaluated model instances obtained for specific query params (name=value). I assume that the queries evaluate to unique rows in the table.

To use it, you first need to add rows to the database for John and Fred:

In [1]: EnumModel.objects.create(name="Fred")
In [2]: EnumModel.objects.create(name="John")

Then you can then access the model instances using the class attributes:

In [3]: EnumModel.JOHN                                                                                                                  
Out[3]: <EnumModel: EnumModel object (1)>
In [4]: EnumModel.FRED                                                                                                                  
Out[4]: <EnumModel: EnumModel object (2)>
like image 31
btel Avatar answered Nov 11 '22 02:11

btel