Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Singe CRUD / request->response Operation two serializers#9630

Discussion options

G'Day,

Cant seem to find an answer anywhere, so hoping for an idea/solution.

  • TL;DR. How to specify the serializer to use for the response. I wish to use one serializer for add/update and another for the JSON response within one CRUD operation / request-response.

Within my app, depending upon the situation I use different serializers. which serializer is used is dyno-magically determined by the viewsets logic. i.e. I have a view serializer forALL get requests with the creation of the object being dynamic, serializer wise.

I have discovered recently that when creating an object, the correct serializer is used, however the serializer that is used for the JSON response is not the one I wish to use.

Looking at the code for the create of an object, It uses the same serializer for the create and the JSON return. (or have i looked in the wrong area?)

  • classCreateModelMixin:
    """
    Create a model instance.
    """
    defcreate(self,request,*args,**kwargs):
    serializer=self.get_serializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    self.perform_create(serializer)
    headers=self.get_success_headers(serializer.data)
    returnResponse(serializer.data,status=status.HTTP_201_CREATED,headers=headers)
    defperform_create(self,serializer):
    serializer.save()
    defget_success_headers(self,data):
    try:
    return {'Location':str(data[api_settings.URL_FIELD_NAME])}
    except (TypeError,KeyError):
    return {}

Is this something I will be required to create a custom solution for? or is there a setting or process I have overlooked somewhere?

You must be logged in to vote

I had this problem a few times and there are a few ways to solve the problem, depending on how much your request schema differs from your response schema. Generally speaking, I think it's a good idea to minimise the differences. I'll describe a few options I've adopted over time.

Example

Let's use a simpleBlogPost model in an blogging application:

classBlogPost(models.Model):author=models.ForeignKey("auth.User",on_delete=models.CASCADE)title=models.CharField(max_length=255)body=models.TextField()created_at=models.DateTimeField(auto_now_add=True)

When a post of created, the author provides the title and body, and the other fields as derived from the request. …

Replies: 1 comment 4 replies

Comment options

I had this problem a few times and there are a few ways to solve the problem, depending on how much your request schema differs from your response schema. Generally speaking, I think it's a good idea to minimise the differences. I'll describe a few options I've adopted over time.

Example

Let's use a simpleBlogPost model in an blogging application:

classBlogPost(models.Model):author=models.ForeignKey("auth.User",on_delete=models.CASCADE)title=models.CharField(max_length=255)body=models.TextField()created_at=models.DateTimeField(auto_now_add=True)

When a post of created, the author provides the title and body, and the other fields as derived from the request. When reading a post, however, we want to display its author and creation date.

Solution 1: override the create method

With the solution you mention, the implementation might look like this:

# SerializersclassUserSerializer(serializers.ModelSerializer):classMeta:model=Userfields= ("id","first_name","last_name","username")classCreateBlogPostSerializer(serializers.ModelSerializer):"""Serializer for creating posts."""classMeta:model=BlogPostfields= ("title","body")classBlogPostSerializer(CreateBlogPostSerializer):"""Serializer for reading posts."""author=UserSerializer()classMeta:model=BlogPostfields= ("id","author","title","body","created_at")# viewsclassBlogPostViewSet(CreateModelMixin,RetrieveModelMixin,GenericViewSet):queryset=BlogPost.objects.all()serializer_class=BlogPostSerializerdefcreate(self,request,*args,**kwargs):create_serializer=CreateBlogPostSerializer(data=request.data,context=self.get_serializer_context(),        )create_serializer.is_valid(raise_exception=True)post=create_serializer.save(author=self.request.user)response_serializer=self.get_serializer(post)headers=self.get_success_headers(response_serializer.data)returnResponse(response_serializer.data,status=status.HTTP_201_CREATED,headers=headers)

We override thecreate method to add our customizations:

  • Different serializer for request
  • Derive the author from the request

This is however duplicating a fair bit of boilerplate from DRF, so not ideal.

Solution 2: override theget_serializer_class method

A more chirurgical approach would be to override theget_serializer_class method based on the HTTP verb:

classBlogPostViewSet(CreateModelMixin,RetrieveModelMixin,GenericViewSet):queryset=BlogPost.objects.all()serializer_class=BlogPostSerializerdefget_serializer_class(self):ifself.action=="create":returnCreateBlogPostSerializerreturnsuper().get_serializer_class()

The problem however is that this serializer is used in both the request and the response of thecreate action, soauthor andcreated-at would be omitted.

Solution 3: single serializer with read-only fields

Another solution is to specify which fields are read-only and use a single serializer:

