From 08c7a1979658b5198aa58fa3695bfe0d2a9234ac Mon Sep 17 00:00:00 2001 From: Toksi Date: Mon, 8 Dec 2025 11:40:42 +0500 Subject: [PATCH 1/3] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=BF=D0=BE=D0=BB=D1=8F=20=D0=B2=20=D1=82=D0=B0=D0=B1?= =?UTF-8?q?=D0=B8=D0=BB=D1=86=D0=B5=20excel=20=D0=BF=D1=80=D0=B8=20=D0=92?= =?UTF-8?q?=D1=8B=D0=B3=D1=80=D1=83=D0=B7=D1=86=D0=BA=D0=B5=20=D0=BE=D1=86?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=D0=BA=20=D0=BF=D1=80=D0=BE=D0=B3=D1=80=D0=B0?= =?UTF-8?q?=D0=BC=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- partner_programs/admin.py | 128 ++++++++++++++++++++++++++++---------- 1 file changed, 96 insertions(+), 32 deletions(-) diff --git a/partner_programs/admin.py b/partner_programs/admin.py index fc6016dc..22a1895f 100644 --- a/partner_programs/admin.py +++ b/partner_programs/admin.py @@ -4,7 +4,7 @@ import tablib from django import forms from django.contrib import admin -from django.db.models import QuerySet +from django.db.models import Prefetch, QuerySet from django.http import HttpRequest, HttpResponse from django.urls import path from django.utils import timezone @@ -19,9 +19,7 @@ PartnerProgramProject, PartnerProgramUserProfile, ) -from partner_programs.services import ProjectScoreDataPreparer from project_rates.models import Criteria, ProjectScore -from projects.models import Project class PartnerProgramMaterialInline(admin.StackedInline): @@ -247,46 +245,112 @@ def get_export_rates_view(self, request, object_id): def _get_prepared_rates_data_for_export(self, program_id: int) -> list[dict]: """ - Prepares info (list if dicts) for export about prjects_rates by experts. - Columns example: - ФИО|Email|Регион_РФ|Учебное_заведение|Название_учебного_заведения|Класс_курс|Фамилия эксперта|**criteria + Готовит данные для выгрузки оценок проектов. + Порядок колонок: название проекта → фамилия эксперта → доп. поля программы → + критерии → комментарий. + Если у проекта несколько экспертов, на каждый проект-эксперт создаётся отдельная строка. """ - criterias = Criteria.objects.filter( - partner_program__id=program_id - ).select_related("partner_program") + criterias = list( + Criteria.objects.filter(partner_program__id=program_id) + .select_related("partner_program") + .order_by("id") + ) + if not criterias: + return [] + + comment_criteria = next( + (criteria for criteria in criterias if criteria.name == "Комментарий"), + None, + ) + criterias_without_comment = [ + criteria for criteria in criterias if criteria != comment_criteria + ] + + program_fields = list( + PartnerProgramField.objects.filter(partner_program_id=program_id).order_by( + "id" + ) + ) + scores = ( ProjectScore.objects.filter(criteria__in=criterias) .select_related("user", "criteria", "project") - .order_by("project", "criteria") - ) - user_programm_profiles = PartnerProgramUserProfile.objects.filter( - partner_program__id=program_id - ).select_related("user") - projects = ( - Project.objects.filter(scores__in=scores) - .select_related("leader") - .distinct() + .order_by("project_id", "criteria_id", "id") ) - - # To reduce the number of DB requests. - user_profiles_dict: dict[int, PartnerProgramUserProfile] = { - profile.project_id: profile for profile in user_programm_profiles - } scores_dict: dict[int, list[ProjectScore]] = {} for score in scores: scores_dict.setdefault(score.project_id, []).append(score) + if not scores_dict: + return [] + + project_ids = list(scores_dict.keys()) + + field_values_prefetch = Prefetch( + "field_values", + queryset=PartnerProgramFieldValue.objects.select_related("field").filter( + program_project__partner_program_id=program_id, + program_project__project_id__in=project_ids, + ), + to_attr="_prefetched_field_values", + ) + program_projects = ( + PartnerProgramProject.objects.filter( + partner_program_id=program_id, project_id__in=project_ids + ) + .select_related("project") + .prefetch_related(field_values_prefetch) + ) + program_project_by_project_id: dict[int, PartnerProgramProject] = { + link.project_id: link for link in program_projects + } + prepared_projects_rates_data: list[dict] = [] - for project in projects: - project_data_preparer = ProjectScoreDataPreparer( - user_profiles_dict, scores_dict, project.id, program_id + for project_id, project_scores in scores_dict.items(): + project_link = program_project_by_project_id.get(project_id) + project = ( + project_link.project + if project_link + else (project_scores[0].project if project_scores else None) ) - full_project_rates_data: dict = { - **project_data_preparer.get_project_user_info(), - **project_data_preparer.get_project_expert_info(), - **project_data_preparer.get_project_scores_info(), - } - prepared_projects_rates_data.append(full_project_rates_data) + + field_values_map: dict[int, str] = {} + field_values = ( + getattr(project_link, "_prefetched_field_values", None) + if project_link + else None + ) + if field_values: + for field_value in field_values: + field_values_map[field_value.field_id] = field_value.get_value() + + scores_by_expert: dict[int, list[ProjectScore]] = {} + for score in project_scores: + scores_by_expert.setdefault(score.user_id, []).append(score) + + for _, expert_scores in scores_by_expert.items(): + row_data: dict[str, str] = {} + row_data["Название проекта"] = ( + getattr(project, "name", "") if project else "" + ) + row_data["Фамилия эксперта"] = ( + expert_scores[0].user.last_name if expert_scores else "" + ) + + for field in program_fields: + row_data[field.label] = field_values_map.get(field.id, "") + + scores_map: dict[int, str] = { + score.criteria_id: score.value for score in expert_scores + } + + for criteria in criterias_without_comment: + row_data[criteria.name] = scores_map.get(criteria.id, "") + + if comment_criteria: + row_data["Комментарий"] = scores_map.get(comment_criteria.id, "") + + prepared_projects_rates_data.append(row_data) return prepared_projects_rates_data From 4c43b925649e37085e6b7650d01f86d844c0bedf Mon Sep 17 00:00:00 2001 From: Toksi Date: Mon, 8 Dec 2025 11:53:32 +0500 Subject: [PATCH 2/3] =?UTF-8?q?=D0=9E=D0=BF=D1=82=D0=B8=D0=BC=D0=B8=D0=B7?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=83=D0=B7=D0=BA=D0=B0=20=D0=BC=D0=B5=D0=BD=D0=B5=D0=B4?= =?UTF-8?q?=D0=B6=D0=B5=D1=80=D0=BE=D0=B2=20=D0=B8=20=D0=B4=D1=80=D1=83?= =?UTF-8?q?=D0=B3=D0=B8=D1=85=20=D0=BF=D0=BE=D0=BB=D0=B5=D0=B9=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BE=D0=B1=D0=BB=D0=B5=D0=B3=D1=87=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20SQL=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- partner_programs/admin.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/partner_programs/admin.py b/partner_programs/admin.py index 22a1895f..1416e623 100644 --- a/partner_programs/admin.py +++ b/partner_programs/admin.py @@ -27,6 +27,7 @@ class PartnerProgramMaterialInline(admin.StackedInline): extra = 1 fields = ("title", "url", "file") readonly_fields = ("datetime_created", "datetime_updated") + autocomplete_fields = ("file",) class PartnerProgramFieldInline(admin.TabularInline): @@ -64,7 +65,7 @@ class Meta: ) list_filter = ("city",) - filter_horizontal = ("managers",) + autocomplete_fields = ("managers",) date_hierarchy = "datetime_started" readonly_fields = ("datetime_created", "datetime_updated") fieldsets = ( @@ -100,7 +101,9 @@ class Meta: ) def get_queryset(self, request: HttpRequest) -> QuerySet[PartnerProgram]: - qs = super().get_queryset(request) + qs = super().get_queryset(request).prefetch_related( + "managers", "materials", "fields" + ) if "Руководитель программы" in request.user.groups.all().values_list( "name", flat=True ): From 5e4b2951c05ed41184b629828a6e6f89a3053868 Mon Sep 17 00:00:00 2001 From: Toksi Date: Mon, 8 Dec 2025 11:55:40 +0500 Subject: [PATCH 3/3] =?UTF-8?q?=D0=A2=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20?= =?UTF-8?q?=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=89=D0=B0=D0=B5=D1=82?= =?UTF-8?q?=D1=81=D1=8F=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80?= =?UTF-8?q?=D0=B0=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D1=8B=20=D0=B2?= =?UTF-8?q?=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20=D0=BF=D1=83=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D0=B9=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=8B,=20?= =?UTF-8?q?=D0=B4=D0=B0=D0=B6=D0=B5=20=D0=B5=D1=81=D0=BB=D0=B8=20=D0=BD?= =?UTF-8?q?=D0=B8=20=D0=BE=D0=B4=D0=BD=D0=BE=D0=B9=20=D0=BE=D1=86=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=B8=20=D0=BD=D0=B5=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- partner_programs/admin.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/partner_programs/admin.py b/partner_programs/admin.py index 1416e623..f5c22e02 100644 --- a/partner_programs/admin.py +++ b/partner_programs/admin.py @@ -285,7 +285,17 @@ def _get_prepared_rates_data_for_export(self, program_id: int) -> list[dict]: scores_dict.setdefault(score.project_id, []).append(score) if not scores_dict: - return [] + empty_row: dict[str, str] = { + "Название проекта": "", + "Фамилия эксперта": "", + } + for field in program_fields: + empty_row[field.label] = "" + for criteria in criterias_without_comment: + empty_row[criteria.name] = "" + if comment_criteria: + empty_row["Комментарий"] = "" + return [empty_row] project_ids = list(scores_dict.keys())