使用validated_data
时,我很难从HyperlinkedIdentityField
获取网址(或任何类型的ID)。我需要URL,因为我正在进行可写的嵌套表示,我想知道嵌套表示是指现有对象(指定的url)还是新对象(未指定url)。我已阅读有关自定义ListSerializer
更新的文档中的example,但这似乎依赖于客户端发送不存在的id
,这对我来说不起作用,因为我想要请改用URL。我已检查validated_data
是否有HyperlinkedIdentityField
的顶级(非嵌套)对象,并且ID也未在此处公开。
编辑:我现在知道这是因为HyperlinkedIdentityField
设置了read_only
属性,并且在生成验证数据时会跳过只读字段。欢迎提示如何解决这个问题。
此电话簿应用中有两种型号:Entry
和Phone
。 Entry
有多个Phone
个。如果这是相关的,模型代码如下:
from django.db import models
class Entry(models.Model):
name = models.CharField(max_length=200)
class Phone(models.Model):
type = models.CharField(max_length=20)
number = models.CharField(max_length=50)
parent = models.ForeignKey(Entry, related_name='phones')
我的序列化定义如下:
class PhoneListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
print repr(validated_data)
url_of = lambda p : self.child.to_representation(p)['url']
existing_instances = { url_of(p): p for p in instance }
existing_submitted_instances = { item['url']: item for item in validated_data if 'url' in item }
new_submitted_instances = [ item for item in validated_data if 'url' not in item ]
urls_to_delete = existing_instances.viewkeys() - existing_submitted_instances.viewkeys()
objects_to_delete = [existing_instances[u] for u in urls_to_delete if u in existing_instances]
objects_to_update = [(existing_instances[u], p) for u, p in six.iteritems(existing_submitted_instances) if u in existing_instances]
result = []
for o in objects_to_delete:
o.delete()
for existing, data in objects_to_update:
result.append(self.child.update(existing, data))
for data in new_submitted_instances:
data['parent'] = self.root.instance
result.append(self.child.create(data))
return result
class PhoneSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='phone', lookup_field='id')
class Meta:
model = Phone
list_serializer_class = PhoneListSerializer
fields = ('type', 'number', 'url')
class EntrySerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='entry', lookup_field='id')
phones = PhoneSerializer(many=True, required=False)
class Meta:
model = Entry
fields = ('url', 'name', 'phones')
def update(self, instance, validated_data):
print repr(validated_data)
# pop this first so super does not complain about writable nested
# serializers. we will update phones ourselves.
phone_data = validated_data.pop('phones', [])
phones_field = self.fields['phones']
instance = super(EntrySerializer, self).update(instance, validated_data)
phones_field.update(instance.phones.all(), phone_data)
return instance
def create(self, validated_data):
phone_data = validated_data.pop('phones', [])
new_entry = Entry.objects.create(**validated_data)
# TODO atomically do this
for phone_validated_data in phone_data:
Phone.objects.create(parent=new_entry, **phone_validated_data)
return new_entry
目前,数据输出有效,但重新提交数据(即使使用URL)会导致所有条目的电话被删除,然后使用新ID重新创建。
编辑:尝试使用HyperlinkedRelatedField
(以及ModelSerializer
代替HyperlinkedModelSerializer
)得到更多(该字段成功将对象拉出) ,但在序列化程序调用{{1}}
代码:
fields.set_value
此操作失败,因为它最终调用class PhoneSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedRelatedField(
required=False, view_name='phone', lookup_field='id',
queryset=Phone.objects, source='*')
,并且它将调用rest_framework.fields.set_value(validated_data, [], <Phone object>)
,其失败并显示'Phone object is is iterable'。
validated_data.update(<Phone object>)
其他尝试:
Traceback (most recent call last):
[...snip...]
File "/app/phonebook_be/views.py", line 41, in entry
if serializer.is_valid(raise_exception=True):
File "/venv/lib/python2.7/site-packages/rest_framework/serializers.py", line 191, in is_valid
self._validated_data = self.run_validation(self.initial_data)
File "/venv/lib/python2.7/site-packages/rest_framework/serializers.py", line 371, in run_validation
value = self.to_internal_value(data)
File "/venv/lib/python2.7/site-packages/rest_framework/serializers.py", line 404, in to_internal_value
validated_value = field.run_validation(primitive_value)
File "/venv/lib/python2.7/site-packages/rest_framework/serializers.py", line 522, in run_validation
value = self.to_internal_value(data)
File "/venv/lib/python2.7/site-packages/rest_framework/serializers.py", line 552, in to_internal_value
validated = self.child.run_validation(item)
File "/venv/lib/python2.7/site-packages/rest_framework/serializers.py", line 371, in run_validation
value = self.to_internal_value(data)
File "/venv/lib/python2.7/site-packages/rest_framework/serializers.py", line 414, in to_internal_value
set_value(ret, field.source_attrs, validated_value)
File "/venv/lib/python2.7/site-packages/rest_framework/fields.py", line 96, in set_value
dictionary.update(value)
File "/venv/lib/python2.7/_abcoll.py", line 568, in update
for key, value in other:
TypeError: 'Phone' object is not iterable
:渲染时导致错误
source='id'
答案 0 :(得分:1)
您目前正在手动比较网址。然而,这有一些问题:
url_of = lambda p : self.child.to_representation(p)['url']
existing_instances = { url_of(p): p for p in instance }
第二行很昂贵,因为instance
是一个查询集,因此只要你有大量数据,就可以成为内存耗尽,因为你将为匹配queryset的所有实例创建一个dict。 / p>
DRF本身支持解析URL以获取身份对象,而不是这样做:
class PhoneSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedRelatedField(
required=False,
view_name='phone',
lookup_field='id',
)
请注意HyperlinkedRelatedField
的使用。与HyperlinkedIdentityField
(子类HyperlinkedRelatedField
)不同,HyperlinkedRelatedField
不是只读的。它能够通过反转URL然后使用url中的kwargs在db中查找对象来解析URL并从URL本身获取模型。因此,您可以依赖该行为来查找db中的现有对象。请注意,该字段是可选的,允许API的客户端省略它,在这种情况下,URL将不会被解析,因此可以创建新对象。
class PhoneListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
to_update = filter(lambda i: i.get('url'), validated_data)
to_create = filter(lambda i: not i.get('url'), validated_data)
data = []
for i in to_update:
data.append(self.child.update(i['url'], i))
for i in to_create:
data.append(self.child.create(i))
return data
希望这会有所帮助。请注意,我是从记忆中做到的,因此可能需要注意一些事项,但这个概念通常应该有用。
最后有一些关于HyperlinkedRelatedField
- http://www.django-rest-framework.org/api-guide/relations/#hyperlinkedrelatedfield
答案 1 :(得分:1)
因此,为了解决这个问题,我在HiddenField
上创建了一个只写字段(受PhoneSerializer
启发),该字段从提交的'url'值中获取其值:
class WriteOnlySynonymField(serializers.Field):
def __init__(self, **kwargs):
kwargs['default'] = serializers.empty
kwargs['required'] = False
kwargs['write_only'] = True
self.synonym_for = kwargs.pop('synonym_for')
super(WriteOnlySynonymField, self).__init__(**kwargs)
def get_value(self, dictionary):
return dictionary.get(self.synonym_for, serializers.empty)
def to_internal_value(self, data):
return data
class PhoneSerializer(serializers.ModelSerializer):
url = MultiKeyHyperlinkedIdentityField(
view_name='phone',
lookup_kwarg_to_field={'id': 'id', 'entry_id': 'parent_id'})
submitted_url = WriteOnlySynonymField(synonym_for='url')
class Meta:
model = Phone
list_serializer_class = PhoneListSerializer
fields = ('type', 'number', 'url', 'submitted_url')
列表序列化程序更新的相关位稍有变化,以使用新字段名称:
existing_submitted_instances = {item['submitted_url']: item
for item in validated_data
if 'submitted_url' in item}
new_submitted_instances = [item for item in validated_data
if 'submitted_url' not in item]