반응형
반응형

📝환경변수

Path에 절대경로도 가능하지만 경로값을 변수에 넣어서 Path값에 간단하게 적을 수도 있다

 

📝Path

운영체제가 어떤 프로세스를 실행시킬때, 그 경로를 찾는데 이용

예를 들자면 c:\aaa\bbb 라는 경로에 hello.txt라는 것이 있을 때 저 경로를 path 설정으로 해놓으면 cmd에서 바로 hello.txt를 쳤을 때 그 경로로 안 들어가도 바로 접근이 가능하다

 

자 간단하게 환경변수와 Path를 어떻게 이용하는지에 대해서 알아봅시다.

시스템 변수란 말 그대로 경로값을 간단한 변수로 바꿔서 표현하는 것입니다.

시스템 변수에 등록하면 js = C:\User\.... (경로)가 되는 것이고 이걸 Path영역에 간단하게 적을 수 있죠

이런 변수로 등록한 걸 쓰려면 %변수명% 이렇게 적어야합니다 저는 js로 했습니다. 또한 절대경로를 그대로 입력하셔도 상관 없습니다.

 

그러면 저게 지금 바탕화면 Nodejs라는 폴더에 있는 것인데 그 안에 console.js라는 걸 제가 만들어뒀습니다

cmd를 여시고 경로를 따로 이동 안 해도 console.js를 하면 실행이 됩니다

 

📝index.html

경로 설정을 안 해줘도 기본적인 메인 페이지로 나옵니다.

 

📝cmd 사용 이유

IDE가 없을 때 개발하기 위해 좀 더 세밀한 코딩과정이 필요할 때 → .class파일 생성 등... → 어차피 IDE 기능도 CLI로 구현한 것

반응형
반응형

📝AWS(Amazon Web Service)

Amazon에서 서버 호스팅을 해주는 업체

 

📝시스템 가상화

물리적 자원을 추상화(단순하게)해 논리적 자원 형태로 표현 대상을 단수화 하기 위해 추상화 즉 논리화를 한다.

예) AMD, Intel → CPU

 

📝가상화 대상

물리적 서버 하나에 가상의 시스템 여러개 구성 (여러 운영체제가 분리되어 있음)

 

📝VM

가상머신으로 CentOS 따위처럼 또다른 운영체제를 만들어서 사용한다 따로 서버를 구축하는 것이기 때문에 보안엔 좋고 용량이 크다

→ 윈도우에서 리눅스 환경을 구축하는 행동

 

📝하이퍼바이저

단일 물리적 머신에서 여러 가상 머신을 실행하는 데 사용할 수 있는 소프트웨어 → VMware, Oracle VM 등...

 

 

 

 

 

 

📝베어메탈기반 가상화

호스트 OS[메인 OS]가 없거나 최소한으로만 있다

하드웨어에 하이퍼바이저만 설치하고 그 위에 운영체제를 설치한다 → 하드웨어에 가깝기 때문에 "메탈"

하이퍼 바이저가 메인 OS처럼 운영체제를 처리하기 때문에 속도가 호스트 기반에 비해 상대적으로 빠르다

→ VMware ESX, XenServer

 

📝호스트기반 가상화

메인 OS가 존재하고 그 위에 하이퍼바이저를 설치 해 가상화를 한다

베어메탈에 비해 상대적으로 느리다

→ VMware Workstation / VMware Player

 

📝IDC

IDC는 Internet Data Center의 약어로 인터넷 데이터 센터를 가리킵니다

데이터 센터는 대규모의 컴퓨터 시스템과 네트워크 장비, 저장장치, 전력 및 냉각 시스템 등을 호스팅하는 시설입니다

IDC는 주로 웹 호스팅, 클라우드 서비스, 온라인 서비스, 대용량 데이터 저장 등과 관련된 서버와 네트워크 인프라를 제공합니다

반응형
반응형

📝버전관리 = 형상기억

기능 추가로 프로그램이 바뀔 때 버전이 바뀐다고 한다 [버전1 → 기능 추가 → 버전1.1]

예를 들어 RPG게임을 만들어서 1.0버전이라고 뒀다 그 게임에 여러 기능을 추가 및 삭제 한 버전이 1.1버전 이런식이 되는 것이다.

