Wecode-最初のプロジェクト後期-Trendi


위코드 부트 캠프最初のプロジェクトが終わりました.Backendで関連APIに参加し実現した.初めてなので、これまで勉強してきたPythonDjangoのORMに対して実習のコンセプトです.思ったほど簡単ではありません.思ったより開発時間が短く、モデリングに時間がかかりました.
Wecode第一項目:Brandi Service PartClone
https://www.brandi.co.kr/
  • 期間    : 11月16日~11月27日(約11日)
  • チーム:フロントエンド(2人)、バックエンド(3人)
  • 1.モデリング


    まずモデリングをします.バックエンド開発者として、RDBMSを理解する必要があります.開発期間を定め、어디까지 기능 구현을 할 것 인가を追及し、その放棄すべきものを果敢に放棄した.また、進行に伴ってコラムを修正したり追加したりすることがよくあります.
    これまで作成してきた表関係図を見てDMLだけを書きました.最初からデータベースモデリングを考慮する機会はあまりありません.だからおもしろい
    鉄塔の名称や属性、長さ、制限事項を特定する際には、苦悩と根拠が必要である.また、政策は基礎となるべきだ.
    これらの部分を短時間で特定するのは難しい.でもとにかくAgile-Scrumどこかで会ったことがあります...프로그래밍하고 싶어? 그럼 무조껀 만들고 배포해봐!この言葉はある程度正しいので、これらを全部計算すれば、数ヶ月かかっても配置のレベルに達することはできません.どうせやってみよう.
    最終版は以下の通り.これから見るときは恥ずかしがってほしいこれはモデリングが成長したことを意味します...

    2.初期データを作成します。

    APIを実装し、テストを行うには、データが必要です.
    最も困難な部分は著作権のないイメージを探すことだ.
    データの担当者この仕事をしているうちに、ORMの便利さと速さを感じました.ORMは便利すぎるのではないかと思うので、初期の大量のデータを作るのにあまり時間がかかりませんでした.
    アクセルを用いてデータをcsvにし,データを表に読み込む操作を考えると,どれだけの攻守が必要かを考える.気をつけないとまた修正します...ORM万歳.
    一般データの入力は問題なく、注文情報の入力に手間がかかります.フィルタリングおよびソート機能を実現するために、注文情報テーブルの생성 날짜または갱신 날짜の値はランダムでなければならない.
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    Objects.save()のため、日付値を更新できないという問題が発生しました.長い間探していたが...editable=True属性を与えなければsave()メソッドを使用して更新できません.editable=Trueをプロパティに追加したくない場合は、update()メソッドで変更できます.
    やはりそう簡単にはいかない.このような試行錯誤はいつでも歓迎される.
    実際、データ作成の過程でORMやPythonの様々な方法を学びました.
    つまり、10000個を超える基本データの受注情報を迅速に生成することができる.10000枚のレコードを生成する必要はありません...フロンテンドで非同期の練習もできます
    insert_data.py, delete_data.py
    上に2つのファイルを添付したいのですが、添付の機能がないようなので、コードの大まかな流れしか使えません...
    insert_data.py
    import csv
    import os
    import django
    import random
    from django.db.models import Max, Q
    from datetime import date, datetime, timedelta
    from django.utils import timezone
    import pytz
    
    # unset constraint =======================================================
    # SET FOREIGN_KEY_CHECKS = 0;
    # SET FOREIGN_KEY_CHECKS = 1;
    
    # reset AI =======================================================
    # ALTER TABLE categories AUTO_INCREMENT = 1
    # ALTER TABLE sub_categories AUTO_INCREMENT = 1
    # ALTER TABLE orders AUTO_INCREMENT = 1;
    # ALTER TABLE order_lists AUTO_INCREMENT = 1;
    # ALTER TABLE deliveries AUTO_INCREMENT = 0;
    
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "trendi.settings")
    django.setup()
    
    from user.models import *
    from product.models import *
    from order.models import *
    from review.models import *
    from favor.models import *
    
    def insert_category():
        CSV_PATH_1 = "csv/1_categories.csv"
        with open(CSV_PATH_1, newline='', encoding='utf-8-sig') as csv_file:
            data_reader = csv.reader(csv_file)
            next(data_reader, None)
            for row in data_reader:
                name = row[1]
                Category.objects.create(
                    name=name
                )
      
        print("=================================================================================")
        print("categories 데이터가 정상적으로 추가되었습니다.")
    
    #...
    #...
    #...
    #...
    
    def insert_reviews():
        content = [
            "만족합니다. 별 다섯개 드립니다. 짱짱!!",
            "가격 대비 입기 좋아요. 그런데 한 번 빨면 옷감이 조금 상해요..ㅠㅠ",
            "넘 마음에들고 이뻐요..! 슬림이라서 더 얇아보이는 효과가 있기도하고 ??",
            "재질도 부드럽고 핏도 예쁘고 데일리로 입기 좋아요!ㅎㅎ",
            "만족하지만 배송이 조금 느렸습니다...",
            "완전히 핏되진 않은데 이가격에 두개면 개이득입니다.. ! ^^",
            "생각한거만큼 안예뻤어요 ㅎㅎㅎ 역시 믿고보는 트랜디입니다:) 마지막엔 품질이 다른거보다 살짝쿵살짝 없네 했는데 진짜진짜 너무 이뽀!! 파세요",
            "까끌까끌한 필링이여서 싫어요 종종 안을 것 같아요..",
            "리뷰가 적은 이유를 알겠어요! 다 안 예쁘다 해서 품질고민을 오래하다 초록색 샀는데 너무 싫어요!!!!",
            "같이 입어도 색이 따뜻해보여서 좋아요 !! 상의는 항상 밑이 어정쩡해서 행복한데 상의는 딱 여리여자해서 치마나 치킨마요 다 잘어울리는 "
            "상의이에요! 다른 색상도 너무 사고싶당...",
            "지하상가에서 산거같아도 입으면 진짜 조낸 이쁘고 남들이 다 탐나할거같은 그런그런그런 너무너무너무 이뻐용 히히",
            "진짜 이런 상품이 있다니 삼만구천팔백원에 팔아도 될듯 >_+<",
            "단추 끼우는 구멍이 너무 작아ㅕ ㅠ공장에서 공정이 잘 안된듯,. 다시 공정하게 해주세여ㅑ",
            "완전 이뻐요~~ 인스타 들어가서 영상보시면 더 이뽀용 >_< 털 빠짐 제로!!",
        ]
        
        for row in range(1, 10000):
            product = Product.objects.get(id=random.randint(1, 253))
            review = Review(
                content = content[random.randint(0, len(content)-1)],
                star = random.randint(2, 5),
                user = User.objects.get(id=random.randint(1, 50)),
                product = product
            )
            review.save()
       
        print("=================================================================================")
        print("reviews 데이터가 정상적으로 추가되었습니다.")
    
    def insert_basic_order():
        # 주문 기본 데이터
        # 유저 1에서 10이 구매하는 새로운 1개의 오더가 만들어짐
        ordered = Order.objects.create(
            order_number=str(1000),
            delivery_fee = 0,
            user=User.objects.get(id=random.randint(1, 10)),
            orderstatus=OrderStatus.objects.get(id=random.randint(1, 5)),
        )
        
        order_list = OrderList(
            quantity=random.randint(1, 2),
            order=ordered,
            product=Product.objects.get(id=random.randint(1, 10)),
            size = Size.objects.get(id=random.randint(1, len(Size.objects.all()))),
            color = Color.objects.get(id=random.randint(1, len(Color.objects.all())))
        )
        order_list.save()
    
    def random_date(start, end):
        delta = end - start
        int_delta = (delta.days * 24 * 60 * 60) + delta.seconds
        random_second = random.randrange(int_delta)
        return start + timedelta(seconds=random_second)
    
    def insert_order_lists():
        print("insert_order_lists=======================================")
        d1 = datetime.strptime('11/13/2020 10:30 AM', '%m/%d/%Y %I:%M %p')
        d2 = datetime.strptime('11/26/2020 04:50 AM', '%m/%d/%Y %I:%M %p')
        
        for i in range(1, 10000):
            # order_id +1 시키기
            new_order_id = Order.objects.all().aggregate(Max('id'))['id__max'] + 1
            
            # order_number +1 시키기 (현재 str 값)
            order_number = Order.objects.all().aggregate(Max('order_number'))['order_number__max']
            new_order_number = str(int(order_number) + 1)
            
            # 2020/10/20 ~ 2020/11/21 까지의 랜덤 날짜
            ordered_date = random_date(d1, d2)
            
            ordered = Order.objects.create(
                order_number = new_order_number,
                user = User.objects.get(id=random.randint(1, 10)),
                orderstatus = OrderStatus.objects.get(id=random.randint(1, 5)),
            )
            
            product_ids = []
            for i in range(0, 3):
                product_ids.append((random.randint(1, 253)))
    
            product_ids_set = list(set(product_ids))
    
            for i in range(len(product_ids_set)):
                OrderList.objects.create(
                    quantity = random.randint(1, 3),
                    order = ordered,
                    product = Product.objects.get(id=product_ids_set[i]),
                )
            
            Order.objects.filter(order_number=new_order_number).update(updated_at=ordered_date)
            order = Order.objects.get(order_number=new_order_number)
            OrderList.objects.filter(order=order).update(updated_at=ordered_date)
        
        print("=================================================================================")
        print("order 데이터 10000개가 정상적으로 추가되었습니다.")
        print("order_list 10000개가 데이터가 정상적으로 추가되었습니다.")
        print("만들어진 데이터는 order_status 1~5 까지의 데이터 입니다.")
        print("=================================================================================")
    delete_data.py
    # print("=================전체 데이터 삭제 시작=================")
    # def delete_all_data():
    #     User.objects.all().delete()
    #     Seller.objects.all().delete()
    #     Color.objects.all().delete()
    #     Size.objects.all().delete()
    #     Sale.objects.all().delete()
    #     Delivery.objects.all().delete()
    #     OrderStatus.objects.all().delete()
    #     Order.objects.all().delete()
    #     OrderList.objects.all().delete()
    #     Destination.objects.all().delete()
    #     Review.objects.all().delete()
    #     ProductDetailImage.objects.all().delete()
    #     SubCategory.objects.all().delete()
    #     Category.objects.all().delete()
    #     Product.objects.all().delete()
    # print("=================전체 데이터 삭제 완료=================")
    #
    # # alter table users auto_increment = 1;
    # # alter table sellers auto_increment = 1;
    # # alter table colors auto_increment = 1;
    # # alter table sizes auto_increment = 1;
    # # alter table sale_ratios auto_increment = 1;
    # # alter table deliveries auto_increment = 1;
    # # alter table status auto_increment = 1;
    # # alter table orders auto_increment = 1;
    # # alter table order_lists auto_increment = 1;
    # # alter table destinations auto_increment = 1;
    # # alter table reviews auto_increment = 1;
    # # alter table product_detail_urls auto_increment = 1;
    # # alter table sub_categories auto_increment = 1;
    # # alter table categories auto_increment = 1;
    # # alter table products auto_increment = 1;
    最初は確かにcsvの仕事をしたことがありますね.ファイルI/Oを勉強した以上、もったいないことではありません.
    その後DDL truncateが用いられ、初期化シーケンスを必要とせずに自動的に1から入ることができるようになった.

    3.商品リストの配布(フィルタリング、注文)


    まず要求事項を整理した.
    1.ホームページボタンをクリックした場合(全商品基準)
  • お勧め:Columbradi pickはtrueの商品リストです.
  • 画面に表示されている商品は、商品の販売量順に配布されます.
  • 2.ランキングボタンをクリックした場合(全商品中)
  • 日の販売量を基準に、カタログを公開します.(オプションボックス)
  • または、週単位販売量を基準として商品リストを配布します.(オプションボックス)
  • または、月間販売量を基準にして商品リストを発表します.(オプションボックス)
  • の上の規格を加えると、「1日配送」の商品しか濾過できません.
  • さらに
  • の上の規格を加えると、「割引」の中の商品しか濾過できません.
  • の上の規格を加えると、商品の「カテゴリ」をフィルタリングすることができます.
  • このように、最大で起こり得る条件の例を以下に示す.
    日间の贩売量の顺番によって、ただ1日の出荷の商品だけを出して、その中の割引の商品の、分类の何回の商品のリスト
    3.毎日配送ボタンをクリックした場合(全商品基準)

  • 毎日配送される商品を人気順に商品リストを配布します.
    △人気順は販売量を基準にしています.人気をどう決めるかという政策を立てていないので、勝手に決めました.

  • または、최신 순で商品リストを送信します.

  • または、가격 순で商品リストを送信します.

  • 以上の規格に加えて세일 중の商品を濾過することができる.

  • 上記の規格を加えると、商品の카테고리を濾過することができます.
  • このように最大で起こり得る条件の例を以下に示す.
    人気順に1日配送された商品を抽出し、価格順にソートして割引商品のみを表示し、カテゴリNの商品のみを表示します.
    4.ショッピングモール/スーパーボタンをクリックした場合(全商品中)

  • brandi pickがtrueの商品リストを表示します.

  • 上記仕様と異なり、카테고리 별서브 카테고리별の商品を表示することができる.

  • カテゴリまたはサブカテゴリ別に商品を表示する場合は、次の条件を追加できます.판매량순 / 최신 순 / 리뷰 많은 순 / 가격 순

  • さらに하루 배송 / 세일 중人分の商品を濾過できる.
  • このように最大で起こり得る条件の例を以下に示す.
    カテゴリ番号2、コメントの多い順、1日配送、割引商品を表示します.
    以上の条件はすべてFrontend側からGETメソッドのQuery String値に遷移する.
    1つのビューを使用してAPIを実装することは最も困難であり、Qオブジェクトを使用してほとんどの困難を解決した.
    コードは以下の通りです.
    def get(self, request):
            try:
                search        = request.GET.get('search')
                is_pick       = request.GET.get('trendi-pick')
                ranking_by    = request.GET.get('ranking')
                is_sale       = request.GET.get('sale')
                is_delivery   = request.GET.get('delivery')
                category      = request.GET.get('category')
                sub_category  = request.GET.get('sub-category')
                ordering      = request.GET.get('ordering')
                
                products = Product.objects.\
                    filter(orderlist__order__orderstatus_id=5).\
                    select_related(
                        'seller',
                        'delivery',
                        'sale',
                        'category',
                        'sub_category'
                    ).\
                    prefetch_related(
                        'orderlist_set',
                        'review_set'
                    ).\
                    annotate(sum=Sum('orderlist__quantity')).\
                    annotate(review_count=Subquery(
                        Review.objects.filter(product=OuterRef('pk')).values('product').
                            annotate(count=Count('pk')).values('count'))
                    )
    
                q = Q()
    
                if is_pick:
                    q.add(Q(trendi_pick=is_pick), Q.AND)
    
                if is_sale:
                    q.add(Q(sale_id__gt=is_sale), Q.AND)
    
                if ranking_by:
                    rank_filter = {'day' : 1, 'week' : 7, 'month' : 30}
                    date = datetime.today() - timedelta(days=rank_filter[ranking_by])
                    q.add(Q(orderlist__updated_at__gt=date), Q.AND)
    
                if category:
                    q.add(Q(category=category), Q.AND)
    
                if sub_category:
                    q.add(Q(sub_category=sub_category), Q.AND)
    
                if is_delivery:
                    q.add(Q(delivery__delivery_type=is_delivery), Q.AND)
    
                sort_type = {
                    'latest'  : '-updated_at',
                    'review'  : '-review_count',
                    'l-price' : 'price',
                    'h-price' : '-price',
                    None      : '-sum'
                }
    
                if not ordering:
                    products = products.order_by(sort_type[ordering])
    
                if ordering in sort_type:
                    products = products.order_by(sort_type[ordering])
    
                if search:
                    products = Product.objects.select_related(
                        'seller', 'delivery', 'sale',
                    )
    
                    q &= Q(title__icontains              = search) |\
                         Q(category__name__icontains     = search) |\
                         Q(sub_category__name__icontains = search) |\
                         Q(seller__name__icontains       = search)
                
                product_list = [
                    {
                        'is_pick'         : product.trendi_pick,
                        'image_url'       : product.thumb_image_url,
                        'seller_name'     : product.seller.name,
                        'title'           : product.title,
                        'delivery'        : product.delivery.delivery_type == 1,
                        'sale'            : convert_sale(product.sale.sale_ratio),
                        'discounted_price': get_discounted_price(
                                                product.price,
                                                product.sale.sale_ratio
                                            ),
                        'price'           : product.price,
                        'updated_date'    : product.updated_at,
                        'product_pk'      : product.pk,
                    } for product in products.filter(q)
                ]
                
                number_of_products = products.filter(q).count()
    
                if not product_list:
                    return JsonResponse({"message": "NO_RESULT"}, status=400)
    
                return JsonResponse(
                    {
                        "number_of_products": number_of_products,
                        "product_list"      : product_list,
                    }, status=200
                )
            
            except TypeError:
                return JsonResponse({"message": "TYPE_ERROR"}, status=400)
    
            except KeyError:
                return JsonResponse({"message": "KEY_ERROR"}, status=400)
    
            except Product.DoesNotExist:
                return JsonResponse({"message": "NOT_EXIST_PRODUCT"}, status=400)
    **raw SQLを使用する場合、mapperはほとんど使用されます.ORMは確かに便利です
    この部分を実装する際にサブクエリを使用して審査数を取得するのは、実際にはよくありません.
    *データがロードされるほどSubQueryのパフォーマンスが低下するためです.groupbyやdistictを使うのは夢でも考えられません.もっと遅いです.
    解決策は?別のテーブルを作ってください.
    でもそれじゃもっと负けちゃう気がして….
    ロック処理とか...初期に...待ってとにかく...うん.言葉では説明できない...Databaseを勉強している間によく勉強できなかった.急にそう思った.何でも言叶で说明できるのが本当の知识だ.

    ポスト


    ターゲットのすべての機能を達成できませんでした.git 린이 로써 Gitを作っている時に多くの人に聞いたことがあるようです
    残念なことに、Agile-Scrumはうまく応用されていますか?この点.
    うん.適用されないようです.何でも、初めては疎遠でした.