Notice
Recent Posts
Recent Comments
05-17 21:28
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

Byeol Lo

Flask - 7. Tutorial Blog Blueprint 본문

BackEnd/Flask

Flask - 7. Tutorial Blog Blueprint

알 수 없는 사용자 2024. 2. 15. 16:48

이제 배웠던 것을 토대로 blog의 blueprint를 구현하자.

 

The Blueprint

# flaskr/blog.py

from flask import (
    Blueprint
)

bp = Blueprint('blog', __name__)

 

어플리케이션을 시작할때 __init__()에서 청사진을 등록하자.

# flaskr/__init__.py

def create_app():
    app = ..
    # omitted
    
    from . import blog
    app.register_blueprint(blog.bp)
    app.add_url_rule('/', endpoint='index')
    
    return app

app_url_rule이 생소할 수 있는데 /로 들어온 것은 index의 endpoint로 여기겠다는 의미이다.

 

Index

인덱스는 모든 포스트에 보여지는 숫자들이다. SQL의 JOIN을 통해 현재 접속한 유저의 게시글들만 볼 수 있도록 할 수 있다.

# flaskr/blog.py
@bp.route('/')
def index():
    db = get_db()
    posts = db.execute(
        'SELECT p.id, title, body, created, author_id, username '
        ' FROM post p JOIN user u ON p.author_id = u.id'
        ' ORDER BY created DESC'
    ).fetchall()
    return render_template('blog/index.html', posts=posts)

가지고 있는 모든 포스트들을 띄워주는 코드이다. db를 가져와서 JOIN을 사용해 등록되어 있는 author들만 보여지도록 한다.

<!-- flaskr/templates/blog/index.html -->
{% extends 'base.html' %}

{% block header %}
  <h1>{% block title %}Posts{% endblock %}</h1>
  {% if g.user %}
    <a class="action" href="{{ url_for('blog.create') }}">New</a>
  {% endif %}
{% endblock %}

{% block content %}
  {% for post in posts %}
    <article class="post">
      <header>
        <div>
          <h1>{{ post['title'] }}</h1>
          <div class="about">by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}</div>
        </div>
        {% if g.user['id'] == post['author_id'] %}
          <a class="action" href="{{ url_for('blog.update', id=post['id']) }}">Edit</a>
        {% endif %}
      </header>
      <p class="body">{{ post['body'] }}</p>
    </article>
    {% if not loop.last %}
      <hr>
    {% endif %}
  {% endfor %}
{% endblock %}

위는 로그인을 했을때는 create가 뜨도록 하며, 위의 view function에서 가져온 posts들을 순회하며 글 목록을 띄워준다. 또한 거기서 자신이 쓴 글이라면 편집을 할 수 있도록 a태그를 설정해준다.

 

Create

create 뷰는 register 뷰와 비슷한데, POST 메소드로 받고, 데이터베이스에 저장시켜주면 된다.

@bp.route('/create', methods=('GET', 'POST'))
@login_required
def create():
    if request.method == 'POST':
        title = request.form['title']
        body = request.form['body']
        error = None
        
        if not title:
            error = 'Title is required.'
        
        if error is not None:
            flash(error)
        else:
            db = get_db()
            db.execute(
                'INSERT INTO post (title, body, author_id)'
                ' VALUES (?, ?, ?)',
                (title, body, g.user['id'])
            )
            db.commit()
            return redirect(url_for('blog.index'))
    return render_template('blog/create.html')

뷰를 만들었다면 템플릿을 만들자

<!-- flaskr/templates/blog/create.html -->

{% extends 'base.html' %}

{% block header %}
  <h1>{% block title %}New Post{% endblock %}</h1>
{% endblock %}

{% block content %}
  <form method="post">
    <label for="title">Title</label>
    <input name="title" id="title" value="{{ request.form['title'] }}" required>
    <label for="body">Body</label>
    <textarea name="body" id="body">{{ request.form['body'] }}</textarea>
    <input type="submit" value="Save">
  </form>
{% endblock %}

 

 

Update

 update는 포스트를 id로 가져와서 작성자가 현재 로그인한 사용자와 일치하는지 확인하는 작업이 필요하고, POST 메소드로 하는 이유는 대량의 데이터를 url 만으로 하기엔 부족하다. 그리고 누군가가 이를 통해 악의적으로 글을 수정할 수 있게 하면 안되기 때문이다. 우선 get_post 함수를 정의하여 post를 반환하는 함수를 만들자.

# flaskr/blog.py

def get_post(id, check_author=True):
    post = get_db().execute(
        'SELECT p.id, title, body, created, author_id, username'
        ' FROM post p JOIN user u ON p.author_id = u.id'
        ' WHERE p.id = ?',
        (id, )
    ).fetchone()
    
    if post is None:
        abort(404, f"Post id {id} doesn't exist.")
    
    if check_author and post['author_id'] != g.user['id']:
        abort(403)
    
    return post

 abort는 special exception으로 HTTP status code를 설정해주고, 에러 메시지를 보여주는 함수이다. 나중에 직접 테스트해보자. 또한 여기서 중요한 코딩하는 방식이 check_author을 인자로 가져가서 코드 스니펫에 명시를 해줌으로써 다른 사용자가 해당 함수를 사용할 때 이 인자를 통해 간편하게 수정할 수 있다는 것이다

# flaskr/blog.py

@bp.route('/<int:id>/update', methods=('GET', 'POST'))
@login_required
def update(id):
    post = get_post(id)
    
    if request.method == 'POST':
        title = request.form['title']
        body = request.form['body']
        error = None
        
        if not title:
            error = 'Title is required'
        
        if error is not None:
            flash(error)
        
        else:
            db = get_db()
            db.execute(
                'UPDATE post SET title = ?, body = ?'
                ' WHERE id = ?',
                (title, body, id)
            )
            db.commit()
            return redirect(url_for('blog.index'))
    
    return render_template('blog/update.html', post=post)

 

<!-- flaskr/templates/blog/update.html -->
{% extends 'base.html' %}

{% block header %}
  <h1>{% block title %}Edit "{{ post['title'] }}"{% endblock %}</h1>
{% endblock %}

{% block content %}
  <form method="post">
    <label for="title">Title</label>
    <input name="title" id="title"
      value="{{ request.form['title'] or post['title'] }}" required>
    <label for="body">Body</label>
    <textarea name="body" id="body">{{ request.form['body'] or post['body'] }}</textarea>
    <input type="submit" value="Save">
  </form>
  <hr>
  <form action="{{ url_for('blog.delete', id=post['id']) }}" method="post">
    <input class="danger" type="submit" value="Delete" onclick="return confirm('Are you sure?');">
  </form>
{% endblock %}

 

Delete

 삭제는 템플릿이 필요 없다. 따라서 그냥 버튼만 있어주면 된다. 기능 구현 후에 view에 바인딩 시켜주자.

# flaskr/blog.py
@bp.route('/<int:id>/delete', methods=('POST',))
@login_required
def delete(id):
    get_post(id)
    db = get_db()
    db.execute('DELETE FROM post WHERE id = ?', (id, ))
    return redirect(url_for('blog.index'))

 

데이터베이스 다루기 CRUD
 CRUD는 create, read, update, delete의 약자로 데이터베이스에서 데이터베이스를 다루는 기본적인 네 비즈니스 프로세스이다. 따라서 해당 기능을 구현한 다음에 view function으로 바인딩만 시켜준다면 게시판이 완성된다. 여기서 read를 제외한 모든 프로세스, 동작은 POST 형태로 다뤄야 한다.

 

Comments