jazzband / django-tinymce

TinyMCE integration for Django
http://django-tinymce.readthedocs.org/
MIT License
1.29k stars 316 forks source link

drag and drop images doesn't work #337

Open gurugeek opened 3 years ago

gurugeek commented 3 years ago

Thanks for your package :)

Drag and Drop images using the paste plugin doesn't seem to work:


TINYMCE_DEFAULT_CONFIG = {
"menubar": "file edit view insert format tools table help",
  "plugins": "paste, code, link, codesample, fullscreen, insertdatetime, media, table, paste, code, help, wordcount, spellchecker",
  "block_unsupported_drop":"false",
  "images_upload_url": "zirkusupload",
  "images_upload_base_path": "/some/basepath",
   "toolbar": "undo redo styleselect fontselect bold italic alignleft aligncenter alignright bullist numlist outdent indent code link codesample paste", 
  "content_style":
   "@import url('https://use.typekit');body { font-family: calibri, sans-serif; font-size:22px; }",
   "paste_data_images": "true",
  "block_unsupported_drop": "false",

  }

returns "dropped file top not supported". It works fine in the plain JS version so I was wondering if this is disabled on Django ?

I would appreciate any assistance on how to use drag and drop images OR add images easily. Thanks!

mr-nazari commented 3 years ago

I have the same problem, please solve it

Natim commented 3 years ago

@mr-nazari feel free to provide a patch I will make the release.

Abednego97 commented 3 years ago

Can anyone help me with how to insert a local image?

StevenMapes commented 1 year ago

You need to write the JS uploader and then the Django view to handle the upload yourself. Here's what I did to achieve this where I use Django Storages as the backend but I use default_storage to write the file to the backend so everything should work for you with the exception of the full URL to the uploaded file.

Step 1. Ensure the the these two keys are set within the TINYMCE_DEFAULT_CONFIG

    "images_upload_url": "/tinymce/upload",
    "images_upload_handler": "tinymce_image_upload_handler"

The first is the path to which you would like the javascript to make the POST. The 2nd is the name of the javascript function to handle the upload

Step 2 Because of CSRF you need to be able to read the token. In order to do this I use the js-cookie lib mentioned within the Django docs

Add the below into your settings.py to tell it yo include two additional JS file. I'm using the jsdelivr CDN hosted version for the above package

TINYMCE_EXTRA_MEDIA = {
    'css': {
        'all': [
        ],
    },
    'js': [
        "https://cdn.jsdelivr.net/npm/js-cookie@3.0.1/dist/js.cookie.min.js",
        "admin/js/tinymce-upload.js",
    ], 
}

The second file is holds the custom JS file upload handler I will post below.

Step 3. Here are the contents of the tinymce-upload.js file I created. Note it uses cookie-js to read the value of the CSRF token and add it to the headers of the AJAX POST. I also hardset the URL path to post to as the same value as the one I add in the settings.py. You may be able to read it from somewhere but I haven't looked into that yet.

function tinymce_image_upload_handler (blobInfo, success, failure, progress) {
    let xhr, formData;
    xhr = new XMLHttpRequest();
    xhr.withCredentials = false;
    xhr.open('POST', '/tinymce/upload');
    xhr.setRequestHeader('X-CSRFToken', Cookies.get("csrftoken"));
    xhr.upload.onprogress = function (e) {
        progress(e.loaded / e.total * 100);
    };
    xhr.onload = function() {
        let json;

        if (xhr.status === 403) {
            failure('HTTP Error: ' + xhr.status, { remove: true });
            return;
        }

        if (xhr.status < 200 || xhr.status >= 300) {
            failure('HTTP Error: ' + xhr.status);
            return;
        }

        json = JSON.parse(xhr.responseText);

        if (!json || typeof json.location != 'string') {
            failure('Invalid JSON: ' + xhr.responseText);
            return;
        }

        success(json.location);
    };
    xhr.onerror = function () {
        failure('Image upload failed due to a XHR Transport error. Code: ' + xhr.status);
    };

    formData = new FormData();
    formData.append('file', blobInfo.blob(), blobInfo.filename());

    xhr.send(formData);
}

Step 4 Write the view to handle the upload. In my case I want to use a subfolder of media called tinymce with a uuid4 named folder to then hold the file in to avoid name clashes.

The key part is to return a JSONResponse with the location of the image you just uploaded.

In my case I am using Django Storages to store files on S3 but I serve them from AWS Cloudfront using a custom domain that I have set within settings.py so I append that to the start

def tinymce_upload(request):
    """Uplaod file to S3 and return the location"""
    file = request.FILES.get('file')
    filename = f"tinymce/{uuid4()}/{str(file)}"
    with default_storage.open(filename, "wb") as f:
        f.write(file.read())

    return JsonResponse({"location": f"https://{settings.AWS_S3_CUSTOM_DOMAIN}/media/{filename}"})

Now I can upload images using the TinvMCE plugin ensuring that CSRF checks are made