2016-05-23 70 views
4

我有一個模型與現場tool_class,其冗長的名字是class和名稱不同:Django REST框架:如何使字段的詳細名稱與其field_name不同?

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

串行器和視圖集只是股票HyperlinkedModelSerializerModelViewSet

所以,當我郵寄或用鑰匙class將數據到服務器,它是認罰:

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

但在響應數據它被稱爲tool_class再次:

{"tool_class": "..."} 

如何使它總是被稱爲class

我不能使用名稱"class"作爲字段名稱,因爲它是python中的保留字,但在API中它絕對必須被稱爲"class",因爲API符合某個開放標準,該標準指定該字。

很顯然,我不能說:

class = CharField(source="tool_class") 
在我 ToolSerializer

,因爲它是一個SyntaxError: invalid syntax


簡化算法: 傢伙在另一個線程提出了一個很好的解決方案。您可以使用vars()語法來規避此問題。舉例來說,我使用下面的代碼:

class Tool(Document): 
    vars()['class'] = mongoengine.fields.DictField(required=True) 

串行器會自動創建相應的字段。我們不是偷偷摸摸嗎?

回答

1

您可以通過重寫了序列化元類做到這一點。這是一個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 
+0

是的,我同意它應該是一個拉請求,修改序列化程序字段接受一個額外的參數。但是,現在需要進行非常多的測試來進行調試,我現在無法自己編寫它。謝謝! –

3

我試圖找到一種方法來在序列化程序上使用一個名爲class的字段,使用setattr的一些技巧,但它變得非常干擾和黑客。 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() 
) 
+0

一個非常好的解決方案,wim。我想,這比其他任何東西都不是一種邪惡。我將不得不像'to_representation(self,instance)'一樣重寫'to_internal_value(self,data)' - 這是一種中間件。 –

+0

是的,這是另一半。但它應該是非常對稱和pythonic。可惜的是,當內部名稱不好時,字段中沒有用於覆蓋此類用例的序列化名稱的kwarg。 'from'是另一個詞,它似乎在rest api中很有用,但與python關鍵字衝突。 – wim

+1

wim,在另一個線程中的傢伙提出了一個很好的解決方案:使用'vars()['class'] = DictField(required = True)'。它的工作原理非常簡單。 –

相關問題