在Django模型中表示多选的星期字段

10 投票
1 回答
6358 浏览
提问于 2025-04-16 03:50

我一直在寻找一种优雅的方法来表示一个可以多选的星期几字段(比如周一、周二、周三……)在Django模型中。最开始我想用整数字段配合位运算来实现,但我不确定这样做是否合适。

这个字段主要是用来读取的。我希望查询的方法能像这样:Entry.objects.get(weekdays__contains=MONDAY),其中MONDAY是一个常量。

也许有人能想出更好的解决方案?或者可能有人做过类似的事情,有一些示例代码可以分享吗?

1 个回答

16

这是个老问题,但我想展示一下在Django中如何简单地解决它。

这里有一个帮助类,用来准备你的选择项:

class BitChoices(object):
  def __init__(self, choices):
    self._choices = []
    self._lookup = {}
    for index, (key, val) in enumerate(choices):
      index = 2**index
      self._choices.append((index, val))
      self._lookup[key] = index

  def __iter__(self):
    return iter(self._choices)

  def __len__(self):
    return len(self._choices)

  def __getattr__(self, attr):
    try:
      return self._lookup[attr]
    except KeyError:
      raise AttributeError(attr)

  def get_selected_keys(self, selection):
    """ Return a list of keys for the given selection """
    return [ k for k,b in self._lookup.iteritems() if b & selection]

  def get_selected_values(self, selection):
    """ Return a list of values for the given selection """
    return [ v for b,v in self._choices if b & selection]

定义你的模型时,使用一个正整数字段(PositiveIntegerField),并列出你想要的选择项:

WEEKDAYS = BitChoices((('mon', 'Monday'), ('tue', 'Tuesday'), ('wed', 'Wednesday'),
               ('thu', 'Thursday'), ('fri', 'Friday'), ('sat', 'Saturday'),
               ('sun', 'Sunday')
           ))

这意味着你可以像这样访问这些值:

>>> print list(WEEKDAYS)
[(1, 'Monday'), (2, 'Tuesday'), (4, 'Wednesday'), (8, 'Thursday'), (16, 'Friday'), (32, 'Saturday'), (64, 'Sunday')]
>>> print WEEKDAYS.fri
16
>>> print WEEKDAYS.get_selected_values(52)
['Wednesday', 'Friday', 'Saturday']

现在定义你的模型,使用一个PositiveIntegerField和这些选择项:

class Entry(models.Model):
    weekdays = models.PositiveIntegerField(choices=WEEKDAYS)

这样你的模型就完成了。对于查询,下面的代码可以实现你的需求:

Entry.objects.extra(where=["weekdays & %s"], params=[WEEKDAYS.fri])

可能有一种方法可以创建一个Q()对象的子类,整齐地封装查询,使它们看起来像这样:

Entry.objects.filter(HasBit('weekdays', WEEKDAYS.fri))

或者甚至可以尝试创建一个F()的子类,做出类似这样的东西:

Entry.objects.filter(weekdays=HasBit(WEEKDAYS.fri))

但我现在没有时间去探索这个。.where方法很好用,也可以抽象成一个查询集函数。

最后一个考虑是,你可能想要创建一个自定义模型字段,将数据库中的位掩码转换为Python中的列表或集合。这样,你就可以使用SelectMultiple小部件(或者CheckboxSelectMultiple)来让用户在管理界面中选择他们的值。

撰写回答