Как в Django определить строковое значение для перечисления IntegerChoices?

Я использую Django 3.2 и Pythnon 3.9. В моей модели я определяю перечисление int. Я хотел бы также определить для него читаемые строковые значения, поэтому я попробовал

class Transaction(models.Model):
    class TransactionTypes(models.IntegerChoices):
        BUY = 0
        SELL = 1

        labels = {
            BUY: 'Buy',
            SELL: 'Sell'
        }

        translation = {v: k for k, v in labels.items()}

но это определение не работает с ошибкой

TypeError: int() argument must be a string, a bytes-like object or a number, not 'dict'

Как мне определить строки для каждого значения? Я не возражаю, если эти строки будут просто буквальными именами переменных (например, "BUY", "SELL")

Один из способов заключается в том, что вы определяете варианты как кортеж кортежей, каждый вариант является значением внешнего кортежа. Первый элемент в каждом внутреннем кортеже - это значение, которое должно быть установлено в модели, а второй элемент - его строковое представление. как в приведенном ниже фрагменте кода:

class Transaction(models.Model):
    BUY = 0
    SELL = 1
    TRANSACTION_TYPE_CHOICES = (
        (BUY, 'Buy'),
        (SELL, 'Sell'),
    )
    type_of_transaction = models.IntegerField(
        max_length=4,
        choices=TRANSACTION_TYPE_CHOICES,
        default=‌BUY,
    )

Я ценю последовательное определение, но есть и другой ответ на этот вопрос - использование перечислений, и я думаю, что тип Enum, безусловно, лучший. Они могут одновременно отображать целое число и строку для элемента, при этом ваш код остается более читабельным.См. фрагменты кода ниже:

app/enums.py

from enum import Enum    

class ChoiceEnum(Enum):

    def __str__(self):
        return self.name

    def __int__(self):
        return self.value
    
    @classmethod
    def choices(cls):
        choices = list()
        for item in cls:  # Loop thru defined enums
            choices.append((item.value, item.name))   
        return tuple(choices)


class TransactionType(ChoiceEnum):
    BUY = 0
    SELL = 1

# Uh oh
TransactionType.BUY._name_ = 'Buy'
TransactionType.SELL._name_ = 'Sell'

app/models.py

from django.db import models
from myapp.enums import TransactionType

class Transaction(models.Model):
     type_of_transaction = models.IntegerField(choices=TransactionType.choices(), default=int(TransactionType.BUY))
# ...

Более простой способ, согласно официальной документации для Django3.2

class Transaction(models.Model):
    class TransactionTypes(models.IntegerChoices):
         BUY = 0, _('Buy')
         SELL = 1, _('Sell')

(или)

class Transaction(models.Model):
    class TransactionTypes(models.IntegerChoices):
         BUY = 0, 'Buy'
         SELL = 1, 'Sell'

Другой способ - использовать Enum functional api, это также упоминается в официальной документации Django 3.2

 TransactionTypes = models.IntegerChoices('TransactionTypes', 'BUY SELL')
 TransactionTypes.choices
 #provides below output
 >>>[(1, 'Buy'), (2, 'Sell')] 

Просто создайте варианты выбора, как указано в Django Choices,

class TransactionTypes(models.IntegerChoices):
    BUY = 0, 'Buy'
    SELL = 1, 'Sell'

Итак, эффективно ваша модель будет выглядеть так,

class Transaction(models.Model):
    class TransactionTypes(models.IntegerChoices):
        BUY = 0, 'Buy'
        SELL = 1, 'Sell'

    type = models.PositiveSmallIntegerField(choices=TransactionTypes.choices)

    def __str__(self):
        return str(self.type)

Я увидел, что у вас есть значение "string" из поля .type. Я твердо уверен, что этого не произойдет, так как это поле integer в БД и, конечно, либо БД выдаст ошибку, либо приведет значение к целому числу, когда вы попытаетесь сохранить поле со строковым значением.

In [12]: txn_1 = Transaction.objects.create(type=Transaction.TransactionTypes.SELL)

In [13]: txn_1.refresh_from_db()

In [14]: type(txn_1.type)
Out[14]: int

In [15]: txn_2 = Transaction.objects.create(type="1")

In [16]: txn_2.refresh_from_db()

In [17]: type(txn_2.type)
Out[17]: int
Вернуться на верх