如何在Django管理后台表示位标志整数字段?

17 投票
4 回答
8708 浏览
提问于 2025-04-16 02:40

我有一个数据模型,里面定义了一个位字段,类似这样:

alter table MemberFlags add column title varchar(50) not null default '';
alter table MemberFlags add column value integer( 3) not null default 0;

insert into MemberFlags (title, value) values
    ("Blacklisted",             1),
    ("Special Guest",           2),
    ("Attend Ad-hoc Sessions",  4),
    ("Attend VIP Sessions",     8),
    ("Access Facility A",      16),
    ("Access Facility B",      32)

然后像这样使用:

alter table Membership add column title varchar(50) not null default '';
alter table Membership add column flags integer( 3) not null default 0;

insert into Membership (title, flags) values
    ("Guest Pass",          4+2 ),
    ("Silver Plan",    16+  4   ),
    ("Gold Plan",   32+16+  4+2 ),
    ("VIP Pass",    32+16+8+4+2 )

我有几个问题:

A) 在管理网站上,最简单的方法是什么来把不同的位标志显示为单独的项目?我应该覆盖模板,还是在表单上做点什么?

B) 那搜索列表呢?我可以在模型里创建函数来表示每一个位,但搜索和排序该怎么做呢?

我刚接触Django。

4 个回答

6

一个很不错的解决方案,虽然它可能一开始不太适合你的模型,但可以试试 django-bitfield 这个工具。

6

我觉得最好的办法是你可以通过创建一个新的字段类型来实现,方法是继承 models.Field。你可以利用 choices 参数来指定有效的位标志及其含义。这样可以让你的模型声明看起来更简洁易读,最终的效果大概是这样的:

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)

   ...

Django 的文档里有一篇很详细的文章,讲解了如何继承 models.Field:

编写自定义模型字段
这篇文章似乎涵盖了你需要做的所有事情,包括:

如果你想找一个继承字段的例子,这个代码片段可能会对你有帮助。它的目标类似(作为模型字段的多选),但它在数据库中存储的方式不同(使用的是 CSV 文本字段,而不是位标志)。

5

根据Andrew的回答中的代码片段,这里是你需要做的修改:

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)

撰写回答