Home > database >  Calling a model's method inside a serializer and handling a received object
Calling a model's method inside a serializer and handling a received object

Time:01-11

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?

CodePudding user response:

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
  • Related