티스토리 뷰

Python

Flask app: RESTful-blog

daylee de vel 2021. 7. 7. 16:32

100 Days of Code - The Complete Python Pro Bootcamp for 2021
Day 67 - Advanced - Blog Capstone Project Part 3 - RESTful Routing

Goals: Building a RESTful Blog with Editing! CRUD로 글 수정 기능을 가진 블로그 만들기

set FLASK_APP=mian.py
set FLASK_ENV=development
(한번 지정해 놓으면 계속 사용가능)

서버 시작
flask run 
서버 연결 끊기
ctrl + c

Requirement 1 - Be Able to GET Blog Post Items

1. 데이터베이스에서 모든 데이터를 가져오려면 db.session.query(BlogPost).all()을 사용한다. html에서 사용하기 위해 all_posts 파라미터에 DB에서 가져온 데이터를 값으로 넣어준다.

main.py

##CONFIGURE TABLE
class BlogPost(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(250), unique=True, nullable=False)
    subtitle = db.Column(db.String(250), nullable=False)
    date = db.Column(db.String(250), nullable=False)
    body = db.Column(db.Text, nullable=False)
    author = db.Column(db.String(250), nullable=False)
    img_url = db.Column(db.String(250), nullable=False)

~생략~

@app.route('/')
def get_all_posts():
    # Fetch objects from database
    posts = db.session.query(BlogPost).all()
    return render_template("index.html", all_posts=posts)

 

2-1. html에서 all_posts = posts 는 객체를 담은 리스트 형태이다. 따라서 반복을 줄이고자 for 문을 사용한다. {% for post in all_posts %}

  index.html

  {% for post in all_posts %}
        <div class="post-preview">
          <a href="{{ url_for('show_post', index=post.id) }}">
            <h2 class="post-title">
              {{post.title}}
            </h2>
            <h3 class="post-subtitle">
              {{post.subtitle}}
            </h3>
          </a>
          <p class="post-meta">Posted by
            <a href="#">{{post.author}}</a>
            on {{post.date}}</p>
        </div>
        <hr>
        {% endfor %}

2-2 포스트 제목,소제목을 눌렀을 때 해당 포스트 페이지로 보내주려면 a href {{ url_for('show\_post', index=post.id) }}을 이용해 링크로 연결해주어야한다.

보내줄 페이지는 show_post함수이고 파라미터로 포스트 아이디를 갖는다. index 파라미터 값으로 show_post에서 지정한 post를 받는다.

@app.route("/post/<int:index>")
def show_post(index):
    requested_post = BlogPost.query.get(index)
    return render_template("post.html", post=requested_post)

 

 

Requirement 2 - Be Able to POST a New Blog Post

새로운 블로그 포스트 만들기

1. /new_post는 GET과 POST method를 가진다. (선)POST가 폼을 가져오면, (후)GET은 입력받은 데이터를 DB에 저장하고 '/' url로 이동한다.

2. #GET에서 if form.validate_on_submit(): 으로 입력받은 form이 form 클래스에서 지정한 validators를 통과했는지 알 수 있다. 만약 통과 됐다면 DB에 새로운 row를 만들기 위해 new_post = BlogPost(title=form.title.data ...) 으로 form에서 입력받은 데이터를 넣어준다. db.session.add(new_post) 와 commit()으로 DB에 저장한다.

3. HTML파일에서 Jinja2 템플릿을 바꿀 때마다 서버를 재시작해줘야 변화가 적용된다.


##WTForm
class CreatePostForm(FlaskForm):
    title = StringField("Blog Post Title", validators=[DataRequired()])
    subtitle = StringField("Subtitle", validators=[DataRequired()])
    author = StringField("Your Name", validators=[DataRequired()])
    img_url = StringField("Blog Image URL", validators=[DataRequired(), URL()])
    body = CKEditorField("Blog Content", validators=[DataRequired()])
    submit = SubmitField("Submit Post")

~생략~

@app.route("/new_post", methods=["GET", "POST"])
def new_post():
    form = CreatePostForm()
    # GET
    if form.validate_on_submit():
        new_post = BlogPost(
            title=form.title.data,
            subtitle=form.subtitle.data,
            body=form.body.data,
            img_url=form.img_url.data,
            author=form.author.data,
            date=date.today().strftime("%B %d, %Y")
        )
        db.session.add(new_post)
        db.session.commit()
        return redirect(url_for("get_all_posts"))
    # POST
    return render_template("make-post.html", form=form)

4. html에서 wtform을 사용하려면 import해줘야 한다. {% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form, novalidate=True) }} 에서 form 은 new_post()에서 받아온 값이다. form=form
5. CKeditor를 사용하려면 {{ ckeditor.load() }} 로 로드해주고, {{ ckeditor.config(name='body') }} config에서 ckeditor를 사용할 요소를 지정해준다. name='body'

CKeditor Useful Docs:

https://flask-ckeditor.readthedocs.io/en/latest/basic.html
https://pythonhosted.org/Flask-Bootstrap/forms.html
https://flask-wtf.readthedocs.io/en/stable/

make-post.html


{% import "bootstrap/wtf.html" as wtf %}

