파이썬 웹 서비스 만들기

7. 컨텐츠 포스팅 및 관리기능 - 7.3. 컨텐츠(글) 삭제기능

영바이트 2020. 11. 17. 23:03

이전 포스팅 7.2에서 포스팅 수정 기능에 대해 살펴보고 플라스크를 이용해서 기능을 만들어보았다. 글을 수정하는 기능과 더불어 글을 삭제하는 기능도 필요하다. 잊을 권리와 잋혀질 권리도 반드시 필요하다고 생각한다.

 

본격적으로 코딩을 시작하기 전에 글을 삭제하는 과정을 어떻게 진행하면 될 지 생각해보자. 글 제목을 클릭하면 글 관리 페이지로 이동하고 이 관리 페이지에 '삭제하기' 버튼을 추가하는 것이 첫 단계가 될 것 같다.

 

글 제목을 클릭해서 글 관리 페이지로 이동한다.

삭제하기 버튼을 클릭한다

글을 삭제한다.

 

하지만 삭제한 글은 복구할 수 없기 때문에 삭제 전에 한 번 더 확인하는 단계를 거치는게 좋을 것 같다. 아래와 같이 삭제 확인 페이지를 추가해서 실제 글을 삭제하기 전에 확인하도록 해보자.

 

글 제목을 클릭해서 글 관리 페이지로 이동한다.

삭제하기 버튼을 클릭한다

글 내용을 보여주고 '삭제 확인' 버튼을 제공한다.

'삭제 확인' 버튼을 클릭한다.

글을 삭제한다.

 

 

웹 서비스로 만들기 위해 MVL 형태로 다시 정리해보자.

 

● 모델/폼(M)

삭제하기 버튼을 클릭하면 '삭제 확인' 페이지로 이동하고 이 페이지에는 '확인' 버튼이 있다. 따라서 '확인' 버튼을 가진 폼이 필요할 것 같다.

 

● 뷰(V)

- 글 관리 페이지에 '삭제' 버튼(링크)이 필요하다. 이 '삭제' 버튼(링크)을 클릭하면 '삭제 확인' 페이지로 이동한다.

- '삭제 확인' 페이지가 필요하다. '삭제 확인' 페이지에서 '확인' 버튼을 클릭하면 실제로 글이 삭제된다.

 

● 로직(L)

- 글 관리 페이지에서 '삭제하기' 버튼을 클릭하면 '삭제 확인' 페이지를 보여준다.

- '삭제 확인' 페이지에서 '삭제 확인' 버튼을 클릭하면 서버의 DB에서 글을 삭제한다.

 


이제 파이썬 프로그램 코드로 정리한 내용을 옮겨보자. MVL 순서에 따라 모델/폼부터 차례로 프로그램으로 구현해보자. 포스팅(글)과 관련한 기능들이기 때문에 역시 post 패키지 안에 구현하는 것이 가장 적절해보인다.

 

7.3.1. 글 삭제하기 - 모델/폼

 

'확인' 버튼을 가진 입력 폼을 추가한다.

 

'삭제 확인'폼 정의

 

▶ appmain/post/forms.py

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, TextAreaField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError

class PostForm(FlaskForm):
    ...

#### '확인(Confirm)' 버튼을 가진 입력폼 정의
class DeletePostForm(FlaskForm):
    submit = SubmitField('Confirm')

 

'확인'을 버튼으로 처리한 이유는 만약 링크로 연결한 경우 '삭제 확인' 페이지를 거치지 않고 URL 주소만 알면 접근이 가능하기 때문이다.

 

7.3.2. 글 삭제하기 - 뷰

 

먼저 글 관리 페이지에 '삭제' 링크를 추가하자. 페이지 템플릿들은 post 패키지의 상위 패키지인 appmain 패키지의 templates 디렉토리에 모여있다.

 

글 삭제 링크 구현

 

▶ appmain/templates/post.html

