Closed aprilahijriyan closed 3 years ago
You are using File
(eg: FileParam
) for other than files:
https://django-ninja.rest-framework.com/tutorial/file-params/
So, how do I get the name and the folder together when the file is uploaded?
I've tried to combine the Form class, but get an error as follows.
Traceback (most recent call last):
File "/home/titiw/.cache/pypoetry/virtualenvs/simpanfile-lylPHfKd-py3.8/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "/home/titiw/.cache/pypoetry/virtualenvs/simpanfile-lylPHfKd-py3.8/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/titiw/.cache/pypoetry/virtualenvs/simpanfile-lylPHfKd-py3.8/lib/python3.8/site-packages/ninja/openapi/views.py", line 25, in openapi_json
schema = api.get_openapi_schema()
File "/home/titiw/.cache/pypoetry/virtualenvs/simpanfile-lylPHfKd-py3.8/lib/python3.8/site-packages/ninja/main.py", line 349, in get_openapi_schema
return get_schema(api=self, path_prefix=path_prefix)
File "/home/titiw/.cache/pypoetry/virtualenvs/simpanfile-lylPHfKd-py3.8/lib/python3.8/site-packages/ninja/openapi/schema.py", line 22, in get_schema
openapi = OpenAPISchema(api, path_prefix)
File "/home/titiw/.cache/pypoetry/virtualenvs/simpanfile-lylPHfKd-py3.8/lib/python3.8/site-packages/ninja/openapi/schema.py", line 44, in __init__
("paths", self.get_paths()),
File "/home/titiw/.cache/pypoetry/virtualenvs/simpanfile-lylPHfKd-py3.8/lib/python3.8/site-packages/ninja/openapi/schema.py", line 56, in get_paths
path_methods = self.methods(path_view.operations)
File "/home/titiw/.cache/pypoetry/virtualenvs/simpanfile-lylPHfKd-py3.8/lib/python3.8/site-packages/ninja/openapi/schema.py", line 67, in methods
result[method.lower()] = self.operation_details(op)
File "/home/titiw/.cache/pypoetry/virtualenvs/simpanfile-lylPHfKd-py3.8/lib/python3.8/site-packages/ninja/openapi/schema.py", line 93, in operation_details
body = self.request_body(operation)
File "/home/titiw/.cache/pypoetry/virtualenvs/simpanfile-lylPHfKd-py3.8/lib/python3.8/site-packages/ninja/openapi/schema.py", line 149, in request_body
assert len(models) == 1
AssertionError
Yes, the reference above does work. But can django-ninja do exactly the same as the above reference?
I'm facing this issue too: any combination of UploadedFile with other params gives me "assert len(models) == 1" error.
@Monstrofil, I believe the assert len(models) == 1
is the same issue as in #162.
The first question in this issue needed List[UploadedFile]
and is covered here:
https://django-ninja.rest-framework.com/tutorial/file-params/
from typing import List
from ninja import NinjaAPI, File
from ninja.files import UploadedFile
@api.post("/upload-many")
def upload_many(request, files: List[UploadedFile] = File(...)):
return [f.name for f in files]
@Monstrofil @aprilahijriyan please check version 0.15
@vitalik yep, works now, thanks.
Hi @vitalik, does it support defining form data using pydantic Schema?
class BodySupplierSchema(Schema):
business_id: UUID
image: Optional[UploadedFile] = None
name: constr(max_length=255)
description: Optional[str] = None
mobile_phone: Optional[str] = None
address_details: Optional[str] = None
def create_supplier(request: HttpRequest, body: BodySupplierSchema = Form(...)):
data = body.dict()
print(data)
return {"detail": "success"}
I still get the above error
@aprilahijriyan No, currently you have to pass files as arguments
class BodySupplierSchema(Schema):
business_id: UUID
name: constr(max_length=255)
description: Optional[str] = None
mobile_phone: Optional[str] = None
address_details: Optional[str] = None
def create_supplier(request: HttpRequest, body: BodySupplierSchema = Form(...), image: Optional[UploadedFile] = File(None)):
...
ok, the above code works fine! thank you.
@aprilahijriyan No, currently you have to pass files as arguments
class BodySupplierSchema(Schema): business_id: UUID name: constr(max_length=255) description: Optional[str] = None mobile_phone: Optional[str] = None address_details: Optional[str] = None def create_supplier(request: HttpRequest, body: BodySupplierSchema = Form(...), image: Optional[UploadedFile] = File(None)): ...
Hello @vitalik, i have the same schema and args structure
def submit_vacancy_api(
request,
cv: UploadedFile,
payload: schemas.SubmitVacancySchema = Form(...),
vacancy: schemas.VacancyIdPathSchema = Path(...)
):
class SubmitVacancySchema(Schema):
message: Optional[str]
and now i have i question!
How could i test file uploading? I've tried this method
data = {
"message": "Test vacancy submition request message",
"cv": (io.BytesIO(b"test_bytes"), 'test_cv.pdf')
}
response = client.post(
f"{self._endpoint}/{job.pk}/submit",
data=data,
content_type='multipart/form-data'
)
But i got error
b'{"detail": [{"loc": ["file", "cv"], "msg": "field required", "type": "value_error.missing"}]}'
also i tried
with tempfile.TemporaryFile(suffix=".pdf") as tmp:
data = submit_vacancy_data()
response = client.post(
f"{self._endpoint}/{job.pk}/submit",
data=data,
files={'cv': ('test', tmp, 'application/pdf')},
)
and got the same problem. How to properly pass a file inside?
@LazavikouArtsiom
something like this:
https://github.com/vitalik/django-ninja/blob/master/tests/test_files.py#L55-L56
from django.core.files.uploadedfile import SimpleUploadedFile
file = SimpleUploadedFile("test.txt", b"data345")
response = client.post("/file2", FILES={"file": file})
@LazavikouArtsiom
something like this:
https://github.com/vitalik/django-ninja/blob/master/tests/test_files.py#L55-L56
from django.core.files.uploadedfile import SimpleUploadedFile file = SimpleUploadedFile("test.txt", b"data345") response = client.post("/file2", FILES={"file": file})
Tnx for answer, will try!
@LazavikouArtsiom
something like this:
https://github.com/vitalik/django-ninja/blob/master/tests/test_files.py#L55-L56
from django.core.files.uploadedfile import SimpleUploadedFile file = SimpleUploadedFile("test.txt", b"data345") response = client.post("/file2", FILES={"file": file})
It works, дзякуй
In my case, I had to read the file in binary mode :
@cookielogin
def assert_upload(self, url, file_path, expected_code, content_type="multipart/media-type"):
with open(file_path, 'rb') as fp:
binary_content = fp.read()
simple_uploaded_file = SimpleUploadedFile(file_path, binary_content, content_type=content_type)
response = self.client.post(url, {'file': simple_uploaded_file})
self.raise_if_invalid_http_code(response, expected_code)
return response.json()
class Movie(models.Model): class Status_Choices(models.IntegerChoices): COMING_UP = 1 STARTING = 2 RUNNING = 3 FINISHED = 4 name = models.CharField(max_length=200, null=True) protagonists = models.CharField(max_length=200, null=True) poster = models.ImageField(upload_to ='posters/', null=True) trailer = models.FileField(upload_to ='trailers/', null=True) start_date = models.DateTimeField(auto_now=False, auto_now_add=False, null=True) status = models.IntegerField(choices=Status_Choices.choices, default=Status_Choices.COMING_UP) ranking = models.IntegerField(validators=[MinValueValidator(0, message="Cannot have ranking below 0")], default=0) created_at = models.DateTimeField(auto_now_add=True) modified_at = models.DateTimeField(auto_now=True) class Meta: verbose_name = "movie" verbose_name_plural = "movies" def __str__(self): return self.name class MovieIn(ModelSchema): class Meta: model = Movie exclude = ["id", "created_at", "modified_at"] fields_optional = "__all__" class MovieOut(ModelSchema): class Meta: model = Movie fields = "__all__" fields_optional = "__all__" class Message(Schema): message: str router = Router() @router.post("/", response=MovieOut) def create_movie( request, payload: MovieIn, poster_file: UploadedFile = None, trailer_file: UploadedFile = None, ): payload_dict = payload.dict() movie = Movie(**payload_dict) if not poster_file or trailer_file: movie.save() if poster_file: movie.poster.save(movie.name, poster_file) # will save model instance as well if trailer_file: movie.trailer.save(movie.name, trailer_file) # will save model instance as well return movie @router.put("/{movie_id}", response=MovieOut) def update_movie( request, movie_id: int, payload: MovieIn, poster_file: UploadedFile = None, trailer_file: UploadedFile = None, ): print(movie_id) print(payload) print(poster_file) print(trailer_file) movie = get_object_or_404(Movie, id=movie_id) for attr, value in payload.dict(exclude_unset=True).items(): setattr(movie, attr, value) movie.save() if not poster_file or trailer_file: movie.save() if poster_file: movie.poster.save(movie.name, poster_file) # will save model instance as well if trailer_file: movie.trailer.save(movie.name, trailer_file) # will save model instance as well return movie
The POST method works fine whether you send a file or not when trying out the api in the swagger interactive docs. But it fails when it is a PUT method. This is the output I get in my terminal:
Unprocessable Entity: /api/cinema/2 [23/Mar/2024 12:27:48] "PUT /api/cinema/2 HTTP/1.1" 422 86
{ "detail": [ { "type": "missing", "loc": [ "body", "payload" ], "msg": "Field required" } ] }
the curl:
curl -X 'PUT' \ 'http://127.0.0.1:8000/api/cinema/2' \ -H 'accept: application/json' \ -H 'Content-Type: multipart/form-data' \ -F 'poster_file=@GID_1127-DeNoiseAI-standard.jpg;type=image/jpeg' \ -F 'trailer_file=@IMG-20230101-WA0129 (1).jpg;type=image/jpeg' \ -F 'payload={ "name": "3535353" }'
Any help will be appreciated. Perhaps I am using the ninja wrong?
EDIT: I updated my PUT to this:
@router.put("/{movie_id}", response=MovieOut) def update_movie( request, movie_id: int, payload: Form[MovieIn], poster_file: Optional[UploadedFile] = File(None), trailer_file: Optional[UploadedFile] = File(None), ): print(movie_id) print(payload) print(poster_file) print(trailer_file) movie = get_object_or_404(Movie, id=movie_id) for attr, value in payload.dict(exclude_unset=True).items(): setattr(movie, attr, value) if not poster_file or trailer_file: movie.save() if poster_file: movie.poster.save(movie.name, poster_file) # will save model instance as well if trailer_file: movie.trailer.save(movie.name, trailer_file) # will save model instance as well return movie
in my terminal i get this:
2 name=None protagonists=None poster=None trailer=None start_date=None status=None ranking=None None None
the payload is always empty even though there is data sent from the client.
Any help would be very much appreciated
The POST method works fine whether you send a file or not when trying out the api in the swagger interactive docs. But it fails when it is a PUT method. This is the output I get in my terminal:
Same problem here Any idea why?
@LexxLuey Found this. I guess PUT does not support multipart..? See Roy T. Fielding's comment here
@LexxLuey Found this. I guess PUT does not support multipart..? See Roy T. Fielding's comment here
Interesting. If this is the case then it should be a matter of updating the docs appropriately to let users know how to make an update request in ninja containing files and data.
Hi @vitalik, I'm trying to upload a file but it's always getting a "field required" message.
Handler:
Has anything been missed?