Django REST框架:如何使字段的verbose name与其field_name不同?

6

我有一个模型,其中有一个字段tool_class,它的显示名称是class,与名称不同:

class Tool(models.Model):
    tool_class = jsonfield.JSONField(verbose_name="class")

Serializer和ViewSet只是标准的HyperlinkedModelSerializerModelViewSet

所以,当我使用关键字class向服务器提交POST或PUT数据时,它可以被很好地识别:

'{"class": "..."}

但是在响应数据中,它再次被称为tool_class

{"tool_class": "..."}

如何让它始终被称为class

在Python中,由于其是一个保留字,因此不能将类名命名为"class"。但是,在API中,必须将其称为"class",因为该API符合某个特定的开放标准,该标准规定了这个词。

显然,我不能这样说:

class = CharField(source="tool_class")

在我的ToolSerializer中,因为出现了SyntaxError:invalid syntax的错误,所以需要进行修改。
简单解决方案: 在另一个线程中,有人建议一个很好的解决方案。您可以使用vars()语法来避免这个问题。例如,我使用以下代码:
class Tool(Document):
    vars()['class'] = mongoengine.fields.DictField(required=True)

序列化器会自动创建相应的字段。我们真是太狡猾了!

2个回答

4
我试图通过使用setattr的一些技巧,在序列化器上创建一个名为class的字段,但这样做会变得非常繁琐和hacky。在将字段绑定到序列化器时,field_name是从该字段收集的,并且没有简单的方法来覆盖绑定行为。
最终,我决定让DRF自己处理,然后在序列化器上添加一个后处理步骤,这样会更好、更简单。
class ToolSerializer(ModelSerializer):
    class Meta:
        model = Tool

    def to_representation(self, instance):
        data = super(ToolSerializer, self).to_representation(instance)
        data['class'] = data.pop('tool_class')
        return data

请注意,to_representation返回的数据结构是一个OrderedDict,这会稍微影响排序 - 这个映射中重命名的键将被从原来的位置移除并推到后面。
对于大多数用例而言,这不太可能成为问题,因此如果没有必要,您不应该担心它。如果确实需要保留顺序,请使用推导式重建一个新的OrderedDict:
data = OrderedDict(
    ('class' if k == 'tool_class' else k, v) for (k, v) in data.items()
)

一个非常好的解决方案,wim。我猜这比其他任何事情都要好。我将不得不以与“to_representation(self, instance)”相同的方式重新编写“to_internal_value(self, data)” - 这是一种中间件。 - Boris Burkov
没错,这就是另一半。但它应该是相当对称和Pythonic的。遗憾的是,在像这样的用例中覆盖序列化名称的字段没有kwarg,当内部名称不好时。"from"是另一个似乎在rest api中有用但与Python关键字冲突的单词。 - wim
2
各位,另一个线程的朋友提出了一个很好的解决方案:使用 vars()['class'] = DictField(required=True)。它非常有效且简单易懂。 - Boris Burkov

2
你可以通过覆盖序列化器的元类来实现这一点。以下是一个serializers.py文件的示例。
元类的主要魔力在于以下部分。
# Remap fields (to use class instead of class_)
fields_ = []
for name, field in fields:
    if name.endswith('_'):
        name = name.rstrip('_')
    fields_.append((name, field))

这个功能会将你在序列化器中定义的任何以下划线结尾的字段(例如:field_)的名称中的下划线去掉,并在绑定Fields并设置序列化器上的_declared_fields属性时使用新名称。
from collections import OrderedDict

from rest_framework import serializers
from rest_framework.fields import Field
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES

class MyMeta(serializers.SerializerMetaclass):

    @classmethod
    def _get_declared_fields(cls, bases, attrs):
        fields = [(field_name, attrs.pop(field_name))
                  for field_name, obj in list(attrs.items())
                  if isinstance(obj, Field)]
        fields.sort(key=lambda x: x[1]._creation_counter)

        # If this class is subclassing another Serializer, add that Serializer's
        # fields.  Note that we loop over the bases in *reverse*. This is necessary
        # in order to maintain the correct order of fields.
        for base in reversed(bases):
            if hasattr(base, '_declared_fields'):
                fields = list(base._declared_fields.items()) + fields

        # Remap fields (to use class instead of class_)
        fields_ = []
        for name, field in fields:
            if name.endswith('_'):
                name = name.rstrip('_')
            fields_.append((name, field))

        return OrderedDict(fields_)


class ToolSerializer(serializers.Serializer):

    __metaclass__ = MyMeta

    ...
    class_ = serializers.JSONField(source='tool_class', label='class')

    def create(self, validated_data):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Update and return an existing `Snippet` instance, given the validated data.
        """
        ...
        instance.class_ = validated_data.get('class', instance.class_)
        instance.save()
        return instance

是的,我同意应该使用 pull-request 来修改序列化器字段以接受额外参数。不过,需要进行大量测试来调试,而我目前无法自己编写它。谢谢! - Boris Burkov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接