반응형
from pybo import db

class Question(db.Model) :
    id = db.Column(db.Integer, primary_key = True)
    subject = db.Column(db.String(200), nullable = False)
    content = db.Column(db.Text(), nullable = False)
    create_date = db.Column(db.DateTime(), nullable = False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False)
    user = db.relationship('User',backref=db.backref('question_set'))
    modify_date = db.Column(db.DateTime(), nullable=True)

class Answer(db.Model) :
    id = db.Column(db.Integer, primary_key = True)
    question_id = db.Column(db.Integer, db.ForeignKey('question.id', ondelete = 'cascade'))
    question = db.relationship('Question', backref=db.backref('answer_set', ))
    # question = db.relationship('Question', backref=db.backref('answer_set', casecade='all, delete-orphan'))
    content = db.Column(db.Text(), nullable = False)
    create_date = db.Column(db.DateTime(), nullable = False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False)
    user = db.relationship('User',backref=db.backref('answer_set'))
    modify_date = db.Column(db.DateTime(), nullable=True)

class User(db.Model) :
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(150), unique=True, nullable=False)
    password = db.Column(db.String(200), nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)

models.py 입니다.

언제 질문, 답변을 수정했는지 알 수 있는 modify_date 필드를 추가하겠습니다.

 

모델이 변경 했으니 migrate, upgrade 명령을 수행하겠습니다.

 

<!DOCTYPE html>
<html lang="en">


<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {% extends 'base.html' %}
    {% block content %}

    <div class="container my-3">
        <h2 class="border-bottom py-2">{{ question.subject }}</h2>
        <div class="card my-3">
            <div class="card-body">
                <div class="card-text" style="white-space: pre-line;">{{ question.content }}</div>
            </div>
            <div class="d-flex justify-content-end">
                <span class="badge bg-light text-dark text-left">
                    <span class="mb-2"> {{ question.user.username }} </span>
                    <span> {{ question.create_date|datetime }} </span>
                </span>
            </div>
        </div>

        {% if g.user == question.user %}
        <div class="my-3">
            <a href="{{ url_for('question.modify', question_id=question.id) }}" class="btn btn-sm btn-outline-secondary">수정</a>
        </div>
        {% endif %}


        <h5 class="border-bottom my-3">{{ question.answer_set|length }}개의 답변이 있습니다.</h5>
        {% for answer in question.answer_set %}
        <div class="card my-3">
            <div class="card-body">
                <div class="card-text" style="white-space : pre-line;">
                    {{ answer.content }}
                </div>
            </div>
            <div class="d-flex justify-content-end">
                <span class="badge bg-light text-dark text-left">
                    <span class="mb-2"> {{ answer.user.username }}  </span>
                    <span> {{ answer.create_date|datetime }} </span>
                </span>
            </div>
        </div>
        {% endfor %}

        <h5> {{ question.answer_set|length }}개의 답변이 있습니다. </h5>

        <div>
            <ul>
                {% for answer in question.answer_set %}
                    <li> {{ answer.content }}</li>
                {% endfor %}
            </ul>
        </div>

        <form class="my-3" action="{{ url_for('answer.create', question_id=question.id) }}" method="post">
          {{ form.csrf_token }}
          <!-- 오류표시 Start -->
          {% for field, errors in form.errors.items() %}
              <div class="alert alert-danger" role="alert">
                  <strong>{{ form[field].label }}</strong>: {{ ','.join(errors) }}
              </div>
          {% endfor %}
          <!-- 오류표시 End -->
            <div class="form-group">
                <textarea {% if not g.user %} disabled {% endif %} name="content" id="content" row="15"></textarea>
                <input class="btn btn-primary" type="submit" value="답변등록">
            </div>
        </form>
    </div>

    {% endblock %}
</body>
</html>

question_detail.html 입니다.

 

HTMl 을 수정해서 로그인한 사용자와 글쓴이가 같은 경우에만 보여야 합니다. 

{% if g.user == question.user %}

 

from datetime import datetime

from flask import Blueprint, render_template, request, url_for, g, flash

