인프런 커뮤니티 질문&답변

kimbarb님의 프로필 이미지

작성한 질문수

남박사의 파이썬으로 실전 웹사이트 만들기

글 작성 폼에 HTML 에디터 기능 추가하기

summernote 설치 후 그림파일 드레그&드롭 그림 올라가지 않음

작성

·

904

1

안녕하십니까~

취미생활로 코딩을 배우는 사람입니다.

강사님의 훌륭한 강의로 일주일 안에 여기까지 많은 도움을 받으면서 진행하여 왔습니다.

감사드리면서~~~~

글쓰기 에디터,  summernote 설치 후 글 작성에서 그림 드레그&드롭이 되지 않습니다. 

- 관련된 코딩을 3번 정도 확인하면서 검토는 하였는데 코딩상에서는 현재까지는 오타 및 잘못 적용된 문법적인 요소를 발견하지 못하였습니다.(아마도 분명 저의 잘못이 있기는 할텐데~~~)

어느 곳을 확인해야 하는지 알려주십시요

*** 현재 csrf 강의까지 해당 코딩을 한 상태입니다.

감사하겠습니다.

@@@ 추가하여 

글 수정에서 체크박스 체크하지 않은 상태에서 수정하기를 누르고 나오면 첨부파일이 없어지는 현상은 어디를 확인해야 하는지도 궁금합니다.

답변 11

0

남박사님의 프로필 이미지
남박사
지식공유자

if file and allowed_file(file.filename):
    	print("파일 체크 완료")
        filename = check_filename(file.filename)
        file.save(os.path.join(app.config["BOARD_ATTACH_FILE_PATH"], filename))
        print("새 첨부파일 저장완료")
        if attach_file:
            print("기존 첨부파일 삭제")
            board_delete_attach_file(attach_file)
    else:
    	print("파일 체크 실패")
    	filename = data.get("attachfile")

위의 코드 내용에서 파일 체크완료 아니면 파일체크 실패 둘 중 하나가 떠야하는데 둘다 안떴다는게 이상합니다. 프로그램이 뻗던가 아니면 뭔가 오류가 발생하지 않은 이상 둘중에 하나가 떠야 하는데 말씀하신 로그 상으로는 아무 내용도 없다는게....

그리고 강좌 내용을 잘못 이해를 하고 계신거 같은데.. cstf.exempt 는 해당 데코레이터를 사용하면 데코레이터가 적용된 함수는 csrf 기능을 적용 받지 않는 상태로 만드는건데 당연히 csrf.exempt 를 하시면 csrf 에서 예외처리가 되게 되고 그로인해 토큰처리를 하지 않았으니 Bad request 가 되는게 맞습니다.

0

kimbarb님의 프로필 이미지
kimbarb
질문자

안녕하십니까~

말씀하신대로 수행한 결과 콘솔상에 시현된 것

POST 처리 idx

현재 첨부파일 테스트.txt

새 첨부파일 존재 <FileStorage: '' ('application/octet-stream')>

새 첨부파일명:

==========================================================

문제가 첨부파일 관련된 문제가 이것 때문은 아닌지요?

그리고, 이 부분도 확인을 좀 해주셔야 될 것으로 보입니다.

http://127.0.0.1:5700/member/join 연결 시 다음과 같은 에러~

Bad Request

The CSRF token is missing.

member.py 에서

# @csrf.exempt

이와 같이 적용하고 회원가입했을 때는 정상적으로 회원가입이 되었는데 - 참고하시구요...

@csrf.exempt

적용 후에는 회원가입을 하면

Bad Request

The CSRF token is missing. 같이 에러 발생합니다.

* csrf 관련 코딩

1. __init__.py : from flask_wtf.csrf import CSRFProtect

2. __init__.py : csrf = CSRFProtect(app)

3. __init__.py : from .common import login_required, allowed_file, rand_generator, check_filename, hash_password, check_password

4. member.py(join 함수 관련된 부분) : post = {

            "name": name,

            "email": email,

            "pass": hash_password(pass1),

            "joindate": current_utc_time,

            "logintime": "",

            "logincount": 0,

        }

5. common.py : from werkzeug.security import generate_password_hash, check_password_hash

6. member.py(login 관련된 부분) : if check_password(data.get("pass"), password):

7. join.html(form 부분) : <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">

8. edit.html(form 부분) : <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">

* 참고사항-1

member.py 에서

# @csrf.exempt 적용하고 회원가입했을 때 mongo db 상에서 csrf_token 적용된 비번 정상으로 기록되었음

** 참고사항-2

코딩 전체에서 csrf_token 적용되어 있음을 알려드립니다.

감사드립니다.

0

남박사님의 프로필 이미지
남박사
지식공유자

HTML 코드도 아무 문제가 없어 보이는데 희안하군요

