[Fixed]-How should I represent a bit flags int field in django admin?

5👍

Working off the snippet in Andrew’s answer, here are the changes you’d need to make:

from django.db import models
from django import forms

class BitFlagFormField(forms.MultipleChoiceField):
    widget = forms.CheckboxSelectMultiple

    def __init__(self, *args, **kwargs):
        super(BitFlagFormField, self).__init__(*args, **kwargs)

class BitFlagField(models.Field):
    __metaclass__ = models.SubfieldBase

    def get_internal_type(self):
        return "Integer"

    def get_choices_default(self):
        return self.get_choices(include_blank=False)

    def _get_FIELD_display(self, field):
        value = getattr(self, field.attname)
        choicedict = dict(field.choices)

    def formfield(self, **kwargs):
        # do not call super, as that overrides default widget if it has choices
        defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 
                    'help_text': self.help_text, 'choices':self.choices}
        if self.has_default():
            defaults['initial'] = self.get_default()
        defaults.update(kwargs)
        return BitFlagFormField(**defaults)

    def get_db_prep_value(self, value):
        if isinstance(value, int):
            return value
        elif isinstance(value, list):
            return sum(value)

    def to_python(self, value):
        result = []
        n = 1
        while value > 0:
            if (value % 2) > 0:
                result.append(n)
            n *= 2
            value /= 2
        return sorted(result)


    def contribute_to_class(self, cls, name):
        super(BitFlagField, self).contribute_to_class(cls, name)
        if self.choices:
            func = lambda self, fieldname = name, choicedict = dict(self.choices):" and ".join([choicedict.get(value,value) for value in getattr(self,fieldname)])
            setattr(cls, 'get_%s_display' % self.name, func)

6👍

I think the best solution here would be for you to create a new field type by subclassing models.Field. You could make use of the choices parameter to assign the valid bit flags and their meanings. This would help keep your model declaration clean and readable, with a final result along the lines of:

class BitFlagField(models.Field):

    ...

class MyModel(models.Model):

    ...

    FLAG_CHOICES = (
        (1, 'Blacklisted'),
        (2, 'Special Guest'),
        (4, 'Attend Ad-hoc Sessions'),
        (8, 'Attend VIP Sessions'),
        (16, 'Access Facility A'),
        (32, 'Access Facility B'),
    )
    flags = BitFlagField(choices=FLAG_CHOICES)

   ...

The Django documentation has a great in-depth article on how to go about subclassing models.Field:

Writing Custom Model Fields
It seems to cover everything you need to do, including:

If you’re looking for an example of a subclassed field, this snippet might be of use. Its goal is similar (multiple choices as a model field), but its manner of storing them in the database differs (it’s using a CSV text field instead of bit flags).

👤Andrew

6👍

A great tested solution, even if it doesn’t fit your model right away, would be using django-bitfield

1👍

This is how I would use the flags with my User class:

FLAGS = {
    1:"Blacklisted",
    2:"SpecialGuest",
    4:"AttendAd-hocSessions",
    8:"AttendVIPSessions",
    16:"AccessFacilityA",
    32:"AccessFacilityB",
}

class User(object):
    def __init__(self, name="John Doe", groups=0):
        self.name = name
        self.groups = groups
    def memberof(self):
        ''' Display string representation of the groups. '''
        for flag in sorted(FLAGS):
            if (flag & self.groups) == flag:
                print FLAGS[flag]

Of course instead of printing the flags, you can create a comma-separated string to display in the admin view, or whatever you desire.

For the admin, just use a boolean for each of the group values.

Leave a comment