Dev./Python

Python - Mini project : Library Management System 3

Ivan'show 2023. 7. 20.
728x90
반응형

main 함수

def main(): 
		# 기본적으로 다시 한번 DB 커넥션이 일어나게 헀다.
		# 왜냐하면 위에서 DB 설정이 끝나면 다시 끊어주게 설정했기 때문
    create_connection()

    while True:
				# 유저가 동작을 선택할 수 있게 시각적인 부분을 행겨주는 함수
        option = start_page()
        if option == 1:
            pass
        elif option == 2:
            pass
        elif option == 3:
            pass
        elif option == 4:
            pass
        elif option == 5:
            pass
        elif option == 6:
            break
        else:
            print("error")

 

start_page()

def start_page():  # 파이썬 실행 후 가장 처음 보여주는
    print("---------------------------------")  # 33 개
    print("         도서 관리 시스템")  # 9개
    print("---------------------------------")
    print(" 1. 도서 입력 ")
    print(" 2. 도서 조회 ")
    print(" 3. 도서 대출 ")
    print(" 4. 도서 반납 ")
    print(" 5. 도서 대출 정보 조회 ")
    print(" 6. 시스템 종료 ")

    # 유저가 예상 밖의 정보값을 주었을 때, 예외처리
    while True:
        try:
            option = int(input("작업 번호를 입력해 주세요 : "))
            if 1 <= option <= 6:
                return option
            else:
                print("잘못된 입력입니다. 원하는 동작 번호를 다시 입력해 주세요.")
        except ValueError:
            print("잘못된 입력입니다. 원하는 동작 번호를 다시 입력해 주세요.")

ValueError: invalid literal for int() with base 10: 'b’

미리 int() 를 이용한 변수로 switch 로 구성해 놓아서 다른 형태의 값이 오면 계속해서 ValueError 로 인해 프로그램이 꺼지길래 구글링해서 예외처리를 해주었다.

 

조회기능 구현

def main():
    create_connection()

    while True:
        option = start_page()
        if option == 1:
            pass
        elif option == 2:
            print("\\n")
            print("도서 검색 기능 실행!")
            print("\\n")
            search_book() # 유저가 2번을 선택했을 때 실행,
        elif option == 3:
            pass
        elif option == 4:
            pass
        elif option == 5:
            pass
        elif option == 6:
            break
        else:
            print("error")

 

 

search_book()

사실 고민을 좀 많이 했는데, 조회를 동작하면서 계속해서 작업하거나 프로그램을 종료하는 과정들이 있을 것이라 생각해서 재귀함수를 사용하게 되었는데, 이게 스택으로 쌓이면서 루프 안에 갇혀 종료를 2번씩 해야하는, 유저입장에서 불편한 상황이 발생했다.

이를 해결하기 위해 class 로 예외처리로 해결해 보았다.

또한, 쿼리를 넣을때 유저의 입력값을 넣으려면 동적 변수를 사용해야하는데 이때 튜플형태로 작성해서 %s 로 넣어줘야한다. 해당 값이 정수형이든 문자형이든 쿼리에서는 모두 문자형으로 사용해야하니 이점 주의해서 작성해야 할 것 같다.

파이썬 에러 TOP 7 (오류 메시지 종류와 해결 방법) | 코드잇

