class ProductManageSearchView(MethodView): """ Presentation Layer Attributes: service : MainCategoryListService 클래스 database : app.config['DB']에 담겨있는 정보(데이터베이스 관련 정보) Author: 심원두 History: 2020-12-31(심원두): 초기 작성 2021-01-03(심원두): 상품 리스트 검색 기능 구현 """ def __init__(self, service, database): self.service = service self.database = database @signin_decorator() @validate_params( Param('lookup_start_date', GET, str, required=False, rules=[DateRule(), NotEmpty()]), Param('lookup_end_date', GET, str, required=False, rules=[DateRule(), NotEmpty()]), Param('seller_name', GET, str, required=False, rules=[DefaultRule(), NotEmpty(), MaxLength(20)]), Param('product_name', GET, str, required=False, rules=[DefaultRule(), NotEmpty(), MaxLength(100)]), Param('product_id', GET, str, required=False, rules=[NumberRule(), NotEmpty()]), Param('product_code', GET, str, required=False, rules=[DefaultRule(), NotEmpty(), MaxLength(20)]), Param('is_sale', GET, int, required=False, rules=[Enum(1, 2)]), Param('is_display', GET, int, required=False, rules=[Enum(1, 2)]), Param('is_discount', GET, int, required=False, rules=[Enum(1, 2)]), Param('page_number', GET, int, required=True, rules=[PageRule()]), Param('limit', GET, int, required=True, rules=[Enum(10, 20, 50)])) def get(self, *args): """GET 메소드: 특정 조건에 해당하는 상품 리스트를 조회한다. Args: 'lookup_start_date' : 조회 시작 기간 'lookup_end_date' : 조회 종료 기간 'seller_name' : 셀러명 'product_name' : 상품명 'product_id' : 상품 아이디 'product_code' : 상품 코드 'seller_attribute_type_id : 셀러 속성 'is_sale' : 할인 여부 'is_display' : 진열 여부 'is_discount' : 할인 여부 'page_number' : 페이지 번호 'limit' : 한 화면에 보여줄 상품의 갯수 Author: 심원두 Returns: return {"message": "success", "result": result} Raises: 400, {'message': 'key error', 'errorMessage': 'key_error' + format(e)} : 잘못 입력된 키값 400, {'message': 'both date field required', 'errorMessage': 'both_date_field_required'}: 필수 값 유효성 체크 에러 400, {'message': 'start date is greater than end date', 'errorMessage': 'start_date_is_greater_than_end_date'}: 날짜 비교 유효성 체크 에러 400, {'message': 'invalid seller attribute type', 'errorMessage': 'invalid_seller_attribute_type'}: 셀러 타입 유효성 체크 에러 History: 2020-12-31(심원두): 초기생성 2021-01-03(심원두): 상품 리스트 검색 기능 구현, Login Decorator 구현 예정 """ try: search_condition = { 'seller_id': g.account_id if g.permission_type_id == 2 else None, 'lookup_start_date': request.args.get('lookup_start_date', None), 'lookup_end_date': request.args.get('lookup_end_date', None), 'seller_name': request.args.get('seller_name', None), 'product_name': request.args.get('product_name', None), 'product_id': request.args.get('product_id', None), 'product_code': request.args.get('product_code', None), 'seller_attribute_type_ids': json.loads(request.args.get('seller_attribute_type_id')) if request.args.get('seller_attribute_type_id') else None, 'is_sale': request.args.get('is_sale', None), 'is_display': request.args.get('is_display', None), 'is_discount': request.args.get('is_discount', None), 'page_number': request.args.get('page_number'), 'limit': request.args.get('limit') } search_condition_back_to_front = { 'lookup_start_date': search_condition['lookup_start_date'], 'lookup_end_date': search_condition['lookup_end_date'], 'seller_name': search_condition['seller_name'], 'product_name': search_condition['product_name'], 'product_id': search_condition['product_id'], 'product_code': search_condition['product_code'], 'seller_attribute_type': search_condition['seller_attribute_type_ids'], 'is_sale': 0 if search_condition['is_sale'] is None else int( search_condition['is_sale']), 'is_display': 0 if search_condition['is_display'] is None else int( search_condition['is_display']), 'is_discount': 0 if search_condition['is_discount'] is None else int( search_condition['is_discount']), 'page_number': int(search_condition['page_number']), 'limit': int(search_condition['limit']) } connection = get_connection(self.database) result = self.service.search_product_service( connection, search_condition) result['search_condition'] = search_condition_back_to_front return jsonify({'message': 'success', 'result': result}) except KeyError as e: traceback.print_exc() raise e except Exception as e: traceback.print_exc() raise e finally: try: if connection: connection.close() except Exception: raise DatabaseCloseFail('database close fail')
def seller_endpoints(app, services, get_session): seller_service = services.seller_service @app.route("/signup", methods=['POST']) @validate_params( Param('brand_crm_number', JSON, str, rules=[Pattern(r'^[0-9]{2,3}-[0-9]{3,4}-[0-9]{4}$')]), Param('password', JSON, str, rules=[Pattern(r'^(?=.*[0-9])(?=.*[A-Za-z])(?=.*[^a-zA-Z0-9]).{8,20}$')]), Param('phone_number', JSON, str, rules=[Pattern(r'^010-[0-9]{3,4}-[0-9]{4}$')]), Param('account', JSON, str, rules=[MinLength(5)]), Param('brand_name_korean', JSON, str), Param('brand_name_english', JSON, str), Param('seller_property_id', JSON, str) ) def sign_up(*args): """ 회원가입 API 회원가입을 위한 정보를 Body 에 받음 Args: *args: brand_crm_number : 브랜드 고객센터 번호 password : 비밀번호 phone_number : 담당자 핸드폰 번호 account : 계정 brand_name_korean : 브랜드명 ( 한글 ) brand_name_english : 브랜드명 ( 영어 ) seller_property_id : 셀러 속성 id ( 로드샵, 마켓 등 ) Returns: 200 : success , 회원가입 성공 시 400 : 같은 계정이 존재할 때, key error 500 : Exception """ session = None try: session = get_session() new_seller = { 'brand_crm_number': args[0], 'password': args[1], 'phone_number': args[2], 'account': args[3], 'brand_name_korean': args[4], 'brand_name_english': args[5], 'seller_property_id': args[6] } new_seller = seller_service.create_new_seller(new_seller, session) # 같은 계정이 이미 존재할 때 if new_seller == 'already exist': return jsonify({'message': 'account already exist'}), 400 session.commit() return jsonify({'message': 'success'}), 200 except KeyError: return jsonify({'message': 'key error'}), 400 except NoAffectedRowException as e: session.rollback() return jsonify({'message': 'no affected row error {}'.format(e.message)}), e.status_code except Exception as e: session.rollback() return jsonify({'message': '{}'.format(e)}), 500 finally: if session: session.close() @app.route("/login", methods=['POST']) @validate_params( Param('account', JSON, str), Param('password', JSON, str) ) def log_in(*args): """ Args: *args: account : 계정 password : 비밀번호 Returns: 200 : 로그인 성공 하면 access token 발행 400 : 계정이 존재하지 않을 때, 비밀번호가 틀렸을 때, soft delete 된 계정일 때, 셀러의 상태가 입점 대기 상태일 때 500 : Exception """ session = None try: session = get_session() seller = { 'account': args[0], 'password': args[1] } token = seller_service.enter_login(seller, session) # 계정이 존재하지 않을 때 if token == 'not exist': return jsonify({'message': 'account not exist'}), 400 # 비밀번호가 틀렸을 때 if token == 'wrong password': return jsonify({'message': 'wrong password'}), 400 # 소프트 딜리트 된 계정일 때 if token == 'deleted account': return jsonify({'message': 'account deleted'}), 400 # 셀러 상태가 입점 대기 일 때 ( seller_status_id = 1 ) if token == 'not authorized': return jsonify({'message': 'not authorized'}), 400 return jsonify({'access_token': token}) except KeyError: return jsonify({'message': 'key error'}), 400 except Exception as e: session.rollback() return jsonify({'message': '{}'.format(e)}), 500 finally: if session: session.close() @app.route("/mypage", methods=['GET']) @login_required def get_my_page(): """ 셀러정보관리(셀러) 셀러가 셀러 정보 관리를 들어갔을 때 등록된 셀러의 데이터 가져오기 Returns: 200 : seller_data ( type : dict ) 500 : Exception """ session = None try: session = get_session() seller_data = seller_service.get_my_page(g.seller_id, session) return jsonify(seller_data) except NoDataException as e: session.rollback() return jsonify({'message': 'no data {}'.format(e.message)}), e.status_code except Exception as e: session.rollback() return jsonify({'message': '{}'.format(e)}), 500 finally: if session: session.close() @app.route("/mypage", methods=['PUT']) @login_required @validate_params( Param('image', JSON, str), Param('simple_introduce', JSON, str), Param('brand_crm_number', JSON, str, rules=[Pattern(r'^[0-9]{2,3}-[0-9]{3,4}-[0-9]{4}$')]), Param('zip_code', JSON, int), Param('address', JSON, str), Param('detail_address', JSON, str), Param('brand_crm_open', JSON, str), Param('brand_crm_end', JSON, str), Param('delivery_information', JSON, str), Param('refund_exchange_information', JSON, str), Param('seller_status_id', JSON, int), Param('background_image', JSON, str, required=False), Param('detail_introduce', JSON, str, required=False), Param('is_brand_crm_holiday', JSON, int, rules=[Enum(0, 1)], required=False), Param('brand_name_korean', JSON, str), Param('brand_name_english', JSON, str), Param('manager_information', JSON, list) ) def put_my_page(*args): """ 셀러 정보 관리 (셀러 ) 셀러의 정보를 Body 로 받아서 데이터에 업데이트하기 Args: *args: image : 셀러 프로필 이미지 simple_introduce : 셀러 한줄 소개 brand_crm_number : 브랜드 고객센터 번호 zip_code : 우편번호 address : 주소 detail_address : 상세 주소 brand_crm_open : 고객센터 오픈시간 brand_crm_end : 고객센터 마감시간 delivery_information : 배송 정보 refund_exchange_information : 교환/환불 정보 seller_status_id : 셀러 상태 id ( 입점, 입점대기 등 ) background_image : 셀러페이지 배경이미지 detail_introduce : 셀러 상세 소개 is_brand_crm_holiday : 고객센터 주말 및 공휴일 운영 여부 brand_name_korean : 브랜드명 ( 한글 ) brand_name_english : 브랜드명 ( 영어 ) manager_information : 담당자 정보 ( 이름, 핸드폰 번호, 이메일 ) Returns: 200 : success, 셀러 데이터 업데이트 성공 시 400 : 담당자 정보 안에 이름, 핸드폰 번호, 이메일 중 하나라도 없을 때, 담당자 이메일 형식 맞지 않을 때, 담당자 핸드폰 번호 형식 맞지 않을 때 500 : Exception """ # 입력한 셀러 정보 받아서 데이터베이스에 넣기 session = None try: session = get_session() seller = { 'image': args[0], 'simple_introduce': args[1], 'brand_crm_number': args[2], 'zip_code': args[3], 'address': args[4], 'detail_address': args[5], 'brand_crm_open': args[6], 'brand_crm_end': args[7], 'delivery_information': args[8], 'refund_exchange_information': args[9], 'seller_status_id': args[10], 'background_image': args[11], 'detail_introduce': args[12], 'is_brand_crm_holiday': args[13], 'brand_name_korean': args[14], 'brand_name_english': args[15], 'manager_information': args[16], 'id': g.seller_id } for manager in seller['manager_information']: # 담당자 정보 안에 이름, 번호, 이메일이 없으면 에러 발생 if manager['name'] is None or manager['phone_number'] is None or manager['email'] is None: return jsonify({'message': 'no manager information data'}), 400 # 이메일 형식이 맞지 않을 때 에러 발생 if re.match(r'^([0-9a-zA-Z_-]+)@([0-9a-zA-Z_-]+)\.([0-9a-zA-Z_-]+)$', manager['email']) is None: return jsonify({'message': 'invalid email'}), 400 # 핸드폰 번호 형식이 맞지 않을 때 에러 발생 if re.match(r'^010-[0-9]{3,4}-[0-9]{4}$', manager['phone_number']) is None: return jsonify({'message': 'invalid phone number'}), 400 seller_service.post_my_page(seller, session) session.commit() return jsonify({'message': 'success'}), 200 except NoAffectedRowException as e: session.rollback() return jsonify({'message': 'no affected row error {}'.format(e.message)}), e.status_code except Exception as e: session.rollback() return jsonify({'message': '{}'.format(e)}), 500 finally: if session: session.close() @app.route("/master/management-seller", methods=['GET']) @login_required @validate_params( Param('limit', GET, int, required=False), Param('offset', GET, int, required=False), Param('number', GET, int, required=False), Param('account', GET, str, required=False), Param('brand_name_korean', GET, str, required=False), Param('brand_name_english', GET, str, required=False), Param('manager_name', GET, str, required=False), Param('manager_number', GET, str, rules=[Pattern(r'^010-[0-9]{3,4}-[0-9]{4}$')], required=False), Param('manager_email', GET, str, rules=[Pattern(r'^([0-9a-zA-Z_-]+)@([0-9a-zA-Z_-]+)\.([0-9a-zA-Z_-]+)$')], required=False), Param('status_id', GET, int, required=False), Param('property_id', GET, int, required=False), Param('start_date', GET, str, required=False), Param('end_date', GET, str, required=False) ) def get_management_seller(*args): """ 셀러 계정 관리 ( 마스터 ) API 쿼리 파라미터로 필터링 될 값을 받아서 필터링 한 후 리스트를 보내줌 Args: *args: limit : pagination 범위 offset : pagination 시작 번호 number : 셀러의 id account : 셀러의 계정 brand_name_korean : 브랜드명 ( 한글 ) brand_name_english : 브랜드명 ( 영어 ) manager_name : 담당자명 manager_number : 담당자 핸드폰 번호 manager_email : 담당자 이메일 status_id : 셀러의 상태 id ( 입점, 입점대기 등 ) property_id : 셀러의 속성 id ( 로드샵, 마켓 등 ) start_date : 해당 날짜 이후로 등록한 셀러 검색 end_date : 해당 날짜 이전에 등록한 셀러 검색 Returns: 200 : seller_list ( type : dict ) 400 : 마스터 계정이 아닌 경우 500 : Exception """ session = None try: session = get_session() # 쿼리스트링을 딕셔너리로 만들어 줌 query_string_list = { 'limit': 10 if args[0] is None else args[0], 'offset': 0 if args[1] is None else args[1], 'number': args[2], 'account': args[3], 'brand_name_korean': args[4], 'brand_name_english': args[5], 'manager_name': args[6], 'manager_number': args[7], 'email': args[8], 'seller_status_id': args[9], 'seller_property_id': args[10], 'start_date': args[11], 'end_date': args[12] } seller_list = seller_service.get_seller_list(query_string_list, g.seller_id, session) # 마스터 계정이 아닐 때 에러 발생 if seller_list == 'not authorized': return jsonify({'message': 'no master'}), 400 return jsonify(seller_list) except NoDataException as e: session.rollback() return jsonify({'message': 'no data {}'.format(e.message)}), e.status_code except Exception as e: session.rollback() return jsonify({'message': '{}'.format(e)}), 500 finally: if session: session.close() @app.route("/master/management-seller", methods=['PUT']) @login_required @validate_params( Param('seller_id', JSON, int), Param('button', JSON, int) ) def put_management_seller(seller_id, button): """ 마스터 셀러계정관리 API 마스터가 버튼을 눌러 셀러의 상태를 변경함 셀러의 상태가 변경될 때마다 슬랙 채널로 "(seller_id)번 셀러의 상태가 (status)로 변경되었습니다" 라는 메세지 전송 Args: seller_id: 셀러의 id button: 셀러의 상태 변경 버튼 ( 입점으로 변경, 휴점으로 변경 등 ) Returns: 200 : success, 셀러의 상태가 정상적으로 변경되었을 때 400 : 눌린 버튼과 셀러의 상태가 맞지 않을 때, 슬랙봇 메세지가 전송 실패되었을 때 500 : Exception """ session = None try: session = get_session() status = seller_service.post_seller_status(seller_id, button, session) # 버튼과 버튼이 눌리는 셀러의 속성이 맞지 않을 때 if status == 'invalid request': return jsonify({'message': 'invalid request'}), 400 # 슬랙봇 메세지가 전송실패되었을 때 if status == 'message fail': return jsonify({'message': 'message failed'}), 400 session.commit() return jsonify({'message': 'success'}), 200 except NoAffectedRowException as e: session.rollback() return jsonify({'message': 'no affected row error {}'.format(e.message)}), e.status_code except NoDataException as e: session.rollback() return jsonify({'message': 'no data {}'.format(e.message)}), e.status_code except Exception as e: session.rollback() return jsonify({'message': '{}'.format(e)}), 500 finally: if session: session.close() @app.route("/home", methods=['GET']) @login_required def get_home_seller(): """ 홈 API 로그인했을 때 나오는 홈 화면의 데이터 보내주기 Returns: 200 : data ( type : dict ) 500 : Exception """ session = None try: session = get_session() data = seller_service.get_home_data(session) return jsonify(data), 200 except Exception as e: session.rollback() return jsonify({'message': '{}'.format(e)}), 500 finally: if session: session.close() @app.route("/master/management-seller/<int:seller_id>", methods=['GET']) @login_required @validate_params( Param('seller_id', PATH, int) ) def get_master_seller_page(seller_id): """ 셀러계정관리 ( 마스터 ) 마스터가 셀러의 데이터 가져오기 Args: seller_id: 셀러 id Returns: 200 : seller_data ( type : dict ) 400 : 마스터 계정이 아닐 때 500 : Exception """ session = None try: session = get_session() seller_data = seller_service.get_seller_page(seller_id, session) # 마스터 계정이 아닐 때 에러 발생 if seller_data == 'not authorized': return jsonify({'message': 'not authorized'}), 400 return jsonify(seller_data) except NoDataException as e: session.rollback() return jsonify({'message': 'no data {}'.format(e.message)}), e.status_code except Exception as e: session.rollback() return jsonify({'message': '{}'.format(e)}), 500 finally: if session: session.close() @app.route("/master/management-seller/<int:seller_id>", methods=['PUT']) @login_required @validate_params( Param('seller_id', PATH, int), Param('image', JSON, str), Param('simple_introduce', JSON, str), Param('brand_crm_number', JSON, str, rules=[Pattern(r'^[0-9]{2,3}-[0-9]{3,4}-[0-9]{4}$')]), Param('zip_code', JSON, int), Param('address', JSON, str), Param('detail_address', JSON, str), Param('brand_crm_open', JSON, str), Param('brand_crm_end', JSON, str), Param('delivery_information', JSON, str), Param('refund_exchange_information', JSON, str), Param('seller_status_id', JSON, int), Param('background_image', JSON, str, required=False), Param('detail_introduce', JSON, str, required=False), Param('is_brand_crm_holiday', JSON, int, rules=[Enum(0, 1)]), Param('brand_name_korean', JSON, str), Param('brand_name_english', JSON, str), Param('manager_information', JSON, list), Param('seller_property_id', JSON, int) ) def put_master_seller_page(*args): """ 셀러 계정 관리 ( 마스터 ) Path parameter 로 셀러의 아이디를 받고 Body 로 셀러의 수정 데이터 받아서 수정하기 Args: *args: seller_id : 셀러 id image : 셀러의 프로필 이미지 simple_introduce : 셀러 한줄 소개 brand_crm_number : 고객센터 전화번호 zip_code : 우편번호 address : 주소 detail_address : 상세주소 brand_crm_open : 고객센터 오픈시간 brand_crm_end : 고객센터 마감시간 delivery_information : 배송 정보 refund_exchange_information : 교환/환불 정보 seller_status_id : 셀러 상태 id ( 입점, 입점 대기 등 ) background_image : 셀러 배경 이미지 detail_introduce : 셀러 상세 정보 is_brand_crm_holiday : 고객센터 휴일/공휴일 영업 여부 brand_name_korean : 브랜드명 ( 한글 ) brand_name_english : 브랜드명 ( 영어 ) manager_information : 담당자 정보 ( 이름, 핸드폰 번호, 이메일 ) seller_property_id : 셀러 속성 id ( 마켓, 로드샵 등 ) Returns: 200 : success, 데이터 수정하기 성공했을 때 400 : 담당자 정보에 이름, 핸드폰 번호, 이메일 중 하나라도 없을 때 , 이메일 형식이 맞지 않을 때, 핸드폰번호 형식이 맞지 않을 때 500 : Exception """ session = None try: session = get_session() # 셀러 데이터 받아서 딕셔너리로 만들기 seller = { 'id': args[0], 'image': args[1], 'simple_introduce': args[2], 'brand_crm_number': args[3], 'zip_code': args[4], 'address': args[5], 'detail_address': args[6], 'brand_crm_open': args[7], 'brand_crm_end': args[8], 'delivery_information': args[9], 'refund_exchange_information': args[10], 'seller_status_id': args[11], 'background_image': args[12], 'detail_introduce': args[13], 'is_brand_crm_holiday': args[14], 'brand_name_korean': args[15], 'brand_name_english': args[16], 'manager_information': args[17], 'seller_property_id': args[18] } # 담당자 정보 안에 이름, 번호, 이메일이 없으면 에러 발생 for manager in seller['manager_information']: if manager['name'] is None or manager['phone_number'] is None or manager['email'] is None: return jsonify({'message': 'no manager information data'}), 400 # 이메일 형식이 맞지 않을 때 에러 발생 if re.match(r'^([0-9a-zA-Z_-]+)@([0-9a-zA-Z_-]+)\.([0-9a-zA-Z_-]+)$', manager['email']) is None: return jsonify({'message': 'invalid email'}), 400 # 핸드폰 번호 형식이 맞지 않을 때 에러 발생 if re.match(r'^010-[0-9]{3,4}-[0-9]{4}$', manager['phone_number']) is None: return jsonify({'message': 'invalid phone number'}), 400 seller_service.put_master_seller_page(seller, session) session.commit() return jsonify({'message': 'success'}), 200 except NoAffectedRowException as e: session.rollback() return jsonify({'message': 'no affected row error {}'.format(e.message)}), e.status_code except Exception as e: session.rollback() return jsonify({'message': '{}'.format(e)}), 500 finally: if session: session.close()
class TestJsonParam(unittest.TestCase): LIST_SCHEMA = JsonParam( { 'person': JsonParam( { 'info': JsonParam({ 'contacts': JsonParam({ 'phones': JsonParam([Enum('+375', '+49')], as_list=True), 'networks': JsonParam( {'name': [Enum('facebook', 'telegram')]}, as_list=True, ), 'emails': JsonParam([IsEmail()], as_list=True), 'addresses': JsonParam({'street': []}, required=False), }), }), }, ), }, ) DICT_SCHEMA = JsonParam( { 'street': CompositeRule(Enum('Jakuba Kolasa')), 'meta': JsonParam( { 'description': JsonParam({ 'color': [Enum('green', 'yellow', 'blue')], }, ), 'buildings': JsonParam({ 'warehouses': JsonParam({ 'small': JsonParam({ 'count': CompositeRule(Min(0), Max(99)), }), 'large': [Min(1), Max(10)] }), }), 'not_required': JsonParam({'text': []}, required=False), }, ), }, ) @parameterized.expand([ # invalid ( DICT_SCHEMA, { 'street': 'Rampische', 'meta': { 'buildings': { 'warehouses': { 'small': { 'count': 100, }, 'large': 0, }, }, }, }, [ [ ['root', 'meta', 'buildings', 'warehouses', 'small'], { 'count': [ValueMaxError] }, ], [ ['root', 'meta', 'buildings', 'warehouses'], { 'large': [ValueMinError] }, ], [ ['root', 'meta'], { 'description': RequiredJsonKeyError }, ], [ ['root'], { 'street': [ValueEnumError], }, ], ], ), # valid ( DICT_SCHEMA, { 'country': 'Belarus', 'city': 'Minsk', 'street': 'Jakuba Kolasa', 'meta': { 'buildings': { 'warehouses': { 'small': { 'count': 99, }, 'large': 1, }, }, 'description': { 'color': 'green', }, }, }, {}, ) ]) def test_dict(self, param: JsonParam, data, exp): value, errors = param.validate(data) for ix, json_error in enumerate(errors): # type: list, JsonError self.assertTrue(isinstance(json_error, JsonError)) exp_depth, epx_errors_map = exp[ix] # type: list, dict self.assertListEqual(json_error.depth, exp_depth) for key, error in json_error.errors.items(): if isinstance(error, RulesError): self.assertEqual(len(error.errors), len(epx_errors_map)) for ix_rule, rule_err in enumerate(error.errors): self.assertTrue( isinstance(rule_err, epx_errors_map[key][0])) else: self.assertTrue(isinstance(error, epx_errors_map[key])) @parameterized.expand([ # invalid ( LIST_SCHEMA, { 'person': { 'info': { 'contacts': { 'phones': [ '+375', '+49', { 'code': '+420' }, { 'code': '+10000' } ], 'emails': [{ 'work': 'bad_type1' }, { 'work': 'bad_type2' }, 'bad_mail'], 'networks': [ { 'name': 'facebook' }, { 'name': 'insta' }, { 'name': 'linkedin' }, ], }, }, }, }, [ [ ['root', 'person', 'info', 'contacts', 'phones'], { 2: JsonListItemTypeError, 3: JsonListItemTypeError, }, ], [ ['root', 'person', 'info', 'contacts', 'networks'], { 1: { 'name': [ValueEnumError], }, 2: { 'name': [ValueEnumError], }, }, ], [ ['root', 'person', 'info', 'contacts', 'emails'], { 0: JsonListItemTypeError, 1: JsonListItemTypeError, 2: [ValueEmailError], }, ], ], ), # valid ( LIST_SCHEMA, { 'person': { 'info': { 'contacts': { 'phones': ['+375', '+49'], 'networks': [ { 'name': 'facebook' }, { 'name': 'telegram' }, { 'name': 'telegram' }, { 'name': 'facebook' }, ], 'emails': ['*****@*****.**'], }, }, }, }, [], ), ]) def test_list(self, param: JsonParam, data, exp): value, errors = param.validate(data) self.assertEqual(len(exp), len(errors)) for err_ix, json_er in enumerate(errors): # type: int, JsonError exp_err = exp[err_ix] exp_rule_err = exp_err[1] self.assertListEqual(json_er.depth, exp_err[0]) self.assertEqual(len(exp_err[1]), len(json_er.errors)) for rules_ix, rules_err in exp_rule_err.items(): json_rules = json_er.errors[ rules_ix] # type: dict or list or RulesError if isinstance(exp_rule_err[rules_ix], list): self.assertTrue(isinstance(json_rules, RulesError)) for k, rule_err in enumerate(json_rules.errors): self.assertTrue( isinstance(rule_err, exp_rule_err[rules_ix][k])) else: if isinstance(rules_err, dict): self.assertTrue(len(json_rules), len(rules_err)) else: # RulesError self.assertTrue( isinstance(json_er.errors[rules_ix], rules_err))
class ProductView: product_app = Blueprint('product_app', __name__, url_prefix='/products') @product_app.route('', methods=['GET']) @login_validator @validate_params( Param('started_date', GET, str, required=False, rules=[ Pattern( r"^\d\d\d\d-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])$") ]), Param('ended_date', GET, str, required=False, rules=[ Pattern( r"^\d\d\d\d-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])$") ]), Param('seller_name', GET, str, required=False), Param('product_name', GET, str, required=False), Param('product_number', GET, str, required=False), Param('product_code', GET, str, required=False), Param('seller_subcategory_id', GET, list, required=False), Param('is_selling', GET, bool, rules=[Enum(0, 1)], required=False), Param('is_visible', GET, bool, rules=[Enum(0, 1)], required=False), Param('is_discount', GET, bool, rules=[Enum(0, 1)], required=False), Param('limit', GET, int, rules=[Enum(10, 20, 50)], required=False), Param('page', GET, int, required=False), ) def get_product_list(*args): connection = None """상품 리스트 엔드포인트 상품 관리 페이지에서 필터링된 상품 리스트를 표출 쿼리 파라미터로 필터링에 사용할 파라미터 값을 받음 Return: 200: 상품 리스트 403: NO_AUTHORIZATION 500: NO_DATABASE_CONNECTION, DB_CURSOR_ERROR NO_DATABASE_CONNECTION History: 2020-11-28 : 초기 생성 2020-11-19 : pagination 수정 수정할 사항 @login_validator로 g.account_info 받을 예정 g.account_info ={ 'account_id' : , 'account_type_id' : , 'seller_id' : id or None } """ #유효성 검사 완료한 쿼리 값 저장 filter_data = { 'started_date': args[0], 'ended_date': args[1], 'seller_name': args[2], 'product_name': args[3], 'product_number': args[4], 'product_code': args[5], 'seller_subcategory_id': args[6], 'is_selling': args[7], 'is_visible': args[8], 'is_discount': args[9], 'limit': args[10], 'page': args[11], 'account_type_id': g.token_info['account_type_id'] } try: connection = connect_db() if connection: product_service = ProductService() products = product_service.get_product_list( filter_data, connection) return jsonify(products), 200 else: return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500 except Exception as e: return jsonify({'message': f'{e}'}), 400 finally: try: connection.close() except Exception as e: return jsonify({'message': f'{e}'}), 500 @product_app.route('/category', methods=['GET']) @login_validator @validate_params(Param('seller_name', GET, str, required=False)) def get_main_category(*args): """ 상품 분류별 main 카테고리 표출 엔드포인트 - master이면 seller검색 - seller이면 validator 본인 id Return: 200: 셀러가 속한 상품 분류에 따른 1차 카테고리 이름과 id { "main_category_id": 8, "main_category_name": "주얼리"} 400: 데이터베이스 연결 에러 500: server error""" connection = None # 마스터인데 셀러이름 검색 안한 경우(커넥션 열기전에 처리해줌) if g.token_info['account_type_id'] == 1 and args[0] is None: return jsonify({'message': 'SELLER_CATEGORY_SEARCHING_ERROR'}), 400 try: connection = connect_db() if connection: filter_data = { 'account_type_id': g.token_info['account_type_id'], 'seller_name': args[0] } product_service = ProductService() main_categories_data = product_service.product_main_category( filter_data, connection) except Exception as e: return jsonify({'message': f'{e}'}), 400 else: return jsonify(main_categories_data), 200 finally: try: connection.close() except Exception as e: return jsonify({'message': f'{e}'}), 500 @product_app.route('/category/<int:main_category_id>', methods=['GET']) @login_validator @validate_params( Param('main_category_id', PATH, int), #유효한 카테고리 범위 벗어날 시 에러 반환 Param('main_category_id', PATH, str, rules=[Pattern(r'^([0-9]|[0-3][0-9])$')]), ) def get_sub_category(*args): """ 상품 분류별 sub 카테고리 목록 표출 엔드포인트 Args: *args: main_category_id(int): 1차 카테고리 인덱스 번호 """ connection = None #쿼리 스트링에 값이 없으면 에러 반환 if args[0] is None: return jsonify({'message': 'MAIN_CATEGORY_ID_ERROR'}), 400 try: connection = connect_db() if connection: filter_data = {'main_category_id': args[0]} product_service = ProductService() sub_categories = product_service.product_sub_category( filter_data, connection) return jsonify({'data': sub_categories}), 200 else: return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 400 except Exception as e: return jsonify({'message': f'{e}'}), 500 finally: try: connection.close() except Exception as e: return jsonify({'message': f'{e}'}), 500 @product_app.route('/register', methods=['GET']) def get_option_list(*args): connection = None try: connection = connect_db() if connection: product_service = ProductService() options = product_service.get_options(connection) return options else: return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500 except Exception as e: return jsonify({'message': f'{e}'}), 500 finally: try: connection.close() except Exception as e: return jsonify({'message': f'{e}'}), 500 @product_app.route('/register', methods=['POST']) @login_validator @validate_params( Param('is_selling', FORM, int, rules=[Enum(0, 1)]), Param('is_visible', FORM, int, rules=[Enum(0, 1)]), Param('sub_category_id', FORM, int, required=False), Param('product_name', FORM, str, rules=[Pattern(r"[^\"\']")]), Param('is_information_notice', FORM, int, rules=[Enum(0, 1)]), Param('manufacturer', FORM, str, required=False), Param('manufacture_date', FORM, str, required=False, rules=[ Pattern( r"^\d\d\d\d-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])$") ]), Param('made_in', FORM, str, required=False), Param('short_description', FORM, str, required=False), Param('is_inventory_management', FORM, int, rules=[Enum(0, 1)]), Param('inventory', FORM, int, required=False), Param('price', FORM, int), Param('discount_rate', FORM, float, required=False), Param('is_discount_period', FORM, int, rules=[Enum(0, 1)]), Param('discount_start_time', FORM, str, required=False), Param('discount_end_time', FORM, str, required=False), Param('min_order', FORM, int), Param('max_order', FORM, int), Param('seller_id', FORM, int, required=False), # integer parameter 범위 지정을 위한 검증 Param('is_selling', FORM, str, rules=[Pattern(r'^([0-1])$')]), Param('is_visible', FORM, str, rules=[Pattern(r'^([0-1])$')]), Param('sub_category_id', FORM, str, rules=[Pattern(r'^([0-9]|[0-9][0-9]|[1][0][0-9]|[1][1][0-4])$') ]), Param('max_order', FORM, str, rules=[Pattern(r'^([1-9]|[1-2][0-9])$')]), Param('min_order', FORM, str, rules=[Pattern(r'^([1-9]|[1-2][0-9])$')])) def create_product(*args): connection = None # min_order, max_order 예외처리 if args[16] > 20 or args[17] > 20: return jsonify({'message': 'ORDER_VALUE_ERROR'}), 400 filter_data = { 'editor_id': g.token_info['account_id'], 'is_selling': args[0], 'is_visible': args[1], 'sub_category_id': args[2], 'product_name': args[3], 'is_information_notice': args[4], 'manufacturer': args[5], 'manufacture_date': args[6], 'made_in': args[7], 'short_description': args[8], 'is_inventory_management': args[9], 'inventory': args[10], 'price': args[11], 'discount_rate': args[12], 'is_discount_period': args[13], 'discount_start_datetime': args[14], 'discount_end_datetime': args[15], 'min_order': args[16], 'max_order': args[17] } if args[18]: filter_data['seller_id'] = args[18] else: filter_data['seller_id'] = g.token_info['seller_id'] print(filter_data['seller_id']) try: connection = connect_db() if connection: # option_list = '[{"name":"bb","age":29},{"name":"hh","age":20}]' options = request.form.get('option_list') option_list = json.loads(options) #image 저장을 위한 S3 connection instance 생성 """ images : File Request(List) [ { 'product_image_<int>' : <FileStorage: {filename} ({content_type})>} ] """ images = request.files image_bucket_dir = datetime.datetime.now().strftime('%Y-%m-%d') desc_image = request.files.get('desc_image') desc_image_url = Image_uploader.upload_desc_images( desc_image, image_bucket_dir) filter_data['desc_img_url'] = desc_image_url product_service = ProductService() create_info = product_service.create_product( filter_data, option_list, connection) product_id = create_info['product_id'] editor_id = filter_data['editor_id'] insert_count = create_info['create_count'] #상품 이미지 URL화, S3에 올리기 product_images = Image_uploader.upload_product_images( images, image_bucket_dir) #상품 이미지를 DB에 Insert하는 함수 실행 product_service.upload_product_image(product_images, product_id, editor_id, connection) connection.commit() return jsonify( {'message': f'{insert_count}products are created'}), 201 else: return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500 except KeyError: connection.rollback() return jsonify({'message': 'KEY_ERROR'}), 400 except Exception as e: connection.rollback() # raise e return jsonify({'message': f'{e}'}), 500 finally: try: connection.close() except Exception as e: return jsonify({'message': f'{e}'}), 500 @product_app.route('/download', methods=['GET']) @login_validator @validate_params( Param('started_date', GET, str, required=False, rules=[ Pattern( r"^\d\d\d\d-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])$") ]), Param('ended_date', GET, str, required=False, rules=[ Pattern( r"^\d\d\d\d-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])$") ]), Param('seller_name', GET, str, required=False), Param('product_name', GET, str, required=False), Param('product_number', GET, str, required=False), Param('product_code', GET, str, required=False), Param('seller_subcategory_id', GET, list, required=False), Param('is_selling', GET, bool, rules=[Enum(0, 1)], required=False), Param('is_visible', GET, bool, rules=[Enum(0, 1)], required=False), Param('is_discount', GET, bool, rules=[Enum(0, 1)], required=False), Param('limit', GET, int, required=False), Param('page', GET, int, required=False)) def product_list_excel(*args): connection = None filter_data = { 'started_date': args[0], 'ended_date': args[1], 'seller_name': args[2], 'product_name': args[3], 'product_number': args[4], 'product_code': args[5], 'seller_subcategory_id': args[6], 'is_selling': args[7], 'is_visible': args[8], 'is_discount': args[9], 'account_type_id': g.token_info['account_type_id'], 'account_id': g.token_info['account_id'] } try: connection = connect_db() if connection: product_service = ProductService() product_service.product_excel(filter_data, connection) return jsonify({'message': 'SUCCESS'}), 201 else: return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500 except Exception as e: return jsonify({'message': f'{e}'}), 400 finally: try: connection.close() except Exception as e: return jsonify({'message': f'{e}'}), 500
def create_product_endpoints(product_service): product_app = Blueprint('products', __name__, url_prefix='/products') @product_app.route('/product_list', methods=['GET']) #@login_required @validate_params( Param('registerStatus', GET, bool, required=False), Param('filterDateFrom', GET, str, rules=[Pattern(r"^\d\d\d\d-\d{1,2}-\d{1,2}$")], required=False), Param('filterDAteTo', GET, str, rules=[Pattern(r"^\d\d\d\d-\d{1,2}-\d{1,2}$")], required=False), Param('sellerName', GET, required=False), Param('productName', GET, required=False), Param('product_id', GET, list, required=False), Param('productCode', GET, required=False), Param('sellerType', GET, list, required=False), #str이 아니라 int Param('salesStatus', GET, int, rules=[Enum(1, 2, 3)], required=False), Param('displayStatus', GET, int, rules=[Enum(1, 2, 3)], required=False), Param('discountStatus', GET, int, rules=[Enum(1, 2, 3)], required=False), Param('offset', GET, int, required=False), Param('limit', GET, int, required=False)) def get_products(*args): """상품 정보 리스트 전달 API 쿼리 파라미터로 필터링에 사용될 값을 받아 필터링된 상품의 데이터 리스트를 보내줍니다. args: *args: filterDateFrom : 조회기간 시작 filterDateTo : 조회기간 끝 sellerName : 셀러 이름 검색을 위한 파라미터 productName : SelectFilter 안에 있는 목록 중 상품이름 productNo : SelectFilter 안에 있는 목록 중 상품번호 productCode : SelectFilter 안에 있는 목록 중 상품코드 sellerType : 셀러속성 id salesStatus : 판매여부 displayStatus : 진열여부 discountStatus : 할인여부 } returns : 200: 상품리스트 500: Exception Author: 김성진 History: 2020-11-01 (김성진): 초기 생성 """ try: if args[1]: filterDateFrom = datetime.datetime.strptime( args[1], '%Y-%m-%d') else: filterDateFrom = "" if args[2]: filterDateTo = datetime.datetime.strptime(args[2], '%Y-%m-%d') else: filterDateTo = "" db_connection = get_connection() filter_dict = { 'registerStatus': args[0], 'filterDateFrom': args[1], #조회기간 시작 'filterDateTo': args[2], #조회기간 끝 'sellerName': args[3], #셀러명 'productName': args[4], #상품이름 'product_id': args[ 5], #상품번호 'productCode': args[6], #상품코드 'sellerType': args[7], #셀러속성 'salesStatus': args[8], #판매여부 'displayStatus': args[9], #진열여부 'discountStatus': args[10], #할인여부 'offset': 0 if args[11] is None else args[11], #데이터를 어디서부터 받아올것인지 'limit': 10 if args[12] is None else args[12] #페이지의 숫자 } total_number = product_service.get_total_number( filter_dict, db_connection) products = product_service.get_products(filter_dict, db_connection) return jsonify({ 'products': products, 'total_number': total_number }) except InvalidProductInformationError as e: db_connection.rollback() message = internal_code_sheet[e.code] return jsonify(message), (message['code']) except InvalidSellerInformationError as e: db_connection.rollback() message = internal_code_sheet[e.code] return jsonify(message), (message['code']) finally: db_connection.close() @product_app.route('/excel', methods=['GET']) #@login_required @validate_params( Param('product_id', GET, list, required=False), ) def make_excel(*args): """ 상품 정보 엑셀 다운로드 API 전체 상품 또는 선택 상품의 정보를 excel 파일로 다운로드 합니다. args: product_id : 상품의 id 리스트 returns: 200: Excel 파일 다운 500: Exception Author: 김성진 History: 2020-11-04 (김성진): 초기 생성 """ db_connection = get_connection() try: # 선택한 상품들의 id를 list로 받는다. product_id_list = { 'product_id': args[0], } excels = product_service.create_excel(product_id_list, db_connection) return jsonify(excels) except ProgrammingError as e: db_connection.rollback() message = internal_code_sheet[e.code] return jsonify(message), (message['code']) finally: db_connection.close() @product_app.route('/modify', methods=['POST']) #@login_required @validate_params( Param('salesStatusModify', GET, int, rules=[Enum(1, 2, 3)], required=False), #안에 NULL=True 해도 되나? Param('displayStatusModify', GET, int, rules=[Enum(1, 2, 3)], required=False), Param('product_id', GET, list, required=False)) def status_update(*args): """ 상품의 판매여부 및 진열여부 수정 API 선택한 상품의 판매여부 및 진열여부 수정이 있을시 수정. args: product_id: 상품의 id 리스트 returns: 200: 지정된 상품의 판매 및 진열여부 수정 500: Exception Author: 김성진 History: 2020-11-04 (김성진): 초기 생성 """ try: db_connection = get_connection() product_id_list = { 'salesStatusModify': args[0], 'displayStatusModify': args[1], 'product_id': args[2] } fixed_products = product_service.product_status_change( product_id_list, db_connection) db_connection.commit() except InvalidChoiceMade as e: db_connection.rollback() message = internal_code_sheet[e.code] return jsonify(message), (message['code']) else: return jsonify(fixed_products) finally: db_connection.close() return product_app
class CreateProductView(MethodView): """ Presentation Layer Attributes: service : CreateProductService 클래스 database : app.config['DB']에 담겨있는 정보(데이터베이스 관련 정보) Author: 심원두 History: 2020-12-29(심원두): 초기 생성. products insert, product_code updated, product_histories 생성 기능 작성 2020-12-30(심원두): 각 Param rules 추가, stock insert 기능 작성. 2020-01-03(심원두): 상품 등록 Param rules 추가 """ def __init__(self, service, database): self.service = service self.database = database @signin_decorator() @validate_params( Param('seller_name', GET, str, required=False, rules=[MaxLength(20)]), Param('main_category_id', GET, str, required=False, rules=[NumberRule()])) def get(self, *args): """POST 메소드: 상품 정보 등록 초기 화면 Args: 'seller_name' : 사용자가 입력한 셀러명 'main_category_id' : 사용자가 선택한 메인 카테고리 아이디 Author: 심원두 Returns: return {"message": "success", "result": [{}]} Raises: 400, {'message': 'key error', 'errorMessage': 'key_error' + format(e)}: 잘못 입력된 키값 500, {'message': 'fail to get sub category list', 'errorMessage': 'fail_to_get_sub_category_list'}: 색상 정보 취득 실패 500, {'message': 'fail to get product origin types', 'errorMessage': 'fail_to_get_product_origin_types'} : 원산지 정보 취득 실패 500, {'message': 'fail to get color list', 'errorMessage': 'fail_to_get_color_list'}: 색상 정보 취득 실패 500, {'message': 'fail to get color list', 'errorMessage': 'fail_to_get_color_list'}: 색상 정보 취득 실패 History: 2020-12-30(심원두): 초기생성 2021-01-06(심원두): 로그인 데코레이터 처리 추가. 관리자일 경우에만 셀러 검색 허용하도록 수정 """ try: data = { 'seller_name': request.args.get('seller_name', None), 'main_category_id': request.args.get('main_category_id', None) } connection = get_connection(self.database) if data['seller_name'] and g.permission_type_id == 1: sellers = self.service.search_seller_list_service( connection, data) return jsonify({'message': 'success', 'result': sellers}) if data['main_category_id']: sub_categories = self.service.get_sub_category_list_service( connection, data) return jsonify({ 'message': 'success', 'result': sub_categories }) result = dict() result['product_origin_types'] = \ self.service.get_product_origin_types_service( connection ) result['color_list'] = \ self.service.get_color_list_service( connection ) result['size_list'] = \ self.service.get_size_list_service( connection ) return jsonify({'message': 'success', 'result': result}) except KeyError as e: traceback.print_exc() raise e except Exception as e: traceback.print_exc() raise e finally: try: if connection: connection.close() except Exception: raise DatabaseCloseFail('database close fail') @signin_decorator() @validate_params( Param('seller_id', FORM, str, required=True, rules=[NumberRule()]), Param('is_sale', FORM, int, required=True, rules=[Enum(0, 1)]), Param('is_display', FORM, int, required=True, rules=[Enum(0, 1)]), Param('main_category_id', FORM, str, required=True, rules=[NumberRule()]), Param('sub_category_id', FORM, str, required=True, rules=[NumberRule()]), Param('is_product_notice', FORM, int, required=True, rules=[Enum(0, 1)]), Param('manufacturer', FORM, str, required=False, rules=[MaxLength(30)]), Param('manufacturing_date', FORM, str, required=False), Param('product_origin_type_id', FORM, str, required=False), Param('product_name', FORM, str, required=True, rules=[NotEmpty(), MaxLength(100)]), Param('description', FORM, str, required=False, rules=[MaxLength(200)]), Param('detail_information', FORM, str, required=True, rules=[NotEmpty()]), Param('options', FORM, list, required=True), Param('minimum_quantity', FORM, str, required=False, rules=[NumberRule()]), Param('maximum_quantity', FORM, str, required=False, rules=[NumberRule()]), Param('origin_price', FORM, str, required=True, rules=[NumberRule()]), Param('discount_rate', FORM, str, required=True, rules=[NumberRule()]), Param('discounted_price', FORM, str, required=True, rules=[NumberRule()]), Param('discount_start_date', FORM, str, required=False), Param('discount_end_date', FORM, str, required=False)) def post(self, *args): """ POST 메소드: 상품 정보 등록 Args: - 사용자 입력 값(상품 이미지 최대 5개) : image_files - 사용자 입력 값(옵션 정보 리스트) : options - 사용자 입력 값 Form-Data: ( 'seller_id' 'account_id', 'is_sale', 'is_display', 'main_category_id', 'sub_category_id', 'is_product_notice', 'manufacturer', 'manufacturing_date', 'product_origin_type_id', 'product_name', 'description', 'detail_information', 'options', 'minimum_quantity', 'maximum_quantity', 'origin_price', 'discount_rate', 'discounted_price', 'discount_start_date', 'discount_end_date', ) Author: 심원두 Returns: 200, {'message': 'success'} : 상품 정보 등록 성공 Raises: 400, {'message': 'key_error', 'errorMessage': 'key_error_' + format(e)} : 잘못 입력된 키값 400, {'message': 'required field is blank', 'errorMessage': 'required_manufacture_information'} : 제조 정보 필드 없음 400, {'message': 'required field is blank', 'errorMessage': 'required_discount_start_or_end_date'} : 필수 입력 항목 없음 400, {'message': 'compare quantity field check error', 'errorMessage': 'minimum_quantity_cannot_greater_than_maximum_quantity'}: 최소 구매 수량이 최대 보다 큼 400, {'message': 'compare price field check error', 'errorMessage': 'discounted_price_cannot_greater_than_origin_price'} : 할인가가 판매가 보다 큼 400, {'message': 'compare price field check error', 'errorMessage': 'wrong_discounted_price'} : 판매가와 할인가 일치하지 않음 400, {'message': 'compare price field check error', 'errorMessage': 'required_discount_start_or_end_date'} : 할인 시작, 종료 일자 필드 없음 400, {'message': 'start date is greater than end date', 'errorMessage': 'start_date_cannot_greater_than_end_date'} : 할인 시작일이 종료일 보다 큼 400, {'message': 'compare price field check error', 'errorMessage': 'discounted_price_have_to_same_with_origin_price'} : 할인가, 판매가 불일치(할인율 0) 413, {'message': 'invalid file', 'errorMessage': 'invalid_file'} : 파일 이름이 공백, 혹은 파일을 정상적으로 받지 못함 413, {'message': 'file size too large', 'errorMessage': 'file_size_too_large'} : 파일 사이즈 정책 위반 (4메가 이상인 경우) 413, {'message': 'file scale too small, 640 * 720 at least', 'errorMessage': 'file_scale_at_least_640*720'} : 파일 스케일 정책 위반 (680*720 미만인 경우) 413, {'message': 'only allowed jpg type', 'errorMessage': 'only_allowed_jpg_type'} : 파일 확장자 정책 위반 (JPG, JPEG 아닌 경우) 500, {'message': 'image_file_upload_to_amazon_fail', 'errorMessage': 'image_file_upload_fail'} : 이미지 업로드 실패 500, {'message': 'product create denied', 'errorMessage': 'unable_to_create_product'} : 상품 정보 등록 실패 500, {'message': 'product code update denied', 'errorMessage': 'unable_to_update_product_code'} : 상품 코드 갱신 실패 500, {'message': 'product code update denied', 'errorMessage': 'unable_to_update_product_code'} : 상품 코드 갱신 실패 500, {'message': 'product image create denied', 'errorMessage': 'unable_to_create_product_image'} : 상품 이미지 등록 실패 500, {'message': 'stock create denied', 'errorMessage': 'unable_to_create_stocks'} : 상품 옵션 정보 등록 실패 500, {'message': 'product history create denied', 'errorMessage': 'unable_to_create_product_history'} : 상품 이력 등록 실패 500, {'message': 'bookmark volumes create denied', 'errorMessage': 'unable_to_create_bookmark_volumes'} : 북마크 초기 등록 실패 500, {'message': 'database_connection_fail', 'errorMessage': 'database_close_fail'} : 커넥션 종료 실패 500, {'message': 'database_error', 'errorMessage': 'database_error_' + format(e)} : 데이터베이스 에러 500, {'message': 'internal_server_error', 'errorMessage': format(e)}) : 서버 에러 History: 2020-12-29(심원두): 초기 생성 2021-01-03(심원두): 파라미터 유효성 검사 추가 Enum(), NotEmpty() 2021-01-05(심원두): -이미지 저장 처리 순서를 3번째에서 가장 마지막으로 내림. 테이블 인서트 처리에 문제가 있을 경우, S3에 올라간 이미지는 롤백을 할 수 없는 이슈 반영. -북마크 테이블 초기 등록 처리 추가. """ try: data = { 'seller_id': request.form.get('seller_id'), 'account_id': g.account_id, 'is_sale': request.form.get('is_sale'), 'is_display': request.form.get('is_display'), 'main_category_id': request.form.get('main_category_id'), 'sub_category_id': request.form.get('sub_category_id'), 'is_product_notice': request.form.get('is_product_notice'), 'manufacturer': request.form.get('manufacturer'), 'manufacturing_date': request.form.get('manufacturing_date'), 'product_origin_type_id': request.form.get('product_origin_type_id'), 'product_name': request.form.get('product_name'), 'description': request.form.get('description'), 'detail_information': request.form.get('detail_information'), 'minimum_quantity': request.form.get('minimum_quantity'), 'maximum_quantity': request.form.get('maximum_quantity'), 'origin_price': request.form.get('origin_price'), 'discount_rate': request.form.get('discount_rate'), 'discounted_price': request.form.get('discounted_price'), 'discount_start_date': request.form.get('discount_start_date'), 'discount_end_date': request.form.get('discount_end_date') } product_images = request.files.getlist("image_files") stocks = json.loads(request.form.get('options')) connection = get_connection(self.database) product_id = self.service.create_product_service(connection, data) product_code = self.service.update_product_code_service( connection, product_id) self.service.create_stock_service(connection, product_id, stocks) self.service.create_product_history_service( connection, product_id, data) self.service.create_product_sales_volumes_service( connection, product_id) self.service.create_bookmark_volumes_service( connection, product_id) self.service.create_product_images_service(connection, data['seller_id'], product_id, product_code, product_images) connection.commit() return jsonify({'message': 'success'}), 200 except KeyError as e: traceback.print_exc() connection.rollback() raise e except Exception as e: traceback.print_exc() connection.rollback() raise e finally: try: if connection: connection.close() except Exception: traceback.print_exc() raise DatabaseCloseFail('database close fail')
class AccountView: account_app = Blueprint('account_app', __name__, url_prefix='/account') @account_app.route('signup/master', methods=['POST']) @validate_params( Param('email', JSON, str, rules=[Pattern(r'^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$')]), Param('password', JSON, str, rules=[MaxLength(80), MinLength(4)]), Param('account_type_id', JSON, int), Param('account_type_id', JSON, str, rules=[Pattern(r"^[1-2]{1}$")]), Param('name', JSON, str, rules=[Pattern(r"^[가-힣]{1,20}$")]), Param('master_code', JSON, str, required=True) ) def sign_up_master(*args): connection = None account_info = { 'email': args[0], 'password': args[1], 'account_type_id': args[2], 'name': args[4], 'master_code': args[5] } connection = connect_db() if connection: account_service = AccountService() try: account_service.signup_account(account_info, connection) if account_info['master_code'] != 'brandi_team1': connection.rollback() return jsonify({'MESSAGE': 'WRONG_MASTER_CODE'}) connection.commit() return jsonify({'MESSAGE': 'ACCOUNT_CREATED', }), 200 except Exception as e: connection.rollback() return jsonify({'MESSAGE': f'{e}'}), 400 finally: if connection: connection.close() @account_app.route('signup/seller', methods=['POST']) @validate_params( Param('email', JSON, str, rules=[Pattern(r'^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$')]), Param('password', JSON, str, rules=[MaxLength(80), MinLength(4)]), Param('account_type_id', JSON, int), Param('account_type_id', JSON, str, rules=[Pattern(r"^[1-2]{1}$")]), Param('service_number', JSON, str, rules=[Pattern(r"^[0-9]{11}$|^[0-9]{12}$")]), Param('seller_name_kr', JSON, str, rules=[Pattern(r"^[가-힣0-9]{1,20}$")]), Param('seller_name_en', JSON, str, rules=[Pattern( r"^[a-zA-Z0-9àáâäãåąčćęèéêëėįìíîïłńòóôöõøùúûüųūÿýżźñçčšžÀÁÂÄÃÅĄĆČĖĘÈÉÊËÌÍÎÏĮŁŃÒÓÔÖÕØÙÚÛÜŲŪŸÝŻŹÑßÇŒÆČŠŽ∂ð ,.'-]{1,20}$")]), Param('subcategory_id', JSON, str, rules=[Pattern(r"^[1-8]{1}$")]) ) def sign_up_seller(*args): connection = None try: account_info = { 'email': args[0], 'password': args[1], 'account_type_id': args[2], 'name': '미설정', 'service_number': args[4], 'seller_name_kr': args[5], 'seller_name_en': args[6], 'subcategory_id': args[7], 'seller_status_id': 1 } print(account_info) connection = connect_db() account_service = AccountService() account_service.signup_seller(account_info, connection) connection.commit() return jsonify({'MESSAGE': 'SUCCESS'}), 200 except Exception as e: connection.rollback() return jsonify({'MESSAGE': f'{e}'}), 400 finally: if connection: connection.close() @account_app.route('/signin', methods=['POST']) def sign_in(): connection = None try: connection = connect_db() login_data = request.json account_service = AccountService() token = account_service.signin(login_data, connection) return token except Exception as e: return jsonify({'MESSAGE': f'{e}'}), 400 finally: if connection: connection.close() @account_app.route('/seller_list', methods=['GET']) @login_validator @validate_params( Param('seller_id', GET, int, required=False), Param('account_id', GET, int, required=False), Param('email', GET, str, required=False), Param('seller_en', GET, str, required=False), Param('seller_kr', GET, str, required=False), Param('user_id', GET, int, required=False), Param('manager_name', GET, str, required=False), Param('manager_email', GET, str, required=False), Param('seller_status', GET, str, required=False), Param('manager_phone', GET, str, required=False), Param('seller_category', GET, str, required=False), Param('created_lower', GET, str, required=False), Param('created_upper', GET, str, required=False), Param('excel', GET, int, required=False), Param('page', GET, int, required=False), Param('limit', GET, int, required=False), Param('order_by', GET, str, required=False, rules=[Enum('asc', 'desc')]) ) def list_sellers(*args): db_connection = None user = g.token_info filter_info = { 'seller_id': args[0], 'account_id': args[1], 'email': args[2], 'seller_en': args[3], 'seller_kr': args[4], 'user_id': args[5], 'manager_name': args[6], 'manager_email': args[7], 'seller_status': args[8], 'manager_phone': args[9], 'seller_category': args[10], 'created_upper': args[11], 'created_lower': args[12], 'excel': args[13], 'page': args[14] if args[14] else 1, 'limit': args[15] if args[15] else 10, 'order_by': args[16] } try: connection = connect_db() account_service = AccountService() seller_list = account_service.filter_seller(filter_info, user, connection) return jsonify({'seller_list': seller_list}), 200 except Exception as e: return jsonify({'MESSAGE': f'{e}'}), 400 finally: connection.close() @account_app.route('edit', methods=['PATCH']) @login_validator @validate_params( Param('email', JSON, str, rules=[Pattern(r'^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$')], required=False), Param('password', JSON, str, rules=[MaxLength(80), MinLength(4)], required=False), Param('account_type_id', JSON, int, required=False), Param('account_type_id', JSON, str, rules=[Pattern(r"^[1-2]{1}$")], required=False), Param('name', JSON, str, rules=[Pattern(r"^[가-힣]{1,20}$")], required=False), Param('account_id', JSON, int, required=True), Param('is_active', JSON, int, required=False) ) def edit_account(*args): connection = None change_info = { 'email': args[0], 'password': args[1], 'account_type_id': args[2], 'name': args[4], 'id': args[5], 'is_active': args[6] } connection = connect_db() user = g.token_info if connection: account_service = AccountService() try: change_account = account_service.change_account_info(change_info, user, connection) connection.commit() return change_account except Exception as e: connection.rollback() return jsonify({'MESSAGE': f'{e}'}), 400 finally: connection.close() @account_app.route('edit_seller', methods=['PATCH']) @login_validator @validate_params( Param('subcategory_id', JSON, int, required=False), Param('seller_status_id', JSON, int, required=False), Param('seller_name_kr', JSON, str, rules=[Pattern(r"^[가-힣0-9]{1,20}$")], required=False), Param('seller_name_en', JSON, str, rules=[Pattern( r"^[a-zA-Z0-9àáâäãåąčćęèéêëėįìíîïłńòóôöõøùúûüųūÿýżźñçčšžÀÁÂÄÃÅĄĆČĖĘÈÉÊËÌÍÎÏĮŁŃÒÓÔÖÕØÙÚÛÜŲŪŸÝŻŹÑßÇŒÆČŠŽ∂ð ,.'-]{1,20}$")], required= False), Param('seller_number', JSON, str, rules=[Pattern(r"^[0-9]{11}$|^[0-9]{12}$")], required=False), Param('profile_pic_url', JSON, str, required=False), Param('short_desc', JSON, str, required=False), Param('long_desc', JSON, str, required=False), Param('open_time', JSON, str, rules=[Pattern('^(20)[\d]{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$')], required= False), Param('close_time', JSON, str, rules=[Pattern('^(20)[\d]{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$')], required= False), Param('delivery_policy', JSON, str, required=False), Param('return_policy', JSON, str, required=False), Param('zip_code', JSON, int, required=False), Param('address_1', JSON, str, required=False), Param('address_2', JSON, str, required=False), Param('is_open_weekend', JSON, int, required=False), Param('seller_id', JSON, int, required=True) ) def edit_seller(*args): connection = None change_info = { 'subcategory_id': args[0], 'seller_status_id': args[1], 'seller_name_kr': args[2], 'seller_name_en': args[3], 'seller_number': args[4], 'profile_pic_url': args[5], 'short_desc': args[6], 'long_desc': args[7], 'open_time': args[8], 'close_time': args[9], 'delivery_policy': args[10], 'return_policy': args[11], 'zip_code': args[12], 'address_1': args[13], 'address_2': args[14], 'is_open_weekend': args[15], 'seller_id': args[16] } connection = connect_db() user = g.token_info if connection: account_service = AccountService() try: change_account = account_service.change_seller_info(change_info, user, connection) connection.commit() return change_account except Exception as e: connection.rollback() return jsonify({'MESSAGE': f'{e}'}), 400 finally: connection.close() @account_app.route('change_seller_status', methods=['PATCH']) @login_validator @validate_params( Param('action_id', JSON, str), Param('status_id', JSON, str), Param('seller_id', JSON, str) ) def seller_actions(*args): connection = None status = { 'action_id': args[0], 'status_id': args[1], 'seller_id': args[2] } user = g.token_info connection = connect_db() if connection: try: account_service = AccountService() account_service.change_status(status, user, connection) connection.commit() return jsonify({'status_actions': 'SUCCESS'}), 200 except Exception as e: connection.rollback() return jsonify({'MESSAGE': f'{e}'}), 400 finally: connection.close()
def create_product_endpoints(product_service, Session): product_app = Blueprint("product_app", __name__, url_prefix="/api/products") @product_app.route("/category", methods=["GET"]) def product_category(): session = Session() try: # 메뉴 데이터 categories = product_service.get_menu(None, session) # 각 카테고리를 저장하기 위한 리스트 second_category, first_category, main_category = [], [], [] # JOIN 을 하며 생기는 중복을 제거하기 위해서 중복 체크 후 리스트에 저장 for category in categories: # 1. (메인 카테고리의 id, 이름) 이 main_category 에 없을 경우 append if (category.m_id, category.main_category_name) not in main_category: main_category.append((category.m_id, category.main_category_name)) # 2. (1차 카테고리의 id, 이름, 이에 해당하는 메인 카테고리의 id) 가 first_category 에 없을 경우 append if ( category.f_id, category.first_category_name, category.main_category_id, ) not in first_category: first_category.append( (category.f_id, category.first_category_name, category.main_category_id) ) # 3. (2차 카테고리의 id, 이름, 이에 해당하는 1차 카테고리의 id) 가 second_category 에 없을 경우 append second_category.append( (category.s_id, category.second_category_name, category.first_category_id) ) # 카테고리의 계층 구조를 전달하기 위한 JSON 형식 body = [ { # 메인 카테고리의 id 와 이름 "id": m_menu[0], m_menu[1]: [ { # 1차 카테고리의 id 와 이름 "id": f_menu[0], f_menu[1]: [ { # 2차 카테고리의 id 와 이름 "id": s_menu[0], "name": s_menu[1], } for s_menu in second_category if s_menu[2] == f_menu[0] ], } for f_menu in first_category if f_menu[2] == m_menu[0] ], } for m_menu in main_category ] return jsonify(body), 200 except Exception as e: return jsonify({"message": f"{e}"}), 500 finally: session.close() @product_app.route("", methods=["GET"]) @validate_params( Param("limit", GET, int, default=100, required=False), Param("offset", GET, int, required=False), Param("main_category_id", GET, int, rules=[Enum(4, 5, 6)], required=False), Param("first_category_id", GET, int, required=False), Param("second_category_id", GET, int, required=False), Param("is_promotion", GET, int, rules=[Enum(0, 1)], required=False), Param("select", GET, int, rules=[Enum(0, 1)], required=False), Param("q", GET, str, required=False), Param("all_items", GET, int, rules=[Enum(1)], required=False), ) def products(*args): session = Session() try: # args[2]: 메인 카테고리의 pk, args[8]: 전체 상품을 보여줄 지 판단하는 파라미터, args[3]: 1차 카테고리의 pk, args[4]: 2차 카테고리의 pk if args[2] == 5 or args[2] == 6 and not args[8] and not args[3] and not args[4]: # 특정 메인 카테고리 아이디 (5: 브랜드, 6: 뷰티) 파라미터만 들어올 경우 베스트 상품, 추천 상품 데이터 등을 전달 body = { "best_items": [], "brand_items": [], "recommended_items": [], "category_items": [], } # 1. 파라미터로 들어온 카테고리의 id (args[2]) 에 따라 특정 셀러를 지정하고 상품 5개만 가져오기 위해 선언, # 2. 특정 1차 카테고리 아이디로 필터링된 상품 리스트를 가져오기 위해 선언 if args[2] == 5: f_cat_list = (12, 13, 14, 15, 16, 17, 18) seller_id = 30 else: f_cat_list = (23, 24, 25, 26, 27, 28) seller_id = 359 for f_cat_id in f_cat_list: # 1. 첫 번째 카테고리 상품 5개 씩 보여주기 위한 필터 f_category_filter = {"first_category_id": f_cat_id, "limit": 5} # 2. 카테고리의 id, name 과 함께 상품 리스트를 반환한다. category_products = { "category_id": f_cat_id, "category_name": product_service.get_menu(f_cat_id, session)[ 0 ].first_category_name, "product": product_service.get_products(f_category_filter, session), } body["category_items"].append(category_products) # Best 상품 필터 - 해당하는 메인 카테고리의 상품 중 판매량 순 10개만 가져오기 위해 선언 best_prod_filter = { "main_category_id": args[2], "limit": 10, } best_products = product_service.get_products(best_prod_filter, session) # 추천 상품 필터 - 할인율 기준 recommended_prod_filter = { "main_category_id": args[2], "limit": 30, "discount_rate": 1, } recommended_products = product_service.get_products( recommended_prod_filter, session ) # 특정 셀러 상품 리스트 필터 seller_filter = {"main_category_id": args[2], "seller_id": seller_id} brand_products = product_service.get_products(seller_filter, session) body["best_items"] = best_products body["brand_items"] = brand_products body["recommended_items"] = recommended_products return body # 필터링을 위한 딕셔너리 filter_dict = dict() # pagination filter_dict["limit"] = args[0] filter_dict["offset"] = args[1] # 카테고리 filter_dict["main_category_id"] = args[2] filter_dict["first_category_id"] = args[3] filter_dict["second_category_id"] = args[4] # 세일 filter_dict["is_promotion"] = args[5] # 판매량순, 최신순 filter_dict["select"] = args[6] # 검색 필터 filter_dict["q"] = args[7] # 메인 카테고리의 모든 상품 필터 filter_dict["all_items"] = args[8] body = dict() # 상품 데이터 body["products"] = product_service.get_products(filter_dict, session) # 검색어가 들어올 경우 전달하는 셀러 정보 if filter_dict["q"]: # 필터링된 셀러 리스트를 가져오기 위한 필터 seller_info = dict() seller_info["name"] = filter_dict["q"] seller_info["limit"] = 100 # 검색된 셀러 리스트 정의 sellers = dict() # 검색어에 해당하는 셀러의 리스트 seller_list = [ dict(seller) for seller in product_service.get_sellers(seller_info, session) ] # 셀러 검색 결과 개수 sellers["count"] = len(seller_list) # 셀러 데이터 sellers["seller_list"] = seller_list body["sellers"] = sellers return jsonify(body), 200 except Exception as e: return jsonify({"message": f"{e}"}), 500 finally: session.close() @product_app.route("/product/<int:product_id>", methods=["GET"]) def product(product_id): session = Session() try: # 상품 데이터 body = dict(product_service.get_product(product_id, session)) return jsonify(body), 200 except Exception as e: return jsonify({"message": f"{e}"}), 500 finally: session.close() @product_app.route("/seller", methods=["GET"]) @validate_params( Param("limit", GET, int, default=100, required=False), Param("offset", GET, int, required=False), Param("main_category_id", GET, int, rules=[Enum(4, 5, 6)], required=False), Param("select", GET, int, rules=[Enum(0, 1)], default=1, required=False), ) def sellers(*args): session = Session() try: # 필터링을 위한 딕셔너리 seller_dict = dict() seller_dict["limit"] = args[0] seller_dict["offset"] = args[1] seller_dict["main_category_id"] = args[2] seller_dict["select"] = args[3] # 셀러 데이터 body = [dict(seller) for seller in product_service.get_sellers(seller_dict, session)] return jsonify(body), 200 except Exception as e: return jsonify({"message": f"{e}"}), 500 finally: session.close() return product_app
def product_endpoints(app, services, get_session): product_service = services.product_service @app.route("/product/register", methods=['GET']) @login_required def get_register_product(): """ 상품 등록 API 상품 등록 페이지에 들어갔을 때 불러오는 데이터 Returns: 200 : data_list ( type : dict ) 500 : Exception """ session = None try: session = get_session() data_list = product_service.get_category_color_size(session) return jsonify(data_list), 200 except NoDataException as e: session.rollback() return jsonify({'message': 'no data error {}'.format(e.message) }), e.status_code except Exception as e: session.rollback() return jsonify({'message': '{}'.format(e)}), 500 finally: if session: session.close() @app.route("/product/register", methods=['POST']) @login_required @validate_params(Param('sub_categories_id', JSON, int), Param('name', JSON, str), Param('main_image', JSON, str), Param('is_sell', JSON, int, rules=[Enum(0, 1)]), Param('is_display', JSON, int, rules=[Enum(0, 1)]), Param('is_discount', JSON, int, rules=[Enum(0, 1)]), Param('price', JSON, int), Param('detail', JSON, str), Param('maximum_sell_count', JSON, int), Param('minimum_sell_count', JSON, int), Param('options', JSON, list), Param('discount_rate', JSON, int, required=False), Param('discount_start_date', JSON, str, required=False), Param('discount_end_date', JSON, str, required=False), Param('simple_information', JSON, str, required=False), Param('manufacturer', JSON, str, required=False), Param('manufacture_date', JSON, str, required=False), Param('origin', JSON, str, required=False), Param('image_list', JSON, list, required=False)) def post_register_product(*args): """ 상품 등록 API Body 로 상품 데이터를 받아 상품 등록하기 Args: *args: sub_categories_id : 2차 카테고리 id name : 상품명 main_image : 상품 메인 이미지 is_sell : 판매 여부 is_display : 진열 여부 is_discount : 할인 여부 price : 상품 가격 detail : 상품 상세정보 maximum_sell_count : 최대 판매 수량 minimum_sell_count : 최소 판매 수량 options : 리스트. [{ 컬러 id, 사이즈 id, 재고관리여부, 재고수량 }] discount_rate : 할인율 discount_start_date : 할인 시작 날짜 discount_end_date : 할인 마지막 날짜 simple_information : 한줄 상품 설명 manufacturer : 제조사 manufacture_date : 제조일자 origin : 원산지 image_list : 서브이미지 리스트 Returns: 200 : success, 상품 등록에 성공했을 때 400 : key error , 옵션리스트 안에 컬러아이디, 사이즈아이디, 재고관리여부 중 하나라도 없을 때 500 : Exception """ session = None try: session = get_session() # 바디에 담긴 부분 유효성 검사 후에 딕셔너리로 만들기 product_data = { 'sub_categories_id': args[0], 'name': args[1], 'main_image': args[2], 'is_sell': args[3], 'is_display': args[4], 'is_discount': args[5], 'price': args[6], 'detail': args[7], 'maximum_sell_count': args[8], 'minimum_sell_count': args[9], 'options': args[10], 'discount_rate': args[11], 'discount_start_date': args[12], 'discount_end_date': args[13], 'simple_information': args[14], 'manufacturer': args[15], 'manufacture_date': args[16], 'origin': args[17], 'image_list': args[18], 'seller_id': g.seller_id } # 옵션 안의 리스트의 키값들이 None 일 때 에러 발생 for options in product_data['options']: if options['color_id'] is None or options[ 'size_id'] is None or options[ 'is_inventory_manage'] is None: return jsonify({'message': 'option data not exist'}), 400 product_service.post_register_product(product_data, session) session.commit() return jsonify({'message': 'success'}), 200 except KeyError: return jsonify({'message': 'key error'}), 400 except NoAffectedRowException as e: session.rollback() return jsonify( {'message': 'no affected row error {}'.format(e.message)}), e.status_code except Exception as e: session.rollback() return jsonify({'message': '{}'.format(e)}), 500 finally: if session: session.close() @app.route("/category/<int:category_id>", methods=['GET']) @login_required def get_sub_category_list(category_id): """ 2차 카테고리 불러오기 상품 등록 페이지에서 1차 카테고리를 선택했을 때 해당하는 2차 카테고리 리스트 불러오기 Args: category_id: 1차 카테고리 id Returns: 200 : category_list ( type : dict ) 500 : Exception """ session = None try: session = get_session() category_list = product_service.get_sub_categories( category_id, session) return jsonify(category_list), 200 except Exception as e: session.rollback() return jsonify({'message': '{}'.format(e)}), 500 finally: if session: session.close() @app.route("/product/update/<int:product_id>", methods=['GET']) @login_required @validate_params(Param('product_id', PATH, int)) def get_update_product(product_id): """ 상품 상세페이지 ( 수정 ) 상품 수정 페이지 들어갔을 때 등록되어 있는 상품 데이터 가져오기 Args: product_id: 상품 id Returns: 200 : product_data ( type : dict ) 500 : Exception """ session = None try: session = get_session() product_data = product_service.get_product(product_id, session) return jsonify(product_data) except NoDataException as e: session.rollback() return jsonify({'message': 'no data {}'.format(e.message)}), e.status_code except Exception as e: session.rollback() return jsonify({'message': '{}'.format(e)}), 500 finally: if session: session.close() @app.route("/product/update/<int:product_id>", methods=['PUT']) @login_required @validate_params(Param('product_id', PATH, int), Param('sub_categories_id', JSON, int), Param('name', JSON, str), Param('main_image', JSON, str), Param('is_sell', JSON, int, rules=[Enum(0, 1)]), Param('is_display', JSON, int, rules=[Enum(0, 1)]), Param('is_discount', JSON, int, rules=[Enum(0, 1)]), Param('price', JSON, int), Param('detail', JSON, str), Param('maximum_sell_count', JSON, int), Param('minimum_sell_count', JSON, int), Param('options', JSON, list), Param('discount_rate', JSON, int, required=False), Param('discount_start_date', JSON, str, required=False), Param('discount_end_date', JSON, str, required=False), Param('simple_information', JSON, str, required=False), Param('manufacturer', JSON, str, required=False), Param('manufacture_date', JSON, str, required=False), Param('origin', JSON, str, required=False), Param('image_list', JSON, list, required=False)) def put_update_product(*args): """ 상품 데이터 수정 API Path parameter 로 상품 id를 받고 Body 로 상품 데이터를 받아 데이터 업데이트하기 Args: *args: product_id : 상품 id sub_categories_id : 2차 카테고리 id name : 상품명 main_image : 상품 메인 이미지 is_sell : 판매 여부 is_display : 진열 여부 is_discount : 할인 여부 price : 상품 가격 detail : 상품 상세 정보 maximum_sell_count : 최대 판매 수량 minimum_sell_count : 최소 판매 수량 options : 옵션리스트 ( 컬러아이디, 사이즈아이디, 재고관리여부, 재고수량 ) discount_rate : 할인율 discount_start_date : 할인 시작 날짜 discount_end_date : 할인 마지막 날짜 simple_information : 상품 한줄 정보 manufacturer : 제조사 manufacture_date : 제조 날짜 origin : 원산지 image_list : 서브 이미지 리스트 Returns: 200 : success, 상품 데이터 업데이트에 성공했을 때 400 : 옵션리스트에 컬러아이디, 사이즈아이디, 재고관리여부가 하나라도 없을 때 500 : Exception """ session = None try: session = get_session() # 수정할 상품 데이터 딕셔너리로 만들기 product_data = { 'product_id': args[0], 'sub_categories_id': args[1], 'name': args[2], 'main_image': args[3], 'is_sell': args[4], 'is_display': args[5], 'is_discount': args[6], 'price': args[7], 'detail': args[8], 'maximum_sell_count': args[9], 'minimum_sell_count': args[10], 'options': args[11], 'discount_rate': args[12], 'discount_start_date': args[13], 'discount_end_date': args[14], 'simple_information': args[15], 'manufacturer': args[16], 'manufacture_date': args[17], 'origin': args[18], 'image_list': args[19], 'seller_id': g.seller_id } # 옵션 안의 키값들이 None 이면 에러 발생 for options in product_data['options']: if options['color_id'] is None or options[ 'size_id'] is None or options[ 'is_inventory_manage'] is None: return jsonify({'message': 'option data not exist'}), 400 product_service.post_update_product(product_data, session) session.commit() return jsonify({'message': 'success'}), 200 except NoAffectedRowException as e: session.rollback() return jsonify( {'message': 'no affected row error {}'.format(e.message)}), e.status_code except Exception as e: session.rollback() return jsonify({'message': '{}'.format(e)}), 500 finally: if session: session.close() @app.route("/product/management", methods=['GET']) @login_required @validate_params(Param('limit', GET, int, required=False), Param('offset', GET, int, required=False), Param('is_sell', GET, int, rules=[Enum(0, 1)], required=False), Param('is_discount', GET, int, rules=[Enum(0, 1)], required=False), Param('is_display', GET, int, rules=[Enum(0, 1)], required=False), Param('name', GET, str, required=False), Param('code_number', GET, int, required=False), Param('product_number', GET, str, required=False), Param('start_date', GET, str, required=False), Param('end_date', GET, str, required=False), Param('seller_property_id', GET, int, required=False), Param('brand_name_korean', GET, str, required=False)) def management_product(*args): """ 상품 관리 리스트 API 쿼리 파라미터로 필터링 조건들을 받아서 필터링 조건에 따라서 등록되어 있는 상품 리스트를 가져오기 Args: *args: limit : pagination 범위 offset : pagination 시작 번호 is_sell : 판매 여부 is_discount : 할인 여부 is_display : 진열 여부 name : 상품명 code_number : 상품 코드 번호 product_number : 상품 id start_date : 해당날짜 이후에 등록된 상품 end_date : 해당날짜 이전에 등록된 상품 seller_property_id : 셀러 속성 id ( 로드샵, 마켓 등 ) brand_name_korean : 브랜드명 ( 한글 ) Returns: 200 : product_list ( type : dict ) 500 : Exception """ session = None try: session = get_session() # 쿼리스트링을 딕셔너리로 만들기 query_string_list = { 'limit': 10 if args[0] is None else args[0], 'offset': 0 if args[1] is None else args[1], 'is_sell': args[2], 'is_discount': args[3], 'is_display': args[4], 'name': args[5], 'code_number': args[6], 'product_number': args[7], 'start_date': args[8], 'end_date': args[9], 'seller_property_id': args[10], 'brand_name_korean': args[11], 'seller_id': g.seller_id } product_list = product_service.get_product_list( query_string_list, session) return jsonify(product_list) except Exception as e: session.rollback() return jsonify({'message': '{}'.format(e)}), 500 finally: if session: session.close()
def create_product_endpoints(product_service, Session): product_app = Blueprint('product_app', __name__, url_prefix='/api/product') @product_app.route('/products', methods = ['GET'], endpoint='products') @login_required(Session) @validate_params( Param('filterLimit', GET, int, default=10, required=False), Param('page', GET, int, required=False), Param('exhibitionYn', GET, int, rules=[Enum(0, 1)], required=False), Param('exhibitionYn', GET, int, rules=[Enum(0, 1)], required=False), Param('sellYn', GET, int, rules=[Enum(0, 1)], required=False), Param('mdSeNo', GET, int, required=False), Param('selectFilter', GET, str, rules=[Enum('productName', 'productNo', 'productCode')], required=False), Param('filterKeyword', GET, required=False), Param('mdName', GET, required=False), Param('filterDateFrom', GET, str, rules=[Pattern(r"^\d\d\d\d-\d{1,2}-\d{1,2}$")], required=False), Param('filterDateTo', GET, str, rules=[Pattern(r"^\d\d\d\d-\d{1,2}-\d{1,2}$")], required=False) ) def products(*args): """ 상품 정보 리스트 전달 API 쿼리 파라미터로 필터링에 사용될 값을 받아 필터링된 상품의 데이터 리스트를 표출합니다. args: *args: filterLimit: pagination 을 위한 파라미터 page: pagination 을 위한 파라미터 exhibitionYn: 진열 여부 discountYn: 할인 여부 sellYn: 판매 여부 mdSeNo: 셀러 속성 id selectFilter: 상품 검색 시 상품 명, 코드, 번호 중 어떤 것을 선택했는지 판단 위한 파라미터 filterKeyword: 상품 검색을 위한 파라미터 mdName: 셀러 이름 검색을 위한 파라미터 filterDateFrom: 조회 기간 시작 filterDateTo: 조회 기간 끝 returns : 200: 상품 리스트 500: Exception Authors: 고지원 History: 2020-10-01 (고지원): 초기 생성 """ try: session = Session() # 필터링을 위한 딕셔너리 filter_dict = dict() # pagination filter_dict['filterLimit'] = args[0] filter_dict['page'] = args[1] # 진열 여부 filter_dict['exhibitionYn'] = args[2] # 할인 여부 filter_dict['discountYn'] = args[3] # 판매 여부 filter_dict['sellYn'] = args[4] # 셀러 속성 filter_dict['mdSeNo'] = args[5] # 상품 검색 filter_dict['selectFilter'] = args[6] # 상품 검색어 filter_dict['filterKeyword'] = args[7] # 셀러 검색어 filter_dict['mdName'] = args[8] # 조회 기간 시작 filter_dict['filterDateFrom'] = args[9] # 조회 기간 끝 filter_dict['filterDateTo'] = args[10] # 상품 정보 products = product_service.get_products(filter_dict, session) # 상품 쿼리 결과 count count_info = product_service.get_product_count(filter_dict, session) body = { 'orders' : [dict(product) for product in products], 'page_number' : count_info.p_count, 'total_order_number' : round(count_info.p_count / filter_dict['filterLimit']) } return jsonify(body), 200 except exc.ProgrammingError: return jsonify(({'message': 'ERROR_IN_SQL_SYNTAX'})), 500 except Exception as e: traceback.print_exc() return jsonify({'message': f'{e}'}), 500 finally: session.close() @product_app.route('/<int:product_id>', methods=['GET'], endpoint='product') @login_required(Session) def product(product_id): """ 상품 수정 시 기존 등록 정보 전달 API path parameter 로 id 받아 해당 상품의 데이터 표출합니다. args: product_id : 상품의 id returns : 200: 상품 정보 500: Exception Authors: 고지원 History: 2020-10-01 (고지원): 초기 생성 """ session = Session() try: # 상품 데이터 body = dict(product_service.get_product(product_id, session)) return jsonify(body), 200 except exc.ProgrammingError: return jsonify({'message': 'ERROR_IN_SQL_SYNTAX'}), 500 except AttributeError: return jsonify({'message': 'THERE_IS_NO_PRODUCT_DATA'}), 400 except Exception as e: return jsonify({'message': f'{e}'}), 500 finally: session.close() @product_app.route('/history', methods=['GET'], endpoint='product_history') @login_required(Session) def product_history(): """ 상품 수정 이력 전달 API 점 이력으로 관리하는 상품의 수정 이력을 표출합니다. args: product_id : 상품의 id returns : 200: 상품의 수정 이력 리스트 500: Exception Authors: 고지원 History: 2020-10-10 (고지원): 초기 생성 """ session = Session() try: product_id = request.args.get('product_id') body = [dict(history) for history in product_service.get_product_history(product_id, session)] return jsonify(body), 200 except exc.ProgrammingError: return jsonify({'message': 'ERROR_IN_SQL_SYNTAX'}), 500 except Exception as e: return jsonify({'message': f'{e}'}), 500 finally: session.close() @product_app.route('/excel', methods=['GET'], endpoint='make_excel') @login_required(Session) @validate_params( Param('product_id', GET, list, required=False) ) def make_excel(*args): """ 상품 정보 엑셀 다운로드 API 전체 상품 또는 선택 상품의 정보를 excel 파일로 다운로드 합니다. args: product_id : 상품의 id 리스트 returns : 200: Excel 파일 다운 500: Exception Authors: 고지원 History: 2020-10-02 (고지원): 초기 생성 """ session = Session() try: # 선택한 상품들의 id를 list 로 받는다. id_list = args[0] # service 의 make_excel 함수를 호출한다. product_service.make_excel(id_list, session) return jsonify({'message': 'SUCCESS'}), 200 except exc.ProgrammingError: traceback.print_exc() return jsonify({'message': 'ERROR_IN_SQL_SYNTAX'}), 500 except Exception as e: traceback.print_exc() return jsonify({'message': f'{e}'}), 500 finally: session.close() @product_app.route('/seller', methods=['GET'], endpoint='sellers') @login_required(Session) @validate_params( Param('q', GET, str, required = True) ) def sellers(*args): """ 셀러 리스트 전달 API query parameter 를 받아 필터링된 셀러 리스트 데이터를 표출합니다. args: *args: name: 셀러명 검색을 위한 쿼리 스트링 returns : 200: 셀러 리스트 500: Exception Authors: 고지원 History: 2020-10-04 (고지원): 초기 생성 """ session = Session() try: # 필터링을 위한 딕셔너리 seller_dict = dict() seller_dict['name'] = args[0] # 셀러 데이터 body = [dict(seller) for seller in product_service.get_sellers(seller_dict, session)] return jsonify(body), 200 except exc.ProgrammingError: return jsonify({'message': 'ERROR_IN_SQL_SYNTAX'}), 500 except Exception as e: return jsonify({'message': f'{e}'}), 500 finally: session.close() @product_app.route('/category', methods=['GET'], endpoint='product_categories') @login_required(Session) def product_categories(): """ 1차, 2차 카테고리 정보 전달 API 셀러의 속성 아이디를 받아 1차 카테고리, 1차 카테고리 아이디를 받아 2차 카테고리 정보를 전달합니다. args: seller_attr_id: 셀러의 속성 id f_category_id: 1차 카테고리 id returns : 200: 1차 또는 2차 카테고리 정보 500: Exception Authors: 고지원 History: 2020-10-04 (고지원): 초기 생성 """ session = Session() try: # 셀러 속성 아이디 또는 1차 카테고리 아이디를 받는다. seller_attr_id = request.args.get('seller_attr_id') f_category_id = request.args.get('f_category_id') # 셀러 속성 아이디가 들어왔을 경우 1차 카테고리 정보를 반환 if seller_attr_id: body = [dict(cat) for cat in product_service.get_first_categories(seller_attr_id, session)] return jsonify(body), 200 # 1차 카테고리 아이디가 들어왔을 경우 2차 카테고리 정보를 반환 body = [dict(cat)for cat in product_service.get_second_categories(f_category_id, session)] return jsonify(body), 200 except exc.ProgrammingError: return jsonify({'message': 'ERROR_IN_SQL_SYNTAX'}), 500 except Exception as e: return jsonify({'message': f'{e}'}), 500 finally: session.close() @product_app.route('', methods=['POST'], endpoint='insert_product') @login_required(Session) def insert_product(): """ 상품 정보 등록 API returns : 200: 상품 정보를 데이터베이스에 저장 400: NAME_CANNOT_CONTAIN_QUOTATION_MARK, START_DATE_CANNOT_BE_EARLIER_THAN_END_DATE, CANNOT_SET_MORE_THAN_20, CANNOT_SET_LESS_THAN_10, DISCOUNT_RANGE_CAN_BE_SET_FROM_0_TO_99, DUPLICATE_DATA, INVALID_REQUEST 500: Exception, ERROR_IN_SQL_SYNTAX Authors: 고지원 History: 2020-10-03 (고지원): 초기 생성 2020-10-04 (고지원): 상품 정보 입력 시 제한 사항 에러 추가 2020-10-10 (고지원): 여러 개의 이미지를 업로드 할 수 있도록 수정 2020-10-12 (고지원): 에러 발생 시 세션 rollback 과 함께 s3 에 업로드 된 이미지도 삭제되도록 수정 """ session = Session() image_urls = '' is_success = False try: # 상품명에 ' 또는 " 포함 되었는지 체크 pattern = re.compile('[\"\']') if pattern.search(request.form['name']): return jsonify({'message': 'NAME_CANNOT_CONTAIN_QUOTATION_MARK'}), 400 # 할인 시작일이 할인 종료일보다 빠를 경우 if request.form['discount_start_date'] > request.form['discount_end_date']: return jsonify({'message': 'START_DATE_CANNOT_BE_EARLIER_THAN_END_DATE'}), 400 # 최소 수량 또는 최대 수량이 20을 초과할 경우 if int(request.form['min_unit']) > 20 or int(request.form['max_unit']) > 20: return jsonify({'message': 'CANNOT_SET_MORE_THAN_20'}), 400 # 판매가가 10원 미만일 경우 if int(request.form['price']) < 10: return jsonify({'message': 'CANNOT_SET_LESS_THAN_10'}), 400 # 할인률이 0 ~ 99% 가 아닐 경우 if int(request.form['discount_rate']) not in range(0, 99): return jsonify({'message': 'DISCOUNT_RANGE_CAN_BE_SET_FROM_0_TO_99'}), 400 # 상품 코드 product_code = str(uuid.uuid4()) # S3 이미지 저장 images = list() # 1~5 개의 이미지를 가져온다. for idx in range(1, 6): image = request.files.get(f'image_{idx}', None) if image: images.append(image) image_urls = product_service.upload_image(product_code, images) # 반환된 image_urls 가 허용되지 않은 확장자일 경우 400 에러 메시지를 반환한다. if 400 in image_urls: return image_urls # 상품 입력을 위한 데이터를 받는다. product_info = { 'seller_id': request.form['seller_id'], 'is_on_sale': request.form['is_on_sale'], 'is_displayed': request.form['is_displayed'], 'name': request.form['name'], 'simple_description': request.form['simple_description'], 'detail_description': request.form['detail_description'], 'price': request.form['price'], 'is_definite': request.form['is_definite'], 'discount_rate': request.form['discount_rate'], 'discount_price': request.form['discount_price'], 'min_unit': request.form['min_unit'], 'max_unit': request.form['max_unit'], 'is_stock_managed': request.form['is_stock_managed'], 'stock_number': request.form['stock_number'], 'first_category_id': request.form['first_category_id'], 'second_category_id': request.form['second_category_id'], 'modifier_id': request.form['modifier_id'], 'images': image_urls, 'discount_start_date': request.form['discount_start_date'], 'discount_end_date': request.form['discount_end_date'], 'product_code': product_code } product_service.insert_product(product_info, session) session.commit() is_success = True return jsonify({'message': 'SUCCESS'}), 200 except KeyError: return jsonify({'message': 'KEY_ERROR'}), 400 except exc.IntegrityError: return jsonify({'message': 'INTEGRITY_ERROR'}), 400 except exc.InvalidRequestError: return jsonify({'message': 'INVALID_REQUEST'}), 400 except exc.ProgrammingError: return jsonify({'message': 'ERROR_IN_SQL_SYNTAX'}), 500 except Exception as e: return jsonify({'message': f'{e}'}), 500 finally: if is_success is False: session.rollback() delete_image_in_s3(image_urls, None) session.close() @product_app.route('/update', methods=['POST'], endpoint='update_product') @login_required(Session) def update_product(): """ 상품 정보 수정 API returns : 200: 상품 정보를 데이터베이스에 저장 400: NAME_CANNOT_CONTAIN_QUOTATION_MARK, START_DATE_CANNOT_BE_EARLIER_THAN_END_DATE, CANNOT_SET_MORE_THAN_20, CANNOT_SET_LESS_THAN_10, DISCOUNT_RANGE_CAN_BE_SET_FROM_0_TO_99, DUPLICATE_DATA, INVALID_REQUEST 500: Exception, ERROR_IN_SQL_SYNTAX Authors: 고지원 History: 2020-10-10 (고지원): 초기 생성 """ session = Session() is_success = False try: # 상품 입력을 위한 데이터를 받는다. old_images = request.form['images'].split(',') product_info = { 'product_id': request.form['product_id'], 'product_code': request.form['product_code'], 'seller_id': request.form['seller_id'], 'is_on_sale': request.form['is_on_sale'], 'is_displayed': request.form['is_displayed'], 'name': request.form['name'], 'simple_description': request.form['simple_description'], 'detail_description': request.form['detail_description'], 'price': request.form['price'], 'is_definite': request.form['is_definite'], 'discount_rate': request.form['discount_rate'], 'discount_price': request.form['discount_price'], 'min_unit': request.form['min_unit'], 'max_unit': request.form['max_unit'], 'is_stock_managed': request.form['is_stock_managed'], 'stock_number': request.form['stock_number'], 'first_category_id': request.form['first_category_id'], 'second_category_id': request.form['second_category_id'], 'modifier_id': g.seller_info['seller_no'], 'images': old_images, 'discount_start_date': request.form['discount_start_date'], 'discount_end_date': request.form['discount_end_date'] } # S3 이미지 저장 images = list() # 1~5 개의 이미지를 가져온다. for idx in range(1, 6): image = request.files.get(f'image_{idx}', None) if image: images.append(image) new_images = product_service.upload_image(product_info['product_code'], images) # 반환된 image_urls 가 허용되지 않은 확장자일 경우 400 에러 메시지를 반환한다. if 400 in new_images: return new_images product_info['new_images'] = new_images product_service.update_product(product_info, session) session.commit() is_success = True return jsonify({'message': 'SUCCESS'}), 200 except KeyError: return jsonify({'message': 'KEY_ERROR'}), 400 except exc.IntegrityError: return jsonify({'message': 'INTEGRITY_DATA'}), 400 except exc.InvalidRequestError: return jsonify({'message': 'INVALID_REQUEST'}), 400 except exc.ProgrammingError: return jsonify({'message': 'ERROR_IN_SQL_SYNTAX'}), 500 except Exception as e: return jsonify({'message': f'{e}'}), 500 finally: if is_success is False: session.rollback() delete_image_in_s3(old_images, new_images) session.close() return product_app
return Response(json.dumps([ [key, key.__class__.__name__], [sure, sure.__class__.__name__], ]), mimetype='application/json') @validate_params(Param('cities', GET, list), Param('countries', GET, dict)) def get(self, cities, countries): return Response(json.dumps({ 'cities': [cities, cities.__class__.__name__], 'countries': [countries, countries.__class__.__name__], }), mimetype='application/json') type_composite = CompositeRule(Enum('type1', 'type2')) @app.route('/main/<string:key>/<string:uuid>', methods=['POST']) @validate_params( Param('key', PATH, str, rules=[Enum('key1', 'key2')]), Param('uuid', PATH, str, rules=[Pattern(r'^[a-z-_.]{8,10}$')]), Param('sure', GET, bool, False), Param('sys', FORM, str, rules=[Pattern(r'^[a-z.]{3,6}$')]), Param('types', FORM, str, rules=type_composite), Param('price', FORM, float, False), Param('cities', FORM, list, False), Param('dql', FORM, dict, False), Param('default', FORM, dict, False, default=lambda: ['test']), ) def route_form(key, uuid, sure, sys, types, price, cities, dql, default):
def create_qna_endpoints(qna_service, Session): qna_app = Blueprint('qna_app', __name__, url_prefix='/api/qnas') @qna_app.route('/qna', methods=['POST', 'DELETE']) @login_required def qna(): """ question 작성 API 사용자가 입력한 문의를 데이터베이스에 입력합니다. returns : 200: question 데이터베이스 입력 400: KEY_ERROR, DELETE_FAILED 500: Exception Authors: 고지원 History: 2020-09-26 (고지원): 초기 생성 2020-09-28 (고지원): 수정 - delete 메소드 추가 - 로그인 데코레이터 추가 2020-10-05 (고지원): 삭제하려는 유저와 문의한 유저가 같은 유저인지 확인하는 코드 추가 """ session = Session() try: if request.method == 'POST': # 문의 입력을 위한 데이터를 받는다. qna_info = { 'type_id': request.json['type_id'], 'user_id': g.user_id['user_id'], 'product_id': request.json['product_id'], 'content': request.json['content'], 'is_private': request.json['is_private'] } qna_service.insert_question(qna_info, session) session.commit() return jsonify({'message': 'INSERT_SUCCESS'}), 200 # 삭제하려는 유저와 문의한 유저가 일치하는지 id 를 통해 확인 question_info = { 'question_id': request.args.get('question_id'), 'user_id': g.user_id['user_id'] } row_count = qna_service.delete_question(question_info, session) # 유저 아이디가 매칭될 경우 row_count = 1, token의 유저 id 와 삭제하려는 유저의 id 가 다를 경우 0 if row_count == 0: return jsonify({'message': 'DELETE_FAILED'}), 400 session.commit() return jsonify({'message': 'DELETE_SUCCESS'}), 200 except KeyError: return jsonify({'message': 'KEY_ERROR'}), 400 except Exception as e: session.rollback() return jsonify({'message': f'{e}'}), 500 finally: session.close() @qna_app.route('', methods=['GET']) @validate_params(Param('limit', GET, int, default=100, required=False), Param('offset', GET, int, required=False), Param('product_id', GET, int, required=True)) def qnas(*args): """ QnA 리스트 전달 API product_id 에 따른 QnA 리스트를 표출합니다. args: product_id: 상품의 pk returns : 200: QnA 리스트 500: Exception Authors: 고지원 History: 2020-09-27 (고지원): 초기 생성 2020-09-30 (고지원): 파라미터 유효성 검사 추가 2020-10-05 (고지원): pagination 추가 2020-10-12 (고지원): 삭제를 위해 로그인 한 유저의 아이디와 함께 반환하도록 수정 """ session = Session() try: qna_info = dict() # pagination qna_info['limit'] = args[0] qna_info['offset'] = args[1] # 상품 상세페이지 상품 아이디 qna_info['product_id'] = args[2] # 로그인한 유저의 문의인지 판단하기 위해 토큰을 통해 id를 가져온다. access_token = request.headers.get('Authorization') if access_token: payload = jwt.decode(access_token, SECRET_KEY, ALGORITHM) else: payload = None body = { 'login_user_id': payload, 'qna': [dict(qna) for qna in qna_service.get_qnas(qna_info, session)] } return jsonify(body), 200 except Exception as e: return jsonify({'message': f'{e}'}), 500 finally: session.close() @qna_app.route('/user', methods=['GET']) @login_required @validate_params( Param('limit', GET, int, default=100, required=False), Param('offset', GET, int, required=False), Param('product_id', GET, int, required=False), Param('is_answered', GET, int, rules=[Enum(0, 1)], required=False), ) def user_qnas(*args): """ 로그인한 user 의 QnA 리스트 전달 API user_id, product_id 에 따른 QnA 리스트를 표출합니다. args : *args: product_id: 상품의 pk is_answered: 답변, 미답변 여부 판단위한 파라미터 g.user_id: 데코레이터에서 넘어온 user 의 pk returns : 200: QnA 리스트 500: Exception Authors: 고지원 History: 2020-09-29 (고지원): 초기 생성 2020-09-30 (고지원): 파라미터 유효성 검사 추가 2020-10-06 (고지원): 로그인 데코레이터에서 id 가져와 해당 유저의 문의만 보여주도록 수정 """ session = Session() try: qna_info = dict() # pagination qna_info['limit'] = args[0] qna_info['offset'] = args[1] # 상품 아이디 (특정 상품의 상세페이지에서 내가 쓴 문의 필터링을 위해) qna_info['product_id'] = args[2] # 유저의 아이디 qna_info['user_id'] = g.user_id['user_id'] # 답변 여부 qna_info['is_answered'] = args[3] body = [ dict(qna) for qna in qna_service.get_qnas(qna_info, session) ] return jsonify(body), 200 except Exception as e: return jsonify({'message': f'{e}'}), 500 finally: session.close() return qna_app
class OrderView: order_app = Blueprint('order_app', __name__, url_prefix='/orders') @order_app.route('/filter_options/<order_status_id>', methods=['GET']) @validate_params( Param('order_status_id', PATH, int, rules=[Enum(1, 2, 3, 4, 5)])) @login_validator def get_filter_options(order_status_id): # 주문관리 페이지에서 셀러속성 리스트와 주문상태 변경 버튼 보내주는 엔드포인트 order_service = OrderService() connection = None try: connection = connect_db() filter_options = order_service.get_filter_options( connection, order_status_id) return jsonify(filter_options), 200 except Exception as e: return jsonify({"message": f'{e}'}), 400 finally: try: if connection: connection.close() except Exception as e: return jsonify({"message": f'{e}'}), 500 @order_app.route('', methods=['GET']) @validate_params( Param('order_status_id', GET, int, rules=[Enum(1, 2, 3, 4, 5)], required=True), Param('order_number', GET, str, rules=[Pattern('^[0-9]{16,}$')], required=False), Param('detailed_order_number', GET, str, rules=[Pattern('^[0-9]{17,}$')], required=False), Param('buyer_name', GET, str, rules=[Pattern('[\S]')], required=False), Param('phone_number', GET, str, rules=[Pattern('^[0-9]{11}$')], required=False), Param('seller_name', GET, str, rules=[Pattern('[\S]')], required=False), Param('product_name', GET, str, rules=[Pattern('[\S]')], required=False), Param( 'start_date', GET, str, rules=[ Pattern( '^(20)[\d]{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$') ], required=False), Param( 'end_date', GET, str, rules=[ Pattern( '^(20)[\d]{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$') ], required=False), # 셀러속성 다중선택 가능 Param('seller_type_id', GET, list, rules=[MinLength(1), MaxLength(7)], required=False), Param('limit', GET, int, rules=[Enum(10, 20, 50, 100, 150)], required=False), Param('order_by', GET, str, rules=[Enum('desc', 'asc')], required=False), Param('page', GET, int, required=False)) @login_validator def get_order_list(*args): # 주문상태별 주문 리스트 필터하여 보내주는 엔드포인트 order_service = OrderService() # page parameter에 0이나 음수가 들어올 경우 키에러 if args[12] and args[12] <= 0: return jsonify({"key error": "Page cannot be negative"}), 400 order_filter = { 'order_status_id': args[0], 'order_number': args[1], 'detailed_order_number': args[2], 'buyer_name': args[3], 'phone_number': args[4], 'seller_name': args[5], 'product_name': args[6], 'start_date': args[7], 'end_date': args[8], 'seller_type_id': args[9], # 디폴트값: 최신주문일순, 50개씩 보기 'limit': args[10] if args[10] else 50, 'order_by': args[11] if args[11] else 'desc', 'page': args[12] if args[12] else 1 } # 셀러일 경우 필터에 seller_id 추가 if g.token_info['account_type_id'] == 2: order_filter['seller_id'] = g.token_info['seller_id'] connection = None try: connection = connect_db() order_list = order_service.get_order_list(connection, order_filter) return jsonify(order_list), 200 except Exception as e: return jsonify({"message": f"{e}"}), 400 finally: try: if connection: connection.close() except Exception as e: return jsonify({"message": f"{e}"}), 500 @order_app.route('', methods=['POST']) @validate_params( Param('order_item_id', JSON, list, required=True), # 2: 상품준비 3: 배송중 Param('order_status_id', GET, int, rules=[Enum(2, 3)], required=True), # 1: 배송처리 2: 배송완료처리 Param('order_action_id', JSON, int, rules=[Enum(1, 2)], required=True)) @login_validator def update_order_status(*args): # 주문 id를 리스트로 받아서 일괄적으로 주문 상태를 업데이트하는 엔드포인트 order_service = OrderService() update_status = { 'editor_id': g.token_info['account_id'], 'order_item_id': args[0], 'order_status_id': args[1], 'order_action_id': args[2] } connection = None try: connection = connect_db() number_of_orders_updated = order_service.update_order_status( connection, update_status) connection.commit() return jsonify({ "message": f"{number_of_orders_updated} order(s) successfully updated" }), 201 except Exception as e: connection.rollback() return jsonify({"message": f"{e}"}), 400 finally: try: if connection: connection.close() except Exception as e: return jsonify({"message": f"{e}"}), 500 @order_app.route('/<order_item_id>', methods=['GET']) @validate_params(Param('order_item_id', PATH, int)) @login_validator def get_order_detail(order_item_id): # 주문 상세정보 가져오는 엔드포인트 order_service = OrderService() order_filter = {'order_item_id': order_item_id} # # 셀러일 경우 필터에 seller_id 추가 # if g.token_info['account_type_id'] == 2: # order_filter['seller_id'] = g.token_info['seller_id'] connection = None try: connection = connect_db() order_detail = order_service.get_order_detail( connection, order_filter) return jsonify(order_detail), 200 except Exception as e: return jsonify({"message": f"{e}"}), 400 finally: try: if connection: connection.close() except Exception as e: return jsonify({"message": f"{e}"}), 500 @order_app.route('/<order_item_id>', methods=['POST']) @validate_params(Param('order_item_id', PATH, int, required=True), Param('new_order_status_id', JSON, int, rules=[Enum(2, 3, 4, 5)], required=False), Param('phone_number', JSON, str, rules=[Pattern('^[0-9]{11}')], required=False), Param('address_1', JSON, str, rules=[Pattern('[\S]')], required=False), Param('address_2', JSON, str, rules=[Pattern('[\S]')], required=False), Param('zip_code', JSON, str, rules=[Pattern('^[0-9]{5}')], required=False), Param('delivery_instruction', JSON, str, required=False)) @login_validator def update_order_detail(*args): # 주문 상세정보(주문상태, 연락처, 배송지 정보)를 업데이트하고 업데이트된 정보를 리턴 order_service = OrderService() order_filter = {'order_item_id': args[0]} update_order = { 'editor_id': g.token_info['account_id'], 'order_item_id': args[0], 'new_order_status_id': args[1], 'phone_number': args[2], 'address_1': args[3], 'address_2': args[4], 'zip_code': args[5], 'delivery_instruction': args[6], } # 셀러일 경우 필터에 seller_id 추가 if g.token_info['account_type_id'] == 2: order_filter['seller_id'] = g.token_info['seller_id'] connection = None try: connection = connect_db() order_service.update_order_detail(connection, update_order) updated_order_detail = order_service.get_order_detail( connection, order_filter) connection.commit() return jsonify({"updated_order_detail": updated_order_detail}), 201 except Exception as e: connection.rollback() return jsonify({"message": f"{e}"}), 400 finally: try: if connection: connection.close() except Exception as e: return jsonify({"message": f"{e}"})
def create_product_endpoints(product_service, Session): product_app = Blueprint('product_app', __name__, url_prefix='/api/products') @product_app.route('/category', methods=['GET']) def product_category(): """ 카테고리 정보 전달 API 상위 카테고리에 따른 하위 카테고리 리스트를 전달합니다. returns : 200: 카테고리 리스트 500: Exception Authors: 고지원 History: 2020-09-21 (고지원): 초기 생성 2020-09-25 (고지원): 한 번의 쿼리로 3개의 카테고리 데이터를 전달하도록 수정 """ session = Session() try: # 메뉴 데이터 categories = product_service.get_menu(None, session) # 각 카테고리를 저장하기 위한 리스트 second_category, first_category, main_category = [], [], [] # JOIN 을 하며 생기는 중복을 제거하기 위해서 중복 체크 후 리스트에 저장 for category in categories: # 1. (메인 카테고리의 id, 이름) 이 main_category 에 없을 경우 append if (category.m_id, category.main_category_name) not in main_category: main_category.append( (category.m_id, category.main_category_name)) # 2. (1차 카테고리의 id, 이름, 이에 해당하는 메인 카테고리의 id) 가 first_category 에 없을 경우 append if (category.f_id, category.first_category_name, category.main_category_id) not in first_category: first_category.append( (category.f_id, category.first_category_name, category.main_category_id)) # 3. (2차 카테고리의 id, 이름, 이에 해당하는 1차 카테고리의 id) 가 second_category 에 없을 경우 append second_category.append( (category.s_id, category.second_category_name, category.first_category_id)) # 카테고리의 계층 구조를 전달하기 위한 JSON 형식 body = [ { # 메인 카테고리의 id 와 이름 'id': m_menu[0], m_menu[1]: [ { # 1차 카테고리의 id 와 이름 'id': f_menu[0], f_menu[1]: [ { # 2차 카테고리의 id 와 이름 'id': s_menu[0], 'name': s_menu[1] } for s_menu in second_category if s_menu[2] == f_menu[0] ] } for f_menu in first_category if f_menu[2] == m_menu[0] ] } for m_menu in main_category ] return jsonify(body), 200 except Exception as e: return jsonify({'message': f'{e}'}), 500 finally: session.close() @product_app.route('', methods=['GET']) @validate_params(Param('limit', GET, int, default=100, required=False), Param('offset', GET, int, required=False), Param('main_category_id', GET, int, rules=[Enum(4, 5, 6)], required=False), Param('first_category_id', GET, int, required=False), Param('second_category_id', GET, int, required=False), Param('is_promotion', GET, int, rules=[Enum(0, 1)], required=False), Param('select', GET, int, rules=[Enum(0, 1)], required=False), Param('q', GET, str, required=False), Param('all_items', GET, int, rules=[Enum(1)], required=False)) def products(*args): """ 상품 정보 전달 API 여러 상품 정보가 필요한 페이지에서 쿼리 파라미터로 필터링에 사용될 값을 받아 필터링된 상품의 데이터들을 표출합니다. args: *args: limit: pagination 을 위한 파라미터 offset: pagination 을 위한 파라미터 main_category_id: 메인 카테고리의 pk first_category_id: 첫 번째 카테고리의 pk second_category_id: 두 번째 카테고리의 pk is_promotion: 세일 여부를 판단하기 위한 파라미터 select: 최신순, 판매량순을 판단하기 위한 파라미터 q: 검색을 위한 파라미터 all_items: 전체 상품 리스트를 전달할지 판단하기 위한 파라미터 returns : 200: 상품 리스트 500: Exception Authors: 고지원 History: 2020-09-21 (고지원): 초기 생성 2020-09-23 (고지원): 파라미터 유효성 검사 2020-09-24 (고지원): 검색을 위한 파라미터 추가, 검색 시 셀러 리스트 추가 2020-09-28 (고지원): 브랜드, 뷰티 메인에서 필요한 데이터를 위한 필터링 추가 2020-10-02 (고지원): 브랜드, 뷰티 메인의 첫 번째 카테고리 상품 리스트 전달 """ session = Session() try: # args[2]: 메인 카테고리의 pk, args[8]: 전체 상품을 보여줄 지 판단하는 파라미터, args[3]: 1차 카테고리의 pk, args[4]: 2차 카테고리의 pk if args[2] == 5 \ or args[2] == 6 \ and not args[8] \ and not args[3] \ and not args[4]: # 특정 메인 카테고리 아이디 (5: 브랜드, 6: 뷰티) 파라미터만 들어올 경우 베스트 상품, 추천 상품 데이터 등을 전달 body = { 'best_items': [], 'brand_items': [], 'recommended_items': [], 'category_items': [] } # 1. 파라미터로 들어온 카테고리의 id (args[2]) 에 따라 특정 셀러를 지정하고 상품 5개만 가져오기 위해 선언, # 2. 특정 1차 카테고리 아이디로 필터링된 상품 리스트를 가져오기 위해 선언 if args[2] == 5: f_cat_list = (12, 13, 14, 15, 16, 17, 18) seller_id = 30 else: f_cat_list = (23, 24, 25, 26, 27, 28) seller_id = 359 for f_cat_id in f_cat_list: # 1. 첫 번째 카테고리 상품 5개 씩 보여주기 위한 필터 f_category_filter = { 'first_category_id': f_cat_id, 'limit': 5 } # 2. 카테고리의 id, name 과 함께 상품 리스트를 반환한다. category_products = { 'category_id': f_cat_id, 'category_name': product_service.get_menu( f_cat_id, session)[0].first_category_name, 'product': product_service.get_products(f_category_filter, session), } body['category_items'].append(category_products) # Best 상품 필터 - 해당하는 메인 카테고리의 상품 중 판매량 순 10개만 가져오기 위해 선언 best_prod_filter = { 'main_category_id': args[2], 'limit': 10, } best_products = product_service.get_products( best_prod_filter, session) # 추천 상품 필터 - 할인율 기준 recommended_prod_filter = { 'main_category_id': args[2], 'limit': 30, 'discount_rate': 1 } recommended_products = product_service.get_products( recommended_prod_filter, session) # 특정 셀러 상품 리스트 필터 seller_filter = { 'main_category_id': args[2], 'seller_id': seller_id } brand_products = product_service.get_products( seller_filter, session) body['best_items'] = best_products body['brand_items'] = brand_products body['recommended_items'] = recommended_products return body # 필터링을 위한 딕셔너리 filter_dict = dict() # pagination filter_dict['limit'] = args[0] filter_dict['offset'] = args[1] # 카테고리 filter_dict['main_category_id'] = args[2] filter_dict['first_category_id'] = args[3] filter_dict['second_category_id'] = args[4] # 세일 filter_dict['is_promotion'] = args[5] # 판매량순, 최신순 filter_dict['select'] = args[6] # 검색 필터 filter_dict['q'] = args[7] # 메인 카테고리의 모든 상품 필터 filter_dict['all_items'] = args[8] body = dict() # 상품 데이터 body['products'] = product_service.get_products( filter_dict, session) # 검색어가 들어올 경우 전달하는 셀러 정보 if filter_dict['q']: # 필터링된 셀러 리스트를 가져오기 위한 필터 seller_info = dict() seller_info['name'] = filter_dict['q'] seller_info['limit'] = 100 # 검색된 셀러 리스트 정의 sellers = dict() # 검색어에 해당하는 셀러의 리스트 seller_list = [ dict(seller) for seller in product_service.get_sellers( seller_info, session) ] # 셀러 검색 결과 개수 sellers['count'] = len(seller_list) # 셀러 데이터 sellers['seller_list'] = seller_list body['sellers'] = sellers return jsonify(body), 200 except Exception as e: return jsonify({'message': f'{e}'}), 500 finally: session.close() @product_app.route('/product/<int:product_id>', methods=['GET']) def product(product_id): """ 상품 상세 정보 전달 API path parameter 를 받아 한 상품의 상세 데이터를 표출합니다. args: product_id: 상품의 pk returns : 200: 상위 카테고리에 따른 하위 카테고리 리스트 500: Exception Authors: 고지원 History: 2020-09-23 (고지원): 초기 생성 2020-09-24 (고지원): seller 데이터를 한 번의 쿼리로 가지고 오도록 수정 """ session = Session() try: # 상품 데이터 body = dict(product_service.get_product(product_id, session)) return jsonify(body), 200 except Exception as e: return jsonify({'message': f'{e}'}), 500 finally: session.close() @product_app.route('/seller', methods=['GET']) @validate_params( Param('limit', GET, int, default=100, required=False), Param('offset', GET, int, required=False), Param('main_category_id', GET, int, rules=[Enum(4, 5, 6)], required=False), Param('select', GET, int, rules=[Enum(0, 1)], default=1, required=False), ) def sellers(*args): """ 셀러 리스트 전달 API query parameter 를 받아 필터링된 셀러 리스트 데이터를 표출합니다. args: *args: limit: pagination 을 위한 limit offset: pagination 을 위한 offset main_category_id: 셀러 속성을 필터링 하기 위한 파라미터 select: 1 일 경우 판매량 순, 0 일 경우 최신 순으로 필터링 returns : 200: 셀러 리스트 500: Exception Authors: 고지원 History: 2020-09-30 (고지원): 초기 생성 """ session = Session() try: # 필터링을 위한 딕셔너리 seller_dict = dict() seller_dict['limit'] = args[0] seller_dict['offset'] = args[1] seller_dict['main_category_id'] = args[2] seller_dict['select'] = args[3] # 셀러 데이터 body = [ dict(seller) for seller in product_service.get_sellers( seller_dict, session) ] return jsonify(body), 200 except Exception as e: return jsonify({'message': f'{e}'}), 500 finally: session.close() return product_app
return "Unravel SaaS" # E.g., http://127.0.0.1:5000/start_trial?first_name=Alejandro&last_name=Fernandez&company=Unravel&title=Engineer&[email protected]&cloud_provider=EMR&send_email=true @app.route("/start_trial", methods=["GET", "POST"]) @validate_params(Param("first_name", GET, str, required=True), Param("last_name", GET, str, required=True), Param("company", GET, str, required=True), Param("title", GET, str, required=True), Param("email", GET, str, required=True, rules=[RuleEmail()]), Param("cloud_provider", GET, str, required=True, rules=[ Enum("EMR", "HDI", "GCP", "DATABRICKS_ON_AWS", "DATABRICKS_ON_AZURE") ]), Param("send_email", GET, bool, required=False, default=lambda: False)) def start_trial(first_name, last_name, company, title, email, cloud_provider, send_email): """ Start a free-trial of Unravel on the Cloud. :param first_name: First name (str) :param last_name: Last name (str) :param company: Company name (str) :param title: Job title (str) :param email: Email address (str)
def create_qna_endpoints(qna_service, Session): qna_app = Blueprint("qna_app", __name__, url_prefix="/api/qnas") @qna_app.route("/qna", methods=["POST", "DELETE"]) @login_required def qna(): session = Session() try: if request.method == "POST": # 문의 입력을 위한 데이터를 받는다. qna_info = { "type_id": request.json["type_id"], "user_id": g.user_id["user_id"], "product_id": request.json["product_id"], "content": request.json["content"], "is_private": request.json["is_private"], } qna_service.insert_question(qna_info, session) session.commit() return jsonify({"message": "INSERT_SUCCESS"}), 200 # 삭제하려는 유저와 문의한 유저가 일치하는지 id 를 통해 확인 question_info = { "question_id": request.args.get("question_id"), "user_id": g.user_id["user_id"], } row_count = qna_service.delete_question(question_info, session) # 유저 아이디가 매칭될 경우 row_count = 1, token의 유저 id 와 삭제하려는 유저의 id 가 다를 경우 0 if row_count == 0: return jsonify({"message": "DELETE_FAILED"}), 400 session.commit() return jsonify({"message": "DELETE_SUCCESS"}), 200 except KeyError: return jsonify({"message": "KEY_ERROR"}), 400 except Exception as e: session.rollback() return jsonify({"message": f"{e}"}), 500 finally: session.close() @qna_app.route("", methods=["GET"]) @validate_params( Param("limit", GET, int, default=100, required=False), Param("offset", GET, int, required=False), Param("product_id", GET, int, required=True), ) def qnas(*args): session = Session() try: qna_info = dict() # pagination qna_info["limit"] = args[0] qna_info["offset"] = args[1] # 상품 상세페이지 상품 아이디 qna_info["product_id"] = args[2] # 로그인한 유저의 문의인지 판단하기 위해 토큰을 통해 id를 가져온다. access_token = request.headers.get("Authorization") if access_token: payload = jwt.decode(access_token, SECRET_KEY, ALGORITHM) else: payload = None body = { "login_user_id": payload, "qna": [dict(qna) for qna in qna_service.get_qnas(qna_info, session)], } return jsonify(body), 200 except Exception as e: return jsonify({"message": f"{e}"}), 500 finally: session.close() @qna_app.route("/user", methods=["GET"]) @login_required @validate_params( Param("limit", GET, int, default=100, required=False), Param("offset", GET, int, required=False), Param("product_id", GET, int, required=False), Param("is_answered", GET, int, rules=[Enum(0, 1)], required=False), ) def user_qnas(*args): session = Session() try: qna_info = dict() # pagination qna_info["limit"] = args[0] qna_info["offset"] = args[1] # 상품 아이디 (특정 상품의 상세페이지에서 내가 쓴 문의 필터링을 위해) qna_info["product_id"] = args[2] # 유저의 아이디 qna_info["user_id"] = g.user_id["user_id"] # 답변 여부 qna_info["is_answered"] = args[3] body = [dict(qna) for qna in qna_service.get_qnas(qna_info, session)] return jsonify(body), 200 except Exception as e: return jsonify({"message": f"{e}"}), 500 finally: session.close() return qna_app