Project3 - About Brandi Project

Heechul Yoon·2020년 4월 21일
0

LOG

목록 보기
45/62

프로젝트 소개(Project Description)

주제(Topic)

  • (주)브랜디 관리자용 페이지 만들기(developing admin page of Brandi inc)

구성원(Member)

  • Frontend(2), Backend(3)

기간(Developing Period)

  • 4주(20200322 ~ 20200416)

협업(Cooperation)

  • 스크럼방식 협업(Trello)
  • 주단위 백로그작성(Weekly Backlog)
  • 일단위 스탠드업미팅(Daily Standing-up Meeting)
  • git rebase : 커밋관리

적용 기술(Skill Applied)

  • Python : language
  • Flask : web framework
  • Git : cooperation and version management tool
  • Pymysql : database connector
  • Pillow : image file resizing
  • Pandas : excel file generating
  • Boto3 : image uploading
  • Bcrypt : password hashing
  • JWT : token generating
  • AWS RDS : database server
  • AWS S3 : image file repository
  • CORS : server

프로젝트 데모(Project Demo)

깃허브(Github)

나의 역할(My Role)

  • 모델링(ERD)
  • 초기 세팅(Initial project architecture)
    - MVC layered pattern(model, service, view)
    -Blueprint(routing centralizing)
  • 전체 셀러 리스트 표출(GET whole seller list)
    - pagination
    -searching
  • 셀러 리스트 엑셀 다운로드(seller list excel download feature)
    - 키워드 검색결과를 excel로 만들어줌(generating excel file reflecting searching result)
  • 셀러 상태 변경(PUT new seller status on database)
  • 이미지 리사이즈 & 업로더 작성(Image resizing and uploading to S3)
  • S3 버킷 관리(S3 Bucket management)
  • 상품 기획전 등록 & 수정(POST & PUT registering and updating product promotion)
  • 기획전 상세정보 GET(GET promotion detail data)

모델링(ERD)

모델링에서 짚고넘어가야 할 부분 위주로 알아보자

  • 모델링은 크게 seller, product, event로 나뉜다.
    셀러 : 어드민 페이지 서비스를 이용하는 주체
    상품 : seller에게 소속된 상품
    기획전 : 일종의 promotion으로 서비스 자체적으로 여러가지 promotion을 만들 수 있다. ex) promotion 세일 상품, 쿠폰제공 등

  • 어드민 페이지의 특성상 특정 시점에 대한 이력을 관리할 필요가 있다(사용자가 특정시점의 상태를 요구하는 경우가 종종 발생한다고함) 그래서 중요한 데이터의 변경사항을 관리하는 이력관리 테이블을 만들어서 특정시점의 버전을 row로 가진다(선분이력)

  • 셀러 정보의 경우 3개의 테이블이 연동되어있다.
    accounts : 유저의 아이디와 패스워드를 관리하는 테이블이다
    seller_accounts : 유저 중에서 셀러의 번호만 관리하는 테이블이다.
    seller_infos : 셀러의 이력관리용 테이블로 하나의 셀러가 여러개의 이력을 가질 수 있다.

  • 셀러타입과 상품종류가 연동된다. 셀러타입(쇼핑몰, 마켓, 로드샵, 디자이너브랜드, 제너럴브랜드, 내셔널브랜드, 뷰티), 상품종류(트랜드, 브랜드, 뷰티). 즉, 셀러가 만들어지면 셀러타입이 정해지고 그 타입에 따라 부여할 수 있는 상품의 종류가 정해지게 된다. ex) 셀러타입이 쇼핑몰인 셀러는 트랜드상품만 등록할 수 있다.
    트랜드 : 쇼핑몰, 마켓, 로드샵
    브랜드 : 디자이너브랜드, 제네럴브랜드, 내셔널브랜드
    뷰티 : 뷰티

  • 셀러의 경우 권한 타입별로 접근할 수 있는 페이지가 다르다. 마스터권한을 가진 유저의 경우 전체 셀러의 정보를 열람 하고 셀러의 상태를 관리할 수 있고, 기획전을 등록하고 관리할 수 있다. 일반 셀러의 경우 가입 후 상태가 입점대기에서 입점이 되어야 권한이 필요한 페이지에 접근가능하고 상품등록이 가능하다. 이러한 권한에 대한 것은 accounts 테이블에서 관리한다.

  • 상품 이미지의 경우 하나의 사진을 등록하면 3가지 사이즈(소, 중, 대)가 필요하기 때문에 리사이즈 후 s3에 업로다한 url을 데이터베이스에 저장하기 때문에 정규화시킨다.

