My Profile Photo

DongChanS's blog


수학과 학생의 개발일지


Python으로 DB 다루기

파이썬으로 db 접근하기

저번에는 sqlite3에서 직접 접근해서 sql문법을 구성하였는데, 이것을 파이썬으로 어떻게 다루는지 알아보자.

sqlite3 라이브러리

sqlite3이라는 라이브러리를 통해 파이썬을 다룰 수 있다.

  • SQLite3에 접속
  1. 파이썬을 통해서 sqllite에 접속

    sqlite3 라이브러리

    import sqlite3
       
    conn = sqlite3.connect('test.sqlite3')
    #sqlite3 test.sqlite3 명령어와 동일
    

    연결을 마치고 난 객체(파일과 비슷)도 변수에 저장해주자.

  2. 커서를 만들어야함.

    일련의 데이터에 순차적으로 액세스할 때 검색 및, 현재 위치를 포함하는 데이터 요소

    -> 포인터를 위해서 데이터베이스에 접근하고, 얘를 통해서 데이터베이스를 실행시킬거임.

    db = conn.cursor()
    


  • table 관련 SQL작성
  1. execute 메서드를 활용해서 sql문을 작성.

    db.execute('SELECT * FROM users')
    

    단, 세미콜론을 붙이지 않는다!

  2. 커서에게 해당하는 데이터를 가져오라고 부탁함.

    data = db.fetchall()
    print(type(data))
    print(*data, sep="\n")
    
    start666:~/workspace $ python3 py_db.py 
    <class 'list'>
    ('1', '정호', '유', '40', '전라북도', '016-7280-2855', '370')
    ('2', '경희', '이', '36', '경상남도', '011-9854-5133', '5900')
    ('3', '정자', '구', '37', '전라남도', '011-4177-8170', '3100')
    ('4', '미경', '장', '40', '충청남도', '011-9079-4419', '250000')
    ('5', '영환', '차', '30', '충청북도', '011-2921-4284', '220')
    ('6', '서준', '이', '26', '충청북도', '02-8601-7361', '530')
    ('7', '주원', '민', '18', '경기도', '011-2525-1976', '390')
    ('8', '예진', '김', '33', '충청북도', '010-5123-9107', '3700')
    ('9', '서현', '김', '23', '제주특별자치도', '016-6839-1106', '43000')
    ('10', '서윤', '오', '22', '충청남도', '011-9693-6452', '49000')
    


  • CRUD를 파이썬을 이용해서 해보기

    CREATE TABLE dongchans (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        first_name TEXT NOT NULL,
        last_name TEXT NOT NULL,
        age INTEGER NOT NULL,
        country TEXT NOT NULL,
        phone TEXT NOT NULL,
        balance INTEGER NOT NULL );
    
    conn = sqlite3.connect('test.sqlite3')
    db = conn.cursor()
    f = open('create_dongchan.sql','r') # SQL파일을 읽어온다 : 테이블 생성
    string = f.read()
      
    db.execute(string) # .read 대신에 읽은 파일을 실행시킴.
    db.execute("INSERT INTO dongchans VALUES (1,'동찬','신',25,'대구광역시','010-4753-8286',0)")
    conn.commit() # 테이블의 변화를 적용해야함(커밋해야함!)
      
    db.execute('SELECT * FROM dongchans') # 테이블을 읽는다.
    print(cur.fetchone()) # 하나만 가져옴
      
    db.execute('DROP TABLE dongchans') # 삭제는 간단함.
    
    start666:~/workspace $ python3 py_db.py 
    (1, '동찬', '신', 25, '대구광역시', '010-4753-8286', 0)
    


플라스크로 게시판 만들기

code 링크 : https://github.com/DongChanS/c9simple_board

배웠던 데이터베이스를 이용해서 게시판 기능을 하는 웹사이트를 만들어보도록 하자!

  1. 메인페이지
    • 타이틀과 내용을 제출할 수 있는 기능을 만든다.
    • 게시판을 조회할 수 있는 버튼을 만든다.
  2. create페이지
    • 타이틀과 내용을 이 페이지에서 받아서 db에 저장한다.
    • 저장한 다음에는 다시 메인페이지로 redirect한다.
  3. 게시판 페이지
    • 10개단위로 게시물을 보여준다.
from flask import Flask,render_template,request,redirect
import sqlite3
app = Flask(__name__)

@app.route('/') #메인페이지
def index():
    return render_template('index.html')
    
