[Python] 22. [DBMS] Sqlite3 + Python 연동 실습, 140자 일기장 만들기

2017. 8. 5. 00:08·빅데이터 프로그래밍/Python
728x90
반응형

01. Sqlite3 + Python 연동 실습
1. SQL

▷ /sqlite3/diary140.sql
-------------------------------------------------------------------------------------

  CREATE TABLE diary (
    diary_id INTEGER PRIMARY KEY AUTOINCREMENT, 
    createdate DATETIME, 
    note CHAR(140)
  );

  CREATE TABLE diary_img (
    img_id INTEGER PRIMARY KEY AUTOINCREMENT, 
    img BLOB, 
    diary_id INTEGER, 
    FOREIGN KEY(diary_id) REFERENCES diary(diary_id)
  );


SELECT diary_id, createdate, note FROM diary;

DELETE FROM diary
WHERE diary_id = 5;

SELECT img_id, img, diary_id FROM diary_img;
SELECT img_id, diary_id FROM diary_img;

-- LEFT OUTER JOIN은 왼쪽 테이블의 것은 조건에 부합하지 않더라도 모두 결합되어야 함
SELECT D.DIARY_ID, D.CREATEDATE, D.NOTE, I.IMG 
FROM DIARY AS D LEFT OUTER JOIN DIARY_IMG AS I 
ON D.DIARY_ID = I.DIARY_ID 
ORDER BY D.CREATEDATE DESC
LIMIT 1 OFFSET 0;
            
             
           

-------------------------------------------------------------------------------------
 
 
2. database 및 테이블 생성 
 
▷ /sqlite3/create_db.py
-------------------------------------------------------------------------------------
# -*- coding: utf-8 -*-

import sqlite3

conn = sqlite3.connect('python.db')
cursor = conn.cursor()

cursor.execute("""
  CREATE TABLE diary (
  diary_id INTEGER PRIMARY KEY AUTOINCREMENT, 
  createdate DATETIME, 
  note CHAR(140))
""")

cursor.execute("""
  CREATE TABLE diary_img (
  img_id INTEGER PRIMARY KEY AUTOINCREMENT, 
  img BLOB, 
  diary_id INTEGER, 
  FOREIGN KEY(diary_id) REFERENCES diary(diary_id))
""")

cursor.execute("SELECT name FROM sqlite_master")
for row in cursor:
    print ("TABLE = ", row[0])
   
cursor.close()
conn.close()


-------------------------------------------------------------------------------------​

 

3. Python script
▷ /sqlite3/Diary140.py
-------------------------------------------------------------------------------------
# -*- coding: utf-8 -*-

from datetime import datetime
import sqlite3
import wx 
import wx.html2 # HTML Window
import ntpath    # FILE NAME Extraction
import base64    # IMAGE URI Encoding

RECORD_PER_PAGE = 5 # 한페이지당 출력할 레코드 갯수

def sum():
    pass

