From e163656dfd9f0e2b382d683575b4271e67e95b0e Mon Sep 17 00:00:00 2001 From: ljy2855 Date: Tue, 5 Mar 2024 10:01:13 +0900 Subject: [PATCH 1/3] feat: Apply image file size validator --- app/image/models.py | 6 +++++- app/image/utils.py | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/image/models.py b/app/image/models.py index f3db18f..7485725 100644 --- a/app/image/models.py +++ b/app/image/models.py @@ -8,7 +8,11 @@ class Image(models.Model): title = models.CharField(null=False, max_length=50) - image = models.ImageField(null=False, upload_to=image_rename) + image = models.ImageField( + null=False, + upload_to=image_rename, + validators=[validate_file_size], + ) uploaded_at = models.DateTimeField(auto_now_add=True) class Meta: diff --git a/app/image/utils.py b/app/image/utils.py index 344a016..1a85a4a 100644 --- a/app/image/utils.py +++ b/app/image/utils.py @@ -1,5 +1,14 @@ import os from django.utils.text import slugify +from django.core.exceptions import ValidationError + + +def validate_file_size(value): + max_file_size = 20 * 1024 * 1024 # 20MB + if value.size > max_file_size: + raise ValidationError( + f"파일 크기는 {max_file_size // (1024 * 1024)}MB를 초과할 수 없습니다." + ) def image_rename(instance, filename): From e1cbddce5f87e1a4ef35fd30184fd08c97cc2890 Mon Sep 17 00:00:00 2001 From: ljy2855 Date: Tue, 5 Mar 2024 10:28:30 +0900 Subject: [PATCH 2/3] feat: Apply interview time validator --- app/apply/models.py | 7 ++++--- app/apply/utils.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 app/apply/utils.py diff --git a/app/apply/models.py b/app/apply/models.py index 82f4a1d..72a4eb0 100644 --- a/app/apply/models.py +++ b/app/apply/models.py @@ -1,6 +1,7 @@ from django.db import models from django.utils import timezone from user.models import Applicant +from .utils import * class TermType(models.TextChoices): @@ -42,8 +43,8 @@ def check_process(self): class InterviewTime(models.Model): - time = models.DateTimeField() - is_fixed = models.BooleanField(default=False) + time = models.DateTimeField(validators=[validate_interview_time]) + is_fixed = models.BooleanField(default=False) # 면접 시간 모두 배정되었는지 여부 def __str__(self): return self.time.strftime("%Y/%m/%d %H:%M:%S") @@ -71,7 +72,7 @@ class Resume(models.Model): InterviewTime, related_name="interview_time" ) fixed_interview_time = models.DateTimeField(null=True, blank=True) - interview_requirement = models.TextField(default="") + interview_requirement = models.TextField(default="") # 면접 요구 사항 interview_place = models.ForeignKey( InterviewPlace, on_delete=models.CASCADE, null=True, blank=True ) diff --git a/app/apply/utils.py b/app/apply/utils.py new file mode 100644 index 0000000..b2b8421 --- /dev/null +++ b/app/apply/utils.py @@ -0,0 +1,10 @@ +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ + + +def validate_interview_time(value): + if value.minute != 0 or value.second != 0: + raise ValidationError( + _("시간은 정각이어야 합니다."), + params={"value": value}, + ) From 518aeff14b6e69dcc167323d091a9b2e1a6c6f7f Mon Sep 17 00:00:00 2001 From: darami819 Date: Wed, 6 Mar 2024 22:57:56 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=EB=A9=B4=EC=A0=91=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EB=B0=B0=EC=B9=98=20=EC=95=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=EC=A6=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/apply/admin.py | 153 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 116 insertions(+), 37 deletions(-) diff --git a/app/apply/admin.py b/app/apply/admin.py index cb4bf6d..fa7919f 100644 --- a/app/apply/admin.py +++ b/app/apply/admin.py @@ -49,44 +49,123 @@ def get_all_resume(self, request, queryset): def set_interview_time( self, request, queryset ): # self = resume model , queryset = 체크했던 object들 - times = [queryset.time for queryset in InterviewTime.objects.all()] - meta = self.model._meta + # 면접 시간(is_fixed)과 지원자의 면접 시간(fixed_interview_time) 초기화 + InterviewTime.objects.all().update(is_fixed=False) + Resume.objects.all().update(fixed_interview_time=None) + + # 면접 시간 객체를 datetime 객체로 변환 + interview_times = { + interview_time.id: interview_time.time + for interview_time in InterviewTime.objects.all() + } + + # 면접시간 별 수요도 체크를 위한 딕셔너리 + # 시간당 최대 3명만 배정하기 위해 카운트하는 배열 + time_count = {id: 0 for id in interview_times.keys()} + fixed_applicant_count = {id: 0 for id in interview_times.keys()} + + # 서류 통과한 사람들에 한해서 면접 시간 배치 + applicants = ( + Resume.objects.filter(is_pass_document=True) + .annotate(c=Count("interview_time_choice")) + .order_by("c") + ) + + # 면접 시간 수요도 조사 + for applicant in applicants: + for choice in applicant.interview_time_choice.all(): + # choice_time = datetime.strptime(choice.time, "%Y/%m/%d %H:%M:%S") + choice_time = choice.time + # 선택된 시간이 면접 시간 목록에 있다면 count 증가 + for interview_time_id, interview_time in interview_times.items(): + if choice_time == interview_time: + time_count[interview_time_id] += 1 + + # 수요도가 낮은 시간부터 배정하기 위해 오름차순 정렬 + sorted_time_counts = ( + InterviewTime.objects.annotate(applicant_count=Count("interview_time")) + .filter(is_fixed=False) + .order_by("applicant_count", "time") + ) - q = Resume.objects.annotate(c=Count("interview_time_choice")).order_by("c") - - for i in q: - for j in i.interview_time_choice.all(): - if j.time in times: - time_num = 0 - s = Resume.objects.annotate() - for ( - _resume - ) in s: # resume에 대해 call하는 부분 문제 어떤방식으로 call해야하나? - if j.time == _resume.fixed_interview_time: - time_num = time_num + 1 - if time_num == 1: - plus20 = j.time + datetime.timedelta(minutes=20) - a = Resume.objects.annotate() - for Res in a: - if plus20 == Res.fixed_interview_time: - time_num = time_num + 1 - if time_num == 2: # 2개 있을 때 - i.fixed_interview_time = plus20 + datetime.timedelta(minutes=20) - i.save() - j.is_fixed = True - j.save() - times.remove(j.time) - break - - else: # 1개 있을 - i.fixed_interview_time = plus20 - i.save() - break - - else: # 0개일때 - i.fixed_interview_time = j.time - i.save() - break + # 각 지원자에게 가장 수요가 적은 면접 시간을 할당 + for interview_time in sorted_time_counts: + for applicant in applicants: + if applicant.fixed_interview_time == None: + # 해당 시간대가 꽉 찼으면 건너뛰기 (이미 3명이 배정됨) + if fixed_applicant_count[interview_time.id] >= 3: + continue + + chosen_times = applicant.interview_time_choice.all() + for choice in chosen_times: + choice_time = choice.time + # 선택된 시간이 현재 순회 중인 면접 시간과 같은지 확인 + if choice_time == interview_time.time and not choice.is_fixed: + plus20 = choice_time + timedelta( + minutes=20 * fixed_applicant_count[interview_time.id] + ) + # print(plus20) + # 지원자에게 면접 시간 할당 + applicant.fixed_interview_time = plus20 + applicant.save() + + # 할당된 시간의 수요를 1 증가시킴 + fixed_applicant_count[interview_time.id] += 1 + + # 시간대가 꽉 찼으면 is_fixed 속성을 True로 설정 + if fixed_applicant_count[interview_time.id] == 3: + interview_time.is_fixed = True + interview_time.save() + + # break + else: + # 선택한 모든 시간이 이미 할당되었을 경우 + # 지원자에게는 면접 시간을 할당하지 않고 다음 지원자로 넘어감 + continue + + +# @admin.action(description="면접 시간 자동 배치") +# def set_interview_time( +# self, request, queryset +# ): # self = resume model , queryset = 체크했던 object들 +# times = [queryset.time for queryset in InterviewTime.objects.all()] +# meta = self.model._meta + +# q = Resume.objects.annotate(c=Count("interview_time_choice")).order_by("c") + +# for i in q: +# for j in i.interview_time_choice.all(): +# if j.time in times: +# time_num = 0 +# s = Resume.objects.annotate() +# for ( +# _resume +# ) in s: # resume에 대해 call하는 부분 문제 어떤방식으로 call해야하나? +# if j.time == _resume.fixed_interview_time: +# time_num = time_num + 1 +# if time_num == 1: +# plus20 = j.time + datetime.timedelta(minutes=20) +# a = Resume.objects.annotate() +# for Res in a: +# if plus20 == Res.fixed_interview_time: +# time_num = time_num + 1 +# if time_num == 2: # 2개 있을 때 +# i.fixed_interview_time = plus20 + datetime.timedelta(minutes=20) +# i.save() +# j.is_fixed = True +# j.save() +# times.remove(j.time) +# break + +# else: # 1개 있을 +# i.fixed_interview_time = plus20 +# i.save() +# break + +# else: # 0개일때 +# i.fixed_interview_time = j.time +# i.save() +# break @admin.action(description="면접 장소 일괄 지정")