@app.route('/create') #잠깐 들어갔다 나오는 페이지
def create():
    c = sqlite3.connect('board.sqlite3')
    db = c.cursor()
    title = request.args.get('title')
    content = request.args.get('content')
    db.execute("INSERT INTO articles (title,content) VALUES (?,?)",(title,content))
    c.commit()
    c.close()
    return redirect("/")
    
@app.route('/page/<int:num>') #게시판 페이지
def page(num):
    c = sqlite3.connect('board.sqlite3')
    db = c.cursor()
    sql_read = "SELECT * FROM articles LIMIT 10 OFFSET ?"
    res = db.execute(sql_read,(10*num,)).fetchall()
    c.close()
    return render_template('page.html',res=res)
  • 메인페이지
<!-- index.html -->
<div class="container" style="background-color:rgb(18,18,18,0.5);">
    <!-- 입력기능 만들기 -->
    <div class="row">
        <div class="col-4 d-flex align-items-center" 
             style="font-weight:700; font-size:50px; color:#fff">
            게시판<br>
            입니다
        </div>
        <div class="col-4 d-flex justify-content-center">
            <form action="/create">
                <input type="text" class="d-block" style="background-color:#fff;"
                       name="title" placeholder="제목을 입력해 주세요"/>
                <input type="text" style="height:400px; background-color:#fff;"
                       name="content" placeholder="내용을 입력해 주세요"/>
                <input type="submit" value="Submit"/>
            </form>
        </div>
        <div class="col-2"></div>
    </div>
    <!-- 게시판으로 가는 버튼 만들기 -->
    <div class="row d-flex justify-content-center">
        {% for idx in range(10) %}
        <button><a href="/page/"></a></button>
        {% endfor %}
    </div>
</div>

1

  • create 페이지는 어자피 redirect하기 위한 용도이므로 html을 구성하지 않았음.

    (url에서 지정된 인자를 가져오기위함)

  • 게시판 페이지

<div class="container">
    <!-- 게시글을 보여주는 기능 -->
    <div class="row">
        <div class="col-1 border border-info">id</div>
        <div class="col-3 border border-info">제목</div>
        <div class="col-8 border border-info">내용</div>
    </div>
    {% for r in res %}
    <div class="row">
        <div class="col-1 border border-info">{{r[0]}}</div>
        <div class="col-3 border border-info">{{r[1]}}</div>
        <div class="col-8 border border-info">{{r[2]}}</div>
    </div>
    {% endfor %}
	<!-- 홈페이지로 돌아가는 기능 -->
    <form action="/">
        <input type="submit" value="홈페이지로 돌아가기"/>
    </form>
</div>

2

이렇게 구성하면 메인페이지에서 입력한 제목과 내용이 그대로 디비에 저장이 되서 각 게시판의 링크마다 10개씩 뿌려준다.


게시글 삭제하기

그런데 의문점이 하나 생긴다.

1편에서 DB의 장점은 삭제와 수정이 용이하다고 했는데 그 부분이 구현이 안되어있다.

특정 글에 삭제버튼을 누르면 글이 지워지는 기능을 만들어보자.

아이디어는 간단하다.

  1. 삭제버튼을 만든다.

  2. 삭제버튼을 누르면 delete링크로 보낸다.

    (단 삭제할 글을 구별할 수 있게 /delete/게시물id/게시판num 의 형식으로 보내야한다. )

  3. delete링크에서 해당하는 글을 삭제하고 다시 해당하는 게시판으로 redirect한다.

(단! type을 신중하게 정해야한다. return값은 무조건 string이므로 num이 숫자여도 string으로 받아야함)


@app.route('/delete/<int:id_>/<num>')
def delete(id_,num):
    c = sqlite3.connect('board.sqlite3')
    db = c.cursor()
    db.execute('DELETE FROM articles WHERE id = ?',(id_,))
    c.commit()
    return redirect('/page/'+num)
<div class="col-2">
    <button><a href="/delete/{{r[0]}}/{{num}}">삭제</a></button>
</div>
<!-- num은 게시판 번호이고, r[0]은 id입니다 -->

여기서 6번글 삭제버튼을 누르면,

3

이런 식으로 글이 없어지는 것을 확인할 수 있다.

4


게시글 수정

글을 수정하는건 삭제하는거보다 구현이 더 어려운데,

왜냐하면 수정을 위해서는 수정하기위한 페이지를 만들어줘야하기 때문이다.

(예시: 깃허브)

5

