为什么在 Django 中使用 ajax 调用时不提交图像?

Why images are not submitted when using ajax call in django?

我正在使用 modelForm 通过 ajax 创建 post 个对象。图片字段是表单的一部分,但不会传递到 Meta class 的字段,因为这样可以先保存 post 并添加之后上传的图片。我的问题是,如果我确实使用常规视图(没有 ajax),request.FILES 会被正确提交,但是当我通过 ajax 使用时,这些文件不是 request.files 的一部分呈现空 <MultiValueDict: {}> 我真的不知道为什么。 这是我的代码。

    def post(self, request, *args, **kwargs):
    form = PostForm(request.POST or None, request.FILES or None)
    result = {}
    files = request.FILES
    print(files)
    if is_ajax(request=request) and form.is_valid():
        print("the request is ajax and the form is valid")
        title = form.cleaned_data.get("content", "")
        print("Title ", title)
        post_instance = form.save(commit=False)
        post_instance.author = request.user
        result['success'] = True
        return JsonResponse(result)

         $.ajax({
        url: $("#CreatePostModal").attr("data-url"),
        data:$("#CreatePostModal #createPostForm").serialize(),
        method: "post",
        dataType: "json",
        success: (data) => {
            if (data.success) {
                setTimeout(() => {
                    $(e.target).next().fadeOut();
                    ResetForm('createPostForm', 'PreviewImagesContainer')
                    $("#CreatePostModal").modal('hide')
                    $(e.target.nextElementSibling).fadeOut()
                    alertUser("Post", "has been created successfully!")// alerting the user 
                }, 1000)
                console.log(data.title)
            } else {
                $("#createPostForm").replaceWith(data.formErrors);
                $("#PreviewImagesContainer").html("");
                $("#CreatePostModal").find("form").attr("id", "createPostForm");
                $(e.target.nextElementSibling).fadeOut()
            };

            $(e.target).prop("disabled", false);
        },
        error: (error) => {
            console.log(error)
        }
    })
});

这是表格文件

class PostForm(forms.ModelForm):
images = forms.ImageField(
    required=False,
    widget=forms.ClearableFileInput(attrs={
        'multiple': True,
    })
)

class Meta:
    model = Post
    fields = ("content",)
    widgets = {
        "content": forms.Textarea(attrs={"placeholder": "Tell us something today....", "rows": 5, "label": ""})
    }

同样,图像域与 post 模型存在多对多关系。 我做错了什么?

这是模态,我用脆皮形式渲染表单本身

        <!-- create post modal -->
    <div class="modal fade" id="CreatePostModal" data-url="{% url 'post-list-view' %}" tabindex="-1">
        <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Creating Post</h5>
                    <button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
                        &times;
                    </button> 
            </div>       
            <div class="modal-body">
                <div class="container-fluid">
                   {% crispy form %}
                   <div id="PreviewImagesContainer">
                       
                   </div>
                </div>
            </div>
            <div class="modal-footer float-right">
                <button type="button" class="btn btn-sm btn-dark" data-bs-dismiss="modal">Cancel</button>
                <button type="submit" form="createPostForm" id="createPostBtn" class="btn btn-sm btn-primary">Post</button>
                <span class="loading-icon"><i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
            </div>
            
        </div>
        </div>
    </div>
    <!-- end of create post modal -->

我终于使用 javascript 的 FormData 解决了这个问题,方法是循环遍历我从输入中获取的文件,然后将其附加到 FormData 的数据中。

    let imageFiles = []
$("#CreatePostModal").on('change', (e) => {
    $(postImagesPreviewContainer).html("")
    if ($(e.target).attr("id") !== "id_images") return;
    var filenames = "";
    for (let i = 0; i < e.target.files.length; i++) {
        filenames += (i > 0 ? ", " : "") + e.target.files[i].name;
    }
    e.target.parentNode.querySelector('.custom-file-label').textContent = filenames;

    //why is the this element returning the document and not the target itself
    // check the length of the files to know what template to make
    const files = e.target.files
    const numberOfImages = files.length
    let gridColumnSize;
    if (numberOfImages > 5 | numberOfImages === 0) return;
    var row = document.createElement("div")
    row.setAttribute("class", "post-images")

    for (file of files) {
        
        const postImageChild = document.createElement("div");
        postImageChild.setAttribute("class", "post-images__child_down")
        const reader = new FileReader();
        reader.onload = () => {
            img = document.createElement("img")
            img.setAttribute("src", reader.result)

            img.onload = (e) => {
                // here i will process on resizing the image
                const canvas = document.createElement("canvas")
                const max_width = 680
                const scaleSize = max_width / e.target.width
                canvas.width = max_width
                canvas.height = e.target.height * scaleSize
                var ctx = canvas.getContext("2d") // setting the context of the canvas
                ctx.drawImage(e.target, 0, 0, canvas.width, canvas.height)
                const encodedSource = ctx.canvas.toDataURL(e.target, 'image/png', 1)
                const processedImg = document.createElement("img") // create a processed image and return it.
                processedImg.src = encodedSource
                $(postImageChild).append(processedImg)
                imageFiles.push(processedImg)
            }
        }
        $(row).prepend(postImageChild)
        $(postImagesPreviewContainer).append(row);
        reader.readAsDataURL(file)
    }

获取所有图像并调整大小后,我进行了 ajax 调用:

    $("#CreatePostModal").on("click", (e) => {
    if ($(e.target).attr("id") !== "createPostBtn") return;
    e.preventDefault();
    e.target.setAttribute("disabled", true);
    $(e.target.nextElementSibling).fadeIn()
    var form = $("#createPostForm")[0]
    var data = new FormData(form); // getting the form data
    console.log("this is the data", data)
    for (var i = 0; i < imageFiles.length; i++) { // appending images to data
        data.append('images', imageFiles[i]);
    };

    $.ajax({
        url: $("#CreatePostModal").attr("data-url"),
        data: data, //$("#CreatePostModal #createPostForm").serialize(),
        method: "post",
        processData: false,
        cache: false,
        contentType: false,
        dataType: "json",
        success: (data) => {
            if (data.success) {
                setTimeout(() => {
                    $(e.target).next().fadeOut();
                    ResetForm('createPostForm', 'PreviewImagesContainer')
                    $("#CreatePostModal").modal('hide')
                    $(e.target.nextElementSibling).fadeOut()
                    alertUser("Post", "has been created successfully!")// alerting the user 
                }, 1000)

            } else {
                $("#createPostForm").replaceWith(data.formErrors);
                $("#PreviewImagesContainer").html("");
                $("#CreatePostModal").find("form").attr("id", "createPostForm");
                $(e.target.nextElementSibling).fadeOut()
            };

            $(e.target).prop("disabled", false);
        },
        error: (error) => {
            console.log(error)
        }
    })
});

并最终在 views.py 文件中从 request.FILES 获取图像。

    def post(self, request, *args, **kwargs):
    form = PostForm(request.POST or None, request.FILES or None)
    result = {}
    files = request.FILES.getlist("images")
    if is_ajax(request=request) and form.is_valid():
        post_obj = form.save(commit=False)
        post_obj.author = request.user
        print(post_obj)
        post_obj.save()
        for file in files:
            new_file = Files(image=file)
            new_file.save()
            post_obj.images.add(new_file)
        post_obj.save()

        result['success'] = True

        return JsonResponse(result)