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

Fix Django-Ninja PUT/PATCH request with UploadedFile #397

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

deniswvieira
Copy link

@deniswvieira deniswvieira commented Mar 18, 2022

Request PUT or PATCH raises an error for endpoints that receives UploadedFile. This happens because:

  1. Since we receive a File within the request, the content-type can not be application/json anymore, forcing to be multipart/form-data (and the ParamModel to be _MultiPartBodyModel);
  2. Django does not handle the request like he does for POST requests, not loading the multipart/form-data from request body into request.POST and request.FILES.

This crashes in Django Ninja while processing the request (Operation.run()) for ParamModel _MultiPartBodyModel and FileModel since it tries to retrieve data from request.POST and request.FILES.

Possible solutions I reached:

  1. Add some logic into _MultiPartBodyModel and FileModel to load the request body data like the Django does for POST request (essencially, replicate Django logic)
  2. Ask "nicely" to Django load the data for us, making he think he is loading a POST request.

I've choose the second approach.

@guicarvalho
Copy link

@vitalik What do you think of this proposal?

ninja/operation.py Outdated Show resolved Hide resolved
Co-authored-by: Charlie Wilson <charlie.wilson4@gmail.com>
@baseplate-admin
Copy link
Contributor

Hi @vitalik,

Do you think we can merge this?

This is a requirement for some users ( eg : #613 )

@OtherBarry
Copy link
Contributor

@deniswvieira you'll need to write some tests and (likely) do some codestyle fixes before this is able to be merged. I'm happy to help out if needed.

@baseplate-admin
Copy link
Contributor

@vitalik, this pull request is better than middleware approach. Please merge this. ( i can port my tests if you want )

@@ -31,6 +31,28 @@
__all__ = ["Operation", "PathView", "ResponseObject"]


def load_request(request: HttpRequest):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def load_request(request: HttpRequest) -> None:

Comment on lines +47 to +54
try:
request.method = "POST"
request.META['REQUEST_METHOD'] = 'POST'
request._load_post_and_files()
request.META['REQUEST_METHOD'] = original_method
request.method = original_method
except Exception:
pass
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do this.. why not use contextlib.supress ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more than just a style fix too. request.META['REQUEST_METHOD'] and request.method should be returned to their original method even if the file loading method fails (silently).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with contextlib.supress(Exception):
    request.method = "POST"
    request.META['REQUEST_METHOD'] = 'POST'
    request._load_post_and_files()

request.META['REQUEST_METHOD'] = original_method
request.method = original_method

I was thinking something like this


request.META['REQUEST_METHOD'] and request.method should be returned to their original method even if the file loading method fails (silently).

I have a small question ( if you dont mind ).. Can this method actually fail ? If it does, don't you think its a problem on either django's or our side ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, your code suggestion is what I had in mind.
I'm not quite sure if it can actually fail, and maybe we shouldn't be catching and silently swallowing an exception anyway... Yeah, this just seems like a bad idea altogether unless @deniswvieira can say why we need to catch it (and with such a broad catch at that)

@baseplate-admin
Copy link
Contributor

@vitalik do you want anything added to this PR?

try:
request.method = "POST"
request.META['REQUEST_METHOD'] = 'POST'
request._load_post_and_files()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should just hit request.FILES rather than manually accessing an undocumented underscored method?

Suggested change
request._load_post_and_files()
request.FILES

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if i am wrong but this is probably due to how django handles FILES.

Quoting @deniswvieira,

Ask "nicely" to Django load the data for us, making he think he is loading a POST request.

The alternative is to,

Add some logic into _MultiPartBodyModel and FileModel to load the request body data like the Django does for POST request (essencially, replicate Django logic)

Which in turn adds churn

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@baseplate-admin request.FILES is a property of WSGIRequest in django that triggers _load_post_and_files if ._files doesn't exist. Which it doesn't, because it was deleted a few lines earlier in this PR ;)
https://github.com/django/django/blob/main/django/core/handlers/wsgi.py#L104-L108

@baseplate-admin
Copy link
Contributor

@vitalik, What's your take on this PR?

If i rebase this PR with some tests would you be willing to merge this?

@bigusef
Copy link

bigusef commented Jan 1, 2024

Hi @vitalik
First. thanks for creating this project! I recently merging my project from DRF to ninja. hope to you all the best.
regarding to this issue I facing it in my project and there are many issues about it. what are the plan here to fix this issue?

@yokotoka
Copy link

Hello! When you accept that patch? That problem with post/patch is annoying (

@pmdevita
Copy link

It looks like the user who originally wrote this PR is inactive now and has been for two years. I'd be willing to pick this up and add the suggested changes along with tests if that is alright. @vitalik do you have any input for this?

@baseplate-admin
Copy link
Contributor

Due to in-activeness of this pr, i have decided to create ninja_put_patch_file_upload_middleware

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

Successfully merging this pull request may close these issues.

None yet

9 participants