기억에 남는 기능과 코드

  • 셀러 리스트 검색
  • restful api 형태에 맞게 이미지 업로더 수정
  • 셀러 리스트 Excel 다운로드
  • 스칼라 서브쿼리를 통한 셀러상품 Count
  • 셀러 상태 수정시 SELECT INSERT

이렇게 5개의 기억에 남는 기능과 코드에 대해서 알아보겠다.

  • 셀러 리스트 검색
[1] select_seller_list_statement = '''
            SELECT 
            seller_account_id, 
            accounts.login_id,
            name_en,
            name_kr
            FROM seller_infos
            right JOIN seller_accounts ON seller_accounts.seller_account_no = seller_infos.seller_account_id
            LEFT JOIN accounts ON seller_accounts.account_id = accounts.account_no
            LEFT JOIN seller_statuses ON seller_infos.seller_status_id = seller_statuses.status_no
            LEFT JOIN seller_types ON seller_infos.seller_type_id = seller_types.seller_type_no
            LEFT JOIN manager_infos on manager_infos.seller_info_id = seller_infos.seller_info_no 
            WHERE seller_infos.close_time = '2037-12-31 23:59:59.0'
            AND accounts.is_deleted = 0
            AND seller_accounts.is_deleted = 0
[2]         AND manager_infos.ranking = 1
        '''
        # 쿼리파라미터에 키워드가 들어왔는지 확인하고 위에서 정의해준 명령문에 쿼리를 추가해줌.
[3]     if valid_param.get('seller_account_no', None):
            select_seller_list_statement += " AND seller_accounts.seller_account_no = %(seller_account_no)s"
            filter_query_values_count_statement += " AND seller_accounts.seller_account_no = %(seller_account_no)s"

        if valid_param.get('login_id', None):
            select_seller_list_statement += " AND accounts.login_id = %(login_id)s"
            filter_query_values_count_statement += " AND accounts.login_id = %(login_id)s"

        # 셀러 한글명 같은 경우는 키워드로 들어온 값을 포함하는 모든 셀러를 검색해야 하기 때문에 like 문을 사용한다.
        name_kr = valid_param.get('name_kr', None)
        if valid_param.get('name_kr', None):
            valid_param['name_kr'] = '%'+name_kr+'%'
            select_seller_list_statement += " AND name_kr LIKE %(name_kr)s"
            filter_query_values_count_statement += " AND name_kr LIKE %(name_kr)s"

[1] 우선 sql 명령문을 제일 위에서 정의해준다.
[3] 만약 쿼리파라미터로 검색키워드가 들어온다면 sql 명령문에 조건을 추가해준다. 결론적으로 데이터베이스에 sql명령문이 실행될 때는 [2] 뒤에 조건이 추가되어 명령문이 실행된다.

  • restful api 형태에 맞게 이미지 업로더 수정
    여기에서 설명한 이미지 업로더를 utils.py라는 모듈을 만들어서 전부 넣고 import해서 사용하는 방식을 택했다.
    def change_seller_info(*args):
        # 이미지 업로드 함수를 호출해서 이미지를 업로드하고 url 을 딕셔너리로 가져옴.
[1]     image_upload = ImageUpload()
        seller_image = image_upload.upload_images(request)

[2]     if (400 in seller_image) or (500 in seller_image):
            return seller_image

        # validation 확인이 된 data 를 account_info 로 재정의
[3]     account_info = {
            'auth_type_id': g.account_info['auth_type_id'],
            'decorator_account_no': g.account_info['account_no'],
            'parameter_account_no': args[0],
[4]         'profile_image_url': seller_image.get('seller_profile_image', None),
            'certificate_image_url': seller_image.get('certificate_image', None),
            'online_business_image_url': seller_image.get('online_business_image', None),
            'background_image_url': seller_image.get('background_image', None)
            }

[1] 우선 이미지 업로더 클래스의 인스턴스를 만들어주고 이미지 업로드를 하는 매서드를 실행해서 결과를 변수에 담는다.
[2] 이미지 업로더가 있는 모듈에서 리턴값이 400이나 500이 있으면 그 리턴값을 그대로 클라이언트에게 리턴한다.
[3] 이미지 업로드 실행결과(이미지 url의 dictionary형태)에서 value만 가져온다. 없으면 None을 가져와서 저장한다.

  • 셀러 리스트 Excel 다운로드
                # 쿼리파라미터에 excel 키가 1로 들어오면 엑셀파일을 만듦.
[1]             if valid_param['excel'] == 1:
[2]                 s3 = get_s3_connection()

                    # 엑셀파일로 만들경우 페이지네이션 적용을 받지않고 검색 적용만 받기 때문에 페이지네이션 부분 쿼리를 제거해준다.
