diff --git "a/\341\204\200\341\205\252\341\206\274\341\204\200\341\205\251\341\206\274\341\204\214\341\205\256\341\206\274\341\204\202\341\205\241#4/board.py" "b/\341\204\200\341\205\252\341\206\274\341\204\200\341\205\251\341\206\274\341\204\214\341\205\256\341\206\274\341\204\202\341\205\241#4/board.py" new file mode 100644 index 0000000..50f3258 --- /dev/null +++ "b/\341\204\200\341\205\252\341\206\274\341\204\200\341\205\251\341\206\274\341\204\214\341\205\256\341\206\274\341\204\202\341\205\241#4/board.py" @@ -0,0 +1,133 @@ +import os +import csv +from post import Post + +SAVE_FILE_PATH = './광일공방중고나라_게시판목록.csv' + +def auto_save(func): + # caller.save가 구현되지 않은 곳에 데코레이터가 붙은 경우에 대한 예외 처리 + def decorated(caller, *args, **kwargs): + func(caller, *args, **kwargs) + caller.save() + return decorated + +class Board(): + PAGINATION_SIZE = 15 + + def __init__(self, user_id): + self.user_id = user_id + self.posts = list() + self.actions = [self.create_post, self.update_post, self.delete_post, self.save, self.go_to_previous_page, self.go_to_next_page] + self.current_page_num = 1 + self.load() + + @staticmethod + def print_actions(): + print('-' * 100) + print('1. 작성하기') + print('2. 수정하기') + print('3. 삭제하기') + print('4. 저장하기') + print('5. 이전 페이지로') + print('6. 다음 페이지로') + print('-' * 100) + + # iterate posts and print + def show_posts(self): + reversed_posts = list(reversed(self.posts)) + reversed_existed_posts = [post for post in reversed_posts if post.existence_state] # filter soft_deleted posts + + fromIdx = Board.PAGINATION_SIZE * (self.current_page_num - 1) + toIdx = Board.PAGINATION_SIZE * self.current_page_num + for post in reversed_existed_posts[fromIdx:toIdx]: + print('{:^6} | {:60.60} | {:^10} | {}'.format(post.posting_num, post.posting_title, post.posting_user, post.posting_time)) + + # check existence of post in posts and handle messages + def check_existence_of_posts(self): + if len(self.posts) == 0: + print('게시판에 아직 작성된 글이 없습니다.') + print('한 번 작성해 보는 것은 어떨까요?') + return + self.show_posts() + + def find_target_post(self, target_posting_num): + target_post = None + i = 0 + while target_post is None and i < len(self.posts): + target_post = self.posts[i] if self.posts[i].posting_num == target_posting_num else None + i += 1 + return target_post + + @auto_save + def create_post(self): + post = Post(posting_user = self.user_id) + self.posts.append(post) + print('{}번 글이 성공적으로 작성되었습니다.'.format(post.posting_num)) + + @auto_save + def update_post(self): + if len(self.posts) == 0: + raise Exception('현재 글이 없습니다.') + + update_idx = int(input('몇 번 글을 수정할까요? ')) + post_to_update = self.find_target_post(update_idx) + if post_to_update is None or post_to_update.existence_state is False: + raise Exception('수정할 해당 번호 글이 없습니다.') + + post_to_update.update_posting_title(self.user_id) + + @auto_save + def delete_post(self): + if len(self.posts) == 0: + raise Exception('현재 글이 없습니다.') + + delete_idx = int(input('몇 번 글을 지울까요? ')) + post_to_delete = self.find_target_post(delete_idx) + if post_to_delete is None or post_to_delete.existence_state is False: + raise Exception('삭제할 해당 번호 글이 없습니다.') + + post_to_delete.soft_delete_post(self.user_id) + + def save(self): + global SAVE_FILE_PATH + + f = open(SAVE_FILE_PATH, 'w', encoding='utf-8') + for post in self.posts: + print(post.to_csv(), file=f) + f.close() + + def load(self): + global SAVE_FILE_PATH + if os.path.exists(SAVE_FILE_PATH) is False: + return + + f = open(SAVE_FILE_PATH, 'r', encoding='utf-8') + posts = f.readlines() + for post in posts: + self.posts.append(Post.from_csv(post)) + f.close() + + def go_to_previous_page(self): + if self.current_page_num == 1: + input('이전 페이지가 없습니다.') + return + self.current_page_num -= 1 + + def go_to_next_page(self): + if (self.current_page_num - 1) * Board.PAGINATION_SIZE < len(self.posts) <= self.current_page_num * Board.PAGINATION_SIZE: + input('다음 페이지가 없습니다.') + return + self.current_page_num += 1 + + # manage CRUD of posts + def post_CRUD_handler(self): + try: + menuSel = int(input('> ')) + self.actions[menuSel - 1]() + return + except TypeError: + input('잘못 입력하셨습니다. 올바른 번호를 선택하세요.') + except IndexError: + input('잘못 입력하셨습니다. 현재 글 번호를 맞게 입력하세요.') + except Exception as e: + input('잘못 입력하셨습니다.', e) diff --git "a/\341\204\200\341\205\252\341\206\274\341\204\200\341\205\251\341\206\274\341\204\214\341\205\256\341\206\274\341\204\202\341\205\241#4/membership_manager.py" "b/\341\204\200\341\205\252\341\206\274\341\204\200\341\205\251\341\206\274\341\204\214\341\205\256\341\206\274\341\204\202\341\205\241#4/membership_manager.py" new file mode 100644 index 0000000..55b941e --- /dev/null +++ "b/\341\204\200\341\205\252\341\206\274\341\204\200\341\205\251\341\206\274\341\204\214\341\205\256\341\206\274\341\204\202\341\205\241#4/membership_manager.py" @@ -0,0 +1,78 @@ +import hashlib +import os +from user import User + +USER_FILE_PATH = './광일공방중고나라_사용자목록.csv' + +class Membership_manager(): + def __init__(self): + self.actions = [self.log_in, self.sign_up] + + # 로그인 / 회원가입 메뉴 + def menu(self): + print('-' * 100) + print('1. 로그인') + print('2. 회원가입') + print('-' * 100) + + def action_handler(self): + try: + menuSel = int(input('> ')) + if menuSel <= 0: + raise IndexError + return self.actions[menuSel - 1]() + except ValueError: + input('잘못 입력하셨습니다. 올바른 번호를 선택하세요.') + except IndexError: + input('잘못 입력하셨습니다. 메뉴의 번호를 알맞게 입력하세요.') + except Exception as e: + input('잘못 입력하셨습니다.', e) + + # 입력한 ID와 사용자 목록에서의 아이디 비교 + def check_existing_id(self, id): + global USER_FILE_PATH + if os.path.exists(USER_FILE_PATH) is False: + return None + + f = open(USER_FILE_PATH, 'r', encoding='utf-8') + users = f.readlines() + target_user = None + i = 0 + while i < len(users) and target_user is None: + target_id, target_pwd = users[i][:-1].split(',') + target_user = (target_id, target_pwd) if target_id == id else None + i += 1 + f.close() + return target_user + + def log_in(self): + global USER_FILE_PATH + + id = input('아이디 : ') + pwd = input('비밀번호 : ') + target_id, target_pwd = self.check_existing_id(id) or (None, None) + if target_id is None: + input('로그인 실패 : 아이디가 존재하지 않습니다. 처음이시라면 회원가입을 선택해주세요.') + return None + if target_pwd != hashlib.sha256(pwd.encode()).hexdigest(): + input('로그인 실패 : 비밀번호가 틀렸습니다. 다시 시도해주세요.') + return None + + input('로그인 성공 : 환영합니다 {}님!'.format(id)) + return id + + def sign_up(self): + id = input('아이디 : ') + pwd = input('비밀번호 : ') + target_id, _ = self.check_existing_id(id) or (None, None) + if target_id is not None: + input('회원가입 실패 : 해당 아이디가 이미 존재합니다. 다른 아이디를 작성해주세요.') + return None + + new_user = User(id, pwd) + f = open(USER_FILE_PATH, 'a', encoding='utf-8') + print(new_user.to_csv(), file=f) + f.close() + + input('회원가입 및 로그인 성공 : 환영합니다 {}님!'.format(id)) + return id \ No newline at end of file diff --git "a/\341\204\200\341\205\252\341\206\274\341\204\200\341\205\251\341\206\274\341\204\214\341\205\256\341\206\274\341\204\202\341\205\241#4/post.py" "b/\341\204\200\341\205\252\341\206\274\341\204\200\341\205\251\341\206\274\341\204\214\341\205\256\341\206\274\341\204\202\341\205\241#4/post.py" new file mode 100644 index 0000000..be7cf38 --- /dev/null +++ "b/\341\204\200\341\205\252\341\206\274\341\204\200\341\205\251\341\206\274\341\204\214\341\205\256\341\206\274\341\204\202\341\205\241#4/post.py" @@ -0,0 +1,45 @@ +from datetime import datetime + +def auto_check_permission(action_type): + def decorated(func): + def inner(caller, *args, **kwargs): + if caller.check_permission(*args, action_type = action_type): # 처리하려면 조건 True + func(caller, *args, **kwargs) + return inner + return decorated + +class Post(): + CURRENT_POSTING_NUM = 0 + + def __init__(self, posting_user, existence_state = None, posting_num = None, posting_title = None, posting_time = None): + self.posting_user = posting_user + self.existence_state = True if existence_state is None else existence_state == 'True' # check the validation of this post + self.posting_num = Post.CURRENT_POSTING_NUM + 1 if posting_num is None else int(posting_num) + Post.CURRENT_POSTING_NUM = Post.CURRENT_POSTING_NUM + 1 if posting_num is None else max(Post.CURRENT_POSTING_NUM, int(posting_num)) + self.posting_title = input('작성 > ') if posting_title is None else posting_title + self.posting_time = datetime.now().strftime('%Y-%m-%d %H:%M') if posting_time is None else posting_time + + def check_permission(self, user_id, action_type): + action_kwds = ['수정', '삭제'] + if user_id != self.posting_user: + input('{}님께는 해당 글 {} 권한이 없습니다!'.format(user_id, action_kwds[action_type - 1])) + return False + return True + + @auto_check_permission(1) + def update_posting_title(self, user_id): + self.posting_title = input('수정 > ') + input('{}번 글이 성공적으로 수정되었습니다.'.format(self.posting_num)) + + @auto_check_permission(2) + def soft_delete_post(self, user_id): + self.existence_state = False + input('{}번 글이 성공적으로 삭제되었습니다.'.format(self.posting_num)) + + @classmethod + def from_csv(cls, csv): + existence_state, posting_num, posting_title, posting_user, posting_time = csv[:-1].split(',') + return cls(posting_user, existence_state, posting_num, posting_title, posting_time) + + def to_csv(self): + return '{},{},{},{},{}'.format(self.existence_state, self.posting_num, self.posting_title, self.posting_user, self.posting_time) \ No newline at end of file diff --git "a/\341\204\200\341\205\252\341\206\274\341\204\200\341\205\251\341\206\274\341\204\214\341\205\256\341\206\274\341\204\202\341\205\241#4/user.py" "b/\341\204\200\341\205\252\341\206\274\341\204\200\341\205\251\341\206\274\341\204\214\341\205\256\341\206\274\341\204\202\341\205\241#4/user.py" new file mode 100644 index 0000000..23a9f6e --- /dev/null +++ "b/\341\204\200\341\205\252\341\206\274\341\204\200\341\205\251\341\206\274\341\204\214\341\205\256\341\206\274\341\204\202\341\205\241#4/user.py" @@ -0,0 +1,9 @@ +import hashlib + +class User(): + def __init__(self, id, pwd): + self.id = id + self.pwd = hashlib.sha256(pwd.encode()).hexdigest() + + def to_csv(self): + return '{},{}'.format(self.id, self.pwd) \ No newline at end of file diff --git "a/\341\204\200\341\205\252\341\206\274\341\204\200\341\205\251\341\206\274\341\204\214\341\205\256\341\206\274\341\204\202\341\205\241#4/\352\264\221\354\235\274\352\263\265\353\260\251\354\244\221\352\263\240\353\202\230\353\235\274#4.py" "b/\341\204\200\341\205\252\341\206\274\341\204\200\341\205\251\341\206\274\341\204\214\341\205\256\341\206\274\341\204\202\341\205\241#4/\352\264\221\354\235\274\352\263\265\353\260\251\354\244\221\352\263\240\353\202\230\353\235\274#4.py" new file mode 100644 index 0000000..3c08cd3 --- /dev/null +++ "b/\341\204\200\341\205\252\341\206\274\341\204\200\341\205\251\341\206\274\341\204\214\341\205\256\341\206\274\341\204\202\341\205\241#4/\352\264\221\354\235\274\352\263\265\353\260\251\354\244\221\352\263\240\353\202\230\353\235\274#4.py" @@ -0,0 +1,37 @@ +import os +import sys +from board import Board +from membership_manager import Membership_manager + +def clear_screen(): + os.system('cls' if os.name == 'nt' else 'clear') + +def greeting_messages(): + clear_screen() + print(""" + 본 프로그램은 광일공방에서 중고거래 사이트를 기웃기웃 거리던 광일이가 + 어느 날 삘이 꽃혀 '아! 여기가 돈 나오는 방석이다!' 생각하자마자 아주 + 급하게 만들어낸 불안정한 중고거래 사이트입니다. 물론 아직은 허접한 + 게시판이지만 1000만 사용자를 꿈꾸는 광일이는 커다란 희망의 씨앗을 마음에 + 품고서 오늘도 밤을 지새우며 코드를 작성합니다.""") + print() + input('프로그램을 시작하시려면 로그인하세요...') + + +if __name__ == '__main__': + greeting_messages() + membership_manager = Membership_manager() + while True: + clear_screen() + membership_manager.menu() + log_in_user_id = membership_manager.action_handler() # 로그인 성공한 유저가 없으면 None, 있으면 id 반환 + + if log_in_user_id is not None: + break + + board = Board(log_in_user_id) + while True: + clear_screen() + board.check_existence_of_posts() + board.print_actions() + board.post_CRUD_handler()