from .auth_views import login_required
from ..models import Question
from werkzeug.utils import redirect
from .. import db

from ..forms import QuestionForm, AnswerForm

bp = Blueprint('question', __name__, url_prefix='/question')

@bp.route('/list')
def _list():
    page = request.args.get('page', type=int, default=1) # 페이지
    question_list = Question.query.order_by(Question.create_date.desc())
    question_list = question_list.paginate(page, per_page=10)
    return render_template('question/question_list.html', question_list=question_list)

@bp.route('/detail/<int:question_id>')
def detail(question_id):
    form = AnswerForm()
    question = Question.query.get_or_404(question_id)
    return render_template('question/question_detail.html', question=question, form=form)

@bp.route('/create/', methods=('GET', 'POST'))
@login_required
def create():
    form = QuestionForm()
    if request.method == 'POST' and form.validate_on_submit():
        question = Question(subject=form.subject.data, content=form.content.data, create_date=datetime.now(), user=g.user)
        db.session.add(question)
        db.session.commit()
        return redirect(url_for('main.index'))
    return render_template('question/question_form.html', form=form)

@bp.route('/modify/<int:question_id>', methods=('GET','POST'))
@login_required
def modify(question_id):
    question = Question.query.get_or_404(question_id)
    if g.user != question.user:
        flash('수정권한이 없습니다.')
        return redirect(url_for('question.detail', question_id=question_id))
    if request.method == 'POST':
        form = QuestionForm()
        if form.validate_on_submit():
            form.populate_obj(question)
            question.modify_date = datetime.now()
            db.session.commit()
            return redirect(url_for('question.detail', question_id=question_id))
    else:
        form = QuestionForm(obj=question)
    return render_template('question/question_form.html', form=form)

question_views.py 입니다.

 

질문 수정은 로그인이 필요해서 @login_required을 이용해 로그인이 안 됐으면 로그인페이지로 가게 만들었고

현재 유저와 작성자와 같아야 수정할 수 있기 때문에 같지 않으면 flash('수정권한이 없습니다')

 

현재 페이지에 있는 question 정보 obj에 담아 form 저장합니다.

form = QuestionForm(obj=question)

그리고 form 그에 대한 정보를 담아서 question.detail 라우트로 보냅니다.

@bp.route('/detail/<int:question_id>')
def detail(question_id):
    form = AnswerForm()
    question = Question.query.get_or_404(question_id)
    return render_template('question/question_detail.html', question=question, form=form)

 

만약 같고 POST방식으로 요청되면 (수정저장 버튼 눌렀을 때) form 형식이 맞는지 확인하고

현재 페이지 정보(제목 내용 등...)을 form.popluate_obj(question) 이걸 이용해 가져옵니다.

이건 전달된 객체의 폼과 관련된 속성 화면에 있는 폼 데이터로 채웁니다.

 

그 후 question.modify_date = datetime.now() 수정일시를 저장후 commit을 이용해 저장합니다.

 

 

질문 삭제 기능을 만들겠습니다.

<!DOCTYPE html>
<html lang="en">