[3]                 replaced_statement = select_seller_list_statement.replace('ORDER BY seller_account_id DESC LIMIT %(limit)s OFFSET %(offset)s', '')
[4]                 db_cursor.execute(replaced_statement, valid_param)
                    seller_info = db_cursor.fetchall()

                    # pandas 데이터 프레임을 만들기 위한 column 과 value 정리
[5]                 seller_list_dict = {
                        '셀러번호': [seller['seller_account_id'] for seller in seller_info],
                        '관리자계정ID': [seller['login_id'] for seller in seller_info],
                        '셀러영문명': [seller['name_en'] for seller in seller_info],
                        '셀러한글명': [seller['name_kr'] for seller in seller_info],
                        '브랜디회원번호': [seller['brandi_app_user_id'] for seller in seller_info],
                        '담당자명': [seller['manager_name'] for seller in seller_info],
                        '담당자전화번호': [seller['manager_contact_number'] for seller in seller_info],
                        '판매구분': [seller['seller_type_name'] for seller in seller_info],
                        '상품개수': [seller['product_count'] for seller in seller_info],
                        '셀러URL': [seller['site_url'] for seller in seller_info],
                        '셀러등록일': [seller['created_at'] for seller in seller_info],
                        '승인여부': [seller['seller_status'] for seller in seller_info]
                    }

                    # 데이터베이스의 데이터를 기반으로 한 딕셔너리를 판다스 데이터 프레임으로 만들어줌.
[6]                 df = pd.DataFrame(data=seller_list_dict)
                    # 첫번제 인덱스의 컬럼명을 지정해주고, 번호가 1부터 시작하도록 한다.
[7]                 df.index.name = '번호'
                    df.index += 1

                    # 파일이름과 파일경로를 정의해줌.
[9]                 file_name = f'{self.gen_random_name()}.xlsx'
                    file = f'../{file_name}'

                   # 파일을 엑셀파일로 변환해서 로컬에 저장
[10]                df.to_excel(file, encoding='utf8')

                    # 로컬에 저장된 파일을 s3에 업로드. 업로드 할 때 실패할 것을 고려하여 try-except 사용.
                    try:
[11]                    s3.upload_file(file, "brandi-intern", file_name)
                    except Exception as e:
                        print(f'error: {e}')
                        return jsonify({'message': 'S3_UPLOAD_FAIL'}), 500

                    # s3에 올라간 파일을 다운받는 url
[12]                file_url = f'https://brandi-intern.s3.ap-northeast-2.amazonaws.com/{file_name}'

                    # s3에 올라간 후에 로컬에 있는 파일 삭제
[13]                os.remove(file)

[14]                return jsonify({'file_url': file_url}), 200

[1] 쿼리파라미터에 excel=1이 들어오면 셀러정보를 데이터베이스에 가져온 결과를 엑셀파일로 만들어 리턴한다.
[2] 우선 s3에 업로드를 하고 다운로드 url을 리턴해야하기 때문에 s3 커넥션을 열어준다.
[3] 위에서 정의했던(검색키워드가 포함된) sql명령문에서 정렬과 페이지네이션 부분만 제거한 sql명령문을 만들어주고 [4]에서 데이터베스에서 셀러정보들을 가져온다.
[5] 가져온 셀러정보를 엑셀파일로 만들기 위해서 dictionary자료형으로 만들어준다. list comprehension을 사용하여 데이터를 리스트로 만들어준다.
[6] 위에서 만들어준 dictionary를 data변수로 지정하고 판다스 데이터프레임을 만들어서 인스턴스로 만든다.
[7] 인덱스 이름과 인덱스 번호를 지정해주지않으면 인덱스이름은 없고, 번호는 0부터 시작하기 때문에 미리 지정해준다.
[9] 파일이름과 파일경로를 지정해주고 [10]에서 판다스 데이터프레임을 엑셀파일로 만든다.
[11] 로컬에 저장된 엑셀파일을 s3에 업로드해준다.
[12] 업로드된 url을 정의해주고 [13]에서 로컬에 저장된 파일을 삭제한다.
[14] 다운로드 url을 리턴한다.

스스로에 대한 피드백

