From d8111f6070f388d9f6b830468d15dc30a3eb788a Mon Sep 17 00:00:00 2001 From: icurfer <icurfer@gmail.com> Date: Sat, 25 Jan 2025 22:45:27 +0900 Subject: [PATCH] =?UTF-8?q?minio=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...l => _unarchived_create_post_default.html} | 0 blog/templates/blog/create_post.html | 54 ++++++++++++- blog/templates/blog/create_post_bakcup.html | 80 +++++++++++++++++++ butler/migrations/0004_delete_noticeboard.py | 16 ++++ butler_ddochi/settings.py | 1 + butler_ddochi/urls.py | 1 + obs_minio/__init__.py | 0 obs_minio/admin.py | 3 + obs_minio/apps.py | 6 ++ obs_minio/migrations/__init__.py | 0 obs_minio/models.py | 3 + obs_minio/tests.py | 3 + obs_minio/urls.py | 8 ++ obs_minio/views.py | 47 +++++++++++ 14 files changed, 219 insertions(+), 3 deletions(-) rename blog/templates/blog/{create_post_default.html => _unarchived_create_post_default.html} (100%) create mode 100644 blog/templates/blog/create_post_bakcup.html create mode 100644 butler/migrations/0004_delete_noticeboard.py create mode 100644 obs_minio/__init__.py create mode 100644 obs_minio/admin.py create mode 100644 obs_minio/apps.py create mode 100644 obs_minio/migrations/__init__.py create mode 100644 obs_minio/models.py create mode 100644 obs_minio/tests.py create mode 100644 obs_minio/urls.py create mode 100644 obs_minio/views.py diff --git a/blog/templates/blog/create_post_default.html b/blog/templates/blog/_unarchived_create_post_default.html similarity index 100% rename from blog/templates/blog/create_post_default.html rename to blog/templates/blog/_unarchived_create_post_default.html diff --git a/blog/templates/blog/create_post.html b/blog/templates/blog/create_post.html index e5736cd..d97db32 100644 --- a/blog/templates/blog/create_post.html +++ b/blog/templates/blog/create_post.html @@ -23,10 +23,8 @@ <!-- 마크다운 에디터 --> <h2>Contents</h2> + <textarea id="markdown-editor" name="contents" class="form-control" rows="10"></textarea> - <div class="col-md-12"> - {{ form.contents }} - </div> <!-- 버튼 --> <div class="d-flex justify-content-end mt-4"> <button type="submit" class="btn btn-primary me-2">Create Post</button> @@ -75,6 +73,56 @@ hljs.highlightElement(block); }); }); + + // Ctrl+V로 이미지 붙여넣기 처리 + textarea.addEventListener("paste", async function (event) { + const items = (event.clipboardData || event.originalEvent.clipboardData).items; + + for (const item of items) { + if (item.type.startsWith("image/")) { + const file = item.getAsFile(); + if (!file) + return; + + const formData = new FormData(); + formData.append("image", file); + + try { + // 이미지 업로드 API 호출 + const response = await fetch("/obs_minio/upload/", { + method: "POST", + body: formData + }); + + if (response.ok) { + const data = await response.json(); + const imageUrl = data.url; // 업로드된 이미지 URL + + // 마크다운 에디터에 이미지 삽입 + const markdownImage = `\n`; + textarea.value += markdownImage; + + // 미리보기 업데이트 + const markdownContent = textarea.value; + const renderedContent = md.render(markdownContent); + preview.innerHTML = renderedContent; + + // Highlight.js로 코드 블록 강조 + document + .querySelectorAll("#preview pre code") + .forEach((block) => { + hljs.highlightElement(block); + }); + } else { + alert("Image upload failed. Please try again."); + } + } catch (error) { + console.error("Error uploading image:", error); + alert("An error occurred during image upload."); + } + } + } + }); </script> </div> {% endblock %} diff --git a/blog/templates/blog/create_post_bakcup.html b/blog/templates/blog/create_post_bakcup.html new file mode 100644 index 0000000..e5736cd --- /dev/null +++ b/blog/templates/blog/create_post_bakcup.html @@ -0,0 +1,80 @@ +{% extends "components/base.html" %} +{% block title %}Create Post{% endblock %} + +{% block main_area %} + <h1 class="pt-3">Create New Post</h1> + <div class="container"> + <div class="row"> + <div class="col-md-6"> + <form method="POST" id="post-form"> + {% csrf_token %} + <div class="mb-3"> + {{ form.title.label_tag }} + {{ form.title }} + </div> + <div class="mb-3"> + {{ form.summary.label_tag }} + {{ form.summary }} + </div> + <div class="mb-3"> + {{ form.tags.label_tag }} + {{ form.tags }} + </div> + + <!-- 마크다운 에디터 --> + <h2>Contents</h2> + + <div class="col-md-12"> + {{ form.contents }} + </div> + <!-- 버튼 --> + <div class="d-flex justify-content-end mt-4"> + <button type="submit" class="btn btn-primary me-2">Create Post</button> + <a href="{% url 'blog:post_list' %}" class="btn btn-secondary">Cancel</a> + </div> + </form> + </div> + <div class="col-md-6"> + <div id="preview" class="border p-3 bg-light h-100 overflow-auto"></div> + </div> + </div> + + <!-- 마크다운 파서 및 스크립트 --> + <script src="https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js"></script> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github-dark.min.css"> + <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script> + <script> + // 마크다운 파서 초기화 + const md = window.markdownit({ + highlight: function (str, lang) { + if (lang && hljs.getLanguage(lang)) { + try { + return hljs + .highlight(str, {language: lang}) + .value; + } catch (__) {} + } + return ''; // 기본 HTML 이스케이프 처리 + } + }); + + // content 필드와 미리보기 연결 + const textarea = document.getElementById("markdown-editor"); + const preview = document.getElementById("preview"); + + // 실시간 미리보기 업데이트 + textarea.addEventListener("input", function () { + const markdownContent = textarea.value; // textarea 내용 가져오기 + const renderedContent = md.render(markdownContent); // 마크다운 -> HTML 변환 + preview.innerHTML = renderedContent; + + // Highlight.js로 코드 블록 강조 + document + .querySelectorAll("#preview pre code") + .forEach((block) => { + hljs.highlightElement(block); + }); + }); + </script> + </div> +{% endblock %} diff --git a/butler/migrations/0004_delete_noticeboard.py b/butler/migrations/0004_delete_noticeboard.py new file mode 100644 index 0000000..f561588 --- /dev/null +++ b/butler/migrations/0004_delete_noticeboard.py @@ -0,0 +1,16 @@ +# Generated by Django 4.2.14 on 2025-01-25 19:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('butler', '0003_rename_desc_ipmanagementrecord_contents'), + ] + + operations = [ + migrations.DeleteModel( + name='NoticeBoard', + ), + ] diff --git a/butler_ddochi/settings.py b/butler_ddochi/settings.py index b86905c..aa46220 100644 --- a/butler_ddochi/settings.py +++ b/butler_ddochi/settings.py @@ -51,6 +51,7 @@ INSTALLED_APPS = [ 'nhnc_mgmt', 'mm_msg', 'ansible_manager', + 'obs_minio', ] MIDDLEWARE = [ diff --git a/butler_ddochi/urls.py b/butler_ddochi/urls.py index 949e2b3..fcd3c32 100644 --- a/butler_ddochi/urls.py +++ b/butler_ddochi/urls.py @@ -30,6 +30,7 @@ urlpatterns = [ path('nhnc_mgmt/', include('nhnc_mgmt.urls')), path('mm_msg/', include('mm_msg.urls')), path('ansible_manager/', include('ansible_manager.urls')), + path('obs_minio/', include('obs_minio.urls')), ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/obs_minio/__init__.py b/obs_minio/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/obs_minio/admin.py b/obs_minio/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/obs_minio/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/obs_minio/apps.py b/obs_minio/apps.py new file mode 100644 index 0000000..d2bea7c --- /dev/null +++ b/obs_minio/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ObsMinioConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'obs_minio' diff --git a/obs_minio/migrations/__init__.py b/obs_minio/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/obs_minio/models.py b/obs_minio/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/obs_minio/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/obs_minio/tests.py b/obs_minio/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/obs_minio/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/obs_minio/urls.py b/obs_minio/urls.py new file mode 100644 index 0000000..9b719ea --- /dev/null +++ b/obs_minio/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from . import views + +app_name = 'obs_minio' + +urlpatterns = [ + path('upload/', views.upload_image, name='upload_image'), +] diff --git a/obs_minio/views.py b/obs_minio/views.py new file mode 100644 index 0000000..dd5ad2c --- /dev/null +++ b/obs_minio/views.py @@ -0,0 +1,47 @@ +import uuid +from django.http import JsonResponse +from django.views.decorators.csrf import csrf_exempt +from minio import Minio +from minio.error import S3Error +import urllib3 + +# MinIO 설정 +MINIO_ENDPOINT = 'minio.icurfer.com:9000' +MINIO_ACCESS_KEY = 'h5gOXcQieSE0kReVlpDa' +MINIO_SECRET_KEY = '2S5vtc7DtrnUjqsAO6CF3kPVEqDtqmHgnt3OPIPY' +BUCKET_NAME = 'butler-ddochi-image' + +@csrf_exempt +def upload_image(request): + print("이미지업로드 동작시작") + if request.method == 'POST' and 'image' in request.FILES: + image = request.FILES['image'] + unique_filename = f"uploads/{uuid.uuid4()}_{image.name}" + + # MinIO 클라이언트 생성 + client = Minio( + MINIO_ENDPOINT, + access_key=MINIO_ACCESS_KEY, + secret_key=MINIO_SECRET_KEY, + secure=True, + http_client=urllib3.PoolManager(cert_reqs='CERT_NONE') # SSL 검증 비활성화 + ) + + try: + # MinIO에 파일 업로드 + client.put_object( + bucket_name=BUCKET_NAME, + object_name=unique_filename, + data=image, + length=image.size, + content_type=image.content_type + ) + + # Presigned URL 생성 + presigned_url = client.presigned_get_object(BUCKET_NAME, unique_filename) + return JsonResponse({"url": presigned_url}, status=201) + + except S3Error as e: + return JsonResponse({"error": str(e)}, status=500) + + return JsonResponse({"error": "Invalid request"}, status=400)