<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {% extends 'base.html' %}
    {% block content %}

    <div class="container my-3">
        <h2 class="border-bottom py-2">{{ question.subject }}</h2>
        <div class="card my-3">
            <div class="card-body">
                <div class="card-text" style="white-space: pre-line;">{{ question.content }}</div>
            </div>
            <div class="d-flex justify-content-end">
                <span class="badge bg-light text-dark text-left">
                    <span class="mb-2"> {{ question.user.username }} </span>
                    <span> {{ question.create_date|datetime }} </span>
                </span>
            </div>
        </div>

        {% if g.user == question.user %}
        <div class="my-3">
            <a href="{{ url_for('question.modify', question_id=question.id) }}" class="btn btn-sm btn-outline-secondary">수정</a>
            <a href="#" class="delete btn btn-sm btn-outline-secondary" data-uri="{{ url_for('question.delete', question_id=question.id) }}">삭제</a>
        </div>
        {% endif %}


        <h5 class="border-bottom my-3">{{ question.answer_set|length }}개의 답변이 있습니다.</h5>
        {% for answer in question.answer_set %}
        <div class="card my-3">
            <div class="card-body">
                <div class="card-text" style="white-space : pre-line;">
                    {{ answer.content }}
                </div>
            </div>
            <div class="d-flex justify-content-end">
                <span class="badge bg-light text-dark text-left">
                    <span class="mb-2"> {{ answer.user.username }}  </span>
                    <span> {{ answer.create_date|datetime }} </span>
                </span>
            </div>
        </div>
        {% endfor %}

        <h5> {{ question.answer_set|length }}개의 답변이 있습니다. </h5>

        <div>
            <ul>
                {% for answer in question.answer_set %}
                    <li> {{ answer.content }}</li>
                {% endfor %}
            </ul>
        </div>

        <form class="my-3" action="{{ url_for('answer.create', question_id=question.id) }}" method="post">
          {{ form.csrf_token }}
          <!-- 오류표시 Start -->
          {% for field, errors in form.errors.items() %}
              <div class="alert alert-danger" role="alert">
                  <strong>{{ form[field].label }}</strong>: {{ ','.join(errors) }}
              </div>
          {% endfor %}
          <!-- 오류표시 End -->
            <div class="form-group">
                <textarea {% if not g.user %} disabled {% endif %} name="content" id="content" row="15"></textarea>
                <input class="btn btn-primary" type="submit" value="답변등록">
            </div>
        </form>
    </div>

    {% endblock %}
    {% block script %}
    <script type="text/javascript">
        $(document).ready(function(){
            $(".delete").on("click", function(){
                if(confirm("정말로 삭제하시겠습니까?")) {
                    location.href = $(this).data('uri');
                }
            });
        });
    </script>
    {% endblock %}
</body>
</html>

question_detail.html 입니다.

 

href 속성을 #으로 하고 data-uri 라는 기능을 넣었습니다.

jQuery에서 $(this).data('uri')로 삭제 실행하는 URL을 얻으려고 했습니다.

 

{% block script %}

{% endblock %} 을 이용했는데 jQuery를 템플릿에서 사용할 수 있도록 약간 수정해야하기 때문입니다.

 