만약 1.1버전이 문제가 생겨서 1.0버전으로 돌아가야할 경우 따로 보관해야하고 복잡하지만 이걸 버전 관리 시스템이 따로 관리 해줘서 1.1버전에서 1.0버전 또는 1.0버전에서 1.1버전으로 편하게 드나들 수 있다.

 

📝버전 관리 시스템

버전 관리를 지원해주는 시스템이다 → Git, SVN

 

📝중앙집중형 시스템

저장소가 서버에 있어서 서버와 연결이 끊어지면 기존 받아둔 소스 수정 이외의 일(이전 로그를 본다던지...)을 못한다SVN

 

📝분산형 버전관리 시스템

모든 개발자가 각자의 저장소를 가지고, 각자 저장소에서 여러가지 작업을 한 뒤 한번에 서버에 반영 가능
오프라인 환경에서도 로컬저장소에 Commit하면 되고 로그를 볼 수 있다 → Git

 

📝SVN

Subversion (SVN)은 버전 관리 시스템 중 하나로, 소스 코드 및 다른 종류의 파일의 변경 이력을 관리하고 추적하는 데 사용 소프트웨어 개발 프로젝트에서 협업과 소스 코드 관리를 향상시키기 위해 널리 사용 다수의 사용자가 동시에 작업하고, 변경사항을 추적하며, 필요한 경우 이전 버전으로 롤백할 수 있는 기능을 제공

반응형
반응형

📝텐서플로

구글에서 만든, 딥러닝 프로그램을 쉽게 구현할 수 있도록 다양한 기능을 제공해주는 라이브러리

텐서플로 어렵지만 디테일하다는 특징이 있습니다.

 

📝케라스 

텐서플로위에서 동작하고 특정한 일에 좋습니다 (이미 만들어진 라이브러리)

OCR, 이미지 분류, 손금 분석 등...

 

📝OCR 

이미지 속에 있는 글자를 읽는 기술

OCR에 필요한 기술 → 오픈소스 기반의 tesseract(테서랙트)는 다양한 운영 체제를 위한 광학 문자 인식 엔진

 

📝NumPy

("넘파이"라 읽는다)는 행렬이나 일반적으로 대규모 다차원 배열을 쉽게 처리 할 수 있도록 지원하는 파이썬의 라이브러리이다. NumPy는 데이터 구조 외에도 수치 계산을 위해 효율적으로 구현된 기능을 제공

 

📝선형회귀분석

Input변수(X)를 기반으로 output변수(Y)의 값을 알아내고자 하는 통계적 모델을 만드는 것

 

 

 

반응형
반응형

📝비즈니스 로직

로그인하고 난 후 XXX님 로그인 하셨습니다 라는 과정을 만드려고 합니다. 

일단 아이디 비밀번호를 받아와 DB에 있는 아이디 비밀번호랑 맞는지 확인하고 그에 대한 정보(여기에서는 이름)을 가져와서 웹 페이지에 보여주게 하는 과정 이러한 로직들이 필요하죠

이러한 로직들을 비즈니스 로직이라고 합니다. (쉽게 말해서 프로그래밍의 과정을 말로 표현 한 것)

 

📝API

Application Programming Interface의 약자로 작업을 편리하게 해줍니다.

STL(라이브러리) , 카카오API(카카오 기능을 사용할 수 있게함) 등이 있습니다.

 

📝NGINX

NGINX는 웹서버로 정적파일 로딩과 로드밸런서 정적파일을 빠르게 로딩하기 위함입니다.  

보안 때문에 앞단에 웹서버 두고 방화벽 뒤에 WAS 놓고방화벽 뒤에 DB 놓는 구성을 많이 합니다. 
물론 DMZ - WAS - DB 존 망분리 다 해놓습니다.

 

📝WSGI server

WSGI server는 많은 request들을 다룰 수 있도록 설계되어 있습니다. (Gunicorn 등이 있습니다.)

WSGI server로 넘어온 동적처리들을 처리할 수 있는 WSGI application으로 넘기는 작업을 합니다.

Web서버가 받은 호출을 Python 어플리케이션에게 전달하고 응답받기 위한 호출조약입니다.

파이썬의 Web Server → Apache와 동일한 기능

 

