기록과 정리의 공간

[프로젝트] Flask로 게시판 만들기 - 6 본문

프로젝트/게시판1

[프로젝트] Flask로 게시판 만들기 - 6

딸기맛도나쓰 2020. 8. 20. 17:37

Flask로 게시판 만들기 - 6 (참고 강의 링크-인프런 강의)
(공부 하며 기록이 필요한 부분들만 정리함)

  • 개발 환경 : windows 10 / Python 3.8.1 / vscode
  1. 로그인한 유저만 글쓰기 가능하게 하기
  2. 글 수정 기능 추가하기
  3. 글 삭제 기능 추가하기
  4. 글 조회수 증가 시키기

1. 로그인한 유저만 글쓰기 가능하게 하기

  • 포스트와 이어짐
  • 글쓰기 버튼을 누르면 로그인한 사용자에 한하여 글쓰기 페이지로 이동하고, 로그인되어 있지 않다면 로그인 페이지로 이동하게 한다. 로그인이 성공하면, 로그인 전에 사용자가 요청했던 페이지로 이동시키도록 한다.
  • 데코레이터 활용(with functools모듈) : 글쓰기 기능 뿐만 아니라 기타 기능 사용을 위해 유저에게 로그인을 요구하기 위해, 함수마다 로그인 요구 코드를 일일이 작성하는 것은 굉장히 번거로운 일이므로 login_required라는 이름으로 데코레이터를 만들어 필요한 함수에 사용함.
    • 로그인이 성공하면, 로그인 하기 전에 사용자가 요청했던 페이지로 이동시키기 : 데코레이터의 request.url사용자가 요청한 페이지의 url을 가리킨다.(즉, 여기에서는 글쓰기 페이지를 의미함)
  • 세션 객체 활용 : session.get()를 이용해, 사용자의 로그인 여부를 확인한다.
    • member_login함수에서 유저가 로그인할 경우, session객체의 'id'라는 키에 해당 유저의 member컬렉션의 '_id'값(이는 중복이 없는 고유의 값임)을 저장하도록 했다. 따라서, session.get('id')를 통해 이 값이 None인지 아닌지를 확인하면, 로그인 여부를 확인할 수 있다.
  • login.html : next_url값이 존재한다면 input 태그의 type을 hidden으로 하여, form이 submit될 때 next_url값도 함께 submit되도록 함. (html - input태그 hidden 속성)
  • 아래 코드 블럭들의 주석참고
  • board_wirte함수에서 post딕셔너리에 writer_id라는 키를 추가하여 여기에 session.get("id")을 저장하도록 한다. 이는 추후에 글수정, 삭제 시 본인이 작성한 글에 대해서만 수정,삭제가 가능하도록 하기 위함이다.
# 데코레이터 작성

from functools import wraps

def login_required(f):
    @wraps(f)
    def deco_func(*args, **kwargs):
        # 로그인 여부 확인
        if session.get("id") is None:
            return redirect(url_for("member_login", next_url=request.url))
        return f(*args, **kwargs)
    return deco_func
@app.route("/write", methods=["GET", "POST"])
@login_required # 데코레이터 사용
def board_write():
    (중략)
    post = {
            "name": name,
            "title": title,
            "contents": contents,
            "pubdate": current_utc_time,
            # 글 삭제나 수정 시, 본인글만 삭제,수정할 수 있도록 아래 값을 저장
            "writer_id": session.get("id"),
            "view": 0,
        }
<!-- login.html -->

<form name="form" action="/login" method="POST">
    <!-- 바로 아래 코드블럭의 #1에서 넘긴 next_url을 가리킴 -->
    {% if next_url %}
        <input type="hidden" name="next_url" value="{{next_url}}">
    {% endif %}
      (생략)