자바스크립트문을 설명하자면 화면이 준비 됐을 때 $(document).ready(function() .... function 내용이 수행됩니다.

delete 클래스가 클릭 되면 function 이하에 내용이 수행됩니다.

confirm은 확인창어떤 형식으로 나올지에 대한 것입니다.

 

만약 예를 누를 경우

location.href = $(this).data('uri') 가 실행 되는 것이죠 이것의 의미는

현재 누른 것의 data-uri를 href로 연다는 것입니다.

여기선 question.delete로 되어있습니다. 그에 대한 내용을 추가하겠습니다. (라우트)

<!DOCTYPE html>
<html lang="en">
<head>

    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
    <link rel="stylesheet" href="{{ url_for('static', filename='bootstrap.min.css') }}">

    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Title</title>
</head>
<body>

    {% include "navbar.html" %}

    <!-- 기본 템플릿에 삽입할 내용 Start -->
    {% block content %}
    {% endblock %}
    <!-- 기본 템플릿에 삽입할 내용 End -->

    <!-- jQuery JS -->
    <script src="{{ url_for('static', filename='jquery-3.6.0.min.js') }}"> </script>
    <!-- Bootstarp JS -->
    <script src="{{ url_for('static', filename='bootstrap.min.js') }}"> </script>
    <!-- 자바스크립트 -->
    {% block script %}
    {% endblock %}
</body>
</html>

base.html 입니다.

 

{% block script %}

{% endblock %} 을 추가해서 제이쿼리를 이용할 수 있게 만들었습니다.

 

from datetime import datetime

from flask import Blueprint, render_template, request, url_for, g, flash

from .auth_views import login_required
from ..models import Question
from werkzeug.utils import redirect
from .. import db

from ..forms import QuestionForm, AnswerForm

bp = Blueprint('question', __name__, url_prefix='/question')

@bp.route('/list')
def _list():
    page = request.args.get('page', type=int, default=1) # 페이지
    question_list = Question.query.order_by(Question.create_date.desc())
    question_list = question_list.paginate(page, per_page=10)
    return render_template('question/question_list.html', question_list=question_list)

@bp.route('/detail/<int:question_id>')
def detail(question_id):
    form = AnswerForm()
    question = Question.query.get_or_404(question_id)
    return render_template('question/question_detail.html', question=question, form=form)

@bp.route('/create/', methods=('GET', 'POST'))
@login_required
def create():
    form = QuestionForm()
    if request.method == 'POST' and form.validate_on_submit():
        question = Question(subject=form.subject.data, content=form.content.data, create_date=datetime.now(), user=g.user)
        db.session.add(question)
        db.session.commit()
        return redirect(url_for('main.index'))
    return render_template('question/question_form.html', form=form)

@bp.route('/modify/<int:question_id>', methods=('GET','POST'))
@login_required
def modify(question_id):
    question = Question.query.get_or_404(question_id)
    if g.user != question.user:
        flash('수정권한이 없습니다.')
        return redirect(url_for('question.detail', question_id=question_id))
    if request.method == 'POST':
        form = QuestionForm()
        if form.validate_on_submit():
            form.populate_obj(question)
            question.modify_date = datetime.now()
            db.session.commit()
            return redirect(url_for('question.detail', question_id=question_id))
    else:
        form = QuestionForm(obj=question)
    return render_template('question/question_form.html', form=form)

@bp.route('delete/,<int:question_id>')
@login_required
def delete(question_id):
    question = Question.query.get_or_404(question_id)
    if g.user != question.user:
        flash('삭제권한이 없습니다.')
        return redirect(url_for('question.detail', question_id=question_id))
    db.session.delete(question)
    db.session.commit()
    return redirect(url_for('question._list'))

question_views.py 입니다.

 

question.delete구현한 것입니다.

로그인이 되어야하고 로그인한 아이디와 작성자와 같지 않으면 삭제 권한이 없는 것이고

같다면 db에서 question 객체를 삭제적용시키고 list화면으로 나옵니다.

 

 

 

 

답변을 수정하고 삭제하는 버튼을 추가하겠습니다.

 

<!DOCTYPE html>
<html lang="en">


<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {% extends 'base.html' %}
    {% block content %}

    <div class="container my-3">
        <h2 class="border-bottom py-2">{{ question.subject }}</h2>
        <div class="card my-3">
            <div class="card-body">
                <div class="card-text" style="white-space: pre-line;">{{ question.content }}</div>
            </div>
            <div class="d-flex justify-content-end">
                <span class="badge bg-light text-dark text-left">
                    <span class="mb-2"> {{ question.user.username }} </span>
                    <span> {{ question.create_date|datetime }} </span>
                </span>
            </div>
        </div>

        {% if g.user == question.user %}
        <div class="my-3">
            <a href="{{ url_for('question.modify', question_id=question.id) }}" class="btn btn-sm btn-outline-secondary">수정</a>
            <a href="#" class="delete btn btn-sm btn-outline-secondary" data-uri="{{ url_for('question.delete', question_id=question.id) }}">삭제</a>
        </div>
        {% endif %}


        <h5 class="border-bottom my-3">{{ question.answer_set|length }}개의 답변이 있습니다.</h5>
        {% for answer in question.answer_set %}
        <div class="card my-3">
            <div class="card-body">
                <div class="card-text" style="white-space : pre-line;">
                    {{ answer.content }}
                </div>
            </div>
            <div class="d-flex justify-content-end">
                <span class="badge bg-light text-dark text-left">
                    <span class="mb-2"> {{ answer.user.username }}  </span>
                    <span> {{ answer.create_date|datetime }} </span>
                </span>
            </div>
        </div>
        {% if g.user == answer.user %}
        <div class="my-3">
            <a href="{{ url_for('answer.modify', answer_id=answer.id) }}" class="btn btn-sm btn-outline-secondary">수정</a>
            <a href="#" class="delete btn btn-sm btn-outline-secondary" data-uri="{{ url_for('answer.delete', answer_id=answer.id) }}">삭제</a>
        </div>
        {% endif %}

        {% endfor %}

        <h5> {{ question.answer_set|length }}개의 답변이 있습니다. </h5>

        <div>
            <ul>
                {% for answer in question.answer_set %}
                    <li> {{ answer.content }}</li>
                {% endfor %}
            </ul>
        </div>

        <form class="my-3" action="{{ url_for('answer.create', question_id=question.id) }}" method="post">
          {{ form.csrf_token }}
          <!-- 오류표시 Start -->
          {% for field, errors in form.errors.items() %}
              <div class="alert alert-danger" role="alert">
                  <strong>{{ form[field].label }}</strong>: {{ ','.join(errors) }}
              </div>
          {% endfor %}
          <!-- 오류표시 End -->
            <div class="form-group">
                <textarea {% if not g.user %} disabled {% endif %} name="content" id="content" row="15"></textarea>
                <input class="btn btn-primary" type="submit" value="답변등록">
            </div>
        </form>
    </div>

    {% endblock %}
    {% block script %}
    <script type="text/javascript">
        $(document).ready(function(){
            $(".delete").on("click", function(){
                if(confirm("정말로 삭제하시겠습니까?")) {
                    location.href = $(this).data('uri');
                }
            });
        });
    </script>
    {% endblock %}
</body>
</html>

question_detail.html 입니다.

 

댓글 수정 삭제 버튼을 만들었습니다.

from datetime import datetime

from flask import Blueprint, url_for, request, render_template, g, flash
from werkzeug.utils import redirect

from .auth_views import login_required
from .. import db
from ..models import Question, Answer
from ..forms import AnswerForm

bp = Blueprint('answer', __name__, url_prefix='/answer')

@bp.route('/create/<int:question_id>', methods=('POST', ))
@login_required
def create(question_id):
    form = AnswerForm()
    question = Question.query.get_or_404(question_id) # 그 질문에 해당하는 번호를 가져온다.
    if form.validate_on_submit():
        content = request.form['content']  # name 속성이 content인 값
        answer = Answer(content=content, create_date=datetime.now(), user=g.user)  # textArea 내용, 작성시각, 작성자
        question.answer_set.append(answer)  # question.answer_set 은 Answer과 동일 즉, 거기에다가 값을 추가하는 것임
        db.session.commit()  # db에 저장
        return redirect(url_for('question.detail', question_id=question_id))  # 상세 질문과 답변이 있는 페이지로 전달
    return render_template('question/question_detail.html', question=question, form=form)

@bp.route('/modify/<int:answer_id>', methods=('GET','POST'))
@login_required
def modify(answer_id):
    answer = Answer.query.get_or_404(answer_id)
    if g.user != answer.user:
        flash('수정권한이 없습니다.')
        return redirect(url_for('question.detail', question_id=answer.question.id))
    if request.method == "POST":
        form = AnswerForm()
        if form.validate_on_submit():
            form.populate_obj(answer)
            answer.modify_date = datetime.now()
            db.session.commit()
            return redirect(url_for('question.detail', question_id=answer.question.id))
    else:
        form = AnswerForm(obj=answer)
    return render_template('answer/answer_form.html', answer=answer, form=form)

@bp.route('delete/,<int:answer_id>')
@login_required
def delete(answer_id):
    answer = Answer.query.get_or_404(answer_id)
    question_id = answer.question.id
    if g.user != answer.user:
        flash('삭제권한이 없습니다.')
    db.session.delete(answer)
    db.session.commit()
    return redirect(url_for('question.detail', question_id=question_id))

answer_views.py 입니다

 

위에 내용하고 거의 동일하기 때문에 설명은 넘어가겠습니다. (수정 삭제 기능을 넣었습니다.)

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  {% extends 'base.html' %}
  {% block content %}
  <div class="container my-3">
    <form method="post" class="post-form">
        {{ form.csrf_token }}
        {% include "form_errors.html" %}
        <div class="form-group">
            <label for="content">답변내용</label>
            <textarea class="form-control" name="content" id="content" rows="10">
                {{ form.content.data or '' }}
            </textarea>
        </div>
        <button type="sumbmit" class="btn btn-primary">저장하기</button>
    </form>
  </div>
  {% endblock %}
</body>
</html>

templates/answer/answer_form.html 입니다.

 

댓글 수정화면을 새로 만들었습니다.

 

 

<!DOCTYPE html>
<html lang="en">


<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    {% extends 'base.html' %}
    {% block content %}

    <div class="container my-3">
        <h2 class="border-bottom py-2">{{ question.subject }}</h2>
        <div class="card my-3">
            <div class="card-body">
                <div class="card-text" style="white-space: pre-line;">{{ question.content }}</div>
            </div>
            <div class="d-flex justify-content-end">
                {% if question.modify_date %}
                    <span class="badge bg-light text-dark p-2 text-start mx-3">
                        <div class="mb-2">modified at</div>
                        <div> {{ question.modify_date|datetime }}</div>
                    </span>
                {% endif %}
                <span class="badge bg-light text-dark text-start">
                    <div class="mb-2"> {{ question.user.username }} </div>
                    <div> {{ question.create_date|datetime }} </div>
                </span>
            </div>
        </div>

        {% if g.user == question.user %}
        <div class="my-3">
            <a href="{{ url_for('question.modify', question_id=question.id) }}" class="btn btn-sm btn-outline-secondary">수정</a>
            <a href="#" class="delete btn btn-sm btn-outline-secondary" data-uri="{{ url_for('question.delete', question_id=question.id) }}">삭제</a>
        </div>
        {% endif %}


        <h5 class="border-bottom my-3">{{ question.answer_set|length }}개의 답변이 있습니다.</h5>
        {% for answer in question.answer_set %}
        <div class="card my-3">
            <div class="card-body">
                <div class="card-text" style="white-space : pre-line;">
                    {{ answer.content }}
                </div>
            </div>
            <div class="d-flex justify-content-end">
                {% if answer.modify_date %}
                <span class="badge bg-light text-dark text-start p-2 mx-3">
                    <div class="mb-2"> modified at  </div>
                    <div> {{ answer.modify_date|datetime }} </div>
                </span>
                {% endif %}
                <span class="badge bg-light text-dark text-start">
                    <div class="mb-2"> {{ answer.user.username }}  </div>
                    <div> {{ answer.create_date|datetime }} </div>
                </span>
            </div>
        </div>
        {% if g.user == answer.user %}
        <div class="my-3">
            <a href="{{ url_for('answer.modify', answer_id=answer.id) }}" class="btn btn-sm btn-outline-secondary">수정</a>
            <a href="#" class="delete btn btn-sm btn-outline-secondary" data-uri="{{ url_for('answer.delete', answer_id=answer.id) }}">삭제</a>
        </div>
        {% endif %}

        {% endfor %}

        <h5> {{ question.answer_set|length }}개의 답변이 있습니다. </h5>

        <div>
            <ul>
                {% for answer in question.answer_set %}
                    <li> {{ answer.content }}</li>
                {% endfor %}
            </ul>
        </div>

        <form class="my-3" action="{{ url_for('answer.create', question_id=question.id) }}" method="post">
          {{ form.csrf_token }}
          <!-- 오류표시 Start -->
          {% for field, errors in form.errors.items() %}
              <div class="alert alert-danger" role="alert">
                  <strong>{{ form[field].label }}</strong>: {{ ','.join(errors) }}
              </div>
          {% endfor %}
          <!-- 오류표시 End -->
            <div class="form-group">
                <textarea {% if not g.user %} disabled {% endif %} name="content" id="content" row="15"></textarea>
                <input class="btn btn-primary" type="submit" value="답변등록">
            </div>
        </form>
    </div>

    {% endblock %}
    {% block script %}
    <script type="text/javascript">
        $(document).ready(function(){
            $(".delete").on("click", function(){
                if(confirm("정말로 삭제하시겠습니까?")) {
                    location.href = $(this).data('uri');
                }
            });
        });
    </script>
    {% endblock %}
</body>
</html>

quesiton_detail.html 입니다.

 

수정해서 글이나 댓글 수정시각을 추가시켰습니다.

 

 

반응형