[기업협업] 퀀텀AI -Day 4~6-

제갈창민·2022년 1월 1일
0

기업협업[퀀텀AI]

목록 보기
4/18

삶이란 시련과 같은 말이야.

[ 노라조, "형" ]

D

Day4였던 어제는 위코드 수강생 중(다른 기수) 한 명이 양성판정을 받았다고 하여, 기업협업 중이던 우리 기수 포함 위코드 전체가 바로 검사를 받고 재택근무로 전환되었다. 출근을 하고 아침 9시30분쯤 받은 공지였기에, 재빨리 사수에게 보고를 하고 팀원과 함께 집으로 돌아갔다. 정말이지, 우리 27기는 기가막히게 타이밍이 좋은 것 같다는 생각이 새삼 들었던 하루였다.

R

루머였으면 좋겠다는 생각이 들게 만드는 새 변이 바이러스때문에 아침부터 한바탕 어수선했지만, 집에 와서도 당연히 DRF에 대한 공부는 계속되었다. 사수님이 내주신 과제의 2단계를 아직 통과 못했기 때문에 어떻게든 이번주안에 끝내고자 마음을 먹었다.

Shop 검색했을때 해당 매장 총 방문객(total) + 방문객 리스트 (이름)

방문객 total은 비교적 간단하지만, 처음엔 받아오는 데이터의 속성을 잘 몰라서 많이 헤맸다. 일단 먼저 Visited table이 Shop 과 Customer 의 FK를 갖고 있으므로 data를 통해 얻게되는 shop_id 로 Visited table 의 shop_id 를 필터링해서 visitors에 담는다. 그 다음 visitors에는 customer_id도 들어있으므로 Customer 의 id 와 일치하는 id 만 필터링 해서 .count로 세어 보면 어떨까? 라고 생각했다.(왜그랬지?)

class ShopSerializer(ModelSerializer):
    customer_total = serializers.SerializerMethodField()

    class Meta:
        model = Shop
        fields = '__all__'

    def get_customer_total(self, data):
        visitors = Visited.objects.filter(shop_id = data.id)
        customer = Customer.objects.filter(id = visitors.customer_id)
        return customer

Okay, AttibuteError 가 나왔다. 'QuerySet' 은 'customer_id'속성을 갖고 있지 않다. 즉, .customer_id 같은 문법을 사용하려면 .get() 을 사용해 단일 객체로 얻은 value 뒤에 사용하거나, object가 여러개라면 .filterfor loop 로 하나씩 꺼낸 다음 사용해야 했다. 스택오버플로우에 이와 같은 답변이 있었다. Stackoverflow answer
자, 그럼 수정해 보자.

def get_customer_total(self, data):
    visitors = Visited.objects.filter(shop_id = data.id)
    customers = [i.customer_id for i in visitors]
    return customers.count()

음, .count() 에 값이 주어지지 않았다는 에러가 떴다. print를 찍어보니, customers 가 숫자로 된 list 였다. 오호, 그럼 len() 을 쓰면 되겠다.

    return len(customers)

F

Finally, 에러 페이지를 지나 결과값이 나왔는데, 뭔가 이상했다.
??? 손님 이름은 넷인데, total 은 11명이다. print된 결과를 다시 살펴보니 중복 된 값들이 있었다. 중복하는 값들을 포함할지 말지는 데이터의 사용처에 따라 달라질테니, 사수님에게 어떤 결과를 도출 해야할지 여쭤보았고, 손님이 누군지에 상관없이 총 방문 손님 수가 필요하다고 하셔서 답은 11명이 맞았다. 여담으로 중복된 값을 제거 하고 싶다면 list의 중복 값을 삭제해주는 set()을 쓰면 된다.

    return len(set(customers))

정확하게 4가 나왔다.
중복된 값을 포함하는 풀 코드는 아래와 같다.

def get_customer_total(self, data):
    visitors = Visited.objects.filter(shop_id = data.id)
    customers = [i.customer_id for i in visitors]
    return len(customers)

list comprehension도 적용해서 '음, 좋군' 하고 생각하고 있는데, 사수님이 스윽- 오시더니 씨익- 웃으시면서 한 마디 하셨다.

좋은데요, 굳이 customer 의 id를 찾아야 할 필요가 있을까요? DB column 이 어떻게 생겼더라?

어떻게 생기긴요, 이렇게 생겼죠.
오, 이제보니 shop_id 도 11개넹ㅎㅎ.....?

그래 맞다. 애초에 for loop 따위 필요가 없었다. customer_id 를 세나, shop_id를 세나 갯수는 똑같이 11개 였으니까. 이미 코드의 첫 줄에서 답이 거의 나왔던 것이다.

def get_customer_total(self, data):
    customer = Visited.objects.filter(shop_id = data.id).count()
    return customer

