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

오무라이스님의 프로필 이미지

작성한 질문수

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

등록된 글 수정시

20.04.17 13:10 작성

·

176

1

로그인 보안 강화까지 강의를 들었습니다.

보안강화된 아이디로 로그인 한 후, 작성한 글 수정이 안됩니다.

아래와 같은 에러가 나타납니다.

  • File "C:\Python\myweb\main\board.py", line 245, in board_edit

     
                board.update_one({"_id": ObjectId(idx)}, {
                    "$set": {
                        "title": title,
                        "contents": contents,
                        "attachfile": filename
                    }
                })
                flash("수정되었습니다.")
                return redirect(url_for("board.board_view", idx=idx))
            else:
UnboundLocalError: local variable 'filename' referenced before assignment
adit.html
{% extends "main.html" %}
{% block contents %}

<script>
    $(document).ready(function () {
        $("#summernote").summernote({
            heigth: 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) {
    //    console.log(image);
    //}

    function uploadImage(image) {
        //객체를 설정한다(담기 위한 그릇)
        var data = new FormData();
        console.log(image)
        // 넘어온 이미지 객체를 append 시켜준다. 
        data.append("image"image);
        var csrf_token = "{{csrf_token()}}";

        $.ajaxSetup({
            beforeSend: function(xs){
                //s가 GET|HEAD|OPTIONS|TRACE 중 하나이면 
                if(!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(s.type)){
                    //x(내부통신을 전담하는 객체 에이젝스에서)가 해더를 추가하는데 해더의 이름은 X-CSRFToken이고 값은  csrf_token이다.
                    x.setRequestHeader("X-CSRFToken"csrf_token)
                }
            }
        });
        $.ajax({
            //페이지 이동없이 내부적으로 통신한다.
            //url 주소로 찾아준다. 
            url: "{{url_for('board.upload_image')}}",
            cache: false,
            contentType: false,
            processData: false
            //데이터를
            data: data,
            //포스트 형식으로 전달한다.
            type:"post",
            //위 데이터 전송이 성공하면
            success: function(url) {
                //<omg src=서버주소> 태그를 생성해준다.
                var image = $("<img>").attr("src",url).css('max-width'"900px");
                //insertNode:이미지를 강제적으로 추가해준다. 
                $("#summernote").summernote("insertNode",image[0]);
            },
            //성공하지 못하면 오류를 보여준다.
            error: function(data) {
                console.log(data);
                alert(data);
            }
        });
    }
</script>

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

    if($.trim($("#summernote").val()) == "") {
        alert("내용을 입력하세요.");
        $("#summernote").focus();
        return false;
    }

    return true;
}
</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="title" 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-group">
        <label for="contents">내용</label>
        <textarea rows ="8" class="form-control" 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>

<!--
<table>
    <form name="form" method="POST" action="{{url_for('board.board_edit', idx=data._id)}}">
    <tr>
        <td>작성자</td>
        <td><input type="text" name= "name" value="{{session['name']}}" readonly></td>
    </tr>
    <tr>
        <td>제목</td>
        <td><input type="text" name= "title" value={{data.title}}></td>
    </tr>
    <tr>
        <td>내용</td>
        <td><textarea name= "contents">{{data.contents}}</textarea></td>
    </tr>
    <tr>
        <td colspan="2"><input type="submit"></td>
    </tr>
    </form>
</table> -->
{% endblock %}
#board.html
from main import *
# 큰 프로젝트시 /board/list 같은 것을 만들어주는 것
from flask import Blueprint
# 게시판에 관한 내용
from flask import send_from_directory

#이 블루프린트의 이름은 "board"이고 블루프린트가 선언된 곳에 앞에다가는 모두 "/board"를 붙여준다.
blueprint = Blueprint("board"__name__url_prefix="/board")

# 첨부파일을 삭제하는 기능  
def board_delete_attach_file(filename):
    # abs : 절대경로
    abs_path = os.path.join(app.config["BOARD_ATTACH_FILE_PATH"], filename)
    # 파일이 abs에 존재하면
    if os.path.exists(abs_path):
        os.remove(abs_path)
        return True
    return False


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

@blueprint.route("/images/<filename>")
def board_images(filename):
    # send_from_directory : 밖에 폴더에 접근하기 위한 함수
    # app.config["BOARD_IMAGE_PATH"] : 절대경로
    # filename : 그 파일의 이름
    # 독립적인 폴더에 저장하고 웹에서는 이와 같은 방식으로 접근한다.
    return send_from_directory(app.config["BOARD_IMAGE_PATH"], filename)        

@blueprint.route("/files/<filename>")
def board_files(filename):
    # as_attachment=True :다운로드 형태
    return send_from_directory(app.config["BOARD_ATTACH_FILE_PATH"], filename, as_attachment=True)        


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

    search = request.args.get("search", -1type=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(query).skip((page - 1) * limit).limit(limit).sort("pubdate",-1)
  
    # 전체 게시물의 총 개수
    tot_count = board.find(query).count()
    # 마지막 페이지의 수를 구합니다.
    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=datas,
                            limit=limit, 
                            page=page,
                            block_start=block_start, 
                            block_last=block_last,
                            last_page_num=last_page_num,
                            search=search,
                            keyword=keyword,
                            title="게시판 리스트" #main.html의 title 값이 된다.
                            )

# 상세페이지
@blueprint.route("/view/<idx>")
@login_required
# idx 값을 따라서 몽고 db에서 값을 가져오는 역할
def board_view(idx):
    # post 노출되지 않고 GET은 노출 됨
    # 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"""),
                "attachfile": data.get("attachfile","")
            }

            return render_template("view.html"result=result, page=page, search=search, keyword=keyword, title="글 상세보기")
    return abort(404)
    #
# 글쓰기   
@blueprint.route("/write"methods=["GET""POST"])
@login_required
def board_write():
    # if session.get("id") is None:
    #    return redirect(url_for("member_login"))
    if request.method == "POST":
        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))

        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,

        }
        # 위 post에 attachfile가 추가 되어 나온다.
        if filename is not None:
            post["attachfile"] = filename


        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="글 작성")

@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""")
        
        # db에 접속하여
        board = mongo.db.board
        # id 값을 가져와서
        data = board.find_one({"_id": ObjectId(idx)})
        # 둘의 값이 같으면
        if session.get("id") == data.get("writer_id"):
            file = None
            # 첨부파일이 추가된 상태라면 
            if "attachfile" in request.files:
             file = request.files["attachfile"]
             # 파일이 있고 확장자(allowed_file(file.filename):)에도 문제가 없다면
             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:
                # 체크박스에 체크를 한 경우 on이 들어온다.
                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"))

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

답변 1

1

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

2020. 04. 19. 00:28

보안강화된 아이디로 로그인 하게 되면 이전에 보안강화 되기전에 작성한 글은 문제가 있을 수 있습니다. 새로 아이디를 초기화 하고 다시 시도해보시기 바라며

# 파일이 있고 확장자(allowed_file(file.filename):)에도 문제가 없다면
 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:
    filename = data.get("attachfile")

위에 올려주신 코드의 일부분에서 file 에 문제가 있는 경우의 예외를 위처럼 수정해보시기 바랍니다. else 문이 추가된 부분입니다.