vitalik / django-ninja

💨 Fast, Async-ready, Openapi, type hints based framework for building APIs
https://django-ninja.dev
MIT License
7.12k stars 423 forks source link

Inferr Automatic Multipart Data #525

Open martinlombana opened 2 years ago

martinlombana commented 2 years ago

As per your documentations, when uploading files, I can see that you recommend, for uploading files, sending multipart data. Which I have always found hacky (in general in django). Because, given this model and the rest of the code... In order to do POST requests for creation, we need to separate, as per your docs, the sending of the data in a multipart way and separate in a different Schema, the normal fields (not files) OR, mark the files as null=True, in the model, so that we do not need them. But, Is there a way to avoid code repetition and multiple scattered Schemas... in order to do this?

I would like to avoid having to "hack" or compose different POST routes for every single case. Also, I do not want to send my images in JSON format in Base64, because for large files, it is overkill and chunked it is always way better.

This, works (posting here for other's reference). But I want a more elegant way. The more automatic things, the better. So I am trying to wrap my head around on how to avoid having to affect the images on the save, after the upload with multipart, and automate that part. If you have any idea, I am welcome. I am just trying to reduce my code. This is just a very simple example, but for many fields and models, it becomes tiring and code consuming to save each ImageField manually. Also for the "sending part", I guess there is no other method but multipart, and encode it like I did in 2 separate entities (data, being an Stringified json object, for all the fields that are not files, and image, for the image.


class MediaIcon(models.Model):
    name = models.CharField(max_length=255)
    image = models.ImageField(null=True)

class MediaIconSchema(ModelSchema):
    class Config:
        model = MediaIcon
        model_fields = "__all__"

@api.post("/media-icon", response=MediaIconSchema)
def create_media_icon(request, data: MediaIconSchema, image: UploadedFile = None):
    obj = MediaIcon.objects.create(**data.dict())
    obj.image = image
    obj.save()
    return obj

AND

<div class="container">
            <h1>Multipart File Upload</h1>
            <form id="form" enctype="multipart/form-data">
                <div class="input-group">
                    <label for="files">Select files</label>
                    <!--<input id="file" type="file" multiple/>-->
                    <input id="file" type="file"/>
                </div>
                <button class="submit-btn" type="submit">Upload</button>
            </form>
        </div>
        <script>
            const form = document.getElementById("form");
            const inputFile = document.getElementById("file");

            const formData = new FormData();

            const handleSubmit = (event) => {
                event.preventDefault();
                console.log(`inputFile-> `, inputFile.files[0]);
                const image = inputFile.files[0]
                const data = JSON.stringify({
                    name: image.name,
                })

                formData.append("data", data);
                formData.append("image", image);

                fetch("http://127.0.0.1:8000/api/media-icon", {
                    method: "post",
                    body: formData,
                }).catch((error) => ("Something went wrong!", error));
            };

            form.addEventListener("submit", handleSubmit);

        </script>
alirezapla commented 2 years ago

why do not defining a manager for your specific query or even this:

obj=MediaIcon(image=image,**data.dict())
obj.save()