{% extends "layout.html" %}
{% block content %}
<div>
    <p>{{post.title}}</p>
    <p>{{post.content}}</p>
    <p>{{post.date_posted}}</p>
    <!-- 로그인한 사용자가 글의 저자와 같다면 수정 링크를 보여준다. -->
    {%if current_user.is_authenticated%}
        {%if current_user.username == post.author%}
            <div>
                <a href="/post/{{post.id}}/update">Update Post</a>

                <!-- 삭제하기 링크 추가 -->
                <a href="/post/{{post.id}}/delete">Delete Post</a>
                <!-- -->
                
            </div>
        {%endif%}
    {%endif%}
</div>
{% endblock content %}

 

다음으로 삭제 확인 페이지를 아래와 같이 만들자. 삭제 확인 페이지는 삭제 대상 글과 정말 삭제할지 확인하는 '확인'버튼으로 구성된다.

 

삭제 확인 뷰(페이지)

 

▶ appmain/templates/delete_post.html

{% extends "layout.html" %}
{% block content %}
<form method="POST" action="">
    {{form.hidden_tag()}} {# Transferring CSRF token = secret key #}
    <div>
        <!-- 글 내용을 보여준다. -->
        <p>{{post.title}}</p>
        <p>{{post.content}}</p>
        <p>{{post.date_posted}}</p>
        
        <!-- 로그인한 사용자가 글의 저자와 같다면 삭제 확인 버튼을 보여준다. -->
        {%if current_user.is_authenticated%}
        {%if current_user.username == post.author%}
        <div>
            <p>Confirm deletion?</p>
            <!-- 삭제 확인 버튼 -->
            {{form.submit()}}
        </div>
        {%endif%}
        {%endif%}
    </div>
</form>
{% endblock content %}

 

삭제 확인을 버튼으로 처리한 이유는 이를 링크로 구현할 경우 URL 주소만 알면 글을 삭제할 수도 있기 때문이다.

 

7.3.3. 글 삭제하기 - 로직

 

글 삭제하기는 아래와 같이 두 단계로 진행된다. 맨 첫 단계인 링크 클릭은 사용자의 행동이므로 웹 서비스가 해야할 일에 포함시키지 않았다.

 

글 관리 페이지에서 '삭제하기' 링크를 클릭한다.

삭제 확인 페이지를 보여준다.

DB에서 글을 삭제한다.

 

웹 서버 입장에서는 ①삭제 확인 페이지를 보여주는 것, ②DB에서 글을 삭제하는 것 두 기능을 구현할 필요가 있다. 이 두 기능을 하나의 함수로 구현할 수 있다. 정보를 입력 받는 페이지들을 처리하는 로직과 마찬가지로 '삭제(=새 글 쓰기)' 링크를 클릭하면 삭제 확인(=새 글 작성) 페이지를 제공하고, '확인(=제출)' 버튼을 클릭하면 DB에서 데이터를 삭제(=기록)하도록 구현하면 된다. 글 삭제 기능도 글 관리 기능의 한 부분이므로 post 패키지 아래에 작성하자.

 

'글 삭제' 로직 구현

 

▶ appmain/post/routes.html

from flask import render_template, url_for, redirect, request, abort, Blueprint
from flask_login import current_user, login_required
from appmain import db
from appmain.post.forms import PostForm, DeletePostForm
from appmain.post.models import Post

post = Blueprint('post', __name__)

@post.route("/post/new", methods=['GET', 'POST'])
@login_required
def newPost():
    ... 생략

@post.route("/post/<int:postId>")
def displayPost(postId):
    ... 생략

@post.route("/post/<int:postId>/update", methods=['GET', 'POST'])
@login_required
def updatePost(postId):
    ... 생략

#### 글 삭제 로직
@post.route("/post/<int:postId>/delete", methods=['GET', 'POST'])
#### 로그인 여부를 확인한다.
@login_required
def deletePost(postId):
    #### 삭제 대상 글을 보여주기 위해 DB에서 글을 가져온다.
    content = Post.query.get_or_404(postId)
    form = DeletePostForm()
    
    #### 로그인한 사용자가 글 작성자와 다를 경우 권한 없음(403) 오류로 처리한다.
    if content.author != current_user.username:
        abort(403)
        
    #### '삭제 확인' 버튼을 클릭한 경우
    #### '삭제 확인' 버튼을 클릭하면 POST 방식으로 데이터를 전송하게 된다.
    if form.validate_on_submit():
        #### 아래 두 줄의 주석을 해제하면 DB에서 실제로 글 데이터를 삭제하게 된다.
        # db.session.delete(content)
        # db.session.commit()
        print('Post deleted successfully: ', content.title)
        
        #### 글 삭제를 마치면 웹 서비스의 메인 페이지로 이동한다.
        return redirect(url_for('main.home'))
        
    #### 글 관리 페이지의 '삭제'링크를 클릭한 경우 GET 방식으로 페이지에 접근하게 된다.
    #### 따라서 삭제 확인 페이지를 제공하는 아래 코드가 실행된다.
    elif request.method == 'GET':
        return render_template('delete_post.html', title='Confirm Post Deletion', post=content, form=form)

 

글 삭제 함수 deletePost를 감싸고 있는 파이썬 데코레이터(decorator) @post.route()에 전달되는 상대 주소를 보면 중간에 <int/postId>와 같은 표현을 볼 수 있다. 이 표현의 의미는 주소의 해당 자리에 정수(int)형 데이터가 위치하고 이를 'postId'라는 이름의 변수로 받겠다는 뜻이다.

 

글 관리 페이지의 글 삭제 페이지 링크를 보면 <a href="/post/{{post.id}}/delete">와 같이 기술되어 있는데 글(post)의 id(글에 설정한 일련번호, post 패키지의 models.py의 글(post) 모델 참조) {{post.id}}, 즉, 정수 값을 링크에 포함하도록 되어 있음을 볼 수 있다. 로직에서는 이 값을 받아서 지정된 글을 DB에서 가져오고 삭제하게 된다.

 


 

7.3.4. 글 삭제 기능 실행

 

MVL 각각에 해당하는 내용들을 구현했기 때문에 더 이상 만들어야 할 것은 없다. 프로젝트 실행 스크립트를 실행시키고 글 삭제 기능을 시험해보자.

 

프로젝트 실행 스크립트

 

예제 진행을 위해 사용하는 파이썬 개발 환경인 파이참(PyCharm) 기준으로 스크립트 파일 위에 마우스 포인터를 위치시키고 마우스 오른쪽 버튼을 클릭하면 팝업창(Pop-up window)이 나타나고 실행(Run) 메뉴를 선택할 수 있다.

 

팝업 창 - run 스크립트 실행(Run)

 

프로젝트 실행 스크립트를 실행시키고 플라스크 웹 서버가 동작하면 웹 브라우저를 열고 플라스크 웹 서버에 지정한 주소로 접근하자. 지금까지 예제들에서는 127.0.0.1의 5000번 포트를 통해 플라스크 웹 서버가 요청을 받도록 지정하였다.

 

프로젝트 실행 - 서비스 접속

 

포스팅을 생성한 사용자의 ID로 로그인 하고 글 제목을 클릭해서 글 관리 페이지로 이동해보자. 포스팅(글)을 생성한 사용자와 로그인한 사용자가 같다면 아래 그림과 같이 'Delete Post' 링크, 즉 삭제 확인 링크가 보일 것이다.

 

글 관리 페이지

 

이제 'Delete Post' 링크를 클릭해서 삭제 확인 페이지로 넘어가자.

 

글 삭제확인 페이지

 

삭제 확인 페이지에서 '확인(Confirm)' 버튼을 클릭하면 글이 삭제되고 서비스의 첫 화면으로 이동된다. 예제에서는 후속 예제들의 진행을 위해 DB에서 글을 실제로 삭제하지는 않고 아래와 같이 파이참 메시지 창에 글이 삭제되었음을 알리는 메시지를 출력하도록 하였다. 확인 버튼을 클릭해서 확인해보자.

 

글이 삭제되었음을 알리는 메시지

 

실제 서비스를 만들 때는 로직 스크립트(appmain/post/routes.html)의 deletePost 함수의 DB 동작 명령 db.session.delete와 db.session.commit의 주석을 해제하면 DB에서 글을 삭제할 수 있다.

 

...

#### 아래 두 라인의 주석을 해제하면 DB에서 실제 글 정보가 삭제된다.
# db.session.delete(content)
# db.session.commit()

...