英文:
Calling a model's method inside a serializer and handling a received object
问题
I'm supposed to write an API for the endpoints. It should be an application inside an existing project. I should work with its models and i'm not allowed to alter them in any way.
The project consists of multiple applications, and some applications have their own models.
There is an exempt from CategoryMetall/models.py in the CatalogNew application:
class CategoryMetall(MPTTModel):
position = models.ForeignKey(
Menu,
on_delete=models.CASCADE,
verbose_name="foo",
blank=True,
null=True,
)
subPosition = TreeForeignKey(
"self",
on_delete=models.CASCADE,
verbose_name="bar",
blank=True,
null=True,
)
def parent(self):
if self.subPosition:
return self.subPosition
else:
return self.position
As i understood, the parent() method is supposed to return an object of either a CategoryMetall model, or a Menu model. A Menu model is a model of another application from the project.
Here is an exempt from it as well:
Menu/models.py
class Menu(models.Model):
parent = models.ForeignKey(
"self",
on_delete=models.CASCADE,
verbose_name="parent category",
null=True,
blank=True,
)
So, i figured that in order to get a parent category i'm supposed to use the CategoryMetall.parent() method written by some other developer.
The issue is, i'm also supposed to somehow serialize it.
I have written a serializer in my serializers.py:
class CategoryMetallSerializer(serializers.HyperlinkedModelSerializer):
parentCategory = serializers.ReadOnlyField(source='parent')
class Meta:
model = CategoryMetall
fields = ['id', 'name', 'parentCategory']
And a view for it, views.py:
class CategoryMetallViewSet(viewsets.ModelViewSet):
queryset = CategoryMetall.objects.all()
serializer_class = CategoryMetallSerializer
pagination_class = CustomPagination
I have registered a url for this view in my urls.py as well:
router.register(r'catmet', views.CategoryMetallViewSet)
urlpatterns = [
path('myapi/', include(router.urls)),
]
The thing is, when i go to myapi/catmet link to see how it looks, i get an exception:
TypeError: Object of type Menu is not JSON serializable
As i understood, when i use
serializers.ReadOnlyField(source='parent')
it calls the parent() method of a CategoryMetall model, then returns by a foreign key an object of Menu model. It goes straight into a serializer and breaks because its somehow not serializable.
An object itself, as i got it from the debug screen, looks like this:
<Menu: Metallurgy raw materials >
I'm not sure if i'm using the right approach to call the method from the serializer, and even if i do, i have no idea what to do to serialize that.
I tried to search the Django Rest Framework documentation, google, reddit and StackOverflow to find out how to do it properly, or what exactly i do wrong, but failed. I'm still an intern, so i dont have an extensive knowledge of the framework and only started working with it like a week ago.
I investigated on how to serialize the foreign key itself and found out that its done by writing another serializer specifically for the model a foreign key refers to, then using it inside the main one. But i don't know how to do that in this case, or if it even is a solution.
Can you please suggest something?
英文:
I'm supposed to write an API for the endpoints. It should be an application inside an existing project. I should work with its models and i'm not allowed to alter them in any way.
The project consists of multiple applications, and some applications have their own models.
There is an exempt from CategoryMetall/models.py in the CatalogNew application:
class CategoryMetall(MPTTModel):
position = models.ForeignKey(
Menu,
on_delete=models.CASCADE,
verbose_name="foo",
blank=True,
null=True,
)
subPosition = TreeForeignKey(
"self",
on_delete=models.CASCADE,
verbose_name="bar",
blank=True,
null=True,
)
def parent(self):
if self.subPosition:
return self.subPosition
else:
return self.position
As i understood, the parent() method is supposed to return an object of either a CategoryMetall model, or a Menu model. A Menu model is a model of another application from the project.
Here is an exempt from it as well:
Menu/models.py
class Menu(models.Model):
parent = models.ForeignKey(
"self",
on_delete=models.CASCADE,
verbose_name="parent category",
null=True,
blank=True,
)
So, i figured that in order to get a parent category i'm supposed to use the CategoryMetall.parent() method written by some other developer.
The issue is, i'm also supposed to somehow serialize it.
I have written a serializer in my serializers.py:
class CategoryMetallSerializer(serializers.HyperlinkedModelSerializer):
parentCategory = serializers.ReadOnlyField(source='parent')
class Meta:
model = CategoryMetall
fields = ['id', 'name', 'parentCategory']
And a view for it, views.py:
class CategoryMetallViewSet(viewsets.ModelViewSet):
queryset = CategoryMetall.objects.all()
serializer_class = CategoryMetallSerializer
pagination_class = CustomPagination
I have registered a url for this view in my urls.py as well:
router.register(r'catmet', views.CategoryMetallViewSet)
urlpatterns = [
path('myapi/', include(router.urls)),
]
The thing is, when i go to myapi/catmet link to see how it looks, i get an exception:
> TypeError: Object of type Menu is not JSON serializable
As i understood, when i use
> serializers.ReadOnlyField(source='parent')
it calls the parent() method of a CategoryMetall model, then returns by a foreign key an object of Menu model. It goes straight into a serializer and breaks because its somehow not serializable.
An object itself, as i got it from the debug screen, looks like this:
<Menu: Metallurgy raw materials >
I'm not sure if i'm using the right approach to call the method from the serializer, and even if i do, i have no idea what to do to serialize that.
I tried to search the Django Rest Framework documentation, google, reddit and StackOverflow to find out how to do it properly, or what exactly i do wrong, but failed. I'm still an intern, so i dont have an extensive knowledge of the framework and only started working with it like a week ago.
I investigated on how to serialize the foreign key itself and found out that its done by writing another serializer specifically for the model a foreign key refers to, then using it inside the main one. But i don't know how to do that in this case, or if it even is a solution.
Can you please suggest something?
答案1
得分: 0
根据我的理解,当我使用serializers.ReadOnlyField(source='parent')
时,它会调用CategoryMetall模型的parent()
方法,然后通过外键返回一个Menu模型的对象。
这是正确的。
parent
方法的问题在于它返回两种模型类型之一:Menu
或CategoryMetall
(自身)。
我个人认为唯一的选择是在API调用中返回这两个对象,然后稍后在应用程序或任何使用它的地方检查subPosition
是否可用。
采用这种方法,你可以为Menu
定义一个新的序列化程序。Django不知道如何将<Menu: 冶金原材料>
序列化为JSON。你必须告诉它应该序列化哪些字段,就像在CategoryMetallSerializer
中一样。例如:
class MenuSerializer(serializers.ModelSerializer):
class Meta:
model = Menu
fields = ['field_1', 'field_2'] # 你想从菜单中获取的所有字段
现在你可以在CategoryMetallSerializer
中使用这个序列化程序:
class CategoryMetallSerializer(serializers.HyperlinkedModelSerializer):
position = MenuSerializer(read_only=True)
subPosition = CategoryMetallSerializer(read_only=True)
class Meta:
model = CategoryMetall
fields = ['id', 'name', 'position', 'subPosition']
我是凭记忆编写的。代码中可能存在一些语法问题,因为没有经过测试,但我希望我能指导你朝正确的方向前进。顺便说一下,感谢你在问题中提供的详细信息。
编辑 1(评论1:只序列化一个字段)
如果你想更改序列化器的输出,可以重写序列化器的to_representation
函数,像这样:
class CategoryMetallSerializer(serializers.HyperlinkedModelSerializer):
position = MenuSerializer(read_only=True)
subPosition = CategoryMetallSerializer(read_only=True)
class Meta:
model = CategoryMetall
fields = ['id', 'name', 'position', 'subPosition']
def to_representation(self, instance):
data = super().to_representation(instance)
print(data) # 用于调试目的
# 根据需要修改数据 - 我实际上不确定这是否是一个字典,以下代码是否有效
if data.get('subPosition'):
del data['position']
else:
del data['subPosition']
return data
另一种方法是参考官方文档中的这种方式:
https://www.django-rest-framework.org/api-guide/relations/
def to_representation(self, value):
"""
使用书签序列化程序序列化书签实例,
使用笔记序列化程序序列化笔记实例。
"""
if isinstance(value, Bookmark):
serializer = BookmarkSerializer(value)
elif isinstance(value, Note):
serializer = NoteSerializer(value)
else:
raise Exception('Unexpected type of tagged object')
return serializer.data
英文:
As i understood, when i use `serializers.ReadOnlyField(source='parent')` it calls the parent() method of a CategoryMetall model, then returns by a foreign key an object of Menu model
That's correct.
The problem with the parent
method is that it returns one of two model types: Menu
or CategoryMetall
(self).
I personally see only the option to return both objects in the API call and then check later in the app or whatever this is used if the subPosition
is available or not.
With this approach you can define a new serializer for the Menu
. Django doesn't know how to return <Menu: Metallurgy raw materials >
as JSON. You have to tell it which fields it should serialize. Exactly like in the CategoryMetallSerializer
. For example:
class MenuSerializer(serializers.ModelSerializer):
class Meta:
model = Menu
fields = ['field_1', 'field_2'] # all fields you want to fetch from the menu
Now you can use this serializer inside the CategoryMetallSerializer
:
class CategoryMetallSerializer(serializers.HyperlinkedModelSerializer):
position = MenuSerializer(read_only=True)
subPosition = CategoryMetallSerializer(read_only=True)
class Meta:
model = CategoryMetall
fields = ['id', 'name', 'position', 'subPosition']
I've typed this out of my head. There might be some syntax issues in the code as it is not tested but I hope I could point you in the right direction. BTW +1 for the details in your question.
EDIT 1 (comment 1: only serialize one field)
If you want to change the output of the serializer, you can override the to_representation
function of the serializer like that:
class CategoryMetallSerializer(serializers.HyperlinkedModelSerializer):
position = MenuSerializer(read_only=True)
subPosition = CategoryMetallSerializer(read_only=True)
class Meta:
model = CategoryMetall
fields = ['id', 'name', 'position', 'subPosition']
def to_representation(self, instance):
data = super().to_representation(instance)
print(data) # for debug reasons
# modify data as you wish - I'm actually not sure if this is a dict and if the following works
if data.get('subPosition'):
del data['position']
else:
del data['subPosition']
return data
Another approach would be something in this way from the official docs:
https://www.django-rest-framework.org/api-guide/relations/
def to_representation(self, value):
"""
Serialize bookmark instances using a bookmark serializer,
and note instances using a note serializer.
"""
if isinstance(value, Bookmark):
serializer = BookmarkSerializer(value)
elif isinstance(value, Note):
serializer = NoteSerializer(value)
else:
raise Exception('Unexpected type of tagged object')
return serializer.data
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论