From 1f53260f8e98b974f9a5433fdaf56e9e4b226c4c Mon Sep 17 00:00:00 2001 From: Ndibe Raymond Olisaemeka Date: Sun, 22 Oct 2023 18:42:44 +0100 Subject: [PATCH] refactor summernote upload --- zubhub_backend/.gitignore | 1 + zubhub_backend/zubhub/static/js/main.js | 154 ------------------------ zubhub_backend/zubhub/zubhub/admin.py | 22 +--- zubhub_backend/zubhub/zubhub/models.py | 28 +++-- zubhub_backend/zubhub/zubhub/urls.py | 3 +- zubhub_backend/zubhub/zubhub/utils.py | 25 ++++ 6 files changed, 49 insertions(+), 184 deletions(-) delete mode 100644 zubhub_backend/zubhub/static/js/main.js diff --git a/zubhub_backend/.gitignore b/zubhub_backend/.gitignore index 944b09061..3b2ea803b 100644 --- a/zubhub_backend/.gitignore +++ b/zubhub_backend/.gitignore @@ -1,4 +1,5 @@ media_store/ +media/ # migrations/ __pycache__/ *.pyc diff --git a/zubhub_backend/zubhub/static/js/main.js b/zubhub_backend/zubhub/static/js/main.js deleted file mode 100644 index 7bb84d8cf..000000000 --- a/zubhub_backend/zubhub/static/js/main.js +++ /dev/null @@ -1,154 +0,0 @@ - -(function main(){ - - function summerNote(){ - - function sendFile(file, iframe_id){ - data = new FormData(); - data.append("file",file); - data.append("folder","zubhub"); - $.ajax({ - url:`${document.location.origin}/api/upload-file/`, - data: data, - cache: false, - contentType: false, - processData: false, - type: "POST", - headers: { - "X-CSRFToken": getCookie('csrftoken') - }, - success: function(data){ - const imgNode = document.createElement("img"); - const iframe = $(`iframe#${iframe_id}_iframe`); - let public_id = data.image_url.split("/"); - public_id = public_id[public_id.length - 1] - imgNode.setAttribute('id',public_id); - imgNode.setAttribute('src', data.image_url); - imgNode.setAttribute("style","max-width:100%;height:auto") - $(".note-editable", iframe.contents()).append(imgNode); - }, - error: function(jqXHR, textStatus, errorThrown){ - alert(textStatus+" "+errorThrown) - } - }) - }; - - let mObserver = ""; - - const mutationOptions = { - childList: true, - subtree: true, - }; - - const mCallback = (mutations, iframe_id) =>{ - for(let index in mutations){ - if(mutations[index].removedNodes.length > 0 - && mutations[index].removedNodes[0].src !== null - && mutations[index].removedNodes[0].src !== undefined){ - makeDeleteRequest(mutations[index].removedNodes[0].src, iframe_id) - .then(res=>{ - if(res["result"] !== "ok"){ - alert(res["result"]) - } - }) - } - } - }; - - const t0 = performance.now(); - - const overRideOnImageUpload = setInterval(()=>{ - if(!isSummernoteInitialized(getIframeIDs()).includes(false)){ - - let observer; - const iframe_id = getIframeIDs(); - - for(let i=0; i < iframe_id.length; i++){ - window[`settings_${iframe_id[i]}`].callbacks.onImageUpload = function(files){ - sendFile(files[0], iframe_id[i]) - }; - - const iframe = document.querySelector(`iframe#${iframe_id[i]}_iframe`); - - const iframe_inner_doc = iframe.contentDocument || iframe.contentWindow.document; - - const target_elem = iframe_inner_doc.querySelector(".note-editable"); - observer = new MutationObserver((mutations)=>mCallback(mutations, iframe_id[i])); - observer.observe(target_elem, mutationOptions); - - } - - clear(); - } - if((performance.now() - t0) > 10000){ - clear() - } - },1000); - - function clear(){ - clearInterval(overRideOnImageUpload) - } - } - - summerNote(); - - }()) - - - function getIframeIDs(){ - const iframes = document.querySelectorAll("iframe"); - if (!iframes.length) return [] - const id_arr = []; - for(let i=0; i < iframes.length; i++){ - id_arr.push(iframes[i].id.split("_iframe")[0]) - } - return id_arr; - } - - function isSummernoteInitialized(iframe_ids){ - if (!iframe_ids.length) return [false]; - return iframe_ids.map(iframe_id=> window[`settings_${iframe_id}`] - && window[`settings_${iframe_id}`].callbacks - && window[`settings_${iframe_id}`].callbacks.onImageUpload) - } - - function getCookie(name) { - var cookieValue = null; - if (document.cookie && document.cookie !== '') { - var cookies = document.cookie.split(';'); - for (var i = 0; i < cookies.length; i++) { - var cookie = cookies[i].trim(); - // Does this cookie string begin with the name we want? - if (cookie.substring(0, name.length + 1) === (name + '=')) { - cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); - break; - } - } - } - return cookieValue; - } - - function makeDeleteRequest(url, iframe_id){ - let public_id = url.split("/"); - public_id = public_id[public_id.length - 1] - - if(url){ - - const iframe = document.querySelector(`iframe#${iframe_id}_iframe`); - const iframe_inner_doc = iframe.contentDocument || iframe.contentWindow.document; - if(!iframe_inner_doc.querySelector(`#${public_id}`)){ - - url = `${document.location.origin}/api/delete-file/`; - data = new FormData(); - data.append("url", url); - - return fetch(url, - {method: 'POST', - headers: new Headers({ - "X-CSRFToken": getCookie('csrftoken'), - }), - body: data} - ).then(res=>res.json()).then(result=>result) - } - } - } \ No newline at end of file diff --git a/zubhub_backend/zubhub/zubhub/admin.py b/zubhub_backend/zubhub/zubhub/admin.py index 3c1ef9bf3..3fe4f6dce 100644 --- a/zubhub_backend/zubhub/zubhub/admin.py +++ b/zubhub_backend/zubhub/zubhub/admin.py @@ -28,25 +28,14 @@ class PrivacyAdmin(SummernoteModelAdmin): summernote_fields = ('privacy_policy', 'terms_of_use',) readonly_fields = ["edited_on"] - class Media: - js = ('https://code.jquery.com/jquery-3.1.1.js', 'js/main.js',) - - class HelpAdmin(SummernoteModelAdmin): summernote_fields = ('about',) readonly_fields = ["edited_on"] - class Media: - js = ('https://code.jquery.com/jquery-3.1.1.js', 'js/main.js',) - class ChallengeAdmin(SummernoteModelAdmin): summernote_fields = ('challenge',) readonly_fields = ["edited_on"] - class Media: - js = ('https://code.jquery.com/jquery-3.1.1.js', 'js/main.js',) - - class StatusEnum(Enum): INACTIVE = 0 ACTIVE = 1 @@ -63,23 +52,14 @@ def make_selected_active(self, request, queryset): make_selected_active.short_description = "Select and make active" - class FAQAdmin(SummernoteModelAdmin): summernote_fields = ('answer',) - class Media: - js = ('https://code.jquery.com/jquery-3.1.1.js', 'js/main.js',) - - class AmbassadorsAdmin(SummernoteModelAdmin): summernote_fields = ('ambassadors',) readonly_fields = ["edited_on"] search_fields = ["projects"] - class Media: - js = ('https://code.jquery.com/jquery-3.1.1.js', 'js/main.js',) - - admin.site.register(AdminSettings, AdminSettingsAdmin) admin.site.register(Hero, HeroAdmin) admin.site.register(Privacy, PrivacyAdmin) @@ -96,4 +76,4 @@ class Media: admin.site.unregister(Site) admin.site.unregister(SocialAccount) admin.site.unregister(SocialApp) -admin.site.unregister(SocialToken) \ No newline at end of file +admin.site.unregister(SocialToken) diff --git a/zubhub_backend/zubhub/zubhub/models.py b/zubhub_backend/zubhub/zubhub/models.py index 58c280fb8..a94baf6a7 100644 --- a/zubhub_backend/zubhub/zubhub/models.py +++ b/zubhub_backend/zubhub/zubhub/models.py @@ -5,7 +5,7 @@ from django.db import models from django.core.validators import FileExtensionValidator from django.utils.text import slugify -from .utils import MediaStorage, get_upload_path, clean_summernote_html +from .utils import MediaStorage, get_upload_path, clean_summernote_html, replace_summernote_images_with_media_storage_equiv from projects.models import Project from django.utils.html import strip_tags @@ -81,8 +81,12 @@ def __str__(self): return self.edited_on.strftime("ZubHub's Guildlines, Policies and Terms of use as edited on %I:%M %p, %d %b %Y %Z") def save(self, *args, **kwargs): - self.privacy_policy = clean_summernote_html(self.privacy_policy) - self.terms_of_use = clean_summernote_html(self.terms_of_use) + self.privacy_policy = clean_summernote_html( + replace_summernote_images_with_media_storage_equiv(self.privacy_policy) + ) + self.terms_of_use = clean_summernote_html( + replace_summernote_images_with_media_storage_equiv(self.terms_of_use) + ) self.edited_on = timezone.now() super().save(*args, **kwargs) @@ -99,7 +103,9 @@ def __str__(self): return self.edited_on.strftime("About Zubhub as edited on %I:%M %p, %d %b %Y %Z") def save(self, *args, **kwargs): - self.about = clean_summernote_html(self.about) + self.about = clean_summernote_html( + replace_summernote_images_with_media_storage_equiv(self.about) + ) self.edited_on = timezone.now() super().save(*args, **kwargs) @@ -115,7 +121,9 @@ def __str__(self): return self.edited_on.strftime("Challenges as edited on %I:%M %p, %d %b %Y %Z") def save(self, *args, **kwargs): - self.challenge = clean_summernote_html(self.challenge) + self.challenge = clean_summernote_html( + replace_summernote_images_with_media_storage_equiv(self.challenge) + ) self.edited_on = timezone.now() super().save(*args, **kwargs) @@ -131,8 +139,12 @@ def __str__(self): return self.question def save(self, *args, **kwargs): - self.question = clean_summernote_html(self.question) - self.answer = clean_summernote_html(self.answer) + self.question = clean_summernote_html( + replace_summernote_images_with_media_storage_equiv(self.question) + ) + self.answer = clean_summernote_html( + replace_summernote_images_with_media_storage_equiv(self.answer) + ) super().save(*args, **kwargs) @@ -178,4 +190,4 @@ def save(self, *args, **kwargs): super().save(*args, **kwargs) def __str__(self): - return self.Theme_Name \ No newline at end of file + return self.Theme_Name diff --git a/zubhub_backend/zubhub/zubhub/urls.py b/zubhub_backend/zubhub/zubhub/urls.py index 74c97604a..883ed5877 100644 --- a/zubhub_backend/zubhub/zubhub/urls.py +++ b/zubhub_backend/zubhub/zubhub/urls.py @@ -17,6 +17,7 @@ from django.contrib import admin from django.urls import path, include from django.views.generic import TemplateView +from django.conf.urls.static import static schema_url_patterns = [] @@ -34,7 +35,7 @@ import debug_toolbar urlpatterns = [ path('__debug__/', include(debug_toolbar.urls)), - ] + urlpatterns + ] + urlpatterns + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) if settings.DEFAULT_BACKEND_DOMAIN.startswith("localhost"): diff --git a/zubhub_backend/zubhub/zubhub/utils.py b/zubhub_backend/zubhub/zubhub/utils.py index 1a28f2245..b52643ad9 100644 --- a/zubhub_backend/zubhub/zubhub/utils.py +++ b/zubhub_backend/zubhub/zubhub/utils.py @@ -3,6 +3,8 @@ from lxml.html.clean import Cleaner import uuid from math import floor +from pathlib import Path +import re from django.utils.text import slugify from django.core.files.storage import Storage from django.core.files.base import ContentFile @@ -56,6 +58,29 @@ def clean_summernote_html(string): return cleaner.clean_html(string) +def replace_summernote_images_with_media_storage_equiv(text): + """ + Extract all summernote image paths in the html text, + upload them to media storage server, + and replace them with the response url from the media storage server + """ + regex = r']+src=["\']/api/media/([^"\'<>]+\.(?:gif|png|jpe?g))["\']' + temp_image_paths = re.findall(regex, text, re.I) # e.g. ["django-summernote/2023-09-03/image.jpg", "django-summernote/2023-09-03/image2.jpg"] + for temp_image_path in temp_image_paths: + path = Path(settings.MEDIA_ROOT, temp_image_path) + with path.open(mode="rb") as file: + key = get_upload_path( + type('', (object,), {'MEDIA_PATH': 'zubhub'}), + slugify(path.name) + ) + image_url = upload_file_to_media_server(file=file, key=key).json()["url"] + full_temp_image_url = settings.MEDIA_URL + str(temp_image_path) + text = image_url.join(text.split(full_temp_image_url)) + path.unlink() + return text + + + #========================= Docs helper functions =======================