• 카테고리

    질문 & 답변
  • 세부 분야

    풀스택

  • 해결 여부

    미해결

글 작성 폼에 HTML 에디터 기능 추가하기 이미지 저장 오류..ㅠ

22.04.13 14:40 작성 조회수 606

1

남박사님 계속 질문해서 죄송합니다..ㅠ

 

하다보니까 계속 오류가 나네요....

 

이미지 업로드 관련해서 ajax 부분에서 오류가 나는것은 영상을 따라 하면서 해봐도 계속 같은 오류가 나와서 답답해서 질문드립니다.

 

 

- 오류1 -

 

영상대로 수정하고 적용해서 똑같은 오류가 나오더라구요..

 

그래서 이미지 업로드창 눌러서 해봤는데 비슷한 오류가 나옵니다.

 

-오류2-

 

 

-오류3-

 

 

- 코드-

-write.html-

 

{% extends "main.html" %}

{% block contents %}

<script>
    $(document).ready(function() {
        $("#summernote").summernote({
            height: 300,
            minHeight: null,
            maxHeight: null,
            lang: "ko-KR",
            popover: {
                image:[],
                link:[],
                air:[]
            },
            callbacks: {
                onImageUpload: function(image) {
                    for(var i = 0; i < image.length; i++) {
                        uploadImage(image[i]);
                    }
                }
            }
        });
    });

    function uploadImage(image) {
        var data = new FormData();
        data.append("image", image);
        $.ajax({
            url: "{{url_for('board.upload_image')}}",
            cache: false,
            contentType: false,
            processData: false,
            data: data,
            type:"post",
            success: function(url) {
                var image = $("<img>").attr("src",url).css('max-width', "900px");
                $("#summernote").summernote("insertNode",image[0]);
            },
            error: function(data) {
                console.log(data);
                alert(data);
            }
        });
    }
</script>

<script>
    function checkForm() {
        if ($.trim($("#title").val()) == "") {
            alert("제목을 입력하세요");
            $("#title").focus();
            return false;
        }
        if ($.trim($("#summernote").val()) == "") {
            alert("내용을 입력하세요");
            $("#summernote").focus();
            return false;
        }
    }
</script>

<div style="padding: 50px 50px 50px 50px;">
    <form name="form" method="post" action="{{url_for('board.board_write')}}" onsubmit="return checkForm()">
        <div class="form-group">
            <label for="name">작성자</label>
            <input class="form-control" id="name" type="text" name="name" value="{{session['name']}}" readonly>
        </div>
        <div class="form-group">
            <label for="title">제목</label>
            <input class="form-control" type="text" name="title" id="title" placeholder="제목을 입력하세요"/>
        </div>
        <div class="form-group">
            <label for="contents">내용</label>
            <textarea rows="8" class="form-control" type="text" name="contents" id="summernote" placeholder="내용을 입력하세요"></textarea>
        </div>
        <div class="text-center"><input class="btn btn-primary" type="submit" value="작성하기"></div>
    </form>
</div>

{% endblock %}
 
 
 
 
 
 
---------------------------------------------------------------------------------------
 
 
-viev.html-
 
 
{% extends "main.html" %}

{% block contents %}

<div style="padding: 50px 50px 50px 50px;">
    <table class="table table-bordered">
        <tbody>
            <tr>
                <td colspan="2">{{result.title}}</td>
            </tr>
            <tr>
                <td>{{result.name}}</td>
                <td class="text-right">{{result.pubdate|formatdatetime}}</td>
            </tr>
            <tr>
                <td colspan="2"><div style="min-height: 200px;"></div>{% autoescape false %}{{result.contents}}{% endautoescape %}</td>
            </tr>
        </tbody>
    </table>

    <a class="btn btn-primary" href="{{url_for('board.lists', page=page, search=search, keyword=keyword)}}">리스트</a>

    {% if session["id"] == result.writer_id %}
    <a class="btn btn-danger float-right ml-2" href="{{url_for('board.board_delete', idx=result.id)}}">글삭제</a>
    <a class="btn btn-warning float-right" href="{{url_for('board.board_edit', idx=result.id)}}">글수정</a>
    {% endif %}
</div>

{% endblock %}
 
 
 
---------------------------------------------------------------------------------------------------
 
-main.html-
 
 
 
<!DOCTYPE html>
<html lang="kr">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{title}}</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
   
    <script src="https://code.jquery.com/jquery-3.6.0.min.js" type="text/javascript"></script>
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>

    <!-- include summernote css/js -->
    <link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>

    <script>
        function validateEmail(email) {
            var pattern = /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/;
            return pattern.test(email);
        }
    </script>

    {% with messages = get_flashed_messages() %}
        {% if messages %}
            <script>
                alert('{{messages[-1]}}');
            </script>
        {% endif %}
    {% endwith %}
</head>

