Skip to content

Commit

Permalink
More interview updates (#849)
Browse files Browse the repository at this point in the history
* Update InterviewListView

* New Interview features

* Update InterviewListView

* New Interview features

* New Interview features

* test_fix
  • Loading branch information
Dmi4er4 authored Jun 21, 2024
1 parent 71f381e commit 2ff44c2
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@


class Command(CurrentCampaignMixin, BaseCommand):
help = """Give or take back interviewer role from Users in csv"""
help = """
Give or take back interviewer role from Users in csv
Example of usage:
./manage.py create_delete_role_group --filename=interviewers.csv --default_branch=msk --role=INTERVIEWER
"""

def add_arguments(self, parser):
super().add_arguments(parser)
parser.add_argument(
"--filename",
type=str,
default='interviewers.csv',
default='emails.csv',
help="csv file name",
)
parser.add_argument(
Expand All @@ -39,12 +43,19 @@ def add_arguments(self, parser):
dest="take_back",
help="Take roles back"
)
parser.add_argument(
"--role",
type=str,
required=True,
help="Role to give or take back",
)

def handle(self, *args, **options):
delimiter = options["delimiter"]
filename = options["filename"]
take_back = options["take_back"]
default_branch = options["default_branch"]
role = options["role"]
available = Branch.objects.filter(
active=True, site_id=settings.SITE_ID
)
Expand All @@ -58,14 +69,14 @@ def handle(self, *args, **options):
with transaction.atomic():
headers = next(reader)
for row in reader:
interviewer: User = User.objects.get(email__iexact=row[0])
branch = interviewer.branch
user: User = User.objects.get(email__iexact=row[0])
branch = user.branch
if not branch:
self.stdout.write(self.style.WARNING(f"{interviewer} doesn't have branch. Using default one"))
self.stdout.write(self.style.WARNING(f"{user} doesn't have branch. Using default one"))
branch = default_branch
role = Roles.INTERVIEWER
role = getattr(Roles, role)
if take_back:
interviewer.remove_group(role, branch=branch)
user.remove_group(role, branch=branch)
else:
interviewer.add_group(role, branch=branch)
user.add_group(role, branch=branch)

3 changes: 3 additions & 0 deletions apps/admission/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,9 @@ def get_exam_record(self) -> Optional["Exam"]:
except Exam.DoesNotExist:
return None

def get_all_interview_score(self) -> int:
return sum(Comment.objects.filter(interview__applicant=self).values_list('score', flat=True))

def get_university_display(self) -> Optional[str]:
if self.university is not None:
return self.university.name
Expand Down
34 changes: 23 additions & 11 deletions apps/admission/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ def process(self):
applicant_fields = [
f for f in Applicant._meta.fields if f.name not in self.exclude_applicant_fields
]

to_prefetch = [field.name for field in applicant_fields if isinstance(field, models.ForeignKey)]
if "campaign" in to_prefetch:
to_prefetch.append("campaign__branch")
to_prefetch.extend(["interviews",
Prefetch("interviews__comments",
queryset=(Comment.objects.prefetch_related("interviewer")))])

applicants = applicants.prefetch_related(*to_prefetch)

Expand All @@ -50,21 +52,18 @@ def process(self):
"Результаты экзамена",
]
)
interview_section_indexes: dict[int,int] = {}
for index, (value, label) in enumerate(InterviewSections.choices):
self.headers.append(f"{label} / балл")
self.headers.append(f"{label} / комментарии")
interview_section_indexes[value] = 2 * index
# Collect data
for applicant in applicants:
row = []
for field in applicant_fields:
value = getattr(applicant, field.name)
if field.name == "status":
value = applicant.get_status_display()
elif field.name == "level_of_education":
value = applicant.get_level_of_education_display()
elif field.name == "has_diploma":
value = applicant.get_has_diploma_display()
elif field.name == "gender":
value = applicant.get_gender_display()
elif field.name == "diploma_degree":
value = applicant.get_diploma_degree_display()
if field.name in ("status", "level_of_education", "has_diploma", "gender", "diploma_degree"):
value = getattr(applicant, f"get_{field.name}_display")()
elif field.name == "id":
value = reverse("admission:applicants:detail", args=[value])
elif field.name == "created":
Expand All @@ -78,6 +77,17 @@ def process(self):
row.append(applicant.exam.score)
else:
row.append("")
interview_details = ["" for _ in range(2 * len(InterviewSections.values))]
for interview in applicant.interviews.all():
interview_comments = ""
for c in interview.comments.all():
author = c.interviewer.get_full_name()
interview_comments += f"{author}:\n{c.text}\n\n"
index = interview_section_indexes[interview.section]
interview_details[index] = interview.get_average_score_display()
interview_details[index + 1] = interview_comments.rstrip()
row.extend(interview_details)
assert len(row) == len(self.headers)
self.data.append([force_str(x) if x is not None else "" for x in row])

