From 363aa26413eb854d8b90ff9baf2dd58ea6beb8b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=A7=D0=B5?=
<39742182+Dmi4er4@users.noreply.github.com>
Date: Mon, 24 Jun 2024 12:29:29 +0300
Subject: [PATCH] Interview fix (#852)
* script fix
* test fix
* update
---
apps/admission/filters.py | 3 +-
apps/admission/services.py | 19 +++++----
.../templates/admission/interview_detail.html | 1 +
.../templates/admission/interview_list.html | 9 +++-
apps/admission/tests/test_services.py | 2 +-
apps/admission/views.py | 42 ++++++++++++-------
.../admission/interview_invitation_list.html | 28 ++++++-------
.../admission/send_interview_invitations.html | 12 +++---
8 files changed, 69 insertions(+), 47 deletions(-)
diff --git a/apps/admission/filters.py b/apps/admission/filters.py
index cc733d1e0..9431d2776 100644
--- a/apps/admission/filters.py
+++ b/apps/admission/filters.py
@@ -172,6 +172,7 @@ class InvitationCreateInterviewStreamFilter(InterviewStreamFilter):
number_of_misses = django_filters.ChoiceFilter(
label=_("Applicant number of missed interviews"), choices=ApplicantMisses
)
+ last_name = django_filters.CharFilter(label=_("Last Name"))
@property
def form(self):
@@ -182,7 +183,7 @@ def form(self):
self._form.helper.layout = Layout(
Row(
Div("campaign", css_class="col-xs-3"), Div("section", css_class="col-xs-3"),
- Div("format", css_class="col-xs-3")),
+ Div("format", css_class="col-xs-3"), Div("last_name", css_class="col-xs-3")),
Row(
Div("track", css_class="col-xs-3"), Div("way_to_interview", css_class="col-xs-3"),
Div("number_of_misses", css_class="col-xs-3"),
diff --git a/apps/admission/services.py b/apps/admission/services.py
index 13b23396b..61db4dd70 100644
--- a/apps/admission/services.py
+++ b/apps/admission/services.py
@@ -73,7 +73,7 @@ def get_ongoing_interview_streams() -> models.QuerySet[InterviewStream]:
def get_applicants_for_invitation(
- *, campaign: Campaign, section: str, format=None, track=None, way_to_interview=None,
+ *, campaign: Campaign, section: str, format=None, last_name=None, track=None, way_to_interview=None,
number_of_misses=None
) -> models.QuerySet[Applicant]:
"""
@@ -98,15 +98,19 @@ def get_applicants_for_invitation(
.values("applicant_id"))
format_filter = Q()
- if format is not None:
+ if format:
format_filter = Q(interview_format=format) | Q(interview_format=ApplicantInterviewFormats.ANY)
+ last_name_filter = Q()
+ if last_name:
+ last_name_filter = Q(last_name__icontains=last_name)
+
track_filter = Q()
- if track is not None:
- track_filter = Q(new_track=(track=="alternative"))
+ if track:
+ track_filter = Q(new_track=(track == "alternative"))
way_filter = Q()
- if way_to_interview is not None:
+ if way_to_interview:
if way_to_interview == "exam":
way_filter = Q(status=ApplicantStatuses.PASSED_EXAM)
elif way_to_interview == "olympiad":
@@ -116,9 +120,8 @@ def get_applicants_for_invitation(
else:
raise ValueError(f"Unsupported value {way_to_interview}")
-
miss_filter = Q()
- if number_of_misses is not None:
+ if number_of_misses or number_of_misses == 0:
if number_of_misses in range(0, 4):
way_filter = Q(miss_count=number_of_misses)
elif number_of_misses == 4:
@@ -132,6 +135,7 @@ def get_applicants_for_invitation(
track_filter,
way_filter,
miss_filter,
+ last_name_filter,
campaign=campaign,
status__in=ApplicantStatuses.RIGHT_BEFORE_INTERVIEW,
)
@@ -253,7 +257,6 @@ def get_streams(
return {stream: slots for stream, slots in bucket.items() if stream.slots_free_count != 0}
-
# TODO: change exception type
class InterviewCreateError(APIException):
pass
diff --git a/apps/admission/templates/admission/interview_detail.html b/apps/admission/templates/admission/interview_detail.html
index 560b8d4d7..185dea613 100644
--- a/apps/admission/templates/admission/interview_detail.html
+++ b/apps/admission/templates/admission/interview_detail.html
@@ -26,6 +26,7 @@
{{ applicant.full_name }}{% if request.user.is_curator %}
Секция: {{ interview.get_section_display }}
Статус собеседования: {{ interview.get_status_display|default_if_none:"<не указан>" }}
+ Формат собеседования: {{ interview.slot.stream.get_format_display|default_if_none:"<не указан>" }}
diff --git a/apps/admission/templates/admission/interview_list.html b/apps/admission/templates/admission/interview_list.html
index b4ff37674..7a830a8db 100644
--- a/apps/admission/templates/admission/interview_list.html
+++ b/apps/admission/templates/admission/interview_list.html
@@ -37,7 +37,11 @@
Собеседующие |
{% if not filter.form.status.value or filter.form.status.value == "completed" or filter.form.status.value == "agreed" %}
- Средний балл{% endif %} |
+ Средний балл
+ {% else %}
+ Статус
+ {% endif %}
+ Формат |
@@ -89,6 +93,9 @@
{{ interview.get_status_display }}
{% endif %}
+
+ {{ interview.slot.stream.get_format_display }}
+ |
{% empty %}
diff --git a/apps/admission/tests/test_services.py b/apps/admission/tests/test_services.py
index 3f2a80c8d..32448df97 100644
--- a/apps/admission/tests/test_services.py
+++ b/apps/admission/tests/test_services.py
@@ -440,7 +440,7 @@ def test_get_applicants_for_invitation_with_filters(client, settings):
elif applicant.miss_count > 3 and miss_count == 4:
assert applicant in qs
else:
- assert applicant not in qs
+ assert applicant not in qs, f'{applicant=}, {applicant.miss_count=}, {miss_count=}'
@pytest.mark.django_db
def test_get_streams(client, settings):
diff --git a/apps/admission/views.py b/apps/admission/views.py
index d2a0b4a8d..513afc04c 100644
--- a/apps/admission/views.py
+++ b/apps/admission/views.py
@@ -239,6 +239,7 @@ class FilterSerializer(serializers.Serializer):
number_of_misses = serializers.ChoiceField(
choices=InvitationCreateInterviewStreamFilter.ApplicantMisses, required=False
)
+ last_name = serializers.CharField(required=False)
class InputSerializer(serializers.Serializer):
streams = serializers.ListField(
@@ -294,12 +295,14 @@ def post(self, request, *args, **kwargs):
# Create interview invitations
campaign = filter_serializer.validated_data["campaign"]
section = filter_serializer.validated_data["section"]
- format = filter_serializer.validated_data.get("format")
- track = filter_serializer.validated_data.get("track")
- way_to_interview = filter_serializer.validated_data.get("way_to_interview")
- number_of_misses = filter_serializer.validated_data.get("number_of_misses")
- applicants = get_applicants_for_invitation(campaign=campaign, section=section, format=format, track=track,
- way_to_interview=way_to_interview, number_of_misses=number_of_misses)
+ format = filter_serializer.validated_data.get("format", "")
+ track = filter_serializer.validated_data.get("track", "")
+ way_to_interview = filter_serializer.validated_data.get("way_to_interview", "")
+ number_of_misses = filter_serializer.validated_data.get("number_of_misses", "")
+ last_name = filter_serializer.validated_data.get("last_name", "")
+ applicants = get_applicants_for_invitation(campaign=campaign, section=section, format=format,
+ last_name=last_name, track=track, way_to_interview=way_to_interview,
+ number_of_misses=number_of_misses)
applicants = applicants.filter(pk__in=input_serializer.validated_data["ids"])
free_slots = sum(stream.slots_free_count for stream in streams)
if free_slots < len(applicants):
@@ -318,7 +321,10 @@ def post(self, request, *args, **kwargs):
)
messages.success(request, "Приглашения успешно созданы", extra_tags="timeout")
url = reverse("admission:interviews:invitations:send")
- redirect_to = f"{url}?campaign={campaign.id}§ion={section}"
+ redirect_to = f"{url}?campaign={campaign.id}§ion={section}&format={format}" \
+ f"&last_name={last_name}&track={track}&way_to_interview={way_to_interview}" \
+ f"&number_of_misses={number_of_misses}"
+ print(redirect_to)
return HttpResponseRedirect(redirect_to)
@staticmethod
@@ -353,16 +359,18 @@ def get_context_data(self, **kwargs):
filter_serializer = kwargs["filter_serializer"]
campaign = filter_serializer.validated_data["campaign"]
section = filter_serializer.validated_data["section"]
- format = filter_serializer.validated_data.get("format")
- track = filter_serializer.validated_data.get("track")
- way_to_interview = filter_serializer.validated_data.get("way_to_interview")
- number_of_misses = filter_serializer.validated_data.get("number_of_misses")
+ format = filter_serializer.validated_data.get("format", "")
+ track = filter_serializer.validated_data.get("track", "")
+ way_to_interview = filter_serializer.validated_data.get("way_to_interview", "")
+ number_of_misses = filter_serializer.validated_data.get("number_of_misses", "")
+ last_name = filter_serializer.validated_data.get("last_name", "")
interview_stream_filterset = self.get_interview_stream_filterset(
filter_serializer
)
applicants = (
- get_applicants_for_invitation(campaign=campaign, section=section, format=format, track=track,
+ get_applicants_for_invitation(campaign=campaign, section=section, format=format, last_name=last_name,
+ track=track,
way_to_interview=way_to_interview, number_of_misses=number_of_misses)
.select_related(
"exam",
@@ -384,8 +392,9 @@ def get_context_data(self, **kwargs):
page_number = self.request.GET.get("page")
page = paginator.get_page(page_number)
paginator_url = reverse("admission:interviews:invitations:send")
- paginator_url = f"{paginator_url}?campaign={campaign.id}§ion={section}"
-
+ paginator_url = f"{paginator_url}?campaign={campaign.id}§ion={section}&format={format}" \
+ f"&last_name={last_name}&track={track}&way_to_interview={way_to_interview}" \
+ f"&number_of_misses={number_of_misses}"
context = {
"stream_filter_form": interview_stream_filterset.form,
"stream_form": InterviewStreamInvitationForm(
@@ -747,7 +756,7 @@ def get(self, request, *args, **kwargs):
user = self.request.user
is_param_lost = any(param not in self.request.GET for param in ["status", "date_from", "date_to"])
if is_param_lost:
- today = formats.date_format(timezone.now(), "SHORT_DATE_FORMAT")
+ today = formats.date_format(now_local(user.branch.get_timezone()), "SHORT_DATE_FORMAT")
date_to = datetime(timezone.now().year, 8, 1)
date_to = formats.date_format(date_to, "SHORT_DATE_FORMAT")
params = {
@@ -798,7 +807,7 @@ def get_queryset(self):
q = (
Interview.objects.filter(applicant__campaign__branch__in=branches)
.select_related("applicant__campaign__branch", "venue__city")
- .prefetch_related("interviewers")
+ .prefetch_related("interviewers", "slot__stream")
.annotate(average=Coalesce(Avg("comments__score"), Value(0.0)))
.order_by("date", "pk")
)
@@ -886,6 +895,7 @@ def get_context_data(self, **kwargs):
Prefetch(
"comments", queryset=(Comment.objects.select_related("interviewer"))
),
+ "slot__stream"
)
interview = get_object_or_404(qs)
context = get_applicant_context(self.request, interview.applicant_id)
diff --git a/lms/jinja2/lms/admission/interview_invitation_list.html b/lms/jinja2/lms/admission/interview_invitation_list.html
index 20c4683f8..b3ae92eae 100644
--- a/lms/jinja2/lms/admission/interview_invitation_list.html
+++ b/lms/jinja2/lms/admission/interview_invitation_list.html
@@ -29,11 +29,11 @@
Поступающий |
- Секция |
- Дата |
- Время |
- Собеседующие |
- Статус |
+ Секция |
+ Дата |
+ Время |
+ Собеседующие |
+ Статус |
@@ -64,11 +64,11 @@
- {{ interviewer.last_name }}
{% else %}
{{ interviewer.full_name|slice(":1") }}
{% endif %}
+ {{ interviewer.last_name }}
{% endfor %}
@@ -85,12 +85,12 @@
- Поток |
- Секция |
- Формат |
- Слоты |
- Пригл. без ответа |
- Собеседующие |
+ Поток |
+ Секция |
+ Формат |
+ Слоты |
+ Пригл. без ответа |
+ Собеседующие |
@@ -113,14 +113,14 @@
- {{ interviewer.last_name }}
+
{% else %}
{{ interviewer.get_full_name()|slice(":1") }}
- {{ interviewer.last_name }}
{% endif %}
{% endwith -%}
+ {{ interviewer.last_name }}
{% endfor %}
diff --git a/lms/jinja2/lms/admission/send_interview_invitations.html b/lms/jinja2/lms/admission/send_interview_invitations.html
index 50841feda..8ab4a8c25 100644
--- a/lms/jinja2/lms/admission/send_interview_invitations.html
+++ b/lms/jinja2/lms/admission/send_interview_invitations.html
@@ -27,13 +27,13 @@ Ожидают приглашения / {{ paginator.cou
- |
- Поступающий |
- Проп. |
+ |
+ Поступающий |
+ Проп. |
Заметка |
- Формат |
- Тест |
- Экз. |
+ Формат |
+ Тест |
+ Экз. |