Skip to content

Commit d4eac09

Browse files
authored
Merge pull request #584 from PROCOLLAB-github/update-project-grades-export
Обновление выгрузки оценок и ускорение формы программы
2 parents 95b9b37 + 5e4b295 commit d4eac09

1 file changed

Lines changed: 111 additions & 34 deletions

File tree

partner_programs/admin.py

Lines changed: 111 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import tablib
55
from django import forms
66
from django.contrib import admin
7-
from django.db.models import QuerySet
7+
from django.db.models import Prefetch, QuerySet
88
from django.http import HttpRequest, HttpResponse
99
from django.urls import path
1010
from django.utils import timezone
@@ -19,16 +19,15 @@
1919
PartnerProgramProject,
2020
PartnerProgramUserProfile,
2121
)
22-
from partner_programs.services import ProjectScoreDataPreparer
2322
from project_rates.models import Criteria, ProjectScore
24-
from projects.models import Project
2523

2624

2725
class PartnerProgramMaterialInline(admin.StackedInline):
2826
model = PartnerProgramMaterial
2927
extra = 1
3028
fields = ("title", "url", "file")
3129
readonly_fields = ("datetime_created", "datetime_updated")
30+
autocomplete_fields = ("file",)
3231

3332

3433
class PartnerProgramFieldInline(admin.TabularInline):
@@ -66,7 +65,7 @@ class Meta:
6665
)
6766
list_filter = ("city",)
6867

69-
filter_horizontal = ("managers",)
68+
autocomplete_fields = ("managers",)
7069
date_hierarchy = "datetime_started"
7170
readonly_fields = ("datetime_created", "datetime_updated")
7271
fieldsets = (
@@ -102,7 +101,9 @@ class Meta:
102101
)
103102

104103
def get_queryset(self, request: HttpRequest) -> QuerySet[PartnerProgram]:
105-
qs = super().get_queryset(request)
104+
qs = super().get_queryset(request).prefetch_related(
105+
"managers", "materials", "fields"
106+
)
106107
if "Руководитель программы" in request.user.groups.all().values_list(
107108
"name", flat=True
108109
):
@@ -247,46 +248,122 @@ def get_export_rates_view(self, request, object_id):
247248

248249
def _get_prepared_rates_data_for_export(self, program_id: int) -> list[dict]:
249250
"""
250-
Prepares info (list if dicts) for export about prjects_rates by experts.
251-
Columns example:
252-
ФИО|Email|Регион_РФ|Учебное_заведение|Название_учебного_заведения|Класс_курс|Фамилия эксперта|**criteria
251+
Готовит данные для выгрузки оценок проектов.
252+
Порядок колонок: название проекта → фамилия эксперта → доп. поля программы →
253+
критерии → комментарий.
254+
Если у проекта несколько экспертов, на каждый проект-эксперт создаётся отдельная строка.
253255
"""
254-
criterias = Criteria.objects.filter(
255-
partner_program__id=program_id
256-
).select_related("partner_program")
256+
criterias = list(
257+
Criteria.objects.filter(partner_program__id=program_id)
258+
.select_related("partner_program")
259+
.order_by("id")
260+
)
261+
if not criterias:
262+
return []
263+
264+
comment_criteria = next(
265+
(criteria for criteria in criterias if criteria.name == "Комментарий"),
266+
None,
267+
)
268+
criterias_without_comment = [
269+
criteria for criteria in criterias if criteria != comment_criteria
270+
]
271+
272+
program_fields = list(
273+
PartnerProgramField.objects.filter(partner_program_id=program_id).order_by(
274+
"id"
275+
)
276+
)
277+
257278
scores = (
258279
ProjectScore.objects.filter(criteria__in=criterias)
259280
.select_related("user", "criteria", "project")
260-
.order_by("project", "criteria")
281+
.order_by("project_id", "criteria_id", "id")
261282
)
262-
user_programm_profiles = PartnerProgramUserProfile.objects.filter(
263-
partner_program__id=program_id
264-
).select_related("user")
265-
projects = (
266-
Project.objects.filter(scores__in=scores)
267-
.select_related("leader")
268-
.distinct()
269-
)
270-
271-
# To reduce the number of DB requests.
272-
user_profiles_dict: dict[int, PartnerProgramUserProfile] = {
273-
profile.project_id: profile for profile in user_programm_profiles
274-
}
275283
scores_dict: dict[int, list[ProjectScore]] = {}
276284
for score in scores:
277285
scores_dict.setdefault(score.project_id, []).append(score)
278286

