序列化和反序列化Django Enum字段以接受数字和文本表示

11 投票
1 回答
6925 浏览
提问于 2025-04-18 18:12

我正在尝试在Django中创建一个枚举字段。这个字段在收到GET请求时会返回枚举的文本表示,而在收到POST或PATCH请求时会把文本表示转换成对应的整数,然后再保存。

这个

transform_<field>()

方法可以很好地将整数枚举值转换成对应的字符串,但我找不到更好的方法来把字符串转换成对应的整数,除了修改

validate_<field>()

方法。

有没有更好的办法呢?请看下面的代码

模型文件

class Status(enum.Enum):
    RUNNING = 0
    COMPLETED = 1

    labels = {
         RUNNING: 'Running',
         COMPLETED: 'Completed'
    }

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

class Job(models.Model):
    status = enum.EnumField(Status)

序列化器

class JobSeralizer(serializers.ModelSerailzer):
    status = seralizers.CharField(max_length=32, default=Status.QUEUED)

    def transform_status(self, obj, value):
        return JobStatus.labels[value]

    def validate_status(self, attrs, source):
        """Allow status to take numeric or character representation of status
        """
        status = attrs[source]
        if status in JobStatus.translation:
            attrs[source] = JobStatus.translation[status]
        elif status.isdigit():
            attrs[source] = int(status)
        else:
            raise serializers.ValidationError("'%s' not a valid status" % status)
        return attrs

1 个回答

8

正如提问者所说,你可以很简单地在 drf v3.x 中使用 自定义字段 来实现这个功能。下面是一个快速示例,展示了一个通用的自定义字段,用于将值和标签进行转换(比如枚举值和文本表示之间的转换):

class KeyValueField(serializers.Field):
    """ A field that takes a field's value as the key and returns
    the associated value for serialization """

    labels = {}
    inverted_labels = {}

    def __init__(self, labels, *args, **kwargs):
        self.labels = labels
        # Check to make sure the labels dict is reversible, otherwise
        # deserialization may produce unpredictable results
        inverted = {}
        for k, v in labels.iteritems():
            if v in inverted:
                raise ValueError(
                    'The field is not deserializable with the given labels.'
                    ' Please ensure that labels map 1:1 with values'
                )
            inverted[v] = k
        self.inverted_labels = inverted
        return super(KeyValueField, self).__init__(*args, **kwargs)

    def to_representation(self, obj):
        if type(obj) is list:
            return [self.labels.get(o, None) for o in obj]
        else:
            return self.labels.get(obj, None)

    def to_internal_value(self, data):
        if type(data) is list:
            return [self.inverted_labels.get(o, None) for o in data]
        else:
            return self.inverted_labels.get(data, None)

字段初始化的样子大概是这样的:

class MySerializer(serializers.Serializer):
    afield = KeyValueField(labels={0:'enum text 0', 1:'enum text 1'})

撰写回答