I have a serializer

class CategoryListSerializer(serializers.ModelSerializer):
class Meta:
    model = Category
    fields = ["id", "name", "name_en", "about", "parent",]

It is used in two locations:

  1. All Categories API: Used to view rich details about the categories.
  2. All Posts API: Used to know the name of the category only.

In my Posts Serializer, I used:

class PostListSerializer(serializers.ModelSerializer):
    categories = CategoryListSerializer(many=True, )

    class Meta:
        model = Post
        fields = ["id", "title", "description", "publish_date", "thumbnail", "owner", "categories", ]

And in my Post ViewSet:

class PostViewSet(ReadOnlyModelViewSet):
    queryset = Post.objects.all().filter(is_published=True)
    serializer_class = PostListSerializer

This returns All posts with All Categories Details mentioned in CategoryListSerializer, as it should be.

Question:

I want the PostListSerializer to return only the "name" field from the related Categories, without having to define another CategorySimpleSerializer that selects "name" field only. (I still need the CategoryListSerializer fields in another API)

Is it possible to do that?

Note: This is only an example, I'll have more usecases for this and want to know ahead if i'll have to create many custom "to-be-nested" Serialzers, to avoid exposing some unnecessary data to some of the APIs. It seemed like lots of redundant update work if a model or API needs change later.

4

There are 4 answers

0
Ji--- On BEST ANSWER

As Mentioned by @mtzd in the comments:

Creating a generic Dynamic Serializer Class (As in DRF Docs here) worked!

My Category Serializer Looks like this now:

class DynamicFieldsCategorySerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        # Don't pass the 'fields' arg up to the superclass
        fields = kwargs.pop('fields', None)

        # Instantiate the superclass normally
        super().__init__(*args, **kwargs)

        if fields is not None:
            # Drop any fields that are not specified in the `fields` argument.
            allowed = set(fields)
            existing = set(self.fields)
            for field_name in existing - allowed:
                self.fields.pop(field_name)


class CategoryListSerializer(DynamicFieldsCategorySerializer):
    class Meta:
        model = Category
        fields = [ "name", "name_en", "about",]

and in PostListSerializer Categories variable, I added the fields attribute only:

categories = CategoryListSerializer(many=True, fields=['name',])

So now I can manage what fields to show to each View API i use, from a Single Model Serializer that i can modify/update once.

0
Deepak Tripathi On

You should use serializers as described below for your use cases.

class PostListSerializer(serializers.ModelSerializer):
    categories = serializers.SerializerMethodField('get_categories')

    class Meta:
        model = Post
        fields = ["id", "title", "description", "publish_date", "thumbnail", "owner", "categories", ]
        
    def get_categories(self, obj):
        return obj.categories.all().values("name")

Also you need to optimize your Post.objects.all().filter(is_published=True) to Post.objects.filter(is_published=True).select_related("categories")

0
rwan 101 On

in your category Model, you could define a method that returns the name of the category as the following

class Category(models.Model):
     name=models.CharField()
     ``rest of attributes``
     def __str__(self):
       return self.name

in your serializer as explained in docs stringrelatedfield:

class PostListSerializer(serializers.ModelSerializer):
  categories = serializers.StringRelatedField(many=True)

  class Meta:
     model = Post
     fields = ["id", "title", "description", "publish_date", "thumbnail", 
              "owner", "categories", ]

in PostViewset as you need only the read-only mode no more modification is needed but in case it will be used to create a post you should send the category id in serializer.save() method as the following:

def create(self, request):
    serializer = self.get_serializer(data=request.data)
    category = Category.objects.get(pk=request.data['category'])
    if serializer.is_valid():
        serializer.save(category=category) 
        return Response(serializer.data, status=status.HTTP_201_CREATED)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
0
Henshal B On
class Meta:
    read_only_fields = (
        "id",
        "slug",
    )