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

how to accept PATCH with form data #1416

Open
smjt2000 opened this issue Mar 4, 2025 · 6 comments
Open

how to accept PATCH with form data #1416

smjt2000 opened this issue Mar 4, 2025 · 6 comments

Comments

@smjt2000
Copy link

smjt2000 commented Mar 4, 2025

Hi
English is not my native language
I want to have an API to let the user update it's info with PATCH method.
here is the code:

class Error(Schema):
    data: None
    errors: List[str]


UPDATE_RESPONSE = {
    200: None,
    204: None,
    400: Error,
}


@router.patch('me', auth=JWTAuth(), response=UPDATE_RESPONSE, tags=['user'])
def update_user_info(request, payload: Form[schema.UserInSchema] = None, avatar: Optional[File[UploadedFile]] = None):
    user = request.user
    import pprint
    pprint.pprint(payload)
    if avatar:
        user.avatar = avatar

    data = {field: value for field, value in payload.dict().items()
            if value not in ("", None, 0)}

    for field, value in data.items():
        setattr(user, field, value)
    user.save()

    return 204, None

the issue is all fields of payload are empty and I still get 204 status code. and if I change it to @router.post without any other changes, it works properly and everything is good.
I tried using PatchDict instead of Form but I don't think the request will be form data.

I want to know how I can Implement this endpoint with PATCH method and receiving form data.

@smjt2000 smjt2000 changed the title how to access PATCH with form data how to accept PATCH with form data Mar 4, 2025
@wkwkhautbois
Copy link

I hope #1059 is helpful for you.

@baseplate-admin
Copy link
Contributor

baseplate-admin commented Mar 4, 2025

This is a known issue.

How to resolve?

  1. Create a middleware that patches the PUT/PATCH files.
from collections.abc import Callable
from typing import Any

from asgiref.sync import iscoroutinefunction, sync_to_async
from django.http import HttpRequest
from django.utils.decorators import sync_and_async_middleware


@sync_and_async_middleware
def process_put_patch(get_response: Callable) -> Callable:
    async def async_middleware(request: HttpRequest) -> Any:
        if (
            request.method in ("PUT", "PATCH")
            and request.content_type != "application/json"
        ):
            initial_method = request.method
            request.method = "POST"
            request.META["REQUEST_METHOD"] = "POST"
            await sync_to_async(request._load_post_and_files)()
            request.META["REQUEST_METHOD"] = initial_method
            request.method = initial_method

        return await get_response(request)

    def sync_middleware(request: HttpRequest) -> Any:
        if (
            request.method in ("PUT", "PATCH")
            and request.content_type != "application/json"
        ):
            initial_method = request.method
            request.method = "POST"
            request.META["REQUEST_METHOD"] = "POST"
            request._load_post_and_files()
            request.META["REQUEST_METHOD"] = initial_method
            request.method = initial_method

        return get_response(request)

    return async_middleware if iscoroutinefunction(get_response) else sync_middleware
    
  1. Use a package like ninja-put-patch-file-upload-middleware ( maintained by me )

@smjt2000
Copy link
Author

smjt2000 commented Mar 5, 2025

@baseplate-admin
Now payload is not empty and doing its job, but avatar is now None!

@smjt2000
Copy link
Author

smjt2000 commented Mar 5, 2025

I solved this issue by accepting base64 for avatar.
but now for another endpoint I have an issue:

class UserDisease(Schema):
    name: str
    duration: str


class UserMedicalInSchema(Schema):
    gender: Optional[str] = None
    height: Optional[int] = 0
    weight: Optional[int] = 0
    marital: Optional[str] = None
    children: Optional[int] = 0
    pregnancies: Optional[int] = 0
    abortions: Optional[int] = 0
    births: Optional[int] = 0
    alcohol_use: Optional[bool] = None
    drug_use: Optional[bool] = None


@router.patch('medical', auth=JWTAuth(), response=UPDATE_RESPONSE, tags=['user'])
def update_user_medical_info(
    request,
    payload: Form[schema.UserMedicalInSchema] = None,
    diseases: Form[List[schema.UserDisease]] = None,
):
    user = request.user

    data = {field: value for field, value in payload.items()
            if value not in ("", None, 0)}

    for field, value in data.items():
        setattr(user, field, value)
    user.save()

    return 204, None

now if some of input data not provided I got errors like this

{
  "detail": [
    {
      "type": "int_parsing",
      "loc": [
        "form",
        "height"
      ],
      "msg": "Input should be a valid integer, unable to parse string as an integer"
    },
    {
      "type": "int_parsing",
      "loc": [
        "form",
        "weight"
      ],
      "msg": "Input should be a valid integer, unable to parse string as an integer"
    },
    {
      "type": "int_parsing",
      "loc": [
        "form",
        "children"
      ],
      "msg": "Input should be a valid integer, unable to parse string as an integer"
    },
    {
      "type": "int_parsing",
      "loc": [
        "form",
        "pregnancies"
      ],
      "msg": "Input should be a valid integer, unable to parse string as an integer"
    },
    {
      "type": "int_parsing",
      "loc": [
        "form",
        "abortions"
      ],
      "msg": "Input should be a valid integer, unable to parse string as an integer"
    },
    {
      "type": "int_parsing",
      "loc": [
        "form",
        "births"
      ],
      "msg": "Input should be a valid integer, unable to parse string as an integer"
    },
    {
      "type": "missing",
      "loc": [
        "form",
        "diseases",
        0,
        "name"
      ],
      "msg": "Field required"
    },
    {
      "type": "missing",
      "loc": [
        "form",
        "diseases",
        0,
        "duration"
      ],
      "msg": "Field required"
    }
  ]
}

@baseplate-admin
Copy link
Contributor

This sounds like that you are not converting String to Number in frontend before making the request.

According to

   {
      "type": "missing",
      "loc": [
        "form",
        "diseases",
        0,
        "name"
      ],
      "msg": "Field required"
    },
    {
      "type": "missing",
      "loc": [
        "form",
        "diseases",
        0,
        "duration"
      ],
      "msg": "Field required"
    }

These endpoints should be optional.

class UserDisease(Schema):
    name: str
    duration: str

@smjt2000
Copy link
Author

smjt2000 commented Mar 5, 2025

@baseplate-admin
I'm sending request from Ninja docs itself, not postman or any Javascript program or any other ways.
duration should be string, because it may be "1 year" or like this.

Here I don't want any data to be required, all of them should be optional and if one or all of them is null, 0 or empty string, it should be ignored without any errors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants