Representing a multi-select field for weekdays in a Django model

后端 未结 1 1236
礼貌的吻别
礼貌的吻别 2021-02-06 02:00

I\'ve been searching for an elegant way to represent a multi-select weekday field (Mon, Tues, Wed...) in a Django model. I was initially thinking of going integer field using bi

1条回答
  •  情话喂你
    2021-02-06 02:26

    This is an old question, but I thought I would show how it could be done reasonably simply in Django.

    Here is a helper class for preparing your choices:

    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]
    

    Define your model with a PositiveIntegerField, and the choices you would like:

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

    This means you can access the values like this:

    >>> 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']
    

    Now define your model with a PositiveIntegerField and these choices:

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

    And your models are done. For queries, the following does the trick:

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

    There may be a way to create a Q() object subclass that neatly packages queries, so they look like this:

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

    Or even hack at a F() subclass to create something like this:

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

    But I don't have the time to explore that at the moment. .where works fine and can be abstracted into a queryset function.

    One final consideration is that you might light to make a custom model field that converts the bit mask in the database to a list or set in Python. You could then use a SelectMultiple widget (or CheckboxSelectMultiple) to allow the user to select their values in the admin.

    0 讨论(0)
提交回复
热议问题