if "attachfile" in request.files:
    print("새 첨부파일 존재 {}".format(requests.files['attachfile']))
    file = request.files["attachfile"]
    print("새 첨부파일명: {}".format(file.filename))
    if file and allowed_file(file.filename):
    	print("파일 체크 완료")
        filename = check_filename(file.filename)
        file.save(os.path.join(app.config["BOARD_ATTACH_FILE_PATH"], filename))
        print("새 첨부파일 저장완료")
        if attach_file:
            print("기존 첨부파일 삭제")
            board_delete_attach_file(attach_file)
    else:
    	print("파일 체크 실패")
    	filename = data.get("attachfile")

제가 올려드린 코드에서 위의 코드처럼 수정해서 테스트 해보시고 어떤 결과가 나오는지 확인 부탁드립니다.

0

kimbarb님의 프로필 이미지
kimbarb
질문자

edit.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 = 0i < image.lengthi++) {
                        uploadImage(image[i]);
                    }
                }
            }
        });
    });

    function uploadImage(image) {
        var data = new FormData();
        data.append("image"image);
        var csrf_token = "{{ csrf_token() }}";

        $.ajaxSetup({
            beforeSend: function (xs) {
                if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(s.type)) {
                    x.setRequestHeader("x-CSRFToken"csrf_token)
                }
            }
        });

        $.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>

<form name="form" method="POST" action="{{ url_for('board.board_edit', idx=data._id) }}" enctype="multipart/form-data">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
    <div class="form-group">
        <label for="name">작성자</label>
        <input class="form-control" 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" value="{{ data.title }}">
    </div>
    {% if data.attachfile %}
    <div class="form-check text-right">
        <input type="checkbox" class="form-check-input" id="deleteoldfile" name="deleteoldfile">
        <label class="form-check-label" for="deleteoldfile">첨부파일 삭제 ({{ data.attachfile }})</label>
    </div>
    {% endif %}
    <div class="form-froup">
        <label for="contents">내용</label>
        <textarea class="form-control" rows="8" name="contents" id="summernote">{{ data.contents }}</textarea>
    </div>
    <div class="custom-file">
        <input class="custom-file-input" id="customFile" type="file" name="attachfile">
        <label class="custom-file-label" for="customFile">파일선택</label>
    </div>
    <div class="text-center"><input class="btn btn-primary" type="submit" value="수정하기"></div>
</form>

{% endblock %}

** 크롬, 엣지,  brave, 익스플로러에서도 동일한 현상입니다.

감사합니다.

0

남박사님의 프로필 이미지
남박사
지식공유자

html 파일에 문제가 있어 보입니다. html 파일 코드를 봐야 알듯 합니다. 그리고 몇가지 다른 브라우저에서도 증상이 동일한지 항상 확인해야 합니다.

0

kimbarb님의 프로필 이미지
kimbarb
질문자

print문을 돌려서 확인 결과로서 콘솔상에 나타난 것을 보면 아래와 같습니다.

-결과의 내용을 분석하면, 기존의 첨부된 파일이 있는 상태에서 추가 첨부파일 등록없이 작성하기를 클릭했는데... 새로운 첨부파일이 있는 것으로 코딩은 돌아가는 것으로 보입니다.

<콘솔상에 시현된 프린터문>

POST 처리 idx

현재 첨부파일 테스트.txt

새 첨부파일 존재 <FileStorage: '' ('application/octet-stream')>

127.0.0.1 - - [12/Apr/2020 17:42:31] "?[32mPOST /board/edit/5e92d378520729c0f6769948 HTTP/1.1?[0m" 302 -

127.0.0.1 - - [12/Apr/2020 17:42:31] "?[37mGET /board/view/5e92d378520729c0f6769948 HTTP/1.1?[0m" 200 -

127.0.0.1 - - [12/Apr/2020 17:42:33] "?[37mGET /board/comment_list/5e92d378520729c0f6769948?_=1586680951452 HTTP/1.1?[0m"

200 -

* mongo db 상에서는 attachfile - null

* 서버(c:\python uploads)에는 '테스트.txt'(첫번째 글작성 시 올린 파일) 파일 존재

** '글 수정' 모드로 와서 아무것도 변경없이 단지 '작성하기' 버튼을 클릭했는데 서버상에는 파일(테스트.txt)이 존재하고 -- 기존의 첨부파일이 mongo db상에서 null로 나오고 이리하여 view 화면에서 첨부파일을 보여주지 못하고 있습니다.

감사합니다.  

0

남박사님의 프로필 이미지
남박사
지식공유자

다시 말씀 드리지만...

실제 서버에서 파일이 삭제 되는건지 아니면 DB 값만 변경되는건지 부터 확실히 알아야 할 필요가 있습니다.