📝WSGI application

Flask, Django같은 웹프레임워크를 의미합니다

파이썬의 WAS Server → Tomcat와 동일한 기능

반응형
반응형

📝유닉스

운영체제 코드가 공개되어 있지 않아 버그 이슈 같은 경우 IBM사에서 일괄 관리합니다. 

그래서 더욱 비싸지만 전문가들이 관리하기 때문에 전문적으로 처리가 가능합니다.

 

📝리눅스

리눅스는 컴퓨터 운영체제 중 하나입니다. GNU 프로젝트에 따라 쉽게 이용할 수 있게 배포됩니다.

 

📝리눅스 사용 이유

  1. 기계어에 가깝기 때문 적은 메모리 빠른 제어 가능
  2. 서버 운영체제로서 적합 (멀리 있는 곳을 원격으로 제어해야할 때 윈도우의 경우 엄청난 메모리를 먹지만 리눅스같은 경우는 그렇지 않다) 

 

📝GNU 프로젝트

GNU's not Unix의 약자입니다. 유닉스와 호환이 되면서 더 강력한 운영체제를 만들고자하는 프로젝트입니다.

리누스가 만든 리눅스를 도와서 리눅스를 완성시켰다.

 

📝GPL

리눅스를 완성시키고 그에 대한 규칙들을 정했는데 그걸 GPL이라고 한다.

  1. 소스 코드를 용도에 따라 변경할 수 있다.
  2. 프로그램 실행 복사본은 언제나 프로그램의 소스 코드와 함께 판매하거나 무료로 배포해야한다.
  3. 변경된 프로그램 소스 코드를 반드시 공개 배포해야한다.

 

📝우분투

우분투는 데비안(Debian) GNU/리눅스를 기반 리눅스 배포판입니다.

우분투의 개발은 남아프리카 공화국의 사업가이자 캐노니컬의 CEO인 마크 셔틀워스 의해 시작했습니다.

 

📝우분투의 특징

  1. 개인용, 데스크탑 환경에 최적화 대부분의 리눅스 배포판들이 서버용으로 사용되고 있는것에 반하여,우분투는 개인 사용자와 데스크탑 환경에 최적화 되도록 사용자 편의를 중점으로 개발되고 있습니다.
  2. 자유 소프트웨어에 기반으로 앞서 소개한 리눅스의 특징을 그대로 물려 받으며 자유 소프트웨어에 기반하기 때문에 누구나 무료로 다운 받아서 사용할 수 있습니다.

 

📝데비안

리눅스기반으로 만들어진 운영체제입니다. 데비안은 안정성에 중점을 둔 운영체제라서, 안정성의 검증이 덜 된 최신의 패키지는 사용하지 않거나 안정성이 어느 정도 검증된 예전 버전을 이용하는 경우가 많습니다.

 

📝데스크탑버전 vs 서버버전

  • 데스크탑버전
    • 개인이 사용할만한 패키지를 포함합니다
    • 기본적으로 GUI환경이며 오피스 제품들, 동영상 플레이어, 음악 플레이어등을 기본적 패키지로 제공합니다.
  • 서버버전
    • 가장 기본적인 패키지를 포함한체 서버 운영에 필요한 패키지를 설치시 선택하게 되어있습니다
    • 기본적으로 포함할법한 것들도 포함되어있지 않기 때문에 하나하나 설치해야한다.

 

 

 

반응형
반응형

📝Java SE

Standard Edition의 약자로 기본적인 프로그래밍을 다 지원합니다.
Java SE는 Java Software Development Kit(SDK)으로 구현합니다.

데스크톱, 서버, 임베디드시스템 등을 위한 표준 자바 플랫폼입니다.

📝Java EE

Enterprise Edition 기업에서 사용할 때 이 에디션을 구매해서 사용한다 

서버에서 모듈 기반 지원이나 분산 멀티미디어 등등 기업적인 측면 기능들을 지원합니다. (서버 애플리케이션 웹개발자들이 사용하는 배포판)
JAVA SE에 서버측을 위한 기능을 부가하였기 때문에 SE기능을 모두 포함한다.

📝Java ME

Micro Edition의 약자로 임베디드 시스템에서 자바로 프로그램을 개발할 때 이용합니다.

 