287+
if not scores_dict:
288+
empty_row: dict[str, str] = {
289+
"Название проекта": "",
290+
"Фамилия эксперта": "",
291+
}
292+
for field in program_fields:
293+
empty_row[field.label] = ""
294+
for criteria in criterias_without_comment:
295+
empty_row[criteria.name] = ""
296+
if comment_criteria:
297+
empty_row["Комментарий"] = ""
298+
return [empty_row]
299+
300+
project_ids = list(scores_dict.keys())
301+
302+
field_values_prefetch = Prefetch(
303+
"field_values",
304+
queryset=PartnerProgramFieldValue.objects.select_related("field").filter(
305+
program_project__partner_program_id=program_id,
306+
program_project__project_id__in=project_ids,
307+
),
308+
to_attr="_prefetched_field_values",
309+
)
310+
program_projects = (
311+
PartnerProgramProject.objects.filter(
312+
partner_program_id=program_id, project_id__in=project_ids
313+
)
314+
.select_related("project")
315+
.prefetch_related(field_values_prefetch)
316+
)
317+
program_project_by_project_id: dict[int, PartnerProgramProject] = {
318+
link.project_id: link for link in program_projects
319+
}
320+
279321
prepared_projects_rates_data: list[dict] = []
280-
for project in projects:
281-
project_data_preparer = ProjectScoreDataPreparer(
282-
user_profiles_dict, scores_dict, project.id, program_id
322+
for project_id, project_scores in scores_dict.items():
323+
project_link = program_project_by_project_id.get(project_id)
324+
project = (
325+
project_link.project
326+
if project_link
327+
else (project_scores[0].project if project_scores else None)
283328
)
284-
full_project_rates_data: dict = {
285-
**project_data_preparer.get_project_user_info(),
286-
**project_data_preparer.get_project_expert_info(),
287-
**project_data_preparer.get_project_scores_info(),
288-
}
289-
prepared_projects_rates_data.append(full_project_rates_data)
329+
330+
field_values_map: dict[int, str] = {}
331+
field_values = (
332+
getattr(project_link, "_prefetched_field_values", None)
333+
if project_link
334+
else None
335+
)
336+
if field_values:
337+
for field_value in field_values:
338+
field_values_map[field_value.field_id] = field_value.get_value()
339+
340+
scores_by_expert: dict[int, list[ProjectScore]] = {}
341+
for score in project_scores:
342+
scores_by_expert.setdefault(score.user_id, []).append(score)
343+
344+
for _, expert_scores in scores_by_expert.items():
345+
row_data: dict[str, str] = {}
346+
row_data["Название проекта"] = (
347+
getattr(project, "name", "") if project else ""
348+
)
349+
row_data["Фамилия эксперта"] = (
350+
expert_scores[0].user.last_name if expert_scores else ""
351+
)
352+
353+
for field in program_fields:
354+
row_data[field.label] = field_values_map.get(field.id, "")
355+
356+
scores_map: dict[int, str] = {
357+
score.criteria_id: score.value for score in expert_scores
358+
}
359+
360+
for criteria in criterias_without_comment:
361+
row_data[criteria.name] = scores_map.get(criteria.id, "")
362+
363+
if comment_criteria:
364+
row_data["Комментарий"] = scores_map.get(comment_criteria.id, "")
365+
366+
prepared_projects_rates_data.append(row_data)
290367

291368
return prepared_projects_rates_data
292369

0 commit comments

Comments
 (0)