분명 처음엔 .count를 써보려고 했는데, 어느 순간 잊어버리고 다른 쪽을 파고 있었다. data에 대한 이해도와 활용도 면에서 아직도 멀었다는 거겠지.

그렇게 total 에 대한 코드를 완성하고 곧바로 방문한 손님들의 이름을 출력하는 코드를 짜기 시작했다. 이름은 중복되면 안된다는 전제조건이 붙었고, 방문한 손님들의 숫자를 알아냈으니, 이름은 해당 id들이 있는 table에서 name만 추출하면 되지 않을까? 하는 생각으로 출발했다.

def get_customer_name(self, data):
    visitors = Visited.objects.filter(customer_id = data.id).select_related('customer')
    customers = [i.customer.name for i in visitors]
    return customers

하는 김에 프로젝트 막바지에 배웠던 ORM 최적화도 적용해 보고 싶어서 select_related() 를 사용했다. 결과가 나오기는 나왔는데,

같은 이름이 손님의 숫자만큼 출력되었다. visitors의 필터링이 잘못 된 것인데, data.id 는 Shop table 의 id 였을테니, customer_id 역시 하나로 통일 된 것이다. 이후 아래와 같은 여러가지 방법을 시도했다.

"for 를 한번 더 쓰는 코드"
customers = [[a.customer.name for a in i] for i in visitors]

"data 에서 출발하는 for 문"
result=[[visitors.customer.name for visitors in shop.visited_set.all() 
       ]for shop in data]

"data 에 visited table 을 역참조 하면서 시작하는 for 문"
customer = [visitor for visitor in data.visited_set.all()]

여러 곳에 customer, _set.all(), .filter()', 'name 등등 쓸 수 있는건 다 가져다 붙여보다가 마지막 코드에서 다른 종류의 에러를 만났다.

TypeError: Object of type Visited is not JSON serializable

이건.. QuerySet 을 그대로 출력하려고 할때 만날 수 있는 에러다. 그렇다면 출력 할 수 있는 값을 찾아주면 뭐든 나올거라는 얘기. 곧바로 코드를 수정했다.

customer = [visitor.name for visitor in data.visited_set.all()]

name을 찾고 있었으므로 바로 name을 갖다 붙였지만, 에러를 보자마자 잘못 된 부분을 깨달았다.

AttributeError: 'Visited' object has no attribute 'name'

Visited table 에는 name 이 없다. name 이 있는 곳은 Customer table 이니까.

customer = [visitor.customer.name for visitor in data.visited_set.all()]


카페에서 박수쳤다가 옆에 아기랑 눈 마주쳤다. 원하던 customer_name 이 모두 나왔고 이제 남은건 중복 제거 뿐. 그렇게 완성된 풀 코드.

customer = set([visitor.customer.name for visitor in data.visited_set.all()])
    return customer
"만들고 보니, 변수 지정도 필요없어 보였다. 한 줄이라도 더 짧게 만들고 싶다면,"
return set([visitor.customer.name for visitor in data.visited_set.all()])
"이렇게도 가능하다."
serializers.py
class ShopSerializer(ModelSerializer):
    customer_name = serializers.SerializerMethodField()
    customer_total = serializers.SerializerMethodField()

    class Meta:
        model = Shop
        fields = '__all__'

    def get_customer_name(self, data):
        return set([visitor.customer.name for visitor in data.visited_set.all()])
    
    def get_customer_total(self, data):
        customer = Visited.objects.filter(shop_id = data.id).count()
        return customer

2번 과제를 마무리 하며 금요일이 끝났고, 조금 남은 2021년의 마지막 시간은 github을 정리하며 조용하게 마무리 되었다. 과제를 하며 작성한 코드는 위쪽에 작성했으니, 없으면 허전한 오늘의 TIL 은 repogitory 를 정리하면서 사용한 git 명령어를 기록하겠다.

TIL

  • github에 이미 존재하는 repository 끼리 합치려고 할때 :

    • git subtree add --prefix=new_folder git@github.com:aaaa/bbbb.git main
    • git subtree add --prefix=(새 폴더명) (가져오려고 하는 git url) (가져올 branch)
    • 이후 git add -> git commit -> git push origin (branch) 를 진행하면 된다.
      참고 블로그 : git repository, log 함께 합치기
  • 합치다 보면, 갑자기 클릭이 안되고 '화살표'모양이 있는 폴더가 만들어 졌다면 :

    • .git folder 삭제 = rm -rf .git
    • 스테이지에 존재하는 파일 제거 = git rm --cached . -rf
    • 이후 git add -> git commit -> git push origin (branch) 를 진행하면 된다.
      참고 블로그 : 깃허브 접근 불가 폴더 해결법

2022년도 화이팅

부딪히고 실컷 깨지면서 살면, 그게 인생 다야. 넌 멋진 놈이야.

[ 노라조, "형" ]

profile
자기계발 중인 신입 개발자

0개의 댓글