def export_row(self, row):
Expand Down Expand Up @@ -128,6 +138,8 @@ class AdmissionApplicantsYearReport(AdmissionApplicantsReport):
"preferred_study_programs_cs_note",
"your_future_plans",
"admin_note",
"interview_format",
"miss_count"
}

def __init__(self, year):
Expand Down
6 changes: 4 additions & 2 deletions apps/admission/tests/test_timezones.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest
import pytz
from bs4 import BeautifulSoup
from django.utils import formats

from admission.constants import InterviewSections
from admission.tests.factories import (
Expand Down Expand Up @@ -191,8 +192,9 @@ def test_interview_list(settings, client, curator):
msk_interview_date_in_utc = interview.date
localized = msk_interview_date_in_utc.astimezone(tz_msk)
time_str = "{:02d}:{:02d}".format(localized.hour, localized.minute)
today = formats.date_format(msk_interview_date_in_utc, "SHORT_DATE_FORMAT")
assert time_str == "18:00" # expected UTC+3
url = reverse("admission:interviews:list") + "?campaign="
url = reverse("admission:interviews:list") + f"?campaign=&status=approval&date_from={today}&date_to={today}"
response = client.get(url)
assert response.status_code == 200
html = BeautifulSoup(response.content, "html.parser")
Expand All @@ -214,7 +216,7 @@ def test_interview_list(settings, client, curator):
localized = interview_date_in_utc.astimezone(tz)
time_str = "{:02d}:{:02d}".format(localized.hour, localized.minute)
assert time_str == "19:00" # expected UTC + 7
url = reverse("admission:interviews:list") + "?campaign="
url = reverse("admission:interviews:list") + f"?campaign=&status=approval&date_from={today}&date_to={today}"
response = client.get(url)
assert response.status_code == 200
html = BeautifulSoup(response.content, "html.parser")
Expand Down
6 changes: 4 additions & 2 deletions apps/admission/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ def test_simple_interviews_list(client, curator, settings):
campaign = CampaignFactory(current=True, branch=branch_nsk)
today_local_nsk = now_local(branch_nsk.get_timezone())
today_local_nsk_date = formats.date_format(today_local_nsk, "SHORT_DATE_FORMAT")
date_to = datetime.datetime(today_local_nsk.year, 8, 1)
date_to = formats.date_format(date_to, "SHORT_DATE_FORMAT")
interview1, interview2, interview3 = InterviewFactory.create_batch(
3,
interviewers=[interviewer],
Expand All @@ -71,11 +73,11 @@ def test_simple_interviews_list(client, curator, settings):
response = client.get(reverse("admission:interviews:list"))
# For curator set default filters and redirect
assert response.status_code == 302
assert f"campaign={campaign.pk}" in response.url
assert f"campaign=" in response.url
assert f"status={Interview.COMPLETED}" in response.url
assert f"status={Interview.APPROVED}" in response.url
assert f"date_from={today_local_nsk_date}" in response.url
assert f"date_to={today_local_nsk_date}" in response.url
assert f"date_to={date_to}" in response.url

def format_url(campaign_id, date_from: str, date_to: str):
return (
Expand Down
50 changes: 18 additions & 32 deletions apps/admission/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ def get_context_data(self, **kwargs):
return context


class ApplicantDetailView(InterviewerOnlyMixin, TemplateResponseMixin, BaseCreateView):
class ApplicantDetailView(CuratorOnlyMixin, TemplateResponseMixin, BaseCreateView):
form_class = InterviewForm
template_name = "admission/applicant_detail.html"

Expand Down Expand Up @@ -742,26 +742,22 @@ class InterviewListView(InterviewerOnlyMixin, BaseFilterView, generic.ListView):

def get(self, request, *args, **kwargs):
"""
Redirects curator to appropriate campaign if no any provided.
Redirects curator to page with appropriate parameters for correct work of 'download_csv'.
"""
user = self.request.user
if user.is_curator and "campaign" not in self.request.GET:
# Try to find user preferred current campaign id
campaign = get_default_campaign_for_user(user)
if not campaign:
return HttpResponseRedirect(reverse("admission:applicants:list"))
else:
today_local = now_local(campaign.branch.get_timezone())
date = formats.date_format(today_local, "SHORT_DATE_FORMAT")
params = parse.urlencode(
{
"campaign": campaign.pk,
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")
date_to = datetime(timezone.now().year, 8, 1)
date_to = formats.date_format(date_to, "SHORT_DATE_FORMAT")
params = {
"status": [Interview.COMPLETED, Interview.APPROVED],
"date_from": date,
"date_to": date,
},
doseq=True,
)
"date_from": today,
"date_to": date_to,
}
if user.is_curator and "campaign" not in self.request.GET:
params.update({"campaign": ""})
params = parse.urlencode(params, doseq=True)
url = "{}?{}".format(reverse("admission:interviews:list"), params)
return HttpResponseRedirect(redirect_to=url)
if "download_csv" in request.GET:
Expand All @@ -780,18 +776,6 @@ def get_filterset_class(self):
return InterviewsCuratorFilter
return InterviewsFilter

def get_filterset_kwargs(self, filterset_class):
kwargs = super().get_filterset_kwargs(filterset_class)
kwargs["request"] = self.request
if not kwargs["data"]:
today = formats.date_format(timezone.now(), "SHORT_DATE_FORMAT")
kwargs["data"] = {
"status": [Interview.COMPLETED, Interview.APPROVED],
"date_from": today,
"date_to": today,
}
return kwargs

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["filter"] = self.filterset
Expand Down Expand Up @@ -841,7 +825,8 @@ class InterviewListCSVView(CuratorOnlyMixin, generic.base.View):
def get(self, request, *args, **kwargs):
date_from = datetime.strptime(request.GET.get("date_from"), "%d.%m.%Y")
date_to = datetime.strptime(request.GET.get("date_to"), "%d.%m.%Y")
campaign = int(request.GET.get("campaign"))
campaign = request.GET.get("campaign")
campaign = int(campaign) if campaign else None
response = HttpResponse(content_type="text/csv; charset=utf-8")
filename = f"interviews_{date_from.strftime('%d.%m.%Y')}_{date_to.strftime('%d.%m.%Y')}.csv"
response["Content-Disposition"] = f'attachment; filename="{filename}"'
Expand All @@ -855,12 +840,13 @@ def get(self, request, *args, **kwargs):
"applicant_name",
"interviewer_name",
]
campaign_filter = Q(applicant__campaign=campaign) if campaign else Q()
writer.writerow(headers)
interviews = (
Interview.objects.select_related("applicant")
.prefetch_related("interviewers")
.filter(
applicant__campaign=campaign,
campaign_filter,
date__date__gte=date_from.strftime("%Y-%m-%d"),
date__date__lte=date_to.strftime("%Y-%m-%d"),
)
Expand Down
2 changes: 2 additions & 0 deletions lms/jinja2/lms/admission/applicant_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ <h3 class="panel-title">Анкеты поступающих / {{ paginator.count
<th>Направление</th>
<th style="width: 60px;">Тест</th>
<th style="width: 60px;">Экз.</th>
<th style="width: 60px;">Cоб.</th>
</tr>
</thead>
<tbody>
Expand Down Expand Up @@ -86,6 +87,7 @@ <h3 class="panel-title">Анкеты поступающих / {{ paginator.count
</td>
<td>{% if applicant.online_test %}{{ applicant.online_test.score_display() }}{% else %}-{% endif %}</td>
<td>{% if applicant.exam %}{{ applicant.exam.score_display() }}{% else %}-{% endif %}</td>
<td>{{ applicant.get_all_interview_score() }}</td>
</tr>
{% else %}
<tr>
Expand Down

0 comments on commit 2ff44c2

Please sign in to comment.