def search_book():  # 조회 작업
    # 다시 DB 에연결해두기
    connection = psycopg2.connect(host=DATABASE_HOST, user=DATABASE_USER, password=DATABASE_PASSWORD,
                                  dbname=DATABASE_NAME, port=DATABASE_PORT)
    cursor = connection.cursor()

    while True:
        search_option = search_book_option()
        # 도서 제목을 입력받아 해당 글자가 들어간 제목을 가진 도서 모두 조회
        if search_option == 1:
            try:
                book_title = str(input("\\n검색하려는 도서 이름을 입력하세요 :"))
                cursor.execute("""SELECT * FROM books WHERE title LIKE %s;""", ('%' + book_title + '%',))
            except UnicodeError:
                pass

            rows = cursor.fetchall()
            if len(rows) == 0:  # 혹시 입력한 정보로 찾을 수 없을 경우 돌아가게끔 처리
                print("\\n")
                print("\\n")
                print("\\n해당 이름을 가진 도서는 찾을 수 없습니다.")
                print("다시 검색해 주세요.\\n")
                continue

            print("------------------------------------------------------------------")
            print("    ID    |   title   |    Author   |  Publisher  | Availability |")
            print("------------------------------------------------------------------")

            for row in rows:  # 아직 꾸미지 않은 상태
                print(row)
            print("\\n")

            next_action = str(input("다음 작업이 있습니까? [Y/N] :"))
            if next_action.upper() == "Y":
                search_book()  # 재귀
            elif next_action.upper() == "N":
                # 다음 작업이 없이 프로그램이 종료될테니 데이터베이스와 연결 종료
                print("DB close executed")
                cursor.close()
                connection.close()
                print("DB close done")
                raise ExitProgram
            else:
                print("\\n")
                print("잘못 입력하셨습니다. 도서 검색 옵션으로 되돌아 갑니다.")
                print("\\n")

        # 도서 번호를 입력받아 검색
        elif search_option == 2:
            try:
                book_id = int(input("\\n검색하려는 도서의 ID 를 입력해 주세요. :"))
                cursor.execute(
                    """SELECT b.*, l.loan_id, l.loan_date, l.return_date FROM books AS b 
                    LEFT JOIN (SELECT loan_id, book_id, loan_date, return_date FROM loans WHERE return_date IS NULL) AS l ON b.book_id = l.book_id
                    WHERE b.book_id = %s;""", (book_id,)) # SQL 문은 정수형 이런거 상관 없이 %s 로 변수를 받는다.
            except ValueError:
                pass

            row = cursor.fetchall()

            if len(row) == 0:  # 혹시 입력한 정보로 찾을 수 없을 경우 돌아가게끔 처리
                print("\\n해당 ID를 가진 도서는 찾을 수 없습니다.")
                print("다시 검색해 주세요.\\n")
                continue

            print(
                "------------------------------------------------------------------------------------------------------------")
            print(
                "|  Book ID  |   title   |    Author   |  Publisher  | Availability |  LoanID  | Taken Date |  Return Date  |")
            print(
                "------------------------------------------------------------------------------------------------------------")
            print(row)
            print("\\n")

            next_action = str(input("다음 작업이 있습니까? [Y/N] :"))
            if next_action.upper() == "Y":
                search_book()  # 재귀
            elif next_action.upper() == "N":
                # 다음 작업이 없이 프로그램이 종료될테니 데이터베이스와 연결 종료
                print("DB close executed")
                cursor.close()
                connection.close()
                print("DB close done")
                raise ExitProgram
            else:
                print("\\n")
                print("잘못 입력하셨습니다. 도서 검색 옵션으로 되돌아 갑니다.")
                print("\\n")
        # 모든 목록 조회
        elif search_option == 3:
            cursor.execute("""
            SELECT * FROM books;
            """)
            rows = cursor.fetchall()

            print("\\n현재 모든 도서 정보:")
            print("------------------------------------------------------------------")
            print("    ID    |   title   |    Author   |  Publisher  | Availability |")
            print("------------------------------------------------------------------")

            for row in rows:  # 아직 꾸미지 않은 상태
                print(row)
            print("\\n")

            next_action = str(input("다음 작업이 있습니까? [Y/N] :"))
            if next_action.upper() == "Y":
                search_book()
            elif next_action.upper() == "N":
                # 다음 작업이 없이 프로그램이 종료될테니 데이터베이스와 연결 종료
                print("DB close executed")
                cursor.close()
                connection.close()
                print("DB close done")
                raise ExitProgram

        elif search_option == 0:
            print("\\n")
            print("이전 페이지로 돌아갑니다.")
            print("\\n")
            # 다음 작업이 없이 메인으로 돌아가니 데이터베이스와 연결 종료
            print("DB close executed")
            cursor.close()
            connection.close()
            print("DB close done")
            raise ReturnToMainMenu

additional code

# root 인덴트에 작성해서 다른 함수에서도 가져다 쓸 수 있게 하자
class ExitProgram(Exception):  # 중첩된 루프 안에서 프로그램을 바로 종료하기 위함
    pass

class ReturnToMainMenu(Exception):  # 중첩된 루프안에서 프로그램 메인으로 돌아가기 위함
    pass

변경된 main 함수

def main():
    create_connection()
		
		# try/except 문으로 중첩을 벗겨내는 작업을 수행해 보았다.
    try:
        while True:
            option = start_page()
            if option == 1:
                pass
            elif option == 2:
                print("\\n")
                print("도서 검색 기능 실행!")
                print("\\n")
                search_book()
            elif option == 3:
                pass
            elif option == 4:
                pass
            elif option == 5:
                pass
            elif option == 6:
                print("\\n")
                print("시스템을 종료합니다.")
                print("\\n")
                break
            else:
                print("error")
    except ReturnToMainMenu:
        main()
    except ExitProgram:
        print("\\n")
        print("시스템을 종료합니다.")
        print("\\n")

 

