在Django管理界面中,如何表示一个位标志整数字段?

17

我有一个数据模型,其中定义了一个位域,类似于以下内容:

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的新手。


1
我会一开始就摒弃位标志。它们是邪恶的。 - Tomasz Zieliński
4个回答

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进行子类化:编写自定义模型字段。它似乎涵盖了你需要做的一切,包括:
- 指定表单字段(将表单与字段绑定,以便django-admin知道如何显示它)。 - 准备用于数据库查询的值(这将允许您使用该字段进行搜索和过滤)。
如果你正在寻找一个子类化字段的示例,这个代码片段可能会有所帮助。它的目标相似(作为模型字段的多个选择),但它在数据库中存储它们的方式不同(使用CSV文本字段而不是位标志)。

6
一个经过验证的好解决方案,即使它不能立即适用于你的模型,也可以使用 django-bitfield

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)

你仍然需要编写 get_prep_lookup 函数;一种实现方法是只需循环遍历搜索中的每个项目,对于每个项目循环遍历 self.choices,如果搜索词包含在选择项中,则将该选择项的值添加到查找键中。因此,例如,您的搜索可能是“Access Facility A, Special Guest”,因此它会找到每个选择并将它们相加,然后返回 18 - Jordan Reiter
我喜欢它。不过还没来得及试一下。感谢您提供的搜索和筛选技巧。要在管理列表视图中获得单独的列,我需要做什么? - Dagg Nabbit

1

这是我如何在我的User类中使用标志:

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]

当然,你可以创建一个逗号分隔的字符串来在管理员视图中显示标志,或者按照你的意愿进行。

对于管理员来说,只需为每个组值使用boolean即可。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接