소개
MySQL과 Python을 사용하여 간단한 게시판 프로그램을 만들어 보았다.
MVC 디자인 패턴과 DB활용법을 익히기 위한 간단한 프로젝트이다.
최근 일본여행을 자주 다녀왔다. 출국하기 전엔 항상 맛집을 찾곤 했는데 찾아놓은 가게의 간단한 정보를 다시보기 위해서 항상 구글맵을 켜야했다. 맛집에 대한 간단한 정보를 메모하고 공유할 수 있는 작은 프로그램이 있다면 좋을 것 같다는 생각을 바탕으로 제작하였다.
UI는 구현하지 않았고 주피터로 실행한 후 입력창에서 모든 동작을 수행하게끔 했다. 추후 여유가 생긴다면 웹이나 어플리케이션으로 제작해도 재밌을 것 같다.
프로그램 이름은 JBR(Japanese Best Restaurant).
* MVC 패턴을 아직 공부중이기에 패턴에 어긋나는 부분이 있을 수 있음.
프로그램 구성
- 맛집 등록하기
- 등록 형식 안내 문구 출력하기.
- 맛집 찾아보기
- 카테고리
- 카테고리 입력
- 해당하는 맛집 리스트 출력
- 카테고리
- 검색
- 내용이 포함된 모든 결과 출력해줌.
- 베스트
- 평점이 높은 10개 출력
- 마이페이지
- 내가 쓴 글
- 닉네임 변경 -> 실패
- 비밀번호 변경
- 로그아웃
- 프로그램 종료
코드
이미 작성한 코드라 더 수정하지 않고 이대로 기록하려 한다.
스스로 간단한 피드백 한 줄 작성해본다.
Connect With DB
import MySQLdb
# 닉네임은 중복이 안됨
# 닉네임 - 비밀번호 입력 후 간단한 로그인
class Connect:
def __init__(self):
self.db = None
def connect(self):
self.db = MySQLdb.connect('localhost', 'root', '12345678', 'matzip')
def disconnect(self):
self.db.close()
mysql과 파이썬을 연동하기 위한 모듈로 PyMySQL, mysqlclient 등이 있다.
이 중 mysqlclient를 사용한 이유는 상대적으로 오류에 있어서 더 안정적이이 때문이다.
db에 연결하기 위한 Connect 클래스를 따로 작성하였다.
아래 링크를 통해 mysqlclient의 User's Guide를 볼 수 있다.
https://mysqlclient.readthedocs.io/user_guide.html
DTO
Member DTO
class Member:
def __init__(self, userid, userpw, age=0, gender=''):
self.userid = userid
self.userpw = userpw
self.age = age
self.gender = gender
def setUserid(self, userid):
self.userid = userid
def getUserid(self):
return self.userid
def setUserpw(self, userpw):
self.userpw = userpw
def getUserpw(self):
return self.userpw
def setAge(self, age):
self.age = age
def getAge(self):
return self.age
def setGender(self, gender):
self.gender = gender
def getGender(self):
return self.gender
Res DTO
class Res:
def __init__(self, resname, location, star=0, cate='', link=''):
self.resname = resname
self.location = location
self.star = star
self.cate = cate
self.link = link
# resname
def setResname(self, resname):
self.resname = resname
def getResname(self):
return self.resname
# location
def setLocation(self, location):
self.location = location
def getLocation(self):
return self.location
# star
def setStar(self, star):
self.star = star
def getStar(self):
return self.star
# cate
def setCate(self, cate):
self.cate = cate
def getCate(self):
return self.cate
# link
def setLink(self, link):
self.link = link
def getLink(self):
return self.link
member, res 두 개의 Table을 사용하였다.
각각의 테이블은 userid를 PK-FK로 사용한다.
모든 필드에 대해 DTO를 작성해보았다. 굳이 모든 필드를 get, set 할 필요는 없을 것 같다. 특히나 이번 프로젝트의 경우 데이터의 이동이 단순하고 양이 많지 않기 때문에 적절한 DTO 사용을 더 고려해봐야할 것 같다.
DAO
SignUp DAO
회원가입을 위해 member 정보를 DTO 객체에 담아서 가져오도록 하였다.
간단하게 table에 insert하는 쿼리를 사용하였고 회원탈퇴 기능은 구현하지 못했다.
그리고 회원가입을 하기 전 입력한 아이디가 이미 db에 존재하는지 확인하기 위해 메서드 하나를 선언하였다. 내용은 아직 구현하지 못했다.
변명이지만 회원가입 부분보다 맛집 정보 관련한 부분에 신경을 썼던 것 같다..
# 회원가입 Dao
class SignUpDao:
def __init__(self):
self.connect = Connect()
def signUp(self, member):
self.connect.connect()
cur = self.connect.db.cursor()
sql = 'insert into member (userid, userpw, age, gender) values (%s, %s, %s, %s)'
data = (member.getUserid(), member.getUserpw(), member.getAge(), member.getGender())
cur.execute(sql, data)
self.connect.db.commit()
cur.close()
self.connect.disconnect()
def signOut(self):
pass
# 아이디 중복 확인
def isUseridExist(self, userid):
return False
Login DAO
이미 가입한 사용자에 한해 로그인을 도와주는 DAO이다.
입력받은 member 정보를 db와 비교하여 아이디와 패스워드가 일치하면 True를 반환하도록 구현하였다.
# 로그인 Dao
class LoginDao:
def __init__(self):
self.connect = Connect()
def login(self, member):
try:
self.connect.connect()
cur = self.connect.db.cursor()
sql = 'select userid from member where userid=%s and userpw=%s'
data = (member.getUserid(), member.getUserpw())
result = cur.execute(sql, data)
cur.close()
self.connect.disconnect()
except Exception as e:
print(e)
if result > 0:
return True
else:
return False
Res DAO
가게정보를 추가, 검색, 삭제하는 DAO이다.
클래스 하나의 정체성을 확실하게 하기 위해 가게정보에 관한 동작만 구현했는데 프로그램 규모가 커진다면 가게등록, 가게검색, 가게삭제 각각을 클래스로 나눠도 되지 않을까 생각이 든다.
# 가게 정보 Dao
class ResDao:
def __init__(self):
self.connect = Connect()
# db res 테이블에 등록하기
def regist(self, res, userid):
self.connect.connect()
cur = self.connect.db.cursor()
sql = 'insert into res (userid, resname, location, star, cate, link) values (%s, %s, %s, %s, %s, %s)'
data = (userid, res.getResname(), res.getLocation(), res.getStar(), res.getCate(), res.getLink())
cur.execute(sql, data)
self.connect.db.commit()
cur.close()
self.connect.disconnect()
# 존재하는 모든 카테고리 리스트를 반환, 없으면 반환 x
def getCate(self):
self.connect.connect()
cur = self.connect.db.cursor()
sql = 'select cate from res'
cur.execute(sql)
result = cur.fetchall()
cur.close()
self.connect.disconnect()
return result
# 카테고리별 리스트
def cateSearch(self, cate):
self.connect.connect()
cur = self.connect.db.cursor()
sql = 'select resname, location, star, cate, link from res where cate=%s'
data = (cate, )
cur.execute(sql, data)
result = cur.fetchall()
cur.close()
self.connect.disconnect()
return result
# 이름으로 검색
def nameSearch(self, name):
self.connect.connect()
cur = self.connect.db.cursor()
sql = 'select resname, location, star, cate, link from res where resname like %s'
data = ('%' + name + '%', )
cur.execute(sql, data)
result = cur.fetchall()
cur.close()
self.connect.disconnect()
return result
# 베스트 10 보여주기
def recommend(self):
self.connect.connect()
cur = self.connect.db.cursor()
sql = 'select * from res order by star desc limit 10'
cur.execute(sql)
resnameList = cur.fetchall()
# {카메스시:4.5}, {이치란라멘:4.0}
cur.close()
self.connect.disconnect()
return resnameList
MyPage DAO
회원정보에 관한 DAO이다. 내가 쓴 게시물, 아이디 변경, 비밀번호 변경을 구현하였다.
# 회원정보 Dao
class MyPageDao:
def __init__(self):
self.connect = Connect()
def myPost(self, userid):
self.connect.connect()
cur = self.connect.db.cursor()
sql = 'select * from res where userid=%s'
data = (userid, )
result = cur.execute(sql, data)
mypostlist = cur.fetchall()
cur.close()
self.connect.disconnect()
if result > 0:
return mypostlist
else:
return False
def idChange(self, userid, newname):
self.connect.connect()
cur = self.connect.db.cursor()
sql1 = 'update member set userid=%s where userid=%s'
sql2 = 'update res set userid=%s where userid=%s'
data = (newname, userid)
cur.execute(sql1, data)
self.connect.db.commit()
cur.execute(sql2, data)
self.connect.db.commit()
cur.close()
self.connect.disconnect()
def isCorrect(self, userpw):
self.connect.connect()
cur = self.connect.db.cursor()
sql = 'select userpw from member where userpw=%s'
data = (userpw, )
result = cur.execute(sql, data)
cur.close()
self.connect.disconnect()
if result > 0:
return True # 비밀번호 맞음
else:
return False # 비밀번호 틀림
def pwChange(self, userid, newpw):
self.connect.connect()
cur = self.connect.db.cursor()
sql = 'update member set userpw=%s where userid=%s'
data = (newpw, userid)
result = cur.execute(sql, data)
self.connect.db.commit()
cur.close()
self.connect.disconnect()
def logout():
pass
Service
사용자에게 보여지고 어떤 입력을 받을지 결정되는 부분이다.
DAO 부분에 관여하지 않도록 작성하였다.
Signup Service
# 회원가입 UI
class SignUpService:
def __init__(self):
self.dao = SignUpDao()
def signUpService(self):
print('\n* 어서오세요 JBR입니다. 회원가입을 진행하겠습니다. ')
print('* 닉네임, 비밀번호, 생년월일, 성별을 입력해주세요. ')
while True:
try:
userid = input('닉네임: ')
if self.dao.isUseridExist(userid):
print('이미 존재하는 닉네임입니다.')
else:
userpw = input('비밀번호: ')
age = input('생년월일: ')
gender = input('성별: ')
member = Member(userid, userpw, age, gender)
self.dao.signUp(member)
print('* 회원가입 성공!! ')
print('* 프로그램을 다시 실행해주세요. ')
break
except Exception as e:
print(e)
print('에러발생')
# 로그인 UI
class LoginService:
def __init__(self):
self.dao = LoginDao()
def loginService(self):
print('\n* 어서오세요 JBR입니다. 로그인을 진행하겠습니다. ')
ans = input('* 회원가입 여부를 입력하세요. (y/n) ')
if ans.upper() == 'Y':
print('\n* 닉네임, 비밀번호를 입력하세요.')
userid = input('닉네임: ')
userpw = input('비밀번호: ')
member = Member(userid, userpw)
result = self.dao.login(member)
if result:
return member.getUserid()
elif ans.upper() == 'N':
return 'N'
# 회원 탈퇴 서비스
def signOutService(self):
pass
# 게시물 UI
class ResService:
def __init__(self):
self.dao = ResDao()
# 가게이름, 위치, 평점, 카테고리, 링크 입력
def registRes(self, userid):
print('\n* 추천해주실 맛집의 정보를 입력해주세요.')
print('가게이름: 추천해주실 맛집의 이름을 적어주세요.\n가게위치: 대략적인 지역 이름을 적어주세요.\n평점: 1.0 ~ 5.0\n카테고리: 스시, 우동, 와규, 야끼, 덮밥, 라멘, 튀김, 코스, 기타\n링크: 구글 링크 혹은 참고할 링크를 입력해주세요.')
print('예시) 카메스시, 오사카, 4.5, 스시, https://maps.app.goo.gl/dvj2RQzirF61jvnn6\n')
resname = input('가게이름: ')
location = input('가게위치: ')
star = input('평점: ')
cate = input('카테고리: ')
link = input('링크: ')
res = Res(resname, location, star, cate, link)
self.dao.regist(res, userid)
def searchRes(self):
print('\n* 공유된 다양한 맛집을 찾아보세요.\n')
select = input('1. 카테고리 2. 검색 3. 베스트')
if select == '1': # 카테고리별 리스트
cateList = set(self.dao.getCate()) # set으로 중복 제거
# (('스시',), ('라멘',), ('라멘',), ('카페',))
print()
for catelist in cateList:
print(f'{catelist[0]}\n')
cateSel = input('카테고리 선택: ')
selectedCateList = self.dao.cateSearch(cateSel) # 카테고리별 리스트 출력 DAO
for selectedcatelist in selectedCateList:
print(f'\n{selectedcatelist[0]} {selectedcatelist[1]} {selectedcatelist[2]} {selectedcatelist[3]} {selectedcatelist[4]}')
elif select == '2': # 이름으로 검색
name = input('검색: ')
searchResult = self.dao.nameSearch(name)
for searchResult in searchResult:
print(f'\n{searchResult[1]} {searchResult[2]} {searchResult[3]} {searchResult[4]} {searchResult[5]}')
elif select == '3': # 베스트 10
sortedStarList = self.dao.recommend()
for sortedstarlist in sortedStarList:
print(f'\n{sortedstarlist[1]} {sortedstarlist[2]} {sortedstarlist[3]} {sortedstarlist[4]} {sortedstarlist[5]}')
# 마이페이지 UI
class MyPageService:
def __init__(self):
self.dao = MyPageDao()
self.userid = None
def myPage(self, userid):
self.userid = userid
try:
select = input('1. 내가 쓴 글 2. 닉네임 변경 3. 비밀번호 변경')
if select == '1':
result = self.dao.myPost(self.userid)
if result:
for mypost in result:
print(f'\n{mypost[0]} {mypost[1]} {mypost[2]} {mypost[3]} {mypost[4]} {mypost[5]}')
else:
print('No Post')
elif select == '2':
#try:
print(f'현재 닉네임은 "{userid}"입니다.')
newname = input('새로운 닉네임을 입력하세요. ')
result = self.dao.idChange(self.userid, newname)
print('성공적으로 변경되었습니다.')
#except:
# print('변경에 실패하였습니다.')
elif select == '3':
result = input('현재 비밀번호를 입력하세요')
iscorrect = self.dao.isCorrect(result)
if iscorrect: # 비밀번호 맞음
newpw = input('새로운 비밀번호를 입력하세요')
self.dao.pwChange(self.userid, newpw)
print('비밀번호가 변경되었습니다.')
else:
print('비밀번호가 틀렸습니다.')
except Exception as e:
print(e)
print('에러발생')
View
class Home: # 홈 화면
def __init__(self):
print('=========JBR(Japanes Best Restaurant)==========')
# self.login = Login()
self.select = None
self.id = None
self.ss = SignUpService()
self.ls = LoginService()
self.rs = ResService()
self.ms = MyPageService()
def run(self):
self.select = int(input('\n1. 로그인 2. 회원가입\n'))
if self.select == 2: # 회원가입
self.ss.signUpService()
elif self.select == 1: # 로그인
self.id = self.ls.loginService()
if self.id != 'N' and self.id != None:
while True:
isQuit = self.mainPage()
if not isQuit:
break
elif self.id == 'N':
print('프로그램을 다시 실행하여 회원가입해주세요.')
else:
print('로그인에 실패하였습니다..\n')
print('\n프로그램을 종료합니다.')
def mainPage(self):
try:
menu = int(input('\n1.등록하기 2.찾기 3.마이페이지 4.종료하기'))
if menu == 1: # 등록하기
self.rs.registRes(self.id)
return True
elif menu == 2: # 검색하기
self.rs.searchRes()
return True
elif menu == 3: # 마이페이지
self.ms.myPage(self.id) # 로그인한 아이디 전달
return True
elif menu == 4: # 종료하기
return False
except Exception as e:
print('에러발생')
Run
JBR = Home()
JBR.run()
고찰
- Service 부분을 View라고 생각해도 될 지 모호하다. MVC 패턴을 더 깊게 공부하며 다음 프로젝트에서 더 명확한 구분과 완성도 있는 프로그램을 만들어야겠다.
- DTO도 마찬가지로 목적을 정확하게 이해할 필요가 있다.
- DAO에서 MySQL과 데이터를 주고받는 쿼리문을 더 효율적으로 작성할 필요가 있다.
- member, res 두 테이블을 사용했지만 정규화를 고려하면 테이블을 어떻게 더 나눌 수 있을지 생각.
- 패턴의 무결성, 데이터의 무결성, 일관성
앞으로 공부하고 정리해야할 내용이 많은 것 같다. 파이팅.
댓글