classBlogPostSerializer(serializers.ModelSerializer):author=UserSerializer(read_only=True)classMeta:model=BlogPostread_only_fields= ("id","author","created_at")fields=read_only_fields+ ("title","body")classBlogPostViewSet(CreateModelMixin,RetrieveModelMixin,GenericViewSet):queryset=BlogPost.objects.all()serializer_class=BlogPostSerializerdefperform_create(self,serializer):serializer.save(user=self.request.user)

Here the read-only fields are excluded from the request schema, but are included in the response schema. You can mix and match this approach with the previous one to bringCreateBlogPostSerializer more in line with the read-only serializer.

Solution 4: have different fields in the request and response

Now imagine that our blog is divided into categories (each post belonging to a single one):

classCategory(models.Model):title=models.CharField(max_length=255)description=models.TextField()classBlogPost(models.Model):    ...# Same as beforecategory=models.ForeignKey(Category,on_delete=models.CASCADE)

Categories are created ahead of time. When creating a post, we just want to pick one. When reading a post however, we want to get the whole category, with its title and description. We could modify our serializer like this:

classCategorySerializer(serializers.ModelSerializer):classMeta:model=Categoryfields= ("id","title","description")classBlogPostSerializer(serializers.ModelSerializer):author=UserSerializer(read_only=True)category_id=serializers.PrimaryKeyRelatedField(queryset=Category.objects.all(),source="category",write_only=True,    )category=CategorySerializer(read_only=True)classMeta:model=BlogPostread_only_fields= ("id","author","created_at","category")fields=read_only_fields+ ("title","body","category_id")

Here is an explanation:

  • Define a newCategorySerializer to get a nested representation
  • Add a write-onlycategory_id field, to pass the primary key of the category only in the request
  • Add a read-onlycategory field, to be used only on the response and return the whole serialized category, nested under our post.

Conclusion

Hopefully that gives you some pointers around how to achieve what you need. You may have to mix and matches some of these approaches. One thing that helped a lot for me is when thewrite_only option clicked.

You must be logged in to vote
4 replies
@jon-nfc
Comment options

wow, that's an answer.@browniebroke I really appreciate the time you have taken to assist with my dilemma.

I was already leaning towardssolution 1.Solution 2 wont work due to already customizing the serializer "get" functions within the views and the same serializer is still used in a single CRUD.Solution 3 wont work due to the use of the "response" serializer using nested serializers, unlike the request. Functionallysolution 4 would work. Althoughsolution 4 creates issues withdrf_spectacular auto docs features.

For completeness I should have included some context, example serialized objects. The "View" serializers use nested serializers for related models whilst the "create/update" serializers are based off of the model. For completeness, Here's the create serialized object.

{"name":"string","device_type":0,"model_notes":"string","serial_number":"string","uuid":"string","is_global":true,"is_virtual":true,"device_model":0,"config":"string","organization":0}

and the response / view serialized object (ALL get requests and the serializer to use in the CRUD response)

{"id":0,"status_icon": {"additionalProp1":"string","additionalProp2":"string","additionalProp3":"string"  },"display_name":"string","name":"string","device_type": {"id":0,"display_name":"string","name":"string","url":"string"  },"model_notes":"string","serial_number":"string","uuid":"string","is_global":true,"is_virtual":true,"device_model": {"id":0,"display_name":"string","name":"string","url":"string"  },"config":"string","rendered_config":"string","inventorydate":"2025-01-25T06:44:36.557Z","context": {"additionalProp1":"string","additionalProp2":"string","additionalProp3":"string"  },"created":"2025-01-25T06:44:36.557Z","modified":"2025-01-25T06:44:36.557Z","organization": {"id":0,"display_name":"string","name":"string","url":"string"  },"_urls": {"additionalProp1":"string","additionalProp2":"string","additionalProp3":"string"  }}
@browniebroke
Comment options

Functionallysolution 4 would work. Althoughsolution 4 creates issues withdrf_spectacular auto docs features.

I thought you might mention that, have you tried to use theirextend_schema decorator? It accepts arequest andresponses to pass your serializers to it:

https://drf-spectacular.readthedocs.io/en/latest/customization.html#step-2-extend-schema

It's super flexible and can do almost anything

@jon-nfc
Comment options

Yeah am familiar with drf'sextend_schema. However using it, I'm not considering an option due to the additional work it creates.

@wdifruscio
Comment options

Was causally browsing GitHub and stumbled on this.

I want to say - I typically have my own workarounds which mirror what is posted, nevertheless I am very glad that this is written here to validate how I usually go about solutioning my APIs.

Appreciate the hard work on this project 🫡

Answer selected byjon-nfc
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Labels
None yet
3 participants
@jon-nfc@browniebroke@wdifruscio

[8]ページ先頭

©2009-2025 Movatter.jp