class MainFrame (wx.Frame):
    def __init__(self):
        wx.Frame.__init__ (self, 
                           None, 
                           title = "140자 일기장", 
                           size = wx.Size(815,655), 
                           style = wx.DEFAULT_FRAME_STYLE)
        
        self.MaxImageSize = 300 # 미리보기 이미지의 크기 200 X 200
        
        self.mainPanel = wx.Panel(self) # 화면 분할하여 Widget의 배치
        
        # 일기 입력 창
        self.leftPanel = wx.Panel(self.mainPanel)
        self.leftPanel.SetMaxSize(wx.Size(300,-1))        
        self.input_image_path = ""
        self.inputTextCtrl = wx.TextCtrl(  # 일기 내용 입력
            self.leftPanel, size=wx.Size(300,100), style=wx.TE_MULTILINE)  # 여러라인 지원

        self.inputTextCtrl.SetMaxLength(140) # 최대 글자수 140                                      # 최대 입력 글자
        self.inputTextCtrl.Bind(wx.EVT_TEXT, self.OnTypeText) # 입력 발생 이벤트 처리
        # 글자수 출력 레이블 생성
        self.lengthStaticText = wx.StaticText(self.leftPanel, style=wx.ALIGN_RIGHT)
        self.selectImageButton = wx.Button(self.leftPanel, label="이미지 추가")
        # 버튼 클릭시 OnFindImageFile 메소드 실행
        self.selectImageButton.Bind(wx.EVT_BUTTON, self.OnFindImageFile)
        self.imageStaticBitmap = wx.StaticBitmap(self.leftPanel)  # 이미지 미리보기 객체 선언       
        
        # 버튼 클릭시 OnInputButton 메소드 실행
        self.inputButton = wx.Button(self.leftPanel, label="저장")
        self.inputButton.Bind(wx.EVT_BUTTON, self.OnInputButton)
        
        # 일기 표시 창
        self.rightPanel = wx.Panel(self.mainPanel)
        self.outputHtmlWnd = wx.html2.WebView.New(self.rightPanel) # 일기 내용을 출력할 WebView 객체 생성
        
        # Python + HTML 이벤트 연동
        self.outputHtmlWnd.Bind(wx.html2.EVT_WEBVIEW_NAVIGATING, self.OnNavigating) 

        # 위젯 배치        
        leftPanelSizer = wx.StaticBoxSizer(
            wx.VERTICAL, self.leftPanel, "글 남기기")
        leftPanelSizer.Add(self.inputTextCtrl, 0, wx.ALL, 5)
        leftPanelSizer.Add(self.lengthStaticText, 0, wx.ALIGN_RIGHT|wx.RIGHT, 5)
        leftPanelSizer.Add(self.selectImageButton, 0, wx.ALIGN_RIGHT|wx.RIGHT, 5)
        leftPanelSizer.Add(self.imageStaticBitmap, 0, wx.ALIGN_RIGHT|wx.ALL, 5)
        leftPanelSizer.Add(self.inputButton, 0, wx.ALIGN_RIGHT|wx.RIGHT, 5)
        self.leftPanel.SetSizer(leftPanelSizer)
        
        htmlWndSizer = wx.GridSizer(1, 1, 0, 0)
        htmlWndSizer.Add(self.outputHtmlWnd, 0, wx.ALL|wx.EXPAND, 5)

        self.rightPanel.SetSizer(htmlWndSizer)
        self.rightPanel.Layout()
        htmlWndSizer.Fit(self.rightPanel)
        
        mainSizer = wx.BoxSizer(wx.HORIZONTAL)
        mainSizer.Add(self.leftPanel, 1, wx.ALIGN_RIGHT|wx.ALL|wx.EXPAND, 5)
        mainSizer.Add(self.rightPanel, 1, wx.ALL|wx.EXPAND , 5)
        self.mainPanel.SetSizer(mainSizer)
        self.Layout()

        # Database 초기화
        self.conn = sqlite3.connect("python.db")
        self.cursor = self.conn.cursor() # SQL 실행 및 결과 수신
        self.CheckSchema()
        self.LoadDiary(0)
                
    def CheckSchema(self):
        self.cursor.execute("""
        CREATE TABLE IF NOT EXISTS DIARY (
        DIARY_ID INTEGER PRIMARY KEY AUTOINCREMENT, 
        CREATEDATE DATETIME, 
        NOTE CHAR(140))
        """)

        self.cursor.execute("""
        CREATE TABLE IF NOT EXISTS DIARY_IMG (
        IMG_ID INTEGER PRIMARY KEY AUTOINCREMENT, 
        IMG BLOB, 
        DIARY_ID INTEGER, 
        FOREIGN KEY(DIARY_ID) REFERENCES DIARY(DIARY_ID))
        """)

    def OnTypeText(self, event):
        self.lengthStaticText.SetLabel(
            "현재 글자 수 : {0}".format(len(self.inputTextCtrl.GetValue())))
        self.leftPanel.Layout()   # lengthStaticText 크기 변경에 따라 레이아웃 재출력

    def OnFindImageFile(self, event):
        openFileDialog = wx.FileDialog(
            self, "Open", 
            wildcard="Image files (*.png, *.jpg, *.gif)|*.png;*.jpg;*.jpeg;*.gif",
            style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)  # FileDialog 객체 생성

        if openFileDialog.ShowModal() == wx.ID_OK:
            # 선택한 이미지 경로 추출
            self.input_image_path = openFileDialog.GetPath()
            
            # 비율을 유지하면서 이미지 크기를 윈도우에 맞추기
            img = wx.Image(self.input_image_path, wx.BITMAP_TYPE_ANY) # 이미지 열기
            Width = img.GetWidth()
            Height = img.GetHeight()
            if Width > Height:
                NewWidth = self.MaxImageSize
                NewHeight = self.MaxImageSize * Height / Width
            else:
                NewWidth = self.MaxImageSize
                NewHeight = self.MaxImageSize * Width / Height
            img = img.Scale(NewWidth, NewHeight)  # 이미지 객체의 크기 조절
            self.imageStaticBitmap.SetBitmap(wx.Bitmap(img)) # 크기가 변경된 이미지 적용
            self.leftPanel.Layout()
            self.leftPanel.Refresh()
        
        openFileDialog.Destroy()

    # 저장 
    def OnInputButton(self, event):
        # 선택한 파일명 가져오기
        fileName = ntpath.basename(self.input_image_path)
        # 문자열만 저장
        self.cursor.execute(
            "INSERT INTO DIARY (DIARY_ID, CREATEDATE, NOTE) VALUES(NULL, ?, ? )", 
            (str(datetime.now()), self.inputTextCtrl.GetValue()))
        
        # 일련번호 PK 추출
        diary_id = self.cursor.lastrowid
        # print('diary_id: ' + str(diary_id));  # AUTOINCREMENT의 값과 동일

        if self.input_image_path.strip() != "": # 이미지가 존재한다면
            # 파일을 DBMS에 저장
            self.cursor.execute(
                # NULL: AUTOINCREMENT가 선언된 컬럼에 명시
                "INSERT INTO DIARY_IMG (IMG_ID, IMG, DIARY_ID) VALUES(NULL, ?, ?)",
                (sqlite3.Binary(open(self.input_image_path,"rb").read()), diary_id))
        
        self.conn.commit() # 적용

        wx.MessageBox("저장되었습니다.", "140자 일기장", wx.OK) 

        self.inputTextCtrl.SetValue("")     # 위젯 초기화   
        self.input_image_path = ""
        self.imageStaticBitmap.SetBitmap(wx.Bitmap(0,0))

        self.leftPanel.Layout()
        self.leftPanel.Refresh()
        self.LoadDiary(0)  # 신규 등록후 일기 출력

    def LoadDiary(self, page):
        self.cursor.execute(
            "SELECT D.DIARY_ID, D.CREATEDATE, D.NOTE, I.IMG FROM DIARY "
            + "AS D LEFT OUTER JOIN DIARY_IMG AS I "
            + "ON D.DIARY_ID = I.DIARY_ID ORDER BY D.CREATEDATE DESC "
            + "LIMIT {0} OFFSET {1}"
            .format(RECORD_PER_PAGE, page*RECORD_PER_PAGE))

        html = """
            <html>
            <head>
            </head><body>{0}</body></html>
            """
        diary_id = 0
        body = ""
        for row in self.cursor: # 하나의 레코드씩 추출
            # print(row); # tuple
            diary_id = int(row[0]);  # 첫번재 컬럼의 값 산출
            imgTag = ""
            if row[3] != None: # 이미지가 존재한다면
                imgTag = """<img src='data:image/png;base64,{0}' 
                style='width:400px; height:auto;' align=center>
                """.format(
                    base64.b64encode(row[3]).decode("ascii")) # Sqlite3에서 이미지 추출
            
            noteBR = row[2].replace('\n', '<br>')  # Enter를 <br> 태그로 변경
            
            content = """<a name="neural">
                <p style="word-wrap:break-word;font-size=12px;">
                <font size=2><b><i>
                {1}
                </i></b>
                <a href="del:{0}">[삭제]</a></font><br> 
                {2}
                <br>
                {3}
                """.format(diary_id, row[1], noteBR, imgTag) # 날짜, 삭제 링크, 내용, 이미지 출력
            body += content

        pageNavigation = "<p align='center' style='font-size=12px'>"
        
        # Prev 버튼(링크)
        if page > 0:
            pageNavigation += "<a href='nav:{0}'>Prev</a>".format(page - 1)
        
        pageNavigation += " "

        # Next 버튼(링크)
        self.cursor.execute(
            "SELECT count(*) FROM DIARY WHERE DIARY_ID<{0}".format(diary_id))
        row = self.cursor.fetchone() 
        
        if row != None:
            nextRowCount = row[0]
            if nextRowCount > 0:
                pageNavigation += "<a href='nav:{0}'>Next</a>".format(page + 1)

        pageNavigation += "</p>"
        body += pageNavigation

        self.outputHtmlWnd.SetPage(html.format(body), "") # 최종적인 HTML 출력

    def OnNavigating(self, event):
        if event.URL.startswith("del:") == True:
            diary_id = event.URL.rpartition(":")[-1]  # url의 뒤에서 첫번재 문자 추출
            self.cursor.execute(
                "DELETE FROM DIARY WHERE DIARY_ID={0}".format(diary_id))
            self.cursor.execute(
                "DELETE FROM DIARY_IMG WHERE DIARY_ID={0}".format(diary_id))
            self.conn.commit()
            self.LoadDiary(0)
            wx.MessageBox("삭제했습니다.", "140자 일기장", wx.OK)
        elif event.URL.startswith("nav:") == True:
            page = event.URL.rpartition(":")[-1]
            self.LoadDiary(int(page))
        
        event.Skip(False) # WebView 위젯이 해당 이벤트를 받아서 해당 URL로 이동하려는 것을 막기 위함.

