내일배움캠프 아침퀴즈에서 알 수 있었던
ManyToManyField와 ForiegnKey 관계에서
역참조를 통한 Model.objects.filter()의 작동 원리에 대해 정리해보자.
# post/models.py
class SkillSet(models.Model):
name = models.CharField(max_length=128)
job_posts = models.ManyToManyField('JobPost', through='JobPostSkillSet')
class Meta:
db_table = 'skill_sets'
class JobPostSkillSet(models.Model):
skill_set = models.ForeignKey('SkillSet', on_delete=models.SET_NULL, null=True)
job_post = models.ForeignKey('JobPost', on_delete=models.SET_NULL, null=True)
class JobType(models.Model):
job_type = models.CharField(max_length=128)
class Meta:
db_table = 'job_types'
class JobPost(models.Model):
job_type = models.ForeignKey(JobType, on_delete=models.SET_NULL, null=True)
company = models.ForeignKey('Company', on_delete=models.SET_NULL, null=True)
job_description = models.TextField()
salary = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'job_posts'
class Company(models.Model):
company_name = models.CharField(max_length=128)
business_area = models.ManyToManyField('BusinessArea', through='CompanyBusinessArea')
class Meta:
db_table = 'companies'
SkillSet Model과 JobPost Model은 ManyToMany 관계
JobPost Model과 Company Model은 OneToMany 관계
skillset을 django 또는 mysql로 등록한 jobpost를 찾는 것
jobpost에서 역참조하여 skillset이 django 또는 mysql인 object를 찾자.
class SkillView(APIView):
def get(self, request):
skills = self.request.query_params.getlist('skills', '')
query = Q()
for skill in skills:
skill_set = SkillSet.objects.get(name=skill)
query |= Q(skillset=skill_set)
job_posts = JobPost.objects.filter(query)
print(job_posts)
return Response(JobPostSerializer(job_posts, many=True).data, status=status.HTTP_200_OK)
JobPost object (2) 가 2번 포함됨
ManyToMany관계로 JobPost object (2) 하나가 django SkillSet object와 mysql SkillSet object 모두 참조 하고있기 때문
위 코드에서
job_posts = JobPost.objects.filter(query)
는 JobPost Table에서 검색하는 것이 아니라 역참조 Model 해당하는 SkillSet Table에서 (또는 ManyToMany의 중간 테이블인 JobPostSkillSet Table에서) query에 해당하는 JobPost를 찾는다. 따라서 query에 해당하는 jobpost가 중복될 경우 결과 QuerySet에 중복으로 포함됨.
: jobpost의 id를 list로 받아와 set으로 중복제거 후 jobpost object를 탐색
class SkillView(APIView):
def get(self, request):
skills = self.request.query_params.getlist('skills', '')
query = Q()
for skill in skills:
skill_set = SkillSet.objects.get(name=skill)
query |= Q(skill_set=skill_set)
job_post_skill_sets = JobPostSkillSet.objects.filter(query)
job_post_ids = [job_post_skill_set.job_post_id for job_post_skill_set in job_post_skill_sets]
job_post_ids = list(set(job_post_ids))
job_posts = [JobPost.objects.get(id=id) for id in job_post_ids]
return Response(JobPostSerializer(job_posts, many=True).data, status=status.HTTP_200_OK)
class SkillView(APIView):
permission_classes = [permissions.AllowAny]
def get(self, request):
skills = self.request.query_params.getlist('skills', '')
query = Q()
for skill in skills:
skill_set = SkillSet.objects.get(name=skill)
query |= Q(skill_set=skill_set)
job_post_skill_sets = JobPostSkillSet.objects.filter(query)
job_post_ids = job_post_skill_sets.distinct().values_list('job_post_id')
job_posts = [JobPost.objects.get(id=id[0]) for id in job_post_ids]
# job_post_ids = [job_post_skill_set.job_post_id for job_post_skill_set in job_post_skill_sets]
# job_post_ids = list(set(job_post_ids))
# job_posts = [JobPost.objects.get(id=id) for id in job_post_ids]
return Response(JobPostSerializer(job_posts, many=True).data, status=status.HTTP_200_OK)