Login

Model field choices as a namedtuple

Author:
whiteinge
Posted:
March 29, 2011
Language:
Python
Version:
1.2
Tags:
choice choices field
Score:
2 (after 4 ratings)

This is a very flexible and concise way to Handle choices the right way in model fields.

  • Preserves order.
  • Allows both a human-readable value for display in form <select>s as well as a code-friendly short name.
  • Mimic's Django's canonical choices format.
  • Doesn't restrict the value type.
  • Memory efficient.

Inspired by snippet 2373 to use namedtuples as model field choices.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
try:
    from collections import namedtuple
except ImportError:
    # Python 2.4, 2.5 backport:
    # http://code.activestate.com/recipes/500261/
    from somewhere.my_namedtuple_backport import namedtuple

def get_namedtuple_choices(name, choices_tuple):
    """Factory function for quickly making a namedtuple suitable for use in a
    Django model as a choices attribute on a field. It will preserve order.

    Usage::

        class MyModel(models.Model):
            COLORS = get_namedtuple_choices('COLORS', (
                (0, 'BLACK', 'Black'),
                (1, 'WHITE', 'White'),
            ))
            colors = models.PositiveIntegerField(choices=COLORS)

        >>> MyModel.COLORS.BLACK
        0
        >>> MyModel.COLORS.get_choices()
        [(0, 'Black'), (1, 'White')]

        class OtherModel(models.Model):
            GRADES = get_namedtuple_choices('GRADES', (
                ('FR', 'FR', 'Freshman'),
                ('SR', 'SR', 'Senior'),
            ))
            grade = models.CharField(max_length=2, choices=GRADES)

        >>> OtherModel.GRADES.FR
        'FR'
        >>> OtherModel.GRADES.get_choices()
        [('FR', 'Freshman'), ('SR', 'Senior')]

    """
    class Choices(namedtuple(name, [name for val,name,desc in choices_tuple])):
        __slots__ = ()
        _choices = tuple([desc for val,name,desc in choices_tuple])

        def get_choices(self):
            return zip(tuple(self), self._choices)

    return Choices._make([val for val,name,desc in choices_tuple])

More like this

  1. Handling choices the right way by mallipeddi 7 years, 4 months ago
  2. PositionField by jpwatts 6 years, 8 months ago
  3. Improved Pickled Object Field (Fixed for Django 1.2) by danielsokolowski 4 years, 1 month ago
  4. Polymorphic inheritance ala SQLAlchemy by gsakkis 4 years, 5 months ago
  5. Extended i18n base model by alcinnz 1 year, 11 months ago

Comments

buriy (on March 30, 2011):

Great idea!

1) You don't need to use namedtuple there, you can just use setattr on class to add methods after class creation, and very simple __str__ method (exactly what nametuple backport snippet does).

2) Why do I need to write name of choices group twice?

#

whiteinge (on March 30, 2011):
  1. I'm subclassing namedtuple because it avoids the extra boilerplate that rolling your own setattr class requires and allows a clean and full-featured choices solution in seven lines of code. :)

  2. Read the examples in the docstring. It is setting (value, code-friendly name, human-readable name).

#

dudus (on April 2, 2011):

I don't get how that is better than the common way of defining choices.

#

whiteinge (on April 6, 2011):

Read James Bennett's blog post linked in the snippet description. Also look at the dozen or so other implementations on this site.

#

Please login first before commenting.