📝JDK

Java Dveloplment Kit의 약자 자바 개발 키트
개발에 필요한 것들이 들어있습니다  → JRE + 컴파일러나 javadoc jdbc..

 

📝JRE

Java Runtime Enviroment의 약자자바 실행 환경 자바로 만들어진 프로그램으로 JVM이 들어있다고 생각하시면 됩니다.

반응형
반응형
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 입니다.

 

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

 

 

반응형
반응형

로그인, 로그아웃 기능을 만들었으니 누가 작성했는지 표시할 수 있게 됩니다.

그래서 Quesiton, Answer 모델을 수정하여 글쓴이에 해당하는 user 필드를 추가할 것입니다.

 

ORM을 사용할 때 몇 가지 문제점이 있습니다. SQLite 에서만 해당하고 다른 DB는 상관이 없습니다.

SQLite가 발생시킬 수 있는 오류를 먼저 해결하고 넘어가겠습니다.

(이럴 거면 그냥 다른 DB 쓰면 안 되나?) 

 

몇몇 데이터베이스의 제약 조건 이름이 변경되므로

flask db migrate, flask db upgrade로 데이터베이스를 변경해야 합니다..

 

from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import MetaData

import config

naming_convention ={
        "ix" : "ix_%(column_0_label)s",
        "uq" : "uq_%(table_name)s_%(column_0_name)s",
        "ck" : "ck_%(table_name)s_%(column_0_name)s",
        "fk" : "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
        "pk" : "pk_%(table_name)s"
}
db = SQLAlchemy(metadata=MetaData(naming_convention=naming_convention))
migrate = Migrate()

def create_app():
        app = Flask(__name__)
        app.config.from_object(config) # config.py에 작성 내용을 환경 변수로 부르기 위한 작업

        # ORM
        db.init_app(app) # 우리가 적은 환경으로 DB 초기화
        if app.config['SQLALCHEMY_DATABASE_URI'].startswith("sqlite"):
                migrate.init_app(app, db, render_as_batch=True)  # DB와 우리가 적은 환경을 다른 환경으로 옮김
        else:
                migrate.init_app(app, db)
        from . import models

        # bluePrint
        from .views import main_views, question_views, answer_views, auth_views # .view에서 .은 현재위치의 views 폴더로부터 import한다는 말
        app.register_blueprint(main_views.bp) # 블루프린트 객체 bp 등록
        app.register_blueprint(question_views.bp) # 블루프린트 객체 bp 등록
        app.register_blueprint(answer_views.bp)
        app.register_blueprint(auth_views.bp)

        # 필터
        from .filter import format_datetime
        app.jinja_env.filters['datetime'] = format_datetime

        return app

SQLite 데이터베이스에서 사용하는 인덱스 등의 제약 조건 이름MetaData 클래스를 사용하여

규칙을 정의해야 합니다. 정의 하지 않으면 제약 조건에 이름이 없다는 오류 발생시킵니다.

 

render_as_batchTrue로 지정해야 합니다. False면 제약 조건 변경을 지원하지 않는다는 오류를 만듭니다.

 

이러한 과정들은 SQLite DB플라스크 ORM에서 정상 사용하기 위합니다.

자세한 과정은 몰라도 된다고 적혀있네요...

 

(제약조건 : Primary key 이런 걸 의미하는 것 같습니다.)

 

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'))

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)

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)

일단 Question를 먼저 수정하겠습니다.

 

user_id User의 id값하고 값을 공유합니다.

userUser라는 테이블과 연결해 User테이블에 접근할 수 있게 만듭니다.

 

모델을 수정했으므로 flask db migrate 명령으로 리비전 파일을 생성합시다.

 

리비전 파일을 flask db upgrade 명령으로 적용하겠습니다.

오류가 발생합니다. 이유는 user_id 필드가 Null을 허용하지 않기 때문입니다.

앞서 실습진행하면서 데이터 여러 건 저장했습니다. 그 데이터에는 user_id 필드의 값이 없죠

(왜냐하면 지금 추가했으니깐요 예전 데이터의 값이 없겠죠 그래서 값이 들어가있지 않음)

근데 null 허용을 안 하니까 오류가 발생한 것입니다.

 

 