if __name__ == "__main__":
    app = wx.App() # wxPython을 사용하기위한 메인 객체 생성
    frame = MainFrame() # 객체 생성, Widget 생성, Event 등록 DBMS 연결
    frame.Show()

    app.MainLoop()  # Widget 이벤트 수신 시작
    
    
       
-------------------------------------------------------------------------------------

 
 
 

728x90
반응형

'빅데이터 프로그래밍 > Python' 카테고리의 다른 글

[Python] 24. Regular Expression(정규 표현식) 기본 문법 실습 2, Pyperclip library, cx_freeze로 EXE 만들기  (0) 2017.08.05
[Python] 23. Regular Expression(정규 표현식) 기본 문법 실습 1  (0) 2017.08.05
[Python] 21. [DBMS] Sqlite3 + Python 연동 실습  (0) 2017.08.05
[Python] 20. [DBMS] 데이터베이스 개론, SQLite3 사용  (0) 2017.08.02
[Python] 19. [GUI] wxPython 그래픽 사용자 인터페이스, 다양한 Widget, Menu  (0) 2017.08.02
'빅데이터 프로그래밍/Python' 카테고리의 다른 글
  • [Python] 24. Regular Expression(정규 표현식) 기본 문법 실습 2, Pyperclip library, cx_freeze로 EXE 만들기
  • [Python] 23. Regular Expression(정규 표현식) 기본 문법 실습 1
  • [Python] 21. [DBMS] Sqlite3 + Python 연동 실습
  • [Python] 20. [DBMS] 데이터베이스 개론, SQLite3 사용