<body>
    <div class="containter">
        {% include "menu.html" %}
        {% block contents %}
        {% endblock %}
    </div>
</body>

</html>
 
 
 
 
--------------------------------------------------------------------------------------------------
 
 
-board.py-
 
 
 
 
from main import *
from flask import Blueprint, send_from_directory
from flask import send_from_directory

bluerprint = Blueprint("board", __name__, url_prefix="/board")


@bluerprint.route("/upload_image", methods=["POST"])
def upload_image():
    if request.method == "POST":
        file = request.files["image"]
        if file and allowed_file(file.filename):
            filename = "{}.jpg".format(rand_generator())
            savefilepath = os.path.join(app.config["BOARD_IMAGE_PATH"], filename)
            file.save(savefilepath)
            return url_for("board.board_images", filename=filename)


@bluerprint.route("/images/<filename>")
def board_images(filename):
    return send_from_directory(app.config["BOARD_IMAGE_PATH"], filename)


@bluerprint.route("/list")
def lists():
    # 페이지 값 (값이 없는 경우 기본값는 1)
    page = request.args.get("page", 1, type=int)
    # 한페이지당 몇개의 게시물을 출력할지
    limit = request.args.get("limit", 5, type=int)

    search = request.args.get("search", -1, type=int)
    keyword = request.args.get("keyword", "", type=str)

    # 최종적으로 완성된 쿼리를 만들 변수
    query = {}
    # 검색어 상태를 추가할 리스트 변수
    search_list = []

    if search == 0:
        search_list.append({"title": {"$regex": keyword}})
    elif search == 1:
        search_list.append({"contents": {"$regex": keyword}})
    elif search == 2:
        search_list.append({"title": {"$regex": keyword}})
        search_list.append({"contents": {"$regex": keyword}})
    elif search == 3:
        search_list.append({"name": {"$regex": keyword}})

    # 검색 대상이 한개라도 존재할 경우 query 변수에 $or 리스트를 쿼리 합니다.
    if len(search_list) > 0:
        query = {"$or": search_list}

    print(query)

    board = mongo.db.board
    datas = board.find({}).skip(
        (page - 1) * limit).limit(limit).sort("pubdate", -1)

    # 게시물의 총 갯수
    tot_count = board.count_documents({})
    # 마지막 페이지의 수를 구한다.
    last_page_num = math.ceil(tot_count / limit)
    # 페이지 블럭을 5개씩 표기
    block_size = 5
    # 현재 블럭의 위치
    block_num = int((page - 1) / block_size)
    # 블럭의 시작 위치
    block_start = int((block_size * block_num) + 1)
    # 블럭의 끝 위치
    block_last = math.ceil(block_start + (block_size - 1))

    return render_template(
        "list.html",
        datas=list(datas),
        limit=limit,
        page=page,
        block_start=block_start,
        block_last=block_last,
        last_page_num=last_page_num,
        search=search,
        keyword=keyword,
        title="게시판 리스트")


@bluerprint.route("/view/<idx>")
@login_required
def board_view(idx):
    # idx = request.args.get("idx")
    if idx is not None:
        page = request.args.get("page")
        search = request.args.get("search")
        keyword = request.args.get("keyword")

        board = mongo.db.board
        # data = board.find_one({"_id": ObjectId(idx)})
        data = board.find_one_and_update({"_id": ObjectId(idx)}, {
            "$inc": {"view": 1}}, return_document=True)

        if data is not None:
            result = {
                "id": data.get("_id"),
                "name": data.get("name"),
                "title": data.get("title"),
                "contents": data.get("contents"),
                "pubdate": data.get("pubdate"),
                "view": data.get("view"),
                "writer_id": data.get("writer_id", "")
            }

            return render_template(
                "view.html",
                result=result,
                page=page,
                search=search,
                keyword=keyword,
                title="글 상세보기")
    return abort(404)


@bluerprint.route("/write", methods=["GET", "POST"])
def board_write():
    if session.get("id") is None:
        return redirect(url_for("member.member_login"))

    if request.method == "POST":
        name = request.form.get("name")
        title = request.form.get("title")
        contents = request.form.get("contents")
        print(name, title, contents)

        current_utc_time = round(datetime.utcnow().timestamp() * 1000)
        board = mongo.db.board
        post = {
            "name": name,
            "title": title,
            "contents": contents,
            "pubdate": current_utc_time,
            "writer_id": session.get("id"),
            "view": 0,
        }

        x = board.insert_one(post)
        print(x.inserted_id)
        return redirect(url_for("board.board_view", idx=x.inserted_id))
    else:
        return render_template("write.html", title="글 작성")