~생략~
          <!--Load ckeditor-->
        {{ ckeditor.load() }}
        {{ ckeditor.config(name='body') }}

         <!-- Add WTF quickform-->
        {{ wtf.quick_form(form, novalidate=True) }}

 

 

Requirement 3 - Be Able to Edit Existing Blog Posts

포스트 수정하기 

1. 포스트를 보여주는 post.html 의 마지막 부분에는 Edit Post버튼이 있다. 이 버튼을 누르면 포스트 수정화면으로 넘어간다. {{url_for('edit_post', post_id=post.id)}} 이 버튼은 /edit-post/<post_id> 라우트에 GET request를 만든다.

<a class="btn btn-primary float-right" href="{{url_for('edit_post', post_id=post.id)}}">Edit Post</a>

2. edit_post(post_id)는 GET, POST method를 가진다. POST는 DB에서 수정할 포스트를 불러온다. 이 때 form에 불러온 포스트의 내용이 미리 담겨있어야 한다. 

@app.route("/edit_post/<post_id>", methods=["GET", "POST"])
def edit_post(post_id):
    post_to_edit = BlogPost.query.get(post_id)
    edit_form = CreatePostForm(
        title=post_to_edit.title,
        subtitle=post_to_edit.subtitle,
        img_url=post_to_edit.img_url,
        author=post_to_edit.author,
        body=post_to_edit.body
    )
    #GET: save changes in edited post
    if edit_form.validate_on_submit(): #request.method == "GET":
        post_to_edit.title = edit_form.title.data
        post_to_edit.subtitle = edit_form.subtitle.data
        post_to_edit.body = edit_form.body.data
        post_to_edit.img_url = edit_form.img_url.data
        post_to_edit.author = edit_form.author.data
        post_to_edit.date = post_to_edit.date
        db.session.commit()
        return redirect(url_for("show_post", index=post_id))
    #POST: fetch post to edit
    return render_template("make-post.html", form=edit_form, is_edit=True)

3. 수정 할 포스트를 query.get()으로 가져온다. form 객체(edit_form)를 만들고 불러온 포스트의 내용을 form에 넣어준다. (예시title=post_to_edit.title) 받아온 데이터를  form=edit_form 파라미터로 html 템플릿에 넘겨준다.

4. is_edit=True 파라미터와 jinja if문을 사용해 "make-post.html" 템플릿을 공유하는 /new-post 라우트(빈 form)와 /edit-post/ 라우트(불러온 포스트로 채워진 form)을 구분시켜준다.

       {% import "bootstrap/wtf.html" as wtf %}
~생략~
       {% if is_edit: %}
            {{ wtf.quick_form(form, novalidate=True) }}
        {% else: %}
            {{ wtf.quick_form(form, novalidate=True) }}
        {% endif %}

make-post.html" 템플릿을 공유하는  /new-post 라우트(왼쪽)와 /edit-post/ 라우트 (오른쪽)

5.

#GET에서 if form.validate_on_submit(): 이 True이면 DB에 있던 기존 데이터를 업데이트 해준다.

앞의 new_post()와 다르게 edit_post()는 새로운 row를 저장하는 것이 아니라 업데이트 해주는 것이다. 따라서 db.session.add(new_post) 와 commit()으로 DB에 저장하지 않고, post_to_edit.title = edit_form.title.data 객체에 입력받은 새 값을 넣어준뒤 add()하지 않고 바로 db.session.commit() 한다. 

    #GET: save changes in edited post
    if edit_form.validate_on_submit(): #request.method == "GET":
        post_to_edit.title = edit_form.title.data
        post_to_edit.subtitle = edit_form.subtitle.data
        post_to_edit.body = edit_form.body.data
        post_to_edit.img_url = edit_form.img_url.data
        post_to_edit.author = edit_form.author.data
        post_to_edit.date = post_to_edit.date
        db.session.commit()
        return redirect(url_for("show_post", index=post_id))

 

Requirement 4- Be Able DELETE Blog Posts 

1. 삭제 버튼 링크를 만든다.  

<a href="{{ url_for('delete_post', post_id=post.id) }}">✘</a>

2. 삭제할 포스트를  DB에서 get()해준뒤 .delete(), .commit() 을 해주고 get_all_posts 라우트로 redirect해준다. 

@app.route("/delete/<post_id>")
def delete_post(post_id):
    post_to_delete = db.session.query(BlogPost).get(post_id)
    db.session.delete(post_to_delete)
    db.session.commit()
    return redirect(url_for("get_all_posts"))

 

 

깃허브 링크

 

day-lee/RESTful-blog

Contribute to day-lee/RESTful-blog development by creating an account on GitHub.

github.com

 

 

 

 

'Python' 카테고리의 다른 글

Flask app: cafe_api  (0) 2021.07.05
Flight Deal  (0) 2021.06.21
TIL Python Basics Day 30 - Errors, Exceptions and JSON Data  (0) 2021.06.17
파이썬 복습: Computational thinking  (1) 2021.06.10
Udemy Python Pro Bootcamp by Angela Yu  (0) 2021.06.07
댓글