밍글링글링
밍글링글링
mingling - 밍글링, 밍글밍글링. 코드와 어우러지다. IT/ 프로그래밍/소스
    반응형
    250x250
  • 밍글링글링
    mingling
    밍글링글링
  • 전체
    오늘
    어제
    • 밍글링글링 (407) N
      • Flutter (2)
      • 일상생활 (8)
        • 리뷰 (1)
        • 생활정보 (4)
        • 맛집 (0)
        • 여행 (0)
        • 모든정보 (3)
      • JAVA (126)
        • 개념 (6)
        • 예제 (115)
        • Exception (2)
      • C (1)
        • C (1)
        • C++ (0)
        • C# (0)
      • JS (29)
        • JavaScript (18)
        • JQuery (5)
        • AJax (0)
        • NODE.JS (6)
        • Angular.JS 2.0 (0)
      • WEB (87)
        • HTML (6)
        • CSS (61)
        • JSP (20)
        • JSTL (0)
      • FrameWork (8)
        • Spring (8)
        • BootStrap (0)
        • MyBATIS (0)
        • JUnit (0)
      • 외부 라이브러리 (5)
      • 공유 소스 관리 (5)
        • Git (5)
        • SVN (0)
      • 빅데이터 프로그래밍 (37)
        • Python (37)
        • R Programming (0)
      • DB (7)
        • ORACLE (0)
        • MySql (6)
      • Development Tools (7)
        • StarUML (0)
        • eXERD (0)
        • Eclipse (4)
      • SKILL (6)
        • Migration (0)
        • Security (6)
      • MicroSoft (0)
        • Excel (0)
        • Word (0)
      • Android (0)
      • Server (21)
        • Ubuntu (5)
        • Linux (15)
      • IOS (0)
      • XML (0)
      • 미디어 (0)
      • 공지사항 (3)
      • NETWORK (1)
      • 게임 (4)
        • 피파 (1)
        • 리니지M (0)
        • 배틀그라운드 (1)
        • 듀랑고 (2)
      • 세상 이슈 (6)
      • 일렉트론 (0)
      • 대회 소식 (4)
      • 업무 (2)
      • Express, Vue (6)
      • docker (11)
      • svelte (3)
      • 블록체인 (1)
      • IT (10) N
      • Rust (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 미디어로그
    • 위치로그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    jsp parameter
    css tb
    React Compiler
    프런트엔드
    러스트
    css table
    자바 배열
    클론코딩
    proxy pass
    Node
    css block
    오류
    자바 for문
    vscode
    티스토리 자동화
    자바 exception
    css transition
    자바 객체 지향
    nginx ssl 적용
    API 설계
    vue cli
    mysql db
    브라우저 자동화
    자바 생성자
    servlet class
    gitlab 설치
    ssl 인증서 발급
    vue 설치
    Extension Bisect
    에디터 팁
    spring java
    자바 클래스
    VS Code 팁
    AI 코딩 에이전트
    ubuntu
    SSL 인증서
    svelte
    nginx ssl 설정
    nginx
    lang rust
    리눅스 설치
    Rust lang
    css list
    css float
    css perspective
    rust linux
    jsp include
    Java Array
    docker
    java casting
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
밍글링글링
[Python] 22. [DBMS] Sqlite3 + Python 연동 실습, 140자 일기장 만들기
상단으로

티스토리툴바