한 5일동안 만들었다.
아직 완성은 아니지만, 뭐 완성품 올리는 블로그도 아니고, 꽤 잘 작동해서 올리게 되었다.
DbManager.py이다.
import sqlite3 import arrow import os class DbManager: def __init__(self): self.conn = sqlite3.connect(os.path.join(os.path.expanduser('~'), 'documents', 'Diary', 'Diary.db')) self.c = self.conn.cursor() def saveDiaryInfo(self, date, time, todayFeel, todayThoughtsCount, writer): sql = f'INSERT INTO Diary (date, time, todayFeel, TodayThoughtsCount, Writer) VALUES ("{date}","{time}", {todayFeel}, "{todayThoughtsCount}", "{writer}")' try: self.c.execute(sql) except: self.deleteDiaryInfo(date) self.c.execute(sql) self.conn.commit() def readDiaryInfo(self, date, userId): sql = f'SELECT * FROM Diary WHERE date = "{date}" and Writer = "{userId}"' self.c.execute(sql) self.conn.commit() return self.c.fetchone() def deleteDiaryInfo(self, date): sql = f'DELETE FROM Diary WHERE date = "{date}"' self.c.execute(sql) self.conn.commit() def closeDb(self): self.conn.commit() self.c.close() self.conn.close() def saveUserInfo(self, id, pw, sQuestionNum, answer): sql = 'INSERT INTO User (ID, PW, sQuestionNum, answer) VALUES ("{0}","{1}", {2}, "{3}")'.format(id, pw, sQuestionNum, answer) try: self.c.execute(sql) except: print("이 계정은 이미 있습니다.") return -1 self.conn.commit() return 0 def readAllId(self): sql = f'SELECT ID FROM User' self.c.execute(sql) self.conn.commit() return self.c.fetchall() def findDiaryWithCondition(self, startDate, finishDate, startFeelRange, finishFeelRange, thoughtsStartRange, thoughtsFinishRange, userId): sql = f'SELECT * FROM Diary WHERE Writer = "{userId}" and todayFeel >={startFeelRange} and todayFeel<={finishFeelRange} and TodayThoughtsCount>={thoughtsStartRange} and TodayThoughtsCount<={thoughtsFinishRange} '#and CAST(strftime("%s", Date) AS integer )>=CAST(strftime("%s", {startDate}) AS integer ) and CAST(strftime("%s", Date) AS integer )<=CAST(strftime("%s", {finishDate}) AS integer )' self.c.execute(sql) self.conn.commit() return self.c.fetchall() def readUserPw(self, id): sql = f'SELECT PW FROM User WHERE ID = "{id}"' self.c.execute(sql) self.conn.commit() return self.c.fetchone() def readUsersQuestionNum(self, id): sql = f'SELECT sQuestionNum FROM User WHERE ID = "{id}"' self.c.execute(sql) self.conn.commit() return self.c.fetchone() def readUserAnswer(self, id): sql = f'SELECT answer FROM User WHERE ID = "{id}"' self.c.execute(sql) self.conn.commit() return self.c.fetchone() def deleteUserInfo(self, id): sql = f'DELETE FROM User WHERE ID = "{id}"' self.c.execute(sql) self.conn.commit() def readUserIdByQA(self, sQuestionNum, answer):#sQuestionNum이랑 answer로 id찾는 함수 sql = f'SELECT id FROM User WHERE answer = "{answer}" AND sQuestionNum = "{sQuestionNum}"' self.c.execute(sql) self.conn.commit() return self.c.fetchone() def updatePw(self, id, newPw): sql = f'UPDATE Diary SET PW = "{newPw}" WHERE ID = "{id}"' self.c.execute(sql) self.conn.commit() if __name__ == "__main__": #TestCase a = DbManager() a.saveDiaryInfo(arrow.now().date(), str(arrow.now().time())[:5], 1, 4) print(a.readDiaryInfo(arrow.now().date())) a.saveUserInfo('qwerty', 'qwertyuiop1', 1, 'qwerty') print(a.readAllID()) print(a.readUserPw('qwerty')) print(a.readUsersQuestionNum('qwerty')) print(a.readUserAnswer('qwerty')) ''' for i in range(400): a.deleteDiaryInfo(str(i)) a.deleteUserInfo(str(i)) for i in range(400): a.saveDiaryInfo(str(i), str(i), i, i) a.saveUserInfo(str(i), str(i), i, str(i)) ''' a.closeDb() #TestCase
db 파일 주소를 os.path.join(os.path.expanduser('~'), 'documents', 'Diary', 'Diary.db') 이렇게 설정한 이유는 exe로 만들어도 잘 작동하게 하기 위함이다.
if __name__ == "__main__"안에 들어있는 것은 테스트 코드이다.
다음은 Assistant.py이다.
from DbManager import DbManager import os import arrow import hashlib import sqlite3 class WatchDiary: def readCorrectDiaryTxt(self, date): directory = os.path.join(os.path.expanduser('~'), 'documents', 'Diary', 'DiaryStorage', str(date) + '.txt') if os.path.isfile(directory): with open(directory, 'r') as f: return f.read() else: return -1 def readCorrectTodayThoughtsTxt(self, date): directory = os.path.join(os.path.expanduser('~'), 'documents', 'Diary', 'TodayThoughtsStorage', str(date) + '.txt') if os.path.isfile(directory): with open(directory, 'r') as f: return f.read() else: return -1 class WriteDiary: def saveDiaryTxt(self, date, content): directory = os.path.join(os.path.expanduser('~'), 'documents', 'Diary', 'DiaryStorage', str(date) + '.txt') with open(directory, 'w+t') as f: f.write(content) return f.closed def saveTodayThoughtsTxt(self, date,content): directory = os.path.join(os.path.expanduser('~'), 'documents', 'Diary', 'TodayThoughtsStorage', str(date) + '.txt') with open(directory, 'w+t') as f: f.write(content) return f.closed class AccountManager: def __init__(self): self.DbManager = DbManager() def makeHash(self, inputText): inputText = inputText.encode() hashObject = hashlib.sha256() hashObject.update(inputText) hexDig = hashObject.hexdigest() return hexDig def logIn(self, id, pw): hexDig = self.makeHash(pw) userPw = self.DbManager.readUserPw(id) if userPw == (hexDig,): #userPw가 튜플임. return True else: return False def registerUser(self, id, pw, sQuestionNum, answer): pw = self.makeHash(pw) answer = self.makeHash(answer) self.DbManager.saveUserInfo(id = id, pw = pw, sQuestionNum = sQuestionNum, answer = answer) return True def deleteId(self, id): self.DbManager.deleteUserInfo(id) def findId(self, sQuestionNum, answer): answer = self.makeHash(answer) id = self.DbManager.readUserIdByQA(sQuestionNum, answer) if len(id) == 1: id = id[0] else: return -1 return id def resetPw(self, id): newPw = 'aXeYCQV' newPw = newPw.encode() hashObject = hashlib.sha256() hashObject.update(newPw) hexDig = hashObject.hexdigest() newPw = hexDig self.DbManager.updatePw(id, newPw) class Setup: def __init__(self): if os.path.isdir(os.path.join(os.path.expanduser('~'), 'documents', 'Diary', 'DiaryStorage')): pass else: os.makedirs(os.path.join(os.path.expanduser('~'), 'documents', 'Diary', 'DiaryStorage')) if os.path.isdir(os.path.join(os.path.expanduser('~'), 'documents', 'Diary', 'TodayThoughtsStorage')): pass else: os.makedirs(os.path.join(os.path.expanduser('~'), 'documents', 'Diary', 'TodayThoughtsStorage')) if os.path.isfile(os.path.join(os.path.expanduser('~'), 'documents', 'Diary', 'Diary.db')): pass else: conn = sqlite3.connect(os.path.join(os.path.expanduser('~'), 'documents', 'Diary', 'Diary.db')) c = conn.cursor() c.execute('''CREATE TABLE "Diary" ("Date" TEXT NOT NULL,"Time" TEXT NOT NULL,"todayFeel" INTEGER NOT NULL,"TodayThoughtsCount" TEXT,"Writer" TEXT NOT NULL,PRIMARY KEY("Date"));''') c.execute('''CREATE TABLE "User" ( "ID" TEXT NOT NULL UNIQUE, "PW" TEXT NOT NULL, "sQuestionNum" INTEGER NOT NULL, "answer" TEXT NOT NULL, PRIMARY KEY("ID"));''') if __name__ == "__main__": #일기 저장 버튼 누르면 일기는 txt파일로, 나머지는 db에 저장 #일기보기 누르면 DB로 정렬하고 txt파일 따로 읽기 #if click view diary Button-> readCorrectDiaryTxt(DbManager.Read(date)) z = Setup() a = WatchDiary() b = WriteDiary() c = AccountManager() d = c.findId(3,'qwerty') print(d) c.registerUser('asdfgg', 'asdfgzxdcvz1', 3, 'a') b.saveDiaryTxt(arrow.now().date(), input("일기입력")) f = a.readCorrectDiaryTxt(arrow.now().date()) print(f)
마찬가지로 if __name__ == "__main__"안에 들어있는 것은 테스트 코드이다.
inputText = inputText.encode()
hashObject = hashlib.sha256()
hashObject.update(inputText)
hexDig = hashObject.hexdigest()
inputText = hexDig
이 코드는 sha-256을 이용해서 암호화는 코드다.
마지막으로 Main.py이다.
# -*- coding: utf-8 -*- ########################################################################### ## Python code generated with wxFormBuilder (version Oct 26 2018) ## http://www.wxformbuilder.org/ ## ## PLEASE DO *NOT* EDIT THIS FILE! ########################################################################### import wx import wx.xrc from DbManager import DbManager from Assistant import WatchDiary, WriteDiary, AccountManager, Setup import arrow import re import sys #주의: 레이아웃 코드 복붙할때 기존 코드 안날라가게 조심! ########################################################################### ## Class StartMenu ########################################################################### class StartMenu ( wx.Frame ): def __init__( self, parent, size ): wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size(500, 300), style = wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX) ) self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) bSizer1 = wx.BoxSizer( wx.VERTICAL ) self.login_button = wx.Button( self, wx.ID_ANY, u"Login", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer1.Add( self.login_button, 1, wx.ALL|wx.EXPAND, 5 ) self.signup_button = wx.Button( self, wx.ID_ANY, u"회원가입", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer1.Add( self.signup_button, 1, wx.ALL|wx.EXPAND, 5 ) self.SetSizer( bSizer1 ) self.Layout() self.Centre( wx.BOTH ) # Connect Events self.Bind( wx.EVT_CLOSE, self.onClose ) self.login_button.Bind( wx.EVT_BUTTON, self.loginButtonClicked ) self.signup_button.Bind( wx.EVT_BUTTON, self.signUpButtonClicked ) def __del__( self ): pass # Virtual event handlers, overide them in your derived class def onClose(self, event): a = DbManager() a.closeDb() self.Destroy() sys.exit() def loginButtonClicked( self, event ): if self.IsMaximized(): frameSize = 'max' else: frameSize = self.GetSize() frame1 = LoginMenu(parent=None, size=frameSize) frame1.Show(True) self.Show(False) def signUpButtonClicked( self, event ): if self.IsMaximized(): frameSize = 'max' else: frameSize = self.GetSize() frame1 = SignUpMenu(parent = None, size=frameSize) frame1.Show(True) self.Show(False) ########################################################################### ## Class DiaryMainMenu ########################################################################### class DiaryMainMenu ( wx.Frame ): def __init__( self, parent, size, userId): wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size(500, 300), style = wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX) ) self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) bSizer1 = wx.BoxSizer( wx.VERTICAL ) self.watch_button = wx.Button( self, wx.ID_ANY, u"일기보기", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer1.Add( self.watch_button, 1, wx.ALL|wx.EXPAND, 5 ) self.write_button = wx.Button( self, wx.ID_ANY, u"일기쓰기", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer1.Add( self.write_button, 1, wx.ALL|wx.EXPAND, 5 ) self.SetSizer( bSizer1 ) self.Layout() self.Centre( wx.BOTH ) # Connect Events self.Bind( wx.EVT_CLOSE, self.onClose ) self.watch_button.Bind( wx.EVT_BUTTON, self.watchButtonClicked ) self.write_button.Bind( wx.EVT_BUTTON, self.writeButtonClicked ) self.DbManager = DbManager() self.userId = userId def __del__( self ): pass # Virtual event handlers, overide them in your derived class def onClose( self, event ): self.DbManager.closeDb() self.Destroy() sys.exit() def watchButtonClicked( self, event ): if self.IsMaximized(): frameSize = 'max' else: frameSize = self.GetSize() frame1 = WatchDiaryMenu(parent=None, size=frameSize, userId=self.userId) frame1.Show(True) self.Show(False) def writeButtonClicked( self, event ): if self.IsMaximized(): frameSize = 'max' else: frameSize = self.GetSize() frame1 = WriteDiaryMenu(parent=None, mode= 'w', size=frameSize, userId=self.userId) frame1.Show(True) self.Show(False) ########################################################################### ## Class LoginMenu ########################################################################### class LoginMenu ( wx.Frame ): def __init__( self, parent, size ): wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size(300,300), style = wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX) ) self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) bSizer2 = wx.BoxSizer( wx.VERTICAL ) self.go_back_button = wx.Button( self, wx.ID_ANY, u"뒤로가기", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer2.Add( self.go_back_button, 0, wx.ALL, 5 ) self.m_panel7 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer19 = wx.BoxSizer( wx.VERTICAL ) self.id_input_box = wx.TextCtrl( self.m_panel7, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer19.Add( self.id_input_box, 0, wx.ALL, 5 ) self.pw_input_box = wx.TextCtrl( self.m_panel7, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PASSWORD|wx.TE_PROCESS_ENTER ) bSizer19.Add( self.pw_input_box, 0, wx.ALL, 5 ) self.m_panel7.SetSizer( bSizer19 ) self.m_panel7.Layout() bSizer19.Fit( self.m_panel7 ) bSizer2.Add( self.m_panel7, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5 ) self.login_button = wx.Button( self, wx.ID_ANY, u"로그인", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer2.Add( self.login_button, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5 ) self.SetSizer( bSizer2 ) self.Layout() self.Centre( wx.BOTH ) # Connect Events self.Bind( wx.EVT_CLOSE, self.onClose ) self.go_back_button.Bind( wx.EVT_BUTTON, self.goBackButtonClicked ) self.pw_input_box.Bind( wx.EVT_TEXT_ENTER, self.pwInputFinished ) self.login_button.Bind( wx.EVT_BUTTON, self.loginButtonClicked ) self.DbManager = DbManager() self.accountManager = AccountManager() def __del__( self ): pass # Virtual event handlers, overide them in your derived class def onClose( self, event ): self.DbManager.closeDb() self.Destroy() sys.exit() def goBackButtonClicked( self, event ): if self.IsMaximized(): frameSize = 'max' else: frameSize = self.GetSize() frame1 = StartMenu(parent=None, size=frameSize) frame1.Show(True) self.Show(False) def pwInputFinished( self, event ): self.loginButtonClicked(event = None) def loginButtonClicked( self, event ): id = self.id_input_box.GetValue() pw = self.pw_input_box.GetValue() pwFollowRule = True try: if re.findall('[a-zA-Z0-9]+', pw)[0] != pw: pwFollowRule = False except: if len(re.findall('[^a-zA-Z0-9]+', pw)[0])>0: pwFollowRule = False else: raise BaseException idFollowRule = True try: if re.findall('[a-zA-Z0-9]+', id)[0] != id: idFollowRule = False except: if len(re.findall('[^a-zA-Z0-9]+', id)[0])>0: idFollowRule = False else: raise BaseException if pwFollowRule and idFollowRule: loginAccepted = self.accountManager.logIn(id, pw) else: loginAccepted = False if loginAccepted: if self.IsMaximized(): frameSize = 'max' else: frameSize = self.GetSize() frame1 = DiaryMainMenu(parent=None, size=frameSize, userId= self.id_input_box.GetValue()) frame1.Show(True) self.Show(False) else: wx.MessageBox("로그인 실패!") return loginAccepted ########################################################################### ## Class SignUpMenu ########################################################################### class SignUpMenu ( wx.Frame ): def __init__( self, parent, size ): wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size(500, 300), style = wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX) ) self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) gSizer2 = wx.GridSizer( 0, 2, 0, 0 ) bSizer7 = wx.BoxSizer( wx.VERTICAL ) self.m_panel6 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) gSizer7 = wx.GridSizer( 0, 2, 0, 0 ) bSizer17 = wx.BoxSizer( wx.VERTICAL ) self.goBackButton = wx.Button( self.m_panel6, wx.ID_ANY, u"뒤로가기", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer17.Add( self.goBackButton, 0, wx.ALL, 5 ) gSizer7.Add( bSizer17, 1, wx.EXPAND, 5 ) bSizer18 = wx.BoxSizer( wx.VERTICAL ) self.id_confirm_button = wx.Button( self.m_panel6, wx.ID_ANY, u"ID중복확인", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer18.Add( self.id_confirm_button, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5 ) gSizer7.Add( bSizer18, 1, wx.EXPAND, 5 ) self.m_panel6.SetSizer( gSizer7 ) self.m_panel6.Layout() gSizer7.Fit( self.m_panel6 ) bSizer7.Add( self.m_panel6, 0, wx.ALL|wx.EXPAND, 5 ) self.m_panel5 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) gSizer5 = wx.GridSizer( 0, 2, 0, 0 ) bSizer13 = wx.BoxSizer( wx.VERTICAL ) self.id_input_text = wx.StaticText( self.m_panel5, wx.ID_ANY, u"ID", wx.DefaultPosition, wx.DefaultSize, 0 ) self.id_input_text.Wrap( -1 ) bSizer13.Add( self.id_input_text, 0, wx.ALL|wx.EXPAND, 5 ) self.id_input_box = wx.TextCtrl( self.m_panel5, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer13.Add( self.id_input_box, 0, wx.ALL|wx.EXPAND, 5 ) self.pw_input_text = wx.StaticText( self.m_panel5, wx.ID_ANY, u"비밀번호", wx.DefaultPosition, wx.DefaultSize, 0 ) self.pw_input_text.Wrap( -1 ) bSizer13.Add( self.pw_input_text, 0, wx.ALL|wx.EXPAND, 5 ) self.pw_input_box = wx.TextCtrl( self.m_panel5, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PASSWORD ) bSizer13.Add( self.pw_input_box, 0, wx.ALL|wx.EXPAND, 5 ) self.pw_re_input_text = wx.StaticText( self.m_panel5, wx.ID_ANY, u"비밀번호 재입력", wx.DefaultPosition, wx.DefaultSize, 0 ) self.pw_re_input_text.Wrap( -1 ) bSizer13.Add( self.pw_re_input_text, 0, wx.ALL|wx.EXPAND, 5 ) self.pw_re_input_box = wx.TextCtrl( self.m_panel5, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PASSWORD ) bSizer13.Add( self.pw_re_input_box, 0, wx.ALL|wx.EXPAND, 5 ) gSizer5.Add( bSizer13, 1, wx.EXPAND, 5 ) bSizer15 = wx.BoxSizer( wx.VERTICAL ) self.pw_check_text = wx.StaticText( self.m_panel5, wx.ID_ANY, u"id,비밀번호 둘다\n영어 대소문자, \n숫자만 가능\n\n비밀번호는\n10자 이상\n영어,숫자 필수", wx.DefaultPosition, wx.DefaultSize, 0 ) self.pw_check_text.Wrap( -1 ) bSizer15.Add( self.pw_check_text, 1, wx.ALL|wx.EXPAND, 5 ) self.pw_confirm_img = wx.StaticBitmap( self.m_panel5, wx.ID_ANY, wx.NullBitmap, wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer15.Add( self.pw_confirm_img, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5 ) bSizer15.Add( ( 0, 0), 1, wx.EXPAND, 5 ) gSizer5.Add( bSizer15, 1, wx.EXPAND, 5 ) self.m_panel5.SetSizer( gSizer5 ) self.m_panel5.Layout() gSizer5.Fit( self.m_panel5 ) bSizer7.Add( self.m_panel5, 1, wx.EXPAND |wx.ALL, 5 ) self.re_pw_correct_text = wx.StaticText( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) self.re_pw_correct_text.Wrap( -1 ) bSizer7.Add( self.re_pw_correct_text, 0, wx.ALL, 5 ) gSizer2.Add( bSizer7, 1, wx.EXPAND|wx.RIGHT, 5 ) bSizer8 = wx.BoxSizer( wx.VERTICAL ) self.account_repair_question_text = wx.StaticText( self, wx.ID_ANY, u"보안질문", wx.DefaultPosition, wx.DefaultSize, 0 ) self.account_repair_question_text.Wrap( -1 ) bSizer8.Add( self.account_repair_question_text, 0, wx.ALL|wx.EXPAND, 5 ) account_repair_question_choiceChoices = [ u"가장 아끼는 물건은?", u"가장 좋아했던 선생님 성함은?", u"가장 감명깊게 읽은 책은?", u"가장 어려웠던 책은?" ] self.account_repair_question_choice = wx.Choice( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, account_repair_question_choiceChoices, 0 ) self.account_repair_question_choice.SetSelection( 0 ) bSizer8.Add( self.account_repair_question_choice, 0, wx.ALL|wx.EXPAND, 5 ) self.answer_text = wx.StaticText( self, wx.ID_ANY, u"답변", wx.DefaultPosition, wx.DefaultSize, 0 ) self.answer_text.Wrap( -1 ) bSizer8.Add( self.answer_text, 0, wx.ALL|wx.EXPAND, 5 ) self.answer_input_box = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer8.Add( self.answer_input_box, 0, wx.ALL|wx.EXPAND, 5 ) self.sign_up_button = wx.Button( self, wx.ID_ANY, u"회원가입", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer8.Add( self.sign_up_button, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5 ) gSizer2.Add( bSizer8, 1, wx.EXPAND, 5 ) self.SetSizer( gSizer2 ) self.Layout() self.Centre( wx.BOTH ) # Connect Events self.Bind( wx.EVT_CLOSE, self.onClose ) self.goBackButton.Bind( wx.EVT_BUTTON, self.goBackButtonClicked ) self.id_confirm_button.Bind( wx.EVT_BUTTON, self.idConfirmButtonClicked ) self.id_input_box.Bind(wx.EVT_TEXT, self.textInputedOnId) self.pw_input_box.Bind( wx.EVT_TEXT, self.textInputedOnPw ) self.pw_re_input_box.Bind( wx.EVT_TEXT, self.textInputedOnRePw ) self.account_repair_question_choice.Bind( wx.EVT_CHOICE, self.choiced ) self.sign_up_button.Bind( wx.EVT_BUTTON, self.signUpButtonClicked ) self.accountManager = AccountManager() self.sQuestionNum = 0 self.dbManager = DbManager() self.correctSymbolPng = wx.Image('tick.png', wx.BITMAP_TYPE_ANY) self.correctSymbolPng.Rescale(32,32) self.correctSymbolPng = self.correctSymbolPng.ConvertToBitmap() self.incorrectSymbolPng = wx.Image('close.png', wx.BITMAP_TYPE_ANY) self.incorrectSymbolPng.Rescale(32,32) self.incorrectSymbolPng = self.incorrectSymbolPng.ConvertToBitmap() self.pw_confirm_img.SetBitmap(self.incorrectSymbolPng) self.idFlag = -1 def __del__( self ): pass # Virtual event handlers, overide them in your derived class def onClose( self, event ): self.dbManager.closeDb() self.Destroy() sys.exit() def goBackButtonClicked( self, event ): if self.IsMaximized(): frameSize = 'max' else: frameSize = self.GetSize() frame1 = StartMenu(parent=None, size=frameSize) frame1.Show(True) self.Show(False) def idConfirmButtonClicked( self, event ): allId = self.dbManager.readAllId() id = (self.id_input_box.GetValue(),) self.idFlag = False if not(id in allId): self.idFlag = True if self.idFlag: wx.MessageBox(f"'{id[0]}': 사용가능한 아이디 입니다.") else: wx.MessageBox(f"'{id[0]}': 사용불가능한 아이디 입니다.") def textInputedOnId(self, event): self.idFlag = -1 def textInputedOnPw( self, event ): pw = self.pw_input_box.GetValue() self.pwFollowRule = True if len(pw)<9: self.pwFollowRule = False elif re.findall('[a-zA-Z0-9]+', pw)[0] != pw:#re에서 영어, 숫자만 찾기. 만약 다른 문자가 있다면 중간에 끊김 -> 본래 문자열과 달라짐 -> 구별 가능. self.pwFollowRule = False elif len(re.findall('[a-zA-Z]', pw)) == 0: self.pwFollowRule = False elif len(re.findall('\d', pw)) == 0: self.pwFollowRule = False if self.pwFollowRule: self.pw_confirm_img.SetBitmap(self.correctSymbolPng) else: self.pw_confirm_img.SetBitmap(self.incorrectSymbolPng) def textInputedOnRePw( self, event ): self.pwFlag = self.pw_input_box.GetValue() == self.pw_re_input_box.GetValue() if self.pwFlag: self.re_pw_correct_text.SetLabel("비밀번호가 일치합니다!") else: self.re_pw_correct_text.SetLabel("비밀번호가 일치하지 않습니다.") def choiced( self, event ): self.sQuestionNum = self.account_repair_question_choice.GetSelection() print(self.sQuestionNum) return self.sQuestionNum def signUpButtonClicked( self, event ): if self.idFlag == -1: wx.MessageBox("아이디 중복확인 해주세요!") try: if re.findall('[a-zA-Z0-9]+', self.id_input_box.GetValue())[0] != self.id_input_box.GetValue(): wx.MessageBox("이 아이디는 사용할 수 없습니다(영어 대소문자,숫자로 이루어지지 않음)") self.idFlag = False except: if len(re.findall('[^a-zA-Z0-9]+', self.id_input_box.GetValue())[0])>0: wx.MessageBox("이 아이디는 사용할 수 없습니다(영어 대소문자,숫자로 이루어지지 않음)") self.idFlag = False else: raise BaseException if not(self.id_input_box.GetValue() == None and self.pw_input_box.GetValue() == None and self.answer_input_box.GetValue() == None): if self.idFlag == True and self.pwFlag ==True and self.pwFollowRule == True: loginSuccess = self.accountManager.registerUser(self.id_input_box.GetValue(), self.pw_input_box.GetValue(), self.sQuestionNum, self.answer_input_box.GetValue()) if loginSuccess: wx.MessageBox(f"회원가입 성공! 아이디는 '{self.id_input_box.GetValue()}'") if self.IsMaximized(): frameSize = 'max' else: frameSize = self.GetSize() frame1 = DiaryMainMenu(parent=None, size=frameSize, userId=self.id_input_box.GetValue()) frame1.Show(True) self.Show(False) else: wx.MessageBox(f"회원가입 실패ㅜㅜ") else: wx.MessageBox('공란이 있으면 안됨') return self.id_input_box.GetValue(), self.pw_input_box.GetValue(), self.sQuestionNum, self.answer_input_box.GetValue() ########################################################################### ## Class WriteDiaryMenu ########################################################################### class WriteDiaryMenu ( wx.Frame ): def __init__( self, parent, mode ,size, userId, diaryContent=None, todayFeel=5, todayThoughts = None): if size == 'max': wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size(500,300), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) self.Maximize() else: wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size(size), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) gSizer3 = wx.GridSizer( 0, 2, 0, 0 ) bSizer7 = wx.BoxSizer( wx.VERTICAL ) self.go_back_button = wx.Button( self, wx.ID_ANY, u"뒤로가기", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer7.Add( self.go_back_button, 0, wx.ALL, 5 ) if mode == "w": if diaryContent == None: self.diary_input_box = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE ) else: self.diary_input_box = wx.TextCtrl( self, wx.ID_ANY, u"{0}".format(diaryContent), wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE ) else: self.diary_input_box = wx.TextCtrl( self, wx.ID_ANY, u'{0}'.format(diaryContent), wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE|wx.TE_READONLY ) bSizer7.Add( self.diary_input_box, 1, wx.ALL|wx.EXPAND, 5 ) gSizer3.Add( bSizer7, 1, wx.EXPAND, 5 ) bSizer8 = wx.BoxSizer( wx.VERTICAL ) self.m_panel1 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) bSizer9 = wx.BoxSizer( wx.VERTICAL ) self.today_feel_text = wx.StaticText( self.m_panel1, wx.ID_ANY, u"오늘 기분1/5", wx.DefaultPosition, wx.DefaultSize, 0 ) self.today_feel_text.Wrap( -1 ) bSizer9.Add( self.today_feel_text, 0, wx.ALL, 5 ) if mode == 'w': self.today_feel_slider = wx.Slider( self.m_panel1, wx.ID_ANY, todayFeel, 1, 5, wx.DefaultPosition, wx.DefaultSize, wx.SL_HORIZONTAL|wx.SL_SELRANGE|wx.SL_VALUE_LABEL ) bSizer9.Add( self.today_feel_slider, 0, wx.ALL|wx.EXPAND, 5 ) else: self.today_feel_text = wx.StaticText( self.m_panel1, wx.ID_ANY, u"{0}".format(todayFeel), wx.DefaultPosition, wx.DefaultSize, 0 ) self.today_feel_text.Wrap( -1 ) bSizer9.Add( self.today_feel_text, 0, wx.ALL, 5 ) self.m_panel1.SetSizer( bSizer9 ) self.m_panel1.Layout() bSizer9.Fit( self.m_panel1 ) bSizer8.Add( self.m_panel1, 0, wx.EXPAND |wx.ALL, 5 ) self.m_panel2 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) bSizer10 = wx.BoxSizer( wx.VERTICAL ) self.today_thoughts_text = wx.StaticText( self.m_panel2, wx.ID_ANY, u"오늘 생각한것", wx.DefaultPosition, wx.DefaultSize, 0 ) self.today_thoughts_text.Wrap( -1 ) bSizer10.Add( self.today_thoughts_text, 0, wx.ALL, 5 ) if mode == 'w': if todayThoughts == None: self.today_thoughts_input_box = wx.TextCtrl( self.m_panel2, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE ) else: self.today_thoughts_input_box = wx.TextCtrl( self.m_panel2, wx.ID_ANY, u"{0}".format(todayThoughts), wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE ) else: self.today_thoughts_input_box = wx.TextCtrl( self.m_panel2, wx.ID_ANY, u"{0}".format(todayThoughts), wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE|wx.TE_READONLY ) bSizer10.Add( self.today_thoughts_input_box, 1, wx.ALL|wx.EXPAND, 5 ) self.m_panel2.SetSizer( bSizer10 ) self.m_panel2.Layout() bSizer10.Fit( self.m_panel2 ) bSizer8.Add( self.m_panel2, 1, wx.EXPAND |wx.ALL, 5 ) if mode == 'w': self.save_diary_button = wx.Button( self, wx.ID_ANY, u"일기 저장", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer8.Add( self.save_diary_button, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5 ) self.save_diary_button.Bind( wx.EVT_BUTTON, self.saveButtonClicked ) gSizer3.Add( bSizer8, 1, wx.EXPAND, 5 ) self.SetSizer( gSizer3 ) self.Layout() self.Centre( wx.BOTH ) # Connect Events self.Bind( wx.EVT_CLOSE, self.onClose ) self.go_back_button.Bind( wx.EVT_BUTTON, self.goBackButtonClicked ) self.dbManager = DbManager() self.WriteDiary = WriteDiary() self.userId = userId def __del__( self ): pass # Virtual event handlers, overide them in your derived class def onClose( self, event ): self.dbManager.closeDb() self.Destroy() sys.exit() def goBackButtonClicked( self, event ): if self.IsMaximized(): frameSize = 'max' else: frameSize = self.GetSize() frame1 = DiaryMainMenu(parent=None, size=frameSize, userId = self.userId) frame1.Show(True) self.Show(False) def saveButtonClicked( self, event ): self.WriteDiary.saveDiaryTxt(arrow.now().date(), self.diary_input_box.GetValue()) self.WriteDiary.saveTodayThoughtsTxt(arrow.now().date(), self.today_thoughts_input_box.GetValue()) self.dbManager.saveDiaryInfo(arrow.now().date(), str(arrow.now().time())[:5], self.today_feel_slider.GetValue(), self.today_thoughts_input_box.GetValue(), self.userId) wx.MessageBox("일기 저장 성공!") if self.IsMaximized(): frameSize = 'max' else: frameSize = self.GetSize() frame1 = DiaryMainMenu(parent=None, size=frameSize, userId = self.userId) frame1.Show(True) self.Show(False) ########################################################################### ## Class WatchDiaryMenu ########################################################################### class WatchDiaryMenu ( wx.Frame ): def __init__( self, parent, size ,userId): if size == 'max': wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size(500,300), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) self.Maximize() else: wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size(size), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) bSizer12 = wx.BoxSizer( wx.VERTICAL ) self.go_back_button = wx.Button( self, wx.ID_ANY, u"뒤로가기", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer12.Add( self.go_back_button, 0, wx.ALL, 5 ) self.m_panel81 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) gbSizer31 = wx.GridBagSizer( 0, 0 ) gbSizer31.SetFlexibleDirection( wx.BOTH ) gbSizer31.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) self.range_text = wx.StaticText( self.m_panel81, wx.ID_ANY, u"범위", wx.DefaultPosition, wx.DefaultSize, 0 ) self.range_text.Wrap( -1 ) gbSizer31.Add( self.range_text, wx.GBPosition( 0, 0 ), wx.GBSpan( 1, 1 ), wx.ALL, 5 ) range_choiceChoices = [ u"전체", u"선택" ] self.range_choice = wx.Choice( self.m_panel81, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, range_choiceChoices, 0 ) self.range_choice.SetSelection( 0 ) gbSizer31.Add( self.range_choice, wx.GBPosition( 0, 1 ), wx.GBSpan( 1, 1 ), wx.ALL, 5 ) self.feel_text = wx.StaticText( self.m_panel81, wx.ID_ANY, u"기분", wx.DefaultPosition, wx.DefaultSize, 0 ) self.feel_text.Wrap( -1 ) gbSizer31.Add( self.feel_text, wx.GBPosition( 0, 2 ), wx.GBSpan( 1, 1 ), wx.ALL, 5 ) feel_range_choiceChoices = [ u"전체", u"선택" ] self.feel_range_choice = wx.Choice( self.m_panel81, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, feel_range_choiceChoices, 0 ) self.feel_range_choice.SetSelection( 0 ) gbSizer31.Add( self.feel_range_choice, wx.GBPosition( 0, 3 ), wx.GBSpan( 1, 1 ), wx.ALL, 5 ) self.thoughts_count_text = wx.StaticText( self.m_panel81, wx.ID_ANY, u"생각개수", wx.DefaultPosition, wx.DefaultSize, 0 ) self.thoughts_count_text.Wrap( -1 ) gbSizer31.Add( self.thoughts_count_text, wx.GBPosition( 0, 4 ), wx.GBSpan( 1, 1 ), wx.ALL, 5 ) feel_range_choice1Choices = [ u"전체", u"선택" ] self.feel_range_choice1 = wx.Choice( self.m_panel81, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, feel_range_choice1Choices, 0 ) self.feel_range_choice1.SetSelection( 0 ) gbSizer31.Add( self.feel_range_choice1, wx.GBPosition( 0, 5 ), wx.GBSpan( 1, 1 ), wx.ALL, 5 ) self.search_button = wx.Button( self.m_panel81, wx.ID_ANY, u"검색", wx.DefaultPosition, wx.DefaultSize, 0 ) gbSizer31.Add( self.search_button, wx.GBPosition( 0, 6 ), wx.GBSpan( 1, 1 ), wx.ALL, 5 ) self.m_panel81.SetSizer( gbSizer31 ) self.m_panel81.Layout() gbSizer31.Fit( self.m_panel81 ) bSizer12.Add( self.m_panel81, 0, wx.ALL|wx.EXPAND, 5 ) self.m_scrolledWindow2 = wx.ScrolledWindow( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.HSCROLL|wx.VSCROLL ) self.m_scrolledWindow2.SetScrollRate( 5, 5 ) bSizer22 = wx.BoxSizer( wx.VERTICAL ) self.starPng = wx.Image('star.png', wx.BITMAP_TYPE_ANY) frameSizeX, frameSizeY = self.GetSize() self.starPng.Rescale(16,16) self.starPng = self.starPng.ConvertToBitmap() del frameSizeX, frameSizeY self.DbManager = DbManager() self.userId = userId diary = self.DbManager.findDiaryWithCondition(startDate='1970-01-01', finishDate='2099-01-01', startFeelRange=1, finishFeelRange=5, thoughtsStartRange=0, thoughtsFinishRange=100, userId=self.userId) for i in range(len(diary)): date = diary[i][0] time = diary[i][1] todayFeel = diary[i][2] todayThoughtsCount = diary[i][3] diaryPanel = self.makeDiaryPanel(date, time, todayFeel, todayThoughtsCount, len(diary), self.m_scrolledWindow2) bSizer22.Add( diaryPanel, 1, wx.EXPAND |wx.ALL, 5 ) self.m_scrolledWindow2.SetSizer( bSizer22 ) self.m_scrolledWindow2.Layout() bSizer22.Fit( self.m_scrolledWindow2 ) bSizer12.Add( self.m_scrolledWindow2, 1, wx.EXPAND |wx.ALL, 5 ) self.SetSizer( bSizer12 ) self.Layout() self.Centre( wx.BOTH ) # Connect Events self.Bind( wx.EVT_CLOSE, self.onClose ) self.range_choice.Bind( wx.EVT_CHOICE, self.rangeChoiced ) self.feel_range_choice.Bind( wx.EVT_CHOICE, self.feelRangeChoiced ) self.feel_range_choice1.Bind( wx.EVT_CHOICE, self.thoughtsRangeChoiced ) self.search_button.Bind( wx.EVT_BUTTON, self.searchButtonClicked ) self.go_back_button.Bind( wx.EVT_BUTTON, self.goBackButtonClicked) def __del__( self ): pass def makeDiaryPanel(self, date, time, star, thoughtsCount, diaryCount, m_scrolledWindow2): m_panel8 = wx.Panel( m_scrolledWindow2, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) gbSizer3 = wx.GridBagSizer( 0, 0 ) gbSizer3.SetFlexibleDirection( wx.BOTH ) gbSizer3.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) date_text = wx.StaticText( m_panel8, wx.ID_ANY, u'{0}'.format(date), wx.DefaultPosition, wx.DefaultSize, 0 ) date_text.Wrap( -1 ) gbSizer3.Add( date_text, wx.GBPosition( 0, 0 ), wx.GBSpan( 1, 1 ), wx.ALL, 5 ) time_text = wx.StaticText( m_panel8, wx.ID_ANY, u"{0}".format(time), wx.DefaultPosition, wx.DefaultSize, 0 ) time_text.Wrap( -1 ) gbSizer3.Add( time_text, wx.GBPosition( 0, 2 ), wx.GBSpan( 1, 1 ), wx.ALL, 5 ) thoughts_count = wx.StaticText( m_panel8, wx.ID_ANY, u"{0}".format(thoughtsCount), wx.DefaultPosition, wx.DefaultSize, 0 ) thoughts_count.Wrap( -1 ) gbSizer3.Add( thoughts_count, wx.GBPosition( 0, 3 ), wx.GBSpan( 1, 1 ), wx.ALL, 5 ) diary_show_button = wx.Button( m_panel8, wx.ID_ANY, u"일기보기", wx.DefaultPosition, wx.DefaultSize, 0 ) gbSizer3.Add( diary_show_button, wx.GBPosition( 0, 5 ), wx.GBSpan( 1, 1 ), wx.ALL, 5 ) m_panel18 = wx.Panel( m_panel8, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) gbSizer4 = wx.GridBagSizer( 0, 0 ) gbSizer4.SetFlexibleDirection( wx.BOTH ) gbSizer4.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) for i in range(int(star)): star = wx.StaticBitmap( m_panel18, wx.ID_ANY, self.starPng, wx.DefaultPosition, wx.DefaultSize, 0 ) gbSizer4.Add(star, wx.GBPosition(0,i), wx.GBSpan( 1, 1 ), wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5) m_panel18.SetSizer( gbSizer4 ) m_panel18.Layout() gbSizer4.Fit( m_panel18 ) gbSizer3.Add( m_panel18, wx.GBPosition( 0, 4 ), wx.GBSpan( 1, 1 ), wx.EXPAND |wx.ALL, 5 ) m_panel8.SetSizer( gbSizer3 ) m_panel8.Layout() gbSizer3.Fit( m_panel8 ) diary_show_button.Bind( wx.EVT_BUTTON, lambda event: self.diaryShowButtonClicked(event, date)) return m_panel8 # Virtual event handlers, overide them in your derived class def onClose( self, event ): self.DbManager.closeDb() self.Destroy() sys.exit() def rangeChoiced( self, event ): event.Skip() def feelRangeChoiced( self, event ): event.Skip() def thoughtsRangeChoiced( self, event ): event.Skip() def searchButtonClicked( self, event ): event.Skip() def goBackButtonClicked(self, event): if self.IsMaximized(): frameSize = 'max' else: frameSize = self.GetSize() frame1 = DiaryMainMenu(parent=None, size=frameSize, userId = self.userId) frame1.Show(True) self.Show(False) def diaryShowButtonClicked( self, event, date ): watchDiary = WatchDiary() diaryContent = watchDiary.readCorrectDiaryTxt(date) todayThoughtsContent = watchDiary.readCorrectTodayThoughtsTxt(date) diaryInfo = self.DbManager.readDiaryInfo(date, self.userId) if self.IsMaximized(): frameSize = 'max' else: frameSize = self.GetSize() if date == str(arrow.now().date()): frame1 = WriteDiaryMenu(parent=None, mode='w', size=frameSize, userId=self.userId, diaryContent=diaryContent, todayFeel=diaryInfo[2], todayThoughts=todayThoughtsContent) else: frame1 = WriteDiaryMenu(parent=None, mode='r', size=frameSize, userId=self.userId, diaryContent=diaryContent, todayFeel=diaryInfo[2], todayThoughts=todayThoughtsContent) frame1.Show(True) self.Show(False) if __name__ == "__main__": a = Setup() app = wx.App() frame = StartMenu(parent = None, size=(500,300)) frame.Show(True) app.MainLoop()
모든 클래스의 __init__안에 들어있는 것은 UI생성하는 코드다.
그래서 실제 코드는 생각보다 길지 않다.
마지막 부분 makeDiaryPanel함수는 일기 보여주기의 한 패널을 만드는 코드다.

이미지들은 https://www.flaticon.com/ 에서 구했다.
처음에는 frame을 어떻게하면 전환할 수 있을까 고민했는데, 그냥 야매로 다른 frame을 보여주고, 지금 frame은 숨기는 방식으로 구현했다.
이제 아이디, 비밀번호 찾기 기능 만들기, 일기 검색 기능(날짜, 기분, 오늘 한 생각으로) 만들기, 오늘 한 생각 부분 디자인 변경하기 가 목표이다.
https://github.com/arduinocc04/wxPythonDiary