잘한점

  • 새로운 프레임워크에 대해 익숙해진 점이다. 기존에 Django만 사용하다가 새로운 django보다 가벼운느낌의 Flask프레임워크를 처음사용하게 되었다. django와 다르게 프로젝트의 초기설정과 layer 설계를 직접해주어야 했다.(Flask초기 프로젝트 설계)
    그래서 플라스크에 대한 공부와 초기설계를 병행하면서 8일의 시간을 사용했다. 그렇게 새로운 프레임워크에 적응을했고, 이제 django와 같이 많은기능을 지원하지 않는 프레임워크를 처음에 시작해도 무리없이 흡수할 수 있을 정도의 웹프레임워크 전반에 대한 이해도가 올라간 점이 큰 성과라 생각한다.
  • ORM이 없어서 SQL 로우쿼리를 사용해서 데이터베이스와 연결을 해야 했으며 SQL 로우쿼리에 대한 이해가 생기게 되었다. Django를 사용하면서 ORM이 해주었던 일들이 데이터베이스에서 실재로 어떻게 일어나는지 알게되었고 데이터베이스에 대해 조금 더 깊은 이해를 할 수 있는 계기가 되었고 관계형 데이터베이스에 대한 이해와 명령문 사용에 대해 더 배워야겠다는 생각을 가지게 되었다.
  • 실재로 상용화 될 수 있는 데이터베이스 모델링을 할 수 있었는 점이다. accounts테이블에서 회원의 아이디 패스워드를 관리하고 셀러 번호만 관리하는 seller_accounts 테이블 만드는것, 선분이력을 적용해서 이력관리용 테이블을 만드는것 등 현업에서 사용되는 데이터베이스 설계를 할 수 있었던 점이 큰 성과였다. 그리고 선분이력의 개념을 사용하면 이전에 했던 사운드클라우드 프로젝트에서 양 당사자간 주고받은 마지막 메세지를 가져오는 문제도 해결할 수 있을 것이라고 생각했다(조만간 적용해볼 예정)

아쉬운점

  • 프로젝트 초기설계에 너무 많은시간을 투자해서 실재로 개발을 촉박하게 했던 점이다. django를 사용하다가 처음으로 flask를 사용하면서 django에서 startproject와 startapp으로 만들었던 설계를 직접 코딩으로 하게 되었다. 그리고 mvc패턴에 대한 정확한 이해가 없이 설계를 했던 탓에 만들고 수정하는 작업을 반복하게 되었다. 새로운 개념이 추가되어서 설계에 많은 시간을 투자했고 원래계획했던 시간표를 따라가기 위해서 촉박하게 코딩을 했다.
  • 위코드에서 배웠던 것과는 다르게 코드마다 주석을 자세하게 달아야 했으며, 주석다는 습관이 없는상태에서 코드에 대한 설명이 부족했다. 협업을 위해서는 남이 내가 짠 코드를 이해하는게 중요하기 때문에 자세하게 주석을 다는 습관을 들여야 겠다고 생각했다.
  • RESTful API에 대한 개념이 적립되지 않은 상태에서 이미지 업로더를 만들었다. 처음에는 Image라는 앱을 따로만들어서 이미지 API호출로 이미지 업로더를 구현했는데 restful api에서 resource개념을 이해하고나서 seller, product, event에서 각각의 앱 안에 이미지업로더를 넣고 이미지와 다른 데이터를 함께 처리하도록 수정했다. 처음부터 restful api에 맞게 이미지업로더가 만들어졌더라면 투자되지 않았어야 할 시간이 투자되어 촉박하게 시간표를 따라갔었다.

개선방법

  • 프로젝트를 설계할 때는 mvc 레이어드패턴에 대한 정확한 이해와 다음레이어에 대한 의존성, 데이터 커넥션을 만들어주고 끊는 위치를 잘 고려해서 만들어야 겠다.
  • 앤드포인트를 만들때는 restful api의 리소스개념을 적용하여 만들어야 겠다.
  • 코드를 짜고나서 내 코드를 읽는 사람의 입장에서 주석을 달아야 겠다.

느낀점

첫번째로는 django 프래임워크를 벗어나 새롭고 가벼운 프래임워크를 사용하면서 Python과 객체지향 언어에 대한 이해도가 올라갔다고 느꼈다. 그래서 다른 객체지향 언어를 배우고싶다고 느꼇고, Flask와 같은 가벼운 프레임워크를 사용해보면서 다른 가벼운 프레임워크를 사용해보고싶다고 느꼈다(예를들면 Node.js). 그리고 좀더 발전하면 프레임워크 자체를 만들어보는 것도 언어와 웹에 대한 이해를 높이는데 도움이 될 것이라고 생각했다.
두번째로는 현업에서 사용하는 데이터베이스 모델링을 배울 수 있어서 좋았으며 실재 서비스를 하기위해서는 유지보수를 생각하고 시스템에 대한 설계를 한다는 것을 경험으로 깨닫게 되었다.

profile
Quit talking, Begin doing

0개의 댓글