Set Django IntegerField by choices=… name

后端 未结 10 755
南笙
南笙 2020-12-07 09:40

When you have a model field with a choices option you tend to have some magic values associated with human readable names. Is there in Django a convenient way to set these f

相关标签:
10条回答
  • 2020-12-07 10:14

    Here's a field type I wrote a few minutes ago that I think does what you want. Its constructor requires an argument 'choices', which may be either a tuple of 2-tuples in the same format as the choices option to IntegerField, or instead a simple list of names (ie ChoiceField(('Low', 'Normal', 'High'), default='Low') ). The class takes care of the mapping from string to int for you, you never see the int.

      class ChoiceField(models.IntegerField):
        def __init__(self, choices, **kwargs):
            if not hasattr(choices[0],'__iter__'):
                choices = zip(range(len(choices)), choices)
    
            self.val2choice = dict(choices)
            self.choice2val = dict((v,k) for k,v in choices)
    
            kwargs['choices'] = choices
            super(models.IntegerField, self).__init__(**kwargs)
    
        def to_python(self, value):
            return self.val2choice[value]
    
        def get_db_prep_value(self, choice):
            return self.choice2val[choice]
    
    0 讨论(0)
  • 2020-12-07 10:14
    class Sequence(object):
        def __init__(self, func, *opts):
            keys = func(len(opts))
            self.attrs = dict(zip([t[0] for t in opts], keys))
            self.choices = zip(keys, [t[1] for t in opts])
            self.labels = dict(self.choices)
        def __getattr__(self, a):
            return self.attrs[a]
        def __getitem__(self, k):
            return self.labels[k]
        def __len__(self):
            return len(self.choices)
        def __iter__(self):
            return iter(self.choices)
        def __deepcopy__(self, memo):
            return self
    
    class Enum(Sequence):
        def __init__(self, *opts):
            return super(Enum, self).__init__(range, *opts)
    
    class Flags(Sequence):
        def __init__(self, *opts):
            return super(Flags, self).__init__(lambda l: [1<<i for i in xrange(l)], *opts)
    

    Use it like this:

    Priorities = Enum(
        ('LOW', 'Low'),
        ('NORMAL', 'Normal'),
        ('HIGH', 'High')
    )
    
    priority = models.IntegerField(default=Priorities.LOW, choices=Priorities)
    
    0 讨论(0)
  • 2020-12-07 10:14

    Simply replace your numbers with the human readable values you would like. As such:

    PRIORITIES = (
    ('LOW', 'Low'),
    ('NORMAL', 'Normal'),
    ('HIGH', 'High'),
    )
    

    This makes it human readable, however, you'd have to define your own ordering.

    0 讨论(0)
  • 2020-12-07 10:24

    I appreciate the constant defining way but I believe Enum type is far best for this task. They can represent integer and a string for an item in the same time, while keeping your code more readable.

    Enums were introduced to Python in version 3.4. If you are using any lower (such as v2.x) you can still have it by installing the backported package: pip install enum34.

    # myapp/fields.py
    from enum import Enum    
    
    
    class ChoiceEnum(Enum):
    
        @classmethod
        def choices(cls):
            choices = list()
    
            # Loop thru defined enums
            for item in cls:
                choices.append((item.value, item.name))
    
            # return as tuple
            return tuple(choices)
    
        def __str__(self):
            return self.name
    
        def __int__(self):
            return self.value
    
    
    class Language(ChoiceEnum):
        Python = 1
        Ruby = 2
        Java = 3
        PHP = 4
        Cpp = 5
    
    # Uh oh
    Language.Cpp._name_ = 'C++'
    

    This is pretty much all. You can inherit the ChoiceEnum to create your own definitions and use them in a model definition like:

    from django.db import models
    from myapp.fields import Language
    
    class MyModel(models.Model):
        language = models.IntegerField(choices=Language.choices(), default=int(Language.Python))
        # ...
    

    Querying is icing on the cake as you may guess:

    MyModel.objects.filter(language=int(Language.Ruby))
    # or if you don't prefer `__int__` method..
    MyModel.objects.filter(language=Language.Ruby.value)
    

    Representing them in string is also made easy:

    # Get the enum item
    lang = Language(some_instance.language)
    
    print(str(lang))
    # or if you don't prefer `__str__` method..
    print(lang.name)
    
    # Same as get_FOO_display
    lang.name == some_instance.get_language_display()
    
    0 讨论(0)
提交回复
热议问题