해결 하기 위한 5단계를 거쳐야 합니다.

 

1. user_id nullable 설정을 False 대신 True로 바꾸기

2. user_id를 임의 값으로 설정하기 (여기서는 1로 설정)

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=True, server_default='1')
    user = db.relationship('User',backref=db.backref('question_set'))

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)

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)

Question에 있는 user_id 부분 nullable을 바꾸고 server_defualt을 설정했습니다.

 

 

3. flask db migrate 명령, flask db upgrade 명령 다시 실행하기

이전 migrate 명령은 제대로 수행 되었지만 upgrade를 실패하여 정상 종료가 되었지 않기 때문입니다.

 

flask db heads로 최종 리비전을 확인해봅시다. (방금 적용시킨 거)

 

flask db current현재 작업 리비전(저번에 적용시킨 거)을 확인해 봅시다.

서로 같지 않아현재 리비전을 최종 리비전으로 변경해야 합니다.

 

flask db stamp heads이용해 바꿨고 flasdk db current로 제대로 바뀌었는지 확인 했습니다.

 

그러면 정상 종료된 것이기 때문에 다시 flask db migrate와 flask db upgrade를 해줍시다.

Question 모델 데이터 모든 user_id 필드에 1이 저장되었습니다.

 

4. user_id의 nullable 설정을 다시 False로 변경 (다시 원래 False로 변경)

5. flask db migrate 명령, flask db upgrade 명령 다시 실행

 

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'), nullabe =False)
    user = db.relationship('User',backref=db.backref('question_set'))

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)

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)

Question 클래스를 수정flask db migrate, flask db upgrade 명령을 수행하겠습니다.

 

 

 

 

Answer 모델도 같은 방법으로 user_id 필드를 추가하겠습니다.

Question이랑 같은 방법으로 하시면 됩니다. 

 

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'))

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=True, server_default='1')
    user = db.relationship('User',backref=db.backref('answer_set'))

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)

nullable = True 와 server_default = '1'을 수정하고 적용했습니다.

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'))

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'))

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)

nullable = False로 수정하고 적용했습니다.

 

from datetime import datetime

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

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', ))
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)

answer_views.py 입니다.

 

댓글 작성자가 필요하기 때문에 로그인 되어 있으면 g.user에 id가 저장되어 있으므로 그걸 user에다 저장합니다.

 

from datetime import datetime

from flask import Blueprint, render_template, request, url_for, g
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'))
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)

question_views.py 입니다.

 

글 작성자가 필요하기 때문에 로그인 되어 있으면 g.user에 id가 저장되어 있으므로 user에다 저장합니다.

 

 

로그아웃 상태에서 질문 또는 답변을 등록이 불가능하게 오류를 발생시켜보도록 하겠습니다.

 

@login_required 어노테이션을 지정하면 login_required 데코레이션 함수가 먼저 실행 됩니다.

(즉 def login_required(view)라는 것이 @login_required 과 똑같은 의미입니다.)

login_required 함수는 g.user가 있는지 조사하고 없으면 로그인 URL로 리다이렉트합니다.

g.user가 있으면 원래 함수를 그대로 실행합니다.

 

이런걸 데코레이터 함수라고 하는데 나중에 설명해서 올리도록 하겠습니다.

 

from datetime import datetime

from flask import Blueprint, url_for, request, render_template, g
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)

answer_views.py 입니다

 

@login_required를 넣어서 로그인 라우트가 실행하기 전에 로그인 되어있는지 확인하는 것입니다.

 

from datetime import datetime

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

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)