제일 우선적으로 확인하는건 로직이 어느 구간을 빠지는지를 확실하게 알아야 하는건데 각 구간마다 print 문을 출력해서 정확히 로직이 어느쪽으로 빠지는지 부터 확인 한 후 해당 구간에 대한 보강을 해야 하는 부분입니다. 

@blueprint.route("/edit/<idx>", methods=["GET", "POST"])
def board_edit(idx=None):
    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:
        print("POST 처리 {}".format("idx"))
        title = request.form.get("title")
        contents = request.form.get("contents")
        deleteoldfile = request.form.get("deleteoldfile", "")

        board = mongo.db.board
        data = board.find_one({"_id": ObjectId(idx)})

        if data.get("writer_id") == session.get("id"):
            attach_file = data.get("attachfile")
            print("현재 첨부파일 {}".format(attach_file))
            filename = None
            if "attachfile" in request.files:
                print("새 첨부파일 존재 {}".format(requests.files['attachfile']))
                file = request.files["attachfile"]
                if file and allowed_file(file.filename):
                    filename = check_filename(file.filename)
                    file.save(os.path.join(app.config["BOARD_ATTACH_FILE_PATH"], filename))
                    print("새 첨부파일 저장완료")
                    if attach_file:
                        print("기존 첨부파일 삭제")
                        board_delete_attach_file(attach_file)
            else:
                print("새 첨부파일 존재하지 않음")
                if deleteoldfile == "on":
                    print("기존 파일 삭제 옵션 확인")
                    filename = None
                    if attach_file:
                        print("기존 파일 삭제 {}".format(attach_file))
                        board_delete_attach_file(attach_file)
                else:
                    print("기존 파일 보존 {}".format(attach_file))
                    filename = attach_file

            board.update_one({"_id": ObjectId(idx)}, {
                "$set": {
                    "title": title,
                    "contents": contents,
                    "attachfile": filename
                }
            })
            flash("수정되었습니다.")
            return redirect(url_for("board.board_view", idx=idx))
        else:
            flash("글 수정 권한이 없습니다.")
            return redirect(url_for("board.lists"))

위 코드를 그대로 테스트 해보시고 화면에 어떤 내용이 print 되는지 확인하시면 좀 더 정확하게 알 수 있을것 같습니다.

0

kimbarb님의 프로필 이미지
kimbarb
질문자

안녕하십니까~~

확인한 결과를 말씀드립니다.

ㅇ POST 부분 코딩 수정한(즉, else: 이하)

- else를 제외한 edit.py 코딩을 지우고 return 값을 위해 아래와 같이 코딩 후 실행하면 첨부파일은 지워지지 않습니다. 

   물론, 다른 첨부파일 추가 등은 안되는 것은 당연하고요...

else:

        return redirect(url_for("board.board_view", idx=idx))

ㅇ POST 부분 중에서 아래의 이 부분만을 지우고(주석처리하고) - 첨부파일이 있는 상태에서 수정모드로 가서 작성하기를 하면 역시 첨부파일은 지워집니다.  

if session.get("id") == data.get("writer_id"):

             filename = None

             if "attachfile" in request.files:

                 file = request.files["attachfile"]

                 if file and allowed_file(file.filename):

                     filename = check_filename(file.filename)

                     file.save(os.path.join(app.config["BOARD_ATTACH_FILE_PATH"], filename))

                     if data.get("attachfile"): 

                         board_delete_attach_file(data.get("attachfile"))

             else:

                 if deleteoldfile == "on":

                     filename = None

                     if data.get("attachfile"):

                         board_delete_attach_file(data.get("attachfile"))

                 else:

                     filename = data.get("attachfile")

  * board_delete_attach_file(data.get("attachfile")) - 이 함수가 없는데도 첨부파일이 지워지는 현상

** 이 경우에 db확인하면 attachfile에 null로 나옴

   글 작성하였을 경우에는 db에서 정상적으로 파일이 보였다가  수정 후에는 null로 나타납니다.

감사합니다.

0

남박사님의 프로필 이미지
남박사
지식공유자

코드 상에 특별한 부분은 안보이는데...

일단 POST 쪽 

if "attachfile" in request.files:

과 else 쪽에서 각각 print 문을 찍어보시고 어디로 빠지는지부터 확인해보시는게 좋을듯 합니다. 그리고 실제 파일이 서버상에서 삭제되는건지 DB에서만 지워지는건지도 확인해보시고 그에 따른 추적을 해보셔야 할듯 합니다.

만약 파일이 실제 서버에서 삭제 되는거라면 결국 board_delete_attach_file(data.get("attachfile")) 이 함수가 호출되었단 얘기인데 

if data.attachfile:

# if data.get("attachfile"):

이렇게 해놓으신 이 부분을 위에서 attach_file 처럼 임의의 변수에 담아서 처리해보시길 바랍니다.