insert 기능

  • insert 기능id 값이 자동으로 생성되지 않아서 발생하는 에러해결방법으로는 INTEGER 로 선언해서 만들었던 내용을 SERIAL 로 바꾸면 된다고 한다.
    library=# \\dt
              List of relations
     Schema | Name  | Type  |    Owner    
    --------+-------+-------+-------------
     public | books | table | kim
     public | loans | table | kim
    (2 rows)
    
    library=# \\d books
                                             Table "public.books"
        Column    |          Type          | Collation | Nullable |                Default                 
    --------------+------------------------+-----------+----------+----------------------------------------
     book_id      | integer                |           | not null | nextval('books_book_id_seq'::regclass)
     title        | character varying(100) |           | not null | 
     author       | character varying(50)  |           | not null | 
     publisher    | character varying(50)  |           | not null | 
     is_available | boolean                |           | not null | true
    Indexes:
        "books_pkey" PRIMARY KEY, btree (book_id)
    Referenced by:
        TABLE "loans" CONSTRAINT "loans_book_id_fkey" FOREIGN KEY (book_id) REFERENCES books(book_id)
    
    library=# \\d loans;
                                     Table "public.loans"
       Column    |  Type   | Collation | Nullable |                Default                 
    -------------+---------+-----------+----------+----------------------------------------
     loan_id     | integer |           | not null | nextval('loans_loan_id_seq'::regclass)
     book_id     | integer |           |          | 
     loan_date   | date    |           | not null | 
     return_date | date    |           |          | 
    Indexes:
        "loans_pkey" PRIMARY KEY, btree (loan_id)
    Foreign-key constraints:
        "loans_book_id_fkey" FOREIGN KEY (book_id) REFERENCES books(book_id)
    
    library=#
    
    # Default 값에 내용이 추가되었다.
    # 파이썬 실행 상황
    도서 입력 기능 실행!
    
    새로운 도서를 추가합니다.
    도서 제목을 입력하세요 : 제목
    저자를 입력하세요 : 저자 
    출판사를 입력하세요 : 출판사
    도서가 추가되었습니다.
    
    # psql 에서 조회
    library=# SELECT * FROM books;
     book_id | title | author | publisher | is_available 
    ---------+-------+--------+-----------+--------------
           1 | 제목   | 저자    | 출판사      | t
    (1 row)
    
    library=# SELECT * FROM loans;
     loan_id | book_id | loan_date | return_date 
    ---------+---------+-----------+-------------
    (0 rows)
    
    library=#
    
    # 한글, 영어, 숫자 모두 정상적으로 입력 됨
    library=# SELECT * FROM books;
     book_id | title  | author | publisher | is_available 
    ---------+--------+--------+-----------+--------------
           1 | 제목   | 저자   | 출판사    | t
           2 | ㅁㄴㄹ | asfa   | qwd       | t
           3 | 1111   | 2222   | 3333      | t
    (3 rows)
    
    library=#
    
  • # psql 에서 직접 제거 명렁어 실행 DROP TABLE books; DROP TABLE loans; # 파이썬 코드에서 INTEGER 로 만들었던 내용을 SERIAL 로 변경 cursor.execute(""" CREATE TABLE IF NOT EXISTS books ( book_id SERIAL PRIMARY KEY, title VARCHAR(100) NOT NULL, author VARCHAR(50) NOT NULL, publisher VARCHAR(50) NOT NULL, is_available BOOLEAN NOT NULL DEFAULT TRUE ); """) # loans 테이블 채크후 없으면 생성 cursor.execute(""" CREATE TABLE IF NOT EXISTS loans ( loan_id SERIAL PRIMARY KEY, book_id INTEGER, loan_date DATE NOT NULL, return_date DATE, FOREIGN KEY (book_id) REFERENCES books (book_id) ); """)
  • cursor.execute("""INSERT INTO books (title, author, publisher) VALUES (%s, %s, %s);""", (title, author, publisher,)) psycopg2.errors.NotNullViolation: null value in column "book_id" of relation "books" violates not-null constraint DETAIL: Failing row contains (null, 한글로하면?, 되나?, 진짜로?, t).

 

최종 수정된 insert_book()

def insert_book(): # 파일로 추가하는 부분은 나중에
    connection = psycopg2.connect(host=DATABASE_HOST, user=DATABASE_USER, password=DATABASE_PASSWORD,
                                  dbname=DATABASE_NAME, port=DATABASE_PORT)
    cursor = connection.cursor()

    print("새로운 도서를 추가합니다.\\n")
    title = input("도서 제목을 입력하세요 : ")
    author = input("저자를 입력하세요 : ")
    publisher = str(input("출판사를 입력하세요 : "))

    cursor.execute("""INSERT INTO books (title, author, publisher) VALUES (%s, %s, %s);""", (title, author, publisher,))
    connection.commit()
    print("\\n도서가 추가되었습니다.\\n")
    print("DB close executed")
    cursor.close()
    connection.close()
    print("DB close done")

 

입력 기능 추가된 main()

def main():
    create_connection()

    try:
        while True:
            option = start_page()
            if option == 1:
                print("\\n")
                print("도서 입력 기능 실행!")
                print("\\n")
                insert_book()
            elif option == 2:
                print("\\n")
                print("도서 검색 기능 실행!")
                print("\\n")
                search_book()
            elif option == 3:
                pass
            elif option == 4:
                pass
            elif option == 5:
                pass
            elif option == 6:
                print("\\n")
                print("시스템을 종료합니다.")
                print("\\n")
                break
            else:
                print("error")
    except ReturnToMainMenu:
        main()
    except ExitProgram:
        print("\\n")
        print("시스템을 종료합니다.")
        print("\\n")
728x90
반응형