@bluerprint.route("/edit/<idx>", methods=["GET", "POST"])
def board_edit(idx):
    if request.method == "GET":
        board = mongo.db.board
        data = board.find_one({"_id": ObjectId(idx)})
        if data is None:
            flash("해당 게시물이 존재하지 않습니다.")
            return redirect(url_for("board.lists"))
        else:
            if session.get("id") == data.get("writer_id"):
                return render_template("edit.html", data=data, title="글 수정")
            else:
                flash("글 수정 권한이 없습니다.")
                return redirect(url_for("board.lists"))
    else:
        title = request.form.get("title")
        contains = request.form.get("contents")

        board = mongo.db.board
        data = board.find_one({"_id": ObjectId(idx)})
        if session.get("id") == data.get("writer_id"):
            board.update_one({"_id": ObjectId(idx)}, {
                "$set": {
                    "title": title,
                    "contents": contains,
                }
            })
            flash("수정 되었습니다.")
            return redirect(url_for("board.board_view", idx=idx))
        else:
            flash("글 수정 권한이 없습니다.")
            return redirect(url_for("board.lists"))


@bluerprint.route("/delete/<idx>")
def board_delete(idx):
    board = mongo.db.board
    data = board.find_one({"_id": ObjectId(idx)})
    if data.get("writer_id") == session.get("id"):
        board.delete_one({"_id": ObjectId(idx)})
        flash("삭제 되었습니다.")
    else:
        flash("삭제 권한이 없습니다.")
    return redirect(url_for("board.lists"))
 
 
 
 
---------------------------------------------------------------------------------
 
 
 
-__init__.py-
 
 
 
 
from flask import Flask
from flask import request
from flask import render_template
from flask_pymongo import PyMongo
from bson.objectid import ObjectId
from datetime import datetime, timedelta
from flask import abort
from flask import redirect
from flask import url_for
from flask import flash
from flask import session
import math
import time
import os

app = Flask(__name__)
app.config["MONGO_URI"] = "mongodb://localhost:27017/myweb"
app.config["SECRET_KEY"] = "abcd"
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(minutes=30)
mongo = PyMongo(app)

BOARD_IMAGE_PATH = "C:\\python\\images"
ALLOWED_EXTENSIONS =set(["txt", "pdf", "png", "jpg", "jpeg", "gif"])

app.config["BOARD_IMAGE_PATH"] = BOARD_IMAGE_PATH
app.config["MAX-CONTENT_LENGTH"] = 15 * 1024 * 1024

if not os.path.exists(app.config["BOARD_IMAGE_PATH"]):
    os.mkdir(app.config["BOARD_IMAGE_PATH"])

# 아래의 파일들에서 main 의 app 에 접근을 해야하니
# 아래 import 위에 app 가 선언되어야 합니다.
from .common import login_required, allowed_file, rand_generator
from .filter import format_datetime

# member, board 에서 login_required, format_datetime 을 사용하니까
# 당연히 member 와 board 가 위의 import 보다 먼저 나와선 안됩니다.
from . import member  
from . import board

app.register_blueprint(board.bluerprint)
app.register_blueprint(member.bluerprint)
 
 
 
 
 

답변 1

답변을 작성해보세요.

0

다시한번 말씀드리지만 질문을 하시는게 절대 죄송할 일이 아닙니다. 제게 질문을 자꾸 자꾸 하시는건 열심히 강좌를 듣고 연습하고 계시는건데 그게 왜 죄송할 일입니까? 저는 절대 그렇게 생각하지 않습니다.

 

올려주신 에러의 내용을 보면 $.ajax is not a function... 이라고 나오는데 jquery 의 ajax() 함수 자체를 인식하지 못하는 상황입니다. 

올려주신 코드에서는 섬머노트 버전이 0.8.18 버전을 사용하시고 있고 강좌에서는 0.8.12 버전을 사용중입니다.아마 섬머노트가 버전업이 된거 같은데 그로인해 jquery 의 버전도 변경된걸로 보입니다.

섬머노트 버전과 자바스크립트 파일들의 jquery, bootstrap 버전이 달라서 무언가 충돌로 보이는데 강좌 기준으로 버전을 한번 맞춰보시거나 아니면 충돌이 발생하지 않는 jquery, 부트스트랩 버전을 한번 찾아보셔야 할걸로 보입니다. 다만 부트스트랩이나 jquery 가 모두 강좌를 기준으로 맞춰져 있는거라 코드를 항상 백업본을 만드시고 테스트 해보시는게 좋습니다.

그래서 실제 실무에서도 DB버전, 라이브러리리 버전을 함부로 업그레이드 하지 않고 충분히 테스트서버에서 운영하다가 적용하는 이유기도 합니다. 서비스의 관점에서는 업그레이드 된 버전에 꼭 필요한 기능이 있거나 아니면 퍼포먼스가 월등히 높아진게 아니라면 그냥 이유없이 버전업을 하지 않습니다. 아직도 파이썬을 2.x 버전대로 사용하는 서비스도 종종 있는 이유기도 하구요.