idx = request.form.get("idx")
        title = request.form.get("title")
        contents = request.form.get("contents")
        deleteoldfile = request.form.get("deleteoldfile", "")

        board = mongo.db.board
        data = board.find_one({"_id": ObjectId(idx)})

        if data.get("writer_id") == session.get("id"):
            attach_file = data.get("attachfile")
            filename = None
            if "attachfile" in request.files:
                file = request.files["attachfile"]
                if file and allowed_file(file.filename):
                    filename = check_filename(file.filename)
                    file.save(os.path.join(app.config["BOARD_ATTACH_FILE_PATH"], filename))
                    if attach_file:
                        board_delete_attach_file(attach_file)
            else:
                if deleteoldfile == "on":
                    filename = None
                    if attach_file:
                        board_delete_attach_file(attach_file)
                else:
                    filename = attach_file

            board.update_one({"_id": ObjectId(idx)}, {
                "$set": {
                    "title": title,
                    "contents": contents,
                    "attachfile": filename
                }
            })
            flash("수정되었습니다.")
            return redirect(url_for("board.board_view", idx=idx))
        else:
            flash("글 수정 권한이 없습니다.")
            return redirect(url_for("board.lists"))

위 코드를 참고해보시기 바랍니다.

0

kimbarb님의 프로필 이미지
kimbarb
질문자

안녕하십니까.

빠른 답변에 매우 감사함을 표시하면서~~~

강사님께서 말씀하신대로 해도 해결은 안되어서 저의 코딩 내용을 붙입니다.

부탁드립니다.

@blueprint.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")

        contents = request.form.get("contents")

        deleteoldfile = request.form.get("deleteoldfile", "")

        board = mongo.db.board

        data = board.find_one({"_id": ObjectId(idx)})

        if session.get("id") == data.get("writer_id"):

            filename = None

            if "attachfile" in request.files:

                file = request.files["attachfile"]

                if file and allowed_file(file.filename):

                    filename = check_filename(file.filename)

                    file.save(os.path.join(app.config["BOARD_ATTACH_FILE_PATH"], filename))

                    if data.attachfile:

                    # if data.get("attachfile"):

                        board_delete_attach_file(data.get("attachfile"))

            else:

                if deleteoldfile == "on":

                    filename = None

                    if data.get("attachfile"):

                        board_delete_attach_file(data.get("attachfile"))

                else:

                    filename = data.get("attachfile")

            board.update_one({"_id": ObjectId(idx)}, {

                "$set": {

                    "title": title,

                    "contents": contents,

                    "attachfile": filename

                }

            })

            flash("수정되었습니다.")

            return redirect(url_for("board.board_view", idx=idx))

        else:

            flash("글 수정 권한이 없습니다.")

            return redirect(url_for("board.lists"))

그리고,

섬머노트 관련된 부분은 해결되었습니다.

~~~역시나 저의 오타가 있었네요...ㅠㅠ

감사합니다.

0

남박사님의 프로필 이미지
남박사
지식공유자

말씀하신 내용만으로는 사실 뭐가 문제인지 알 수는 없으나 summernote 에만 드랙앤드롭이 되지 않는건지 아니면 브라우저 자체에 드랙앤드롭이 되지 않는건지부터 확인해봐야 할 듯 합니다. 만약 브라우저 자체에 드랙드롭이 되지 않는거라면 아마 윈도우 탐색기와 브라우저의 실행 권한이 다른경우 그럴 수 있습니다.

만약 그 문제가 아니라면 섬머노트의 설정 문제 혹은 섬머노트 자체의 문제일 수 있는데 이런경우 빈 html 에 아무 기능도 구현하지 않고 섬머노트만 추가하여 테스트를 해보며 원인을 찾아봐야 할 듯 합니다.

https://summernote.org/getting-started/#simple-example

위 링크가 섬머노트 공식 사이트인에 여기서 제공하는 샘플페이지에서 드랙드롭을 해보시기 바랍니다.

그리고 체크박스 문제는 수정완료 시점인 edit 라우트 쪽 POST 메서드 일때 어떤 일이 일어나는지를 확인해보셔야 할 듯 합니다. edit POST 쪽에서

attach_file = data.get("attachfile")

원래 DB에 저장된 첨부파일명을 받아 attach_file 변수에 저장한 후

if "attachfile" in request.files:

새로운 첨부파일이 있는지를 확인하고 첨부 파일이 없다면

if deleteoldfile == "on":

기존 파일을 체크하는 옵션을 체크 후 기존 파일 체크 옵션이 on 이 아닌 경우

filename = attach_file

기존의 파일명을 현재 파일명으로 설정하여 해당 filename 을 db에 저장하는 방식으로 구현되어있습니다.

kimbarb님의 프로필 이미지

작성한 질문수

질문하기