이런것처럼 수정버튼을 누르면 수정할 수 있는 페이지로 이동하기때문.

로직은 간단하다.

  1. 각 글마다 수정버튼을 만든다 + /edit으로 이동

    <button><a href="/edit/{{r[0]}}/{{num}}">수정</a></button>
    

    예를 들어서, 0번게시판의 2번째글을 수정하는 버튼을 누르면

    /edit/2/0 으로 이동할 수 있다.

    6


  2. /edit에서 글을 수정해서 제출버튼을 누르면 /edit_result로 수정정보를 보낸다.

    @app.route('/edit/<int:id_>/<num>')
    def edit(id_,num):
        c = sqlite3.connect('board.sqlite3')
        db = c.cursor()
        contents = db.execute('SELECT title,content FROM articles WHERE id = ?',(id_,)).fetchone()
        return render_template('edit.html',contents=contents,id_=id_,num=num)
    
       
    <!-- edit.html -->
    <div class="container" style="background-color:rgb(18,18,18,0.5);">
        <div class="row">
            <div class="col-4 d-flex align-items-center" 
                 style="font-weight:700; font-size:50px; color:#fff">
                글 수정<br>
                하세요
            </div>
            <div class="col-4 d-flex justify-content-center">
                <form action="/edit_result">
                    <input type="text" class="d-block" style="background-color:#fff;" name="title" value="{{contents[0]}}"/>
                    <input type="text" style="height:400px; background-color:#fff;" name="content" value="{{contents[1]}}"/>
                    <input type="hidden" name="id" value="{{id_}}">
                    <input type="hidden" name="num" value="{{num}}">
                    <input type="submit" value="Submit"/>
                </form>
            </div>
            <div class="col-2"></div>
        </div>
    </div>
       
    

    어자피 입력받는 form이랑 유사하게 하는게 가장 편하므로 index.html을 적극 활용한다.

    그러나, 수정하려면 원래 글과 제목이 보존되어있어야하므로 form태그의 value속성을 이용해서 기본값을 지정해준다. (placeholder과는 다름)

    수정할 id와 게시판 넘버는 변하지 않으나, /edit_result 로 보내야하는 값이므로 hidden타입을 이용해서 보내준다.

    7

    글을 수정하고 제출해보자.


  3. /edit_result에서 글을 수정해주고 원래 게시판으로 redirect를 한다.

    @app.route('/edit_result')
    def edit_result():
        c = sqlite3.connect('board.sqlite3')
        title = request.args.get('title')
        content = request.args.get('content')
        id_ = request.args.get('id')
        num = request.args.get('num')
        db = c.cursor()
        sql_update = "UPDATE articles SET title = ?,content = ? WHERE id = ?"
        db.execute(sql_update,(title,content,id_))
        c.commit()
        return redirect('/page/'+num)
    

    글을 수정해주고 원래 게시판으로 redirect 해준 결과를 볼 수 있다.

    8

물론 POST로 하지 않고 GET방식으로 전부 구현하였으나, 실제로는 POST방식을 사용해야한다.

그리고 1번의 a태그의 링크가 보이는 것을 막기 위해서 자바스크립트를 이용하여 링크정보를 json파일로도 보낼수 있다고 합니다.


나중에는 이러한 기능뿐만 아니라 댓글기능이나 로그인기능같은 다양한 기능들이 추가되고, 데이터가 쌓이면서 복잡해지는데 이러한 기능들의 관계를 잘 정의해주는 것이 중요하다.


나중에 할 것

python의 sqlite3 라이브러리는 사실 불편하다.

왜냐하면 파이썬문법과 sql문법이 혼재하기 때문에 가독성이 별로 안좋다.

그래서 pythonic하게 sql을 다루는 라이브러리가 존재하는데,

sqlalchemy라는 라이브러리는 sql문법을 파이썬 객체의 기능으로 전부 mapping을 해놓은 것인데, 이러한 Mapping을 ORM이라고 한다.

예를 들어서, article.save(), article.update()같이 sql문을 파이썬 객체로 구현할 수 있게 된다.


DB 주의할 점

데이터베이스는 파일 자체를 저장하기에 적절한 공간이 아니고, 파일을 다루기에 적절한 공간

왜냐하면 db는 read-write가 오래걸리는 데이터저장소이기때문이다.

따라서, 사진을 넣는게 아니라, 사진의 주소를 넣으면서 활용한다.

static file들은 어딘가에 넣고 걔들의 주소를 디비에 넣는게좋음.

comments powered by Disqus