[pre_onboarding] Wantedlab 코드 분석
<개인 공부를 위한 블로깅... 가독성이 낮다.😮💨>
🍆 select_related()
-> select_related 은 foreign-key , one-to-one 와 같은 single-valued relationships에서만 사용이 가능.
a = Entry.objects.get(id=5)
# Hits the database.
b = a.blog
# Hits the database again to get the related Blog object.
-> a, b모두 데이터베이스를 건드린다.
a = Entry.objects.select_related('blog').get(id=5)
# Hits the database again to get the related Blog object.
b = a.blog
# Doesn't hit the database, because a.blog has been prepopulated
# in the previous query.
-> a에서 관련 블로그 객체를 가져오기위해 데이터 베이스를 건드렸고, b에서는 데이터베이스를 건드리지 않는다. 왜냐면 select_related를 통해 a가 참조하고 있는 대상을 caching. a.blog를 읽을 때 다시 db로 갈 필요 없이 caching되어있는 값을 읽어오니까 db 안건드림. 이게 되게 효율적인 것.
🔍 더 깊게 들어가면!
class City(models.Model):
name = models.CharField(max_length=20)
class Person(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
class Pet(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
# Hits the database with joins to the person and city tables.
choon_sik = Pet.objects.select_related('person__city').get(id=1)
zunky = choon_sik.person # Doesn't hit the database.
seoul = zunky.city # Doesn't hit the database.
-> select_related를 통해 person과 city 테이블을 조인, database를 한번만 건들이게됨.
# Without select_related()...
choon_sik = Pet.objects.get(id=1) # Hits the database.
zunky = choon_sik.person # Hits the database.
seoul = zunky.city # Hits the database
-> 3번을 데이터베이스를 건들이게 됨.
🍆 prefetch_related()
-> prefetch_related는 ManyToMany와 같은 경우 사용.
class City(models.Model):
name = models.CharField(max_length=20)
class Language(models.Model):
name = models.CharField(max_length=100)
class Person(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
language = models.ManyToManyField(Language, through='JoinPersonLanguage')
class JoinPersonLanguage(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
zunky = Person.objects.prefetch_related('language').get(id=1)
zunky.language.values() # Doesn't hit the database.
<QuerySet [{'id': 1, 'name': 'Python'}, {'id': 2, 'name': 'C'}, {'id': 4, 'name': 'Javascript'}]>
-> prefetch_related를 통해 zunky(person)에서 language를 참조해서 값을 가져올 때 database 안건드림.
zunky = Person.objects.get(id=1)
zunky.language.values() # Hits the database.
<QuerySet [{'id': 1, 'name': 'Python'}, {'id': 2, 'name': 'C'}, {'id': 4, 'name': 'Javascript'}]>
-> 그게 아니라면 database를 건드림.
🔍 역참조?
class City(models.Model):
name = models.CharField(max_length=20)
class Language(models.Model):
name = models.CharField(max_length=100)
class Person(models.Model):
city = models.ForeignKey(City, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
language = models.ManyToManyField(Language, through='JoinPersonLanguage')
class JoinPersonLanguage(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
language = models.ForeignKey(Language, on_delete=models.CASCADE)
>>> seoul = City.objects.get(id=1)
>>> seoul.person_set.values()
<QuerySet [{'id': 1, 'city_id': 1, 'name': 'zunky'}, {'id': 4, 'city_id': 1, 'name': 'byeong-min'}, {'id': 5, 'city_id': 1, 'name': 'sae-geul'}]>
-> 이런 경우 seoul이라는 City의 객체하나뿐이지만, 만약 모든 City 객체들에 역참조를 해야하는 상황이라면 person_set이라는 Query를 City의 수마다 DB에 요청하게 됨.
cities = City.objects.all()
for city in cities:
print(city.person_set.values())
-> 위와 같이 명령을 실행한다면 City의 객체수 만큼 Query를 DB에 요청하게 되는 것.
City의 객체가 seoul, incheon, busan이라면 Query는 3번 요청.
-> 이런 상황일 때, prefetch_related()를 사용하면 Query를 줄일 수 있음.
cities = City.objects.prefetch_related('person_set').all()
for city in cities:
print(city.person_set.values())
🍆 Models.py
class Company(models.Model):
tags = models.ManyToManyField('Tag', through='CompanyTag', blank=True)
class Meta:
db_table = 'companies'
# ManyToManyField에서 Tag를 갖고 있다면 Company 테이블에는 실제로 id값 밖에 존재하지 않는다.
# Company에서 바로 tags로 연결이 가능하다. company.tags 이런식으로
class CompanyTag(models.Model):
company = models.ForeignKey('Company', on_delete=models.CASCADE)
tag = models.ForeignKey('Tag', on_delete=models.CASCADE)
class Meta:
db_table = 'company_tags'
# 중간테이블로 작동하는 CompanyTag 테이블은 Company와 Tag를 ForeignKey로 물고 있다.
# 두 컬럼 모두 int 값을 갖고 있음
class Tag(models.Model):
name = models.CharField(max_length=100)
lang = models.CharField(max_length=100)
class Meta:
db_table = 'tags'
# Company와 ManyToManyField인 Tag 테이블은 따로 Company 컬럼을 갖고 있지않음.
class CompanyName(models.Model):
name = models.CharField(max_length=100)
lang = models.CharField(max_length=100)
company = models.ForeignKey('Company', on_delete=models.CASCADE)
class Meta:
db_table = 'company_names'
# Company를 ForeignKey로 물고 있으며 바로 접근 가능.
🍆 Views.py
1. 회사명 자동완성:
-> /search?query="회사명"에서 query를 키워드로 사용 -> 해당 키워드가 포함된 회사명을 모두 찾음.
-> 찾은 회사명들을 이용해 다른 언어로 표현되는 같은 회사의 회사명들을 모두 찾기.
-> 이후에 x-wanted-language 헤더 값에 맞는 언어로 표현된 회사명들을 추려서 반환.
class CompanyView(View):
def get(self, request):
user_lang = request.headers.get('x-wanted-language', 'ko')
# header에 들어온 x-wanted-language 받아주기, 안들어오면 ko가 기본값.
keyword = request.GET.get('query', '')
# 쿼리파라미터 사용해서 query로 들어온걸 keyword에 넣어주고 없으면 빈값처리.
company_names = list(CompanyName.objects.filter(name__icontains=keyword))
# 회사이름에 keyword가 대/소문자 형태로 포함되어있으면 필터링해줌 -> 리스트로 company_names에 넣기.
company_ids = list(map(lambda company_name: company_name.company.id, company_names))
# company_names라는 리스트에서 필터된 회사이름 하나씩 빼서company_name.id 값 찾아주고 걔네들을 리스트 형태로 company_ids 변수에 담는다.
global_company_names = CompanyName.objects.filter(company_id__in=company_ids)
company_ids에 company_id가 있는 애들 필터해서 global_company_names에 담기.
results = [
{'company_name': company_name.name}
for company_name in global_company_names
if company_name.lang == user_lang
]
# if문으로 회사이름의 언어 조건걸어주고, 위에서 필터된 global_company_names를 for 문 돌려주기.
return JsonResponse(data={'data': results}, status=200)
2. 회사명으로 회사 검색하기:
-> /companies/의 url에서 keyword에 회사이름을 입력하여 검색.
-> Header의 x-wanted-language로 ko(한국), en(영어), ja(일본) 등 언어를 선택하여 입력.
-> keyword에 입력된 회사이름과 header에서 선택한 언어를 토대로 회사이름과 태그를 출력할 수 있도록 구현.
-> 검색된 회사가 없는 경우 404를 리턴하고 에러메시지가 출력.
class SearchCompanyView(View):
def get(self, request, keyword):
# keyword 인자를 받아줌 / 이렇게 들어가면 path paratmeter값으로 들어감(/companies/keyword)
user_lang = request.headers.get('x-wanted-language', 'ko')
# header에 들어오는 x-wanted-language의 값을 user_lang에 담음, 값 안들어오면 'ko'로.
try:
company_name = CompanyName.objects.get(name=keyword)
# name=keyword인 CompanyName을 찾고 그걸 변수 company_name에 담아줌.
except CompanyName.DoesNotExist:
return JsonResponse({'message': 'company not found'}, status=404)
# 존재하지않는 회사라면 CompanyName.DoesNotExist error를 띄워주기.
else:
user_company_name = CompanyName.objects.get(company_id=company_name.company.id, lang=user_lang)
# keyword로 거른 company_name의 company.id와 같은 company_id를 찾고, lang은 user_lang으로 찾아서 get으로 하나를 걸러줌. 그걸 user_company_name에 담음.
company_info = {
'company_name': user_company_name.name,
'tags': [tag.name for tag in user_company_name.company.tags.all() if tag.lang == user_lang]
}
# 최종적으로 company_name과 tags를 반환시켜줄건데,
# user_company_name.company.tags.all()로 받고 for 문 돌려줘서 tag.name
# 조건을 걸어주기 -> tag.lang이 user_lang과 같다면! tag에도 언어 걸어주기.
# company_id와 lang이 동일한 company의 이름을 반환.
return JsonResponse(data=company_info, status=200)
Author And Source
이 문제에 관하여([pre_onboarding] Wantedlab 코드 분석), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@majaeh43/Wantedlab-회고저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)