question_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">
        <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">
                    {{ question.create_date|datetime }}
                </span>
            </div>
        </div>


        <h5 class="border-bottom my-3 py-2">{{ 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 class="d-flex justify-content-end">
                    <span class="badge bg-light text-dark">
                        {{ answer.create_date|datetime }}
                    </span>
                </div>
            </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 입니다.

 

<textarea {% if not g.user %} disabled {% endif %} name="content" ....

이 부분을 이용해 아예 로그인이 되어있지 않을 때 입력조차 불가능하게 만들었습니다.

 

 

<!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">
        <table calss="table" width="100%">
            <thead >
                <tr class="bg-dark text-white text-center">
                        <th> 번호 </th>
                        <th style="width:50%"> 제목 </th>
                        <th> 글쓴이 </th>
                        <th> 작성일시 </th>
                </tr>
            </thead>

            <tbody>
                {% if question_list %}
                {% for question in question_list.items %}
                <tr class="text-center">
                        <td>{{ question_list.total - ((question_list.page-1) * question_list.per_page) - loop.index0 }}</td>
                        <td class="text-start">
                            <a href="{{ url_for('question.detail', question_id=question.id) }}">{{question.subject}}</a>
                            {% if question.answer_set|length > 0 %}
                                <span class="text-danger small">{{ question.answer_set|length }}</span>
                            {% endif %}
                        </td>
                        <td>{{ question.user.username }}</td>
                        <td>{{ question.create_date|datetime  }}</td>
                </tr>
                {% endfor %}
                {% else %}
                <tr>
                    <td colspan="3"> 질문이 없습니다. </td>
                </tr>
                {% endif %}
            </tbody>
        </table>
        <!-- 페이징 -->
        <nav aria-label="Page navigation example">
          <ul class="pagination pagination-sm my-5 justify-content-center">
              <!-- 이전 페이징 구현 (이전 페이지 있을시)-->
              {% if question_list.has_prev %}
                <li class="page-item">
                    <a class="page-link" href="?page={{ question_list.prev_num }}">이전</a>
                </li>
              <!-- 이전 페이지가 없을 시 -->
              {% else %}
                <li class="page-item disabled">
                    <a class="page-link" href="">이전</a>
                </li>
              {% endif %}
              <!-- 중간 페이징 -->
              {% for page_num in question_list.iter_pages() %}
                {% if page_num %}
                    {% if page_num != question_list.page %}
                        <li class="page-item">
                            <a class="page-link" href="?page={{ page_num }}">{{ page_num }}</a>
                        </li>
                    {% else %}
                        <li class="page-item active" aria-current="page">
                            <a class="page-link" href="#">{{ page_num }}</a>
                        </li>
                    {% endif %}
                {% else %}
                    <li class="disabled">
                        <a class="page-link" href="#">...</a>
                    </li>
                {% endif %}
              {% endfor %}
              <!-- 다음 페이징 구현 (다음 페이지가 있는 경우_ -->
              {% if question_list.has_next %}
                  <li class="page-item">
                      <a class="page-link" href="?page={{ question_list.next_num }}">다음</a>
                  </li>
              <!-- 다음 페이지 없는 경우 -->
              {% else %}
                  <li class="page-item disabled">
                      <a class="page-link" tabindex="-1" aria-disabled="true" href="#">다음</a>
                  </li>
              {% endif %}
          </ul>
        </nav>

        </ul>
        <a href="{{ url_for('question.create') }}" class="btn btn-primary"> 질문 등록하기 </a>
    </div>

    {% endblock %}

</body>
</html>

question_list.html 입니다.

 

이제 유저값이 들어갔으므로 글쓴이와 댓글 작성자를 표시하겠습니다.

중요한 부분은 {{ question.user.username }} 입니다.

 

 

DB객체에 접근하는 법에 대해서 다시 정리해보겠습니다.

question.answer_set 으로 Answer 객체에 접근 가능합니다. (<Answer[1]> <Answer[2]>....)

Answer에서 선언해야 접근가능합니다 (question = db.relationship('Question', backref=db.backref('answer_set', )))

하지만 내부는 불가능 합니다. (question.answer_set이 Answer[1]이지만 question.answer_set.id 불가능)

 

question.클래스속성으로 Question 객체 클래스 속성 접근 가능합니다. ( {{ question.id }} )

 

user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False)

User.id값하고 Question의 user_id하고 똑같은 값을 가지게 됩니다.

 

relationship(테이블명, back? 이건 모르겠음) 조사해야합니다....

user = db.relationship('User',backref=db.backref('question_set'))

이걸로는 해당 user_id에 해당하는 모든 user 속성에 접근 가능하게 만듭니다.
question.user.username 이렇게 접근할 수 있게 하는 것이죠

<!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>


        <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 입니다.

 

게시글 작성 시각과 댓글 작성 시각을 추가시켰습니다.

 

반응형