7. 컨텐츠 포스팅 및 관리기능 - 7.1. 포스팅
이전 포스팅을 통해 '사용자 등록(계정 생성) - 로그인 - 사용자 페이지(계정 정보 수정)'에 대해 알아보고 실제 코드도 작성해 보았다. 로그인 과정이 추가되긴 했지만, 기본적인 동작은 사용자로부터 정보 받아오기, 받아온 정보를 서버(의 DB)에 저장하기로 요약할 수 있다.
이번 포스팅부터는 웹 서비스 이용자가 글을 남길 수 있는 기능을 알아볼 것이다. 글쓰기 기능을 구성하는 핵심 요소를 먼저 설명하면 역시 '사용자로부터 정보 받아오기'와 '받아온 정보를 서버에 저장하기'라고 할 수 있다.
① 사용자로부터 정보 받아오기
- 글(포스팅)을 표현하는 모델 만들기 = 모델
- 글 입력 페이지 만들기= 폼, 뷰
② 받아온 정보를 서버에 저장하기
- 입력받은 정보를 정리하여 DB에 저장하기 = 로직
할 일의 순서를 정했으니 프로젝트를 생성하고 실제 코딩을 하면서 더 자세히 살펴보자.
7.1.1. 프로젝트 생성
아래 그림과 같이 새 프로젝트 디렉토리(05_posting으로 이름하였다)를 만들고 이전 프로젝트의 내용을 복사해서 가져오자.
7.1.2. 모델/폼 정의
포스팅(=글) 입력을 받기 위해서는 서버의 데이터베이스(DB)에 저장될 데이터 모델과 이 데이터 모델을 채우기 위해 사용자에게 입력받을 폼이 필요하다. 먼저 포스팅을 구성하는 요소들을 정리해보자. 아래와 같은 요소들로 구성할 수 있을 것이다.
- 글 제목
- 작성자
- 작성 시간
- 글 본문
포스팅 관련한 코드들을 모아놓을 디렉토리를 post라는 이름으로 만들고 데이터 모델을 정의하자.
▶ appmain/post/models.py
#### 글이 작성된 시간을 얻기위해 datetime 패키지를 가져온다.
from datetime import datetime
#### DB에 저장될 모델이기 때문에 SQLAlchemy 객체인 db를 가져온다.
from appmain import db
#### DB에 저장될 모델이기 때문에 SQLAlchemy 객체인 db의 Model 클래스를 상속받아 정의한다.
class Post(db.Model):
#### id = DB에 저장할 때 부여되는 고유 번호
id = db.Column(db.Integer, primary_key=True)
#### 제목
title = db.Column(db.String(100), nullable=False)
#### 작성자
author = db.Column(db.String(30), nullable=False)
#### 작성 시간. 기본값은 현재 시간(datetime.utcnow)이다.
datePosted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
#### 글
content = db.Column(db.Text, nullable=False)
웹 서버는 사용자로부터 정보를 입력받아 데이터 모델을 채우게된다. 입력받을 데이터인 '폼(form)'도 정의하자.
▶ 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):
#### 제목 - 문자열(String) 객체
title = StringField('Title', validators=[DataRequired(), Length(min=1, max=90)])
#### 글 - 글 상자(TextArea) 객체
content = TextAreaField('Content', validators=[DataRequired(), Length(min=1, max=200)])
#### 제출 버튼
submit = SubmitField('Post')
폼을 보면 작성자와 작성 시간은 별도로 정의하지 않았다. 그 이유는 작성자는 로그인한 바로 그 사용자의 정보가 되기 때문이고, 작성 시간은 글이 DB에 저장되는 시간을 시스템에서 얻어와서 쓰면 되기 때문이다.
7.1.3. 뷰(페이지) 만들기
모델/폼을 정의했으므로 다음 단계인 뷰(=페이지)를 만들자. 아래와 같이 페이지 템플릿 디렉토리 아래에 작성할 것이다.
▶ appmain/templates/create_post.html
{% extends "layout.html" %}
{% block content %}
<form method="POST" action="">
{{form.hidden_tag()}} {# Transferring CSRF token = secret key #}
<!-- 제목 입력 부분 -->
<div>
{{form.title.label()}}
{{form.title()}}
{%if form.title.errors%}
{%for error in form.title.errors%}
<span>{{error}}</span>
{%endfor%}
{%endif%}
</div>
<!-- 글 본문 입력 부분 -->
<div>
{{form.content.label()}}
{{form.content()}}
{%if form.content.errors%}
{%for error in form.content.errors%}
<span>{{error}}</span>
{%endfor%}
{%endif%}
</div>
<!-- 제출 버튼 -->
<div>
{{form.submit()}}
</div>
</form>
{% endblock content %}
여기까지 '모델/폼 - 뷰' 작성을 마쳤고 이제 로직(logic)을 작성할 차례다.
7.1.4. 입력 정보 처리 로직 작성
아래와 같이 post 디렉토리 아래에 routes.py 파일을 만들고(사용자가 특정 URL에 접속했을 때 플라스크의 route( ) 함수를 실행시키게 되므로 이름을 routes로 지정하였다) 아래와 같이 입력받은 내용을 DB에 저장하는 로직을 만들자.
▶ appmain/post/routes.py
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
from appmain.post.models import Post
#### post라는 이름의 Blueprint 객체를 만들고 이 Blueprint 객체의 route 기능을 활용한다.
#### 프로젝트를 구성하는 하위 패키지들의 Blueprint 객체들은 프로젝트 메인 패키지인 appmain 패키지에서
#### 플라스크 객체에 통합(등록)될 것이다.
post = Blueprint('post', __name__)
@post.route("/post/new", methods=['GET', 'POST'])
@login_required
def newPost():
form = PostForm()
#### 사용자가 '제출' 버튼을 누르면 아래 if 블록 내용이 실행된다.
if form.validate_on_submit():
#### Post 객체를 만들고 제목, 글 내용, 작성자, 작성 시간을 설정한다.
content = Post(title = form.title.data, content = form.content.data, author = current_user.username)
#### DB에 저장한다.
db.session.add(content)
#### 변경(저장) 사항을 반영한다.
db.session.commit()
print('A new post is created: ', form.title.data)
return redirect(url_for('main.home'))
#### 사용자가 '새 포스팅' URL(/post/new)로 접근하면 글 입력 페이지를 보여준다.
return render_template('create_post.html', title='New Post', form=form)
post 디렉토리를 패키지로 만들고 필요한 초기화를 하기 위해 __init__.py 파일을 작성한다.
post 패키지에는 특별히 초기화해야할 내용이 없기 때문에 내용은 비워놓는다. 하지만 post 디렉토리가 파이썬 패키지 디렉토리임을 선언하기 위해 파일은 반드시 존재해야 한다.
▶ appmain/post/__init__.py
7.1.5. 플라스크 서비스에 post 패키지 통합
사용자로부터 글 입력을 받고 서버의 DB에 저장하는 기능을 가진 post 패키지를 플라스크 서비스에 통합시키자. 통합 과정에서 할 일들은 아래와 같다.
① post 패키지를 route 로직(Blueprint 객체) 등록
② 글 입력 페이지(포스팅 페이지) 링크 작성
③ 포스팅된 내용 보여주기
하나씩 만들어보자.
① post 패키지 route 로직(Blueprint 객체) 등록
▶ appmain/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import pymysql
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
app = Flask(__name__)
app.config['SECRET_KEY'] = 'd2eb5380841f5fdc4b70bb3e8be4a614'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://flaskweb:your_password@localhost:3306/flaskdb'
db = SQLAlchemy(app)
loginManager = LoginManager(app)
#loginManager.login_view = 'login'
bcrypt = Bcrypt(app)
from appmain.user.routes import user
#### 새로 추가된 부분
#### post 패키지에서 post라고 명명된 blueprint 객체를 가져온다.
from appmain.post.routes import post
from appmain.routes import main
db.create_all()
app.register_blueprint(user)
#### 새로 추가된 부분
#### post blueprint 객체를 플라스크 앱에 등록한다.
app.register_blueprint(post)
app.register_blueprint(main)
② 글 입력 페이지(포스팅 페이지) 링크 작성
▶ appmain/templates/layout.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="{{url_for('static', filename='css/main.css')}}">
{%if title%}
<title>{{title}}</title>
{%else%}
<title>Flask Exercise</title>
{%endif%}
</head>
<body>
<a href="{{url_for('main.home')}}">Home</a>
<a href="{{url_for('main.about')}}">About</a>
<div>
{%if current_user.is_authenticated%}
<a href="{{url_for('user.logout')}}">Logout</a>
<a href="{{url_for('user.account')}}">Account</a>
{%else%}
<a href="{{url_for('user.login')}}">Login</a>
<a href="{{url_for('user.register')}}">Register</a>
{%endif%}
</div>
<!-- 이 부분이 추가된 부분입니다 -->
<div>
{%if current_user.is_authenticated%}
<a href="{{url_for('post.newPost')}}">New Post</a>
{%endif%}
</div>
<!-- -->
<div>
{%block content%}{%endblock content%}
</div>
</body>
</html>
③ 포스팅된 내용 보여주기
포스팅된 내용을 DB에서 읽어와 웹 서비스의 메인 페이지에서 보여주도록 하였다. 웹 서비스의 메인 페이지 관리 로직이 기술되어 있는 appmain 패키지의 routes.py 파일을 수정한다.
▶ appmain/routes.py
from flask import render_template, Blueprint
#### DB의 포스팅 테이블의 SQLAlchemy 모델을 가져온다.
from appmain.post.models import Post
main = Blueprint('main', __name__)
@main.route("/")
@main.route("/home")
def home():
##### DB에서 Post 테이블의 전체 내용을 읽어온다.
posts = Post.query.all()
##### 읽어온 내용을 메인 페이지에서 보여준다.
return render_template("home.html", posts=posts)
@main.route("/about")
def about():
return render_template("about.html", title='About')
전체 프로젝트 내용을 저장하고 프로젝트 실행 스크립트(run.py)를 실행시키자. 그리고 웹 브라우저를 열고 웹 서비스(http://127.0.0.1:5000)로 접속한다.
로그인하고 나면 아래와 같이 새로운 글쓰기 링크가 생긴 것을 볼 수 있다.
링크를 클릭해서 글쓰기 페이지로 접근하면 아래와 같이 포스팅 페이지를 볼 수 있다.
제목과 글을 입력하고 Post 버튼을 클릭하면 홈 화면으로 이동하고 작성한 포스팅이 게시되어 있는 것을 볼 수 있다.
웹 서비스 DB의 내용을 윈도우 명령 프롬프트를 통해 살펴보면 설정한 테이블인 'post'에 입력한 포스팅이 저장되어 있는 것을 확인할 수 있다.
■