Set Django IntegerField by choices=… name

后端 未结 10 754
南笙
南笙 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:03

    I'd probably set up the reverse-lookup dict once and for all, but if I hadn't I'd just use:

    thing.priority = next(value for value, name in Thing.PRIORITIES
                          if name=='Normal')
    

    which seems simpler than building the dict on the fly just to toss it away again;-).

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

    My answer is very late and might seem obvious to nowadays-Django experts, but to whoever lands here, i recently discovered a very elegant solution brought by django-model-utils: https://django-model-utils.readthedocs.io/en/latest/utilities.html#choices

    This package allows you to define Choices with three-tuples where:

    • The first item is the database value
    • The second item is a code-readable value
    • The third item is a human-readable value

    So here's what you can do:

    from model_utils import Choices
    
    class Thing(models.Model):
        PRIORITIES = Choices(
            (0, 'low', 'Low'),
            (1, 'normal', 'Normal'),
            (2, 'high', 'High'),
          )
    
        priority = models.IntegerField(default=PRIORITIES.normal, choices=PRIORITIES)
    
    thing.priority = getattr(Thing.PRIORITIES.Normal)
    

    This way:

    • You can use your human-readable value to actually choose the value of your field (in my case, it's useful because i'm scraping wild content and storing it in a normalized way)
    • A clean value is stored in your database
    • You have nothing non-DRY to do ;)

    Enjoy :)

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

    Do as seen here. Then you can use a word that represents the proper integer.

    Like so:

    LOW = 0
    NORMAL = 1
    HIGH = 2
    STATUS_CHOICES = (
        (LOW, 'Low'),
        (NORMAL, 'Normal'),
        (HIGH, 'High'),
    )
    

    Then they are still integers in the DB.

    Usage would be thing.priority = Thing.NORMAL

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

    As of Django 3.0, you can use:

    class ThingPriority(models.IntegerChoices):
        LOW = 0, 'Low'
        NORMAL = 1, 'Normal'
        HIGH = 2, 'High'
    
    
    class Thing(models.Model):
        priority = models.IntegerField(default=ThingPriority.LOW, choices=ThingPriority.choices)
    
    # then in your code
    thing = get_my_thing()
    thing.priority = ThingPriority.HIGH
    
    0 讨论(0)
  • 2020-12-07 10:06

    Originally I used a modified version of @Allan's answer:

    from enum import IntEnum, EnumMeta
    
    class IntegerChoiceField(models.IntegerField):
        def __init__(self, choices, **kwargs):
            if hasattr(choices, '__iter__') and isinstance(choices, EnumMeta):
                choices = list(zip(range(1, len(choices) + 1), [member.name for member in list(choices)]))
    
            kwargs['choices'] = choices
            super(models.IntegerField, self).__init__(**kwargs)
    
        def to_python(self, value):
            return self.choices(value)
    
        def get_db_prep_value(self, choice):
            return self.choices[choice]
    
    models.IntegerChoiceField = IntegerChoiceField
    
    GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')
    
    class Gear(Item, models.Model):
        # Safe to assume last element is largest value member of an enum?
        #type = models.IntegerChoiceField(GEAR, default=list(GEAR)[-1].name)
        largest_member = GEAR(max([member.value for member in list(GEAR)]))
        type = models.IntegerChoiceField(GEAR, default=largest_member)
    
        def __init__(self, *args, **kwargs):
            super(Gear, self).__init__(*args, **kwargs)
    
            for member in GEAR:
                setattr(self, member.name, member.value)
    
    print(Gear().HEAD, (Gear().HEAD == GEAR.HEAD.value))
    

    Simplified with the django-enumfields package package which I now use:

    from enumfields import EnumIntegerField, IntEnum
    
    GEAR = IntEnum('GEAR', 'HEAD BODY FEET HANDS SHIELD NECK UNKNOWN')
    
    class Gear(Item, models.Model):
        # Safe to assume last element is largest value member of an enum?
        type = EnumIntegerField(GEAR, default=list(GEAR)[-1])
        #largest_member = GEAR(max([member.value for member in list(GEAR)]))
        #type = EnumIntegerField(GEAR, default=largest_member)
    
        def __init__(self, *args, **kwargs):
            super(Gear, self).__init__(*args, **kwargs)
    
            for member in GEAR:
                setattr(self, member.name, member.value)
    
    0 讨论(0)
  • 2020-12-07 10:13

    Model's choices option accepts a sequence consisting itself of iterables of exactly two items (e.g. [(A, B), (A, B) ...]) to use as choices for this field.

    In addition, Django provides enumeration types that you can subclass to define choices in a concise way:

    class ThingPriority(models.IntegerChoices):
        LOW = 0, _('Low')
        NORMAL = 1, _('Normal')
        HIGH = 2, _('High')
    
    class Thing(models.Model):
        priority = models.IntegerField(default=ThingPriority.NORMAL, choices=ThingPriority.choices)
    
    

    Django supports adding an extra string value to the end of this tuple to be used as the human-readable name, or label. The label can be a lazy translatable string.

       # in your code 
       thing = get_thing() # instance of Thing
       thing.priority = ThingPriority.LOW
    

    Note: you can use that using ThingPriority.HIGH, ThingPriority.['HIGH'], or ThingPriority(0) to access or lookup enum members.

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