Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there an option to add encoding field to the requestBody? #714

Closed
marcintabaka opened this issue Apr 21, 2022 · 4 comments
Closed

Is there an option to add encoding field to the requestBody? #714

marcintabaka opened this issue Apr 21, 2022 · 4 comments
Labels
enhancement New feature or request fix confirmation pending issue has been fixed and confirmation from issue reporter is pending

Comments

@marcintabaka
Copy link

It will be easier to explain the problem on the example.

I have models with Many To Many relationship:

from django.db import models

class Tag(models.Model):
    name = models.CharField(max_length=255)

class Article(models.Model):
    title = models.CharField(max_length=100)
    tags = models.ManyToManyField(Tag, blank=True, related_name="articles")

And simple serializer with PrimaryKeyRelatedField (without read_only, so we can read and write m2m relation) :

from rest_framework import serializers

class ArticleSerializer(serializers.ModelSerializer):
    tags = serializers.PrimaryKeyRelatedField(many=True, queryset=Tag.objects.all())

    class Meta:
        model = Article
        fields = ['title', 'tags']

Then simple ViewSet:

from rest_framework import viewsets, permissions

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all().order_by("id")
    serializer_class = ArticleSerializer
    permission_classes = [permissions.AllowAny]

And Router:

from rest_framework import routers

from projects import views

router = routers.SimpleRouter()
router.register(r"articles", views.ArticleViewSet)

Let's assume that I have an Article with two tags:

curl -X 'GET' \
  'http://localhost:8000/api/articles/1/' \
  -H 'accept: application/json'

Response body:

{
  "title": "Article 1",
  "tags": [
    1,
    2
  ]
}

Now I would like to add third tag to the article, so I would use PATCH. I use Swagger UI with Content-Type set to application/json, and get something like this

curl -X 'PATCH' \
  'http://localhost:8000/api/articles/1/' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'X-CSRFTOKEN: R0LLamjP2tLSjbFJzHtPK3PCRMfajzrIe75eM46h4arf8DxyIMlk4KyuHRpKrafn' \
  -d '{
  "tags": [
    1,2,3
  ]
}'

Response Body:

{
  "title": "Article 1",
  "tags": [
    1,
    2,
    3
  ]
}

Everything works fine so far.

There is a problem, when I would like to use application/x-www-form-urlencoded or multipart/form-data content type in Swagger UI:
image

The form looks ok, but generated Curl Request looks like this:

curl -X 'PATCH' \
  'http://localhost:8000/api/articles/1/' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'X-CSRFTOKEN: R0LLamjP2tLSjbFJzHtPK3PCRMfajzrIe75eM46h4arf8DxyIMlk4KyuHRpKrafn' \
  -d 'tags=1,2'

Django Rest Framework doesn't accept parameters in that form tags=1,2, so the Response is:

{
  "statusCode": 400,
  "errorCode": "invalid",
  "details": {
    "tags": [
      "Incorrect type. Expected pk value, received str."
    ]
  }
}

Swagger UI should generate request like this:

curl -X 'PATCH' \
  'http://localhost:8000/api/articles/1/' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'X-CSRFTOKEN: R0LLamjP2tLSjbFJzHtPK3PCRMfajzrIe75eM46h4arf8DxyIMlk4KyuHRpKrafn' \
  -d 'tags=1&tags=2'

Then it will be accepted by the API.

To achieve this (generating such format of request by Swagger UI), we need to add extra field encoding to OpenAPI schema: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#encoding-object

Example of currently generated OpenAPI file:

openapi: 3.0.3
info:
  title: POSM Evaluator
  version: 1.0.0
  description: Manage POSM projects
paths:
  ...
  /api/articles/{id}/:
    patch:
      operationId: articlesPartialUpdate
      parameters:
      - in: path
        name: id
        schema:
          type: integer
        description: A unique integer value identifying this article.
        required: true
      tags:
      - articles
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/PatchedArticleRequest'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/PatchedArticleRequest'
          application/json:
            schema:
              $ref: '#/components/schemas/PatchedArticleRequest'
      security:
      - cookieAuth: []
      - basicAuth: []
      - {}
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Article'
          description: ''
   ...
components:
  schemas:
    ...
    PatchedArticleRequest:
      type: object
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 100
        tags:
          type: array
          items:
            type: integer
    ...

Example of expected OpenAPI file:

openapi: 3.0.3
info:
  title: POSM Evaluator
  version: 1.0.0
  description: Manage POSM projects
paths:
  ...
  /api/articles/{id}/:
    patch:
      operationId: articlesPartialUpdate
      parameters:
      - in: path
        name: id
        schema:
          type: integer
        description: A unique integer value identifying this article.
        required: true
      tags:
      - articles
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/PatchedArticleRequest'
            encoding:
              tags:
                style: form
                explode: true
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/PatchedArticleRequest'
            encoding:
              tags:
                style: form
                explode: true
          application/json:
            schema:
              $ref: '#/components/schemas/PatchedArticleRequest'
      security:
      - cookieAuth: []
      - basicAuth: []
      - {}
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Article'
          description: ''
   ...
components:
  schemas:
    ...
    PatchedArticleRequest:
      type: object
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 100
        tags:
          type: array
          items:
            type: integer
    ...

The part:

            encoding:
              tags:
                style: form
                explode: true

Is the key.

Do you know if it is possible to add this part using drf-spectacular?

@tfranzel
Copy link
Owner

Hi @marcintabaka, so the short answer is no. It is currently not possible as we do not expose the encoding object and give only limited access to the media type object.

When I read the spec correctly, it can occur in both request and response. The problem is that we kind of compress down these objects to make OpenAPI more accessible. The added convenience limits the flexibility though. We do have something called OpenApiResponse, where encoding could potentially be inserted, but we have no such thing yet for the request.

Your use-case is absolutely valid, but at this point the question is whether this functionality warrants making the interface more complicated. I'm undecided because spectacular has been used a lot by a lot of people and no one ever asked about the encoding object.

@marcintabaka
Copy link
Author

@tfranzel thank you for your quick reply. What do you think about adding some global setting flag, to change Swagger UI behavior in this regard?

Simply if someone set this flag to True, then for every array type field, drf-spectacular will add this part to schema:

            encoding:
              {FIELD_NAME}:
                style: form
                explode: true

@tfranzel
Copy link
Owner

I don't think a global setting is the right way. It is too coarse and also since it's bound to the field name, unwanted collisions might occur. It would be impossible to use this only selectively.

@kingwill101
Copy link

I'm also interested in getting access to the encoding property. My use is case is being able to specify particular image types

'contentType': ["image/png", "image/jpeg"]

 request={
            'multipart/form-data': {
                'type': 'object',
                'properties': {
                    
                    'images': {
                        'description': "List of images",
                        'type': 'array',
                        "items": {
                            "type": "string",
                            "format": "binary",
                        }

                    }
                },
            }

tfranzel added a commit that referenced this issue Mar 26, 2023
Add OpenApiRequest for encoding options #714 #965
@tfranzel tfranzel added enhancement New feature or request fix confirmation pending issue has been fixed and confirmation from issue reporter is pending labels Mar 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request fix confirmation pending issue has been fixed and confirmation from issue reporter is pending
Projects
None yet
Development

No branches or pull requests

3 participants