调用模型的方法并在序列化器内处理接收到的对象

huangapple go评论59阅读模式
英文:

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=&quot;foo&quot;,
        blank=True,
        null=True,
    )
    subPosition = TreeForeignKey(
        &quot;self&quot;,
        on_delete=models.CASCADE,
        verbose_name=&quot;bar&quot;,
        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(
        &quot;self&quot;,
        on_delete=models.CASCADE,
        verbose_name=&quot;parent category&quot;,
        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=&#39;parent&#39;)
    class Meta:
        model = CategoryMetall
        fields = [&#39;id&#39;, &#39;name&#39;, &#39;parentCategory&#39;]

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&#39;catmet&#39;, views.CategoryMetallViewSet)
urlpatterns = [
    path(&#39;myapi/&#39;, 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:

&lt;Menu: Metallurgy raw materials &gt;

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方法的问题在于它返回两种模型类型之一:MenuCategoryMetall(自身)。

我个人认为唯一的选择是在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=&#39;parent&#39;)` 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 &lt;Menu: Metallurgy raw materials &gt; 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 = [&#39;field_1&#39;, &#39;field_2&#39;]  # 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 = [&#39;id&#39;, &#39;name&#39;, &#39;position&#39;, &#39;subPosition&#39;]

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 = [&#39;id&#39;, &#39;name&#39;, &#39;position&#39;, &#39;subPosition&#39;]

    def to_representation(self, instance):
        data = super().to_representation(instance)
        print(data)  # for debug reasons
        # modify data as you wish - I&#39;m actually not sure if this is a dict and if the following works
        if data.get(&#39;subPosition&#39;):
            del data[&#39;position&#39;]
        else:
            del data[&#39;subPosition&#39;]
        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):
    &quot;&quot;&quot;
    Serialize bookmark instances using a bookmark serializer,
    and note instances using a note serializer.
    &quot;&quot;&quot;
    if isinstance(value, Bookmark):
        serializer = BookmarkSerializer(value)
    elif isinstance(value, Note):
        serializer = NoteSerializer(value)
    else:
        raise Exception(&#39;Unexpected type of tagged object&#39;)

    return serializer.data

huangapple
  • 本文由 发表于 2023年1月9日 18:30:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/75055959.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定