</form>
@app.route("/login", methods=["GET", "POST"])
def member_login():
    if request.method == "POST":
        email = request.form.get("email")
        password = request.form.get("pass")
        #2 - login.html에서 hidden으로 넘긴 next_url값 가져오기
        next_url = request.form.get("next_url")

        if email == "" or password == "":
            flash("입력되지 않은 값이 있습니다!")
            return render_template("login.html")

        members = mongo.db.members
        data = members.find_one({"email": email})

        if data is None:
            flash("회원 정보가 없습니다!")
            return redirect(url_for("member_login"))
        else:
            if data.get("pass") == password:
                session["email"] = email
                session["name"] = data.get("name")
                session["id"] = str(data.get("_id"))
                session.permanet = True
                #3 - 로그인이 성공할 경우, #2에서 가져온 next_url값이 존재하면 해당 값으로 redirect시킴
                # 그게 아니라면 그냥 글목록 페이지로 이동시킴
                if next_url is not None:
                    return redirect(next_url)
                return redirect(url_for('board_list'))
            else:
                flash("비밀번호가 일치하지 않습니다!")
                return redirect(url_for("member_login"))
        return ""
    else:
        #0 - 글쓰기 버튼을 누르면 로그인되어 있지 않을 경우,
        # board_write함수에 사용한 login_requried 데코레이터에 의해 next_url=request.rul값이 넘어옴
        next_url = request.args.get("next_url", type=str)
        if next_url is not None:
            #1 - login.html에 next_url값을 넘김
            return render_template("login.html", next_url=next_url)
        else:
            return render_template("login.html")

2. 글 수정 기능 추가하기

  • view.html :현재 session["id"]값과 현재 글의 writer_id값이 (위 1번에서 board_write함수의 post 딕셔너리에 추가한) 동일한지 확인하여 같다면 글수정/글삭제 버튼이 보여지도록 한다.
  • board_edit함수 : idx값은 view.html에서 넘겨준 값임. (현재 글의 '_id'값) view.html에서와 마찬가지로 session객체를 이용해 현재 로그인된 사용자가 글 작성자와 일치하는지 확인한다. 일치한다면 edit.html을 렌더링하는데, data값을 edit.html로 넘겨준다.
  • edit.html : write.html과 동일한 코드에다가 제목과 내용 부분의 input태그에 수정 전의 제목과 내용이 보여지도록 value속성을 추가했다.
<!-- view.html -->

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

<html>
(중략)
{% if session["id"] == result.writer_id %}
<a href="{{url_for('board_edit', idx=result.id)}}">글수정</a>
<a href="{{url_for('board_delete', idx=result.id)}}">글삭제</a>
{% endif %}
</html>
# board_edit함수

@app.route("/edit/<idx>", methods=["GET", "POST"])
def board_edit(idx):
    board = mongo.db.board
    data = board.find_one({"_id": ObjectId(idx)}) 
    if request.method == "GET":
        if data is None:
            flash("해당 게시물이 존재하지 않습니다.")
            return redirect(url_for("lists"))
        else:
            if session.get("id") == data.get("writer_id"):
                return render_template("edit.html", data=data)
            else:
                flash("글 수정 권한이 없습니다.")
                return redirect(url_for("board_list"))
    else:
        title = request.form.get("title")
        contents = request.form.get("contents")
        # 또 한번 더 확인,
        if session.get("id") == data.get('writer_id'):
            board.update_one({"_id": ObjectId(idx)}, {
                "$set": {
                    "title": title,
                    "contents": contents,
                }
            })
            flash("수정되었습니다.")
            return redirect(url_for("board_view", idx=idx))
        else:
            flash("글 수정 권한이 없습니다.")
            return redirect(url_for("board_list"))
<!-- edit.html -->

<html>
<style>
    table {
        border: 1px solid black;
    }
</style>
<body>
    <table>
        <form name="form" method="POST" action="/edit/{{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>
</body>
</html>

3. 글 삭제 기능 추가하기

  • 2번 참고
@app.route("/delete/<idx>")
def board_delete(idx):
    board = mongo.db.board
    data = board.find_one({"_id": ObjectId(idx)})
    if session.get("id") == data.get("writer_id"):
        board.delete_one({"_id": ObjectId(idx)})
        flash("삭제 되었습니다.")
    else:
        flash("삭제 권한이 없습니다.")
    return redirect(url_for("board_list"))

4. 글 조회수 증가 시키기

  • 게시물을 클릭할 때 마다 조회수가 1씩 증가되도록 구현함.

  • board_view함수 : find_one함수 대신 find_one_and_update함수를 사용하여 데이터를 찾아옴과 동시에 update할 수 있도록 한다. $inc 연산자(MongoDB - $inc)를 사용하여 view필드의 값을 1증가 시킨다. 중요한 것은 return_document옵션True로 하여 인자로 넘겨줘야한다는 것이다.

    • pymongo - find_one_and_update : find_one_and_update함수기본적으로 update가 되기 전의 document를 리턴한다. udpate된 후의 document를 리턴받고 싶다면, return_document=True를 인자로 넘겨주면 된다.
@app.route("/view/<idx>")
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_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)
    return abort(404)
Comments