![](/img/trans.png)
[英]Alter django rest framework field before serialization and deserialization
[英]Django Rest Framework - deserialization of a TaggableManager field
我使用django-taggit為我的模型添加標簽。 Django 版本:2.2.10,Python 版本:3.8.1
現在我正在嘗試將標簽與 django rest-framework 集成,例如創建/更新/刪除帶有/不帶標簽的模型實例。
我的問題:我無法使用標簽創建(通過 rest api)我的模型的新實例。 我可以毫無問題地獲取模型實例。
我的models.py:
from taggit.managers import TaggableManager
class Task(models.Model):
name = models.CharField(max_length=100, blank=False)
...
tags = TaggableManager(blank=True)
def get_tags(self):
""" names() is a django-taggit method, returning a ValuesListQuerySet
(basically just an iterable) containing the name of each tag as a string
"""
return self.tags.names()
def __str__(self):
return self.title
我的序列化程序.py:
class TagsField(serializers.Field):
""" custom field to serialize/deserialize TaggableManager instances.
"""
def to_representation(self, value):
""" in drf this method is called to convert a custom datatype into a primitive,
serializable datatype.
In this context, value is a plain django queryset containing a list of strings.
This queryset is obtained thanks to get_tags() method on the Task model.
Drf is able to serialize a queryset, hence we simply return it without doing nothing.
"""
return value
def to_internal_value(self, data):
""" this method is called to restore a primitive datatype into its internal
python representation.
This method should raise a serializers.ValidationError if the data is invalid.
"""
return data
class TaskSerializer(serializers.ModelSerializer):
# tags field in Task model is implemented via TaggableManager class from django-taggit.
# By default, drf is not able to serialize TaggableManager to json.
# get_tags() is a method of the Task model class, which returns a Queryset containing
# the list of tags as strings. This Queryset can be serialized without issues.
tags = TagsField(source="get_tags")
class Meta:
model = Task
fields = [
"name",
...,
"tags",
]
每當我嘗試通過 POST api 創建我的 Task 模型的新實例時,我都會收到以下錯誤:
TypeError at /taskdrop/v1/task/
Got a `TypeError` when calling `Task.objects.create()`. This may be because you have a writable field on the serializer class that is not a valid argument to `Task.objects.create()`. You may need to make the field read-only, or override the TaskSerializer.create() method to handle this correctly.
Original exception was:
Traceback (most recent call last):
File "/home/daniele/prj/ea/TaskDrop/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 948, in create
instance = ModelClass._default_manager.create(**validated_data)
File "/home/daniele/prj/ea/TaskDrop/venv/lib/python3.8/site-packages/django/db/models/manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/daniele/prj/ea/TaskDrop/venv/lib/python3.8/site-packages/django/db/models/query.py", line 420, in create
obj = self.model(**kwargs)
File "/home/daniele/prj/ea/TaskDrop/venv/lib/python3.8/site-packages/django/db/models/base.py", line 501, in __init__
raise TypeError("%s() got an unexpected keyword argument '%s'" % (cls.__name__, kwarg))
TypeError: Task() got an unexpected keyword argument 'get_tags'
我現在有點卡住了……該字段絕對不是只讀的,並且關於覆蓋 TaskSerializer.create() 方法,我不知道如何做到這一點。
另外,我對 TagsField(serializers.Field) 與 .create() 方法覆蓋有點困惑。 根據我的理解,如果我創建自定義序列化程序字段,則不需要額外覆蓋 .create()。
最后,我嘗試使用django-taggit-serializer沒有成功:模型已創建但傳遞的標簽只是丟失了。
我怎樣才能解決這個問題? 謝謝。
好的,我設法讓它工作。
將解決方案留給其他人:
我得到TypeError: Task() got an unexpected keyword argument 'get_tags'
的原因是因為 drf 試圖使用 to_internal_value() 的返回值來填充我的模型的 'get_tags' 字段。
現在,'get_tags' 只是我的模型 Task 類的方法名稱,而不是真正的字段,因此是錯誤的。 當我在序列化程序中使用tags = TagsField(source="get_tags")
,Drf 了解到 'get_tags' 作為字段名稱。
我以這種方式解決了這個問題,覆蓋了我的序列化程序的 create() 方法:
class TaskSerializer(serializers.ModelSerializer):
# tags field in Task model is implemented via TaggableManager class from django-taggit.
# By default, drf is not able to serialize TaggableManager to json.
# get_tags() is a method of the Task model class, which returns a Queryset containing
# the list of tags as strings. This Queryset can be serialized without issues.
tags = TagsField(source="get_tags")
# variables = VariableSerializer()
def create(self, validated_data):
# using "source=get_tags" drf "thinks" get_tags is a real field name, so the
# return value of to_internal_value() is used a the value of a key called "get_tags" inside validated_data dict. We need to remove it and handle the tags manually.
tags = validated_data.pop("get_tags")
task = Task.objects.create(**validated_data)
task.tags.add(*tags)
return task
class Meta:
model = Task
# we exclude all those fields we simply receive from Socialminer
# whenever we get a task or its status
fields = [
"name",
...
"tags",
]
我認為您可能需要某種標簽序列化程序設置。
所以在你的TaskSerializer
我會有: tags = TagSerializer(many=True, read_only=False)
from serializers import (
TagListSerializerField,
TagSerializer
)
class TaskSerializer(TagSerializer, serializers.ModelSerializer):
# tags field in Task model is implemented via TaggableManager class from django-taggit.
# By default, drf is not able to serialize TaggableManager to json.
# get_tags() is a method of the Task model class, which returns a Queryset containing
# the list of tags as strings. This Queryset can be serialized without issues.
tags = TagListSerializerField()
class Meta:
model = Task
fields = [
"name",
...,
"tags",
]
我多年前就實現了這個,你想要的TagList
、 TagListSerializerField
和TagSerializer
是這樣的:
import six
import json
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializer
class TagList(list):
def __init__(self, *args, **kwargs):
pretty_print = kwargs.pop("pretty_print", True)
list.__init__(self, *args, **kwargs)
self.pretty_print = pretty_print
def __add__(self, rhs):
return TagList(list.__add__(self, rhs))
def __getitem__(self, item):
result = list.__getitem__(self, item)
try:
return TagList(result)
except TypeError:
return result
def __str__(self):
if self.pretty_print:
return json.dumps(
self, sort_keys=True, indent=4, separators=(',', ': '))
else:
return json.dumps(self)
class TagListSerializerField(serializers.Field):
child = serializers.CharField()
default_error_messages = {
'not_a_list': _(
'Expected a list of items but got type "{input_type}".'),
'invalid_json': _('Invalid json list. A tag list submitted in string'
' form must be valid json.'),
'not_a_str': _('All list items must be of string type.')
}
order_by = None
def __init__(self, **kwargs):
pretty_print = kwargs.pop("pretty_print", True)
style = kwargs.pop("style", {})
kwargs["style"] = {'base_template': 'textarea.html'}
kwargs["style"].update(style)
super(TagListSerializerField, self).__init__(**kwargs)
self.pretty_print = pretty_print
def to_internal_value(self, value):
if isinstance(value, six.string_types):
value = value.split(',')
if not isinstance(value, list):
self.fail('not_a_list', input_type=type(value).__name__)
for s in value:
if not isinstance(s, six.string_types):
self.fail('not_a_str')
self.child.run_validation(s)
return value
def to_representation(self, value):
if not isinstance(value, TagList):
if not isinstance(value, list):
if self.order_by:
tags = value.all().order_by(*self.order_by)
else:
tags = value.all()
value = [tag.name for tag in tags]
value = TagList(value, pretty_print=self.pretty_print)
return value
class TagSerializer(serializers.Serializer):
def create(self, validated_data):
to_be_tagged, validated_data = self._pop_tags(validated_data)
tag_object = super(TaggitSerializer, self).create(validated_data)
return self._save_tags(tag_object, to_be_tagged)
def update(self, instance, validated_data):
to_be_tagged, validated_data = self._pop_tags(validated_data)
tag_object = super(TaggitSerializer, self).update(
instance, validated_data)
return self._save_tags(tag_object, to_be_tagged)
def _save_tags(self, tag_object, tags):
for key in tags.keys():
tag_values = tags.get(key)
getattr(tag_object, key).set(*tag_values)
return tag_object
def _pop_tags(self, validated_data):
to_be_tagged = {}
for key in self.fields.keys():
field = self.fields[key]
if isinstance(field, TagListSerializerField):
if key in validated_data:
to_be_tagged[key] = validated_data.pop(key)
return (to_be_tagged, validated_data)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.