From 70364d9fefa337a48f50ab5a7211d040398e729f Mon Sep 17 00:00:00 2001 From: Sofya Anokhovskaya Date: Tue, 27 May 2025 22:14:04 +0300 Subject: [PATCH 1/5] feat: partner in filter with priority --- apps/staff/services/diploma_export.py | 60 ++++++++++++++++----------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/apps/staff/services/diploma_export.py b/apps/staff/services/diploma_export.py index cbfefb050..76edd2086 100644 --- a/apps/staff/services/diploma_export.py +++ b/apps/staff/services/diploma_export.py @@ -6,7 +6,7 @@ from typing import Dict, List, Set, Tuple, Any, Iterable from django.http import HttpResponse -from django.db.models import Prefetch, QuerySet, Q +from django.db.models import Q, Prefetch, Case, When, Value, IntegerField, QuerySet from courses.models import MetaCourse from learning.models import Enrollment @@ -29,30 +29,40 @@ def get_student_profiles(site, graduated_year: int) -> QuerySet: Get student profiles for electronic diplomas export with optimized prefetching. """ - return StudentProfile.objects.filter( Q(status=StudentStatuses.WILL_GRADUATE) | Q(status=StudentStatuses.GRADUATE, graduation_year=graduated_year), - site_id=site.id, - type=StudentTypes.REGULAR, - ).select_related( - 'user', - 'branch', - 'user__yandex_data' - ).prefetch_related( - Prefetch( - 'user__shadcourserecord_set', - queryset=SHADCourseRecord.objects.select_related('semester'), - ), - 'user__onlinecourserecord_set', - Prefetch( - 'user__enrollment_set', - queryset=Enrollment.objects.filter( - is_deleted=False, - grade__in=GradeTypes.satisfactory_grades - ).select_related('course', 'course__meta_course'), - to_attr='prefetched_enrollments' - ), - # Prefetch academic_discipline as it's a many-to-many relationship - 'academic_disciplines' - ) + return StudentProfile.objects.filter( + Q(status=StudentStatuses.WILL_GRADUATE) | Q(status=StudentStatuses.GRADUATE, graduation_year=graduated_year), + site_id=site.id, + type__in=[StudentTypes.REGULAR, StudentTypes.PARTNER], + ).annotate( + # Add a priority field - lower value means higher priority + type_priority=Case( + When(type=StudentTypes.REGULAR, then=Value(1)), + When(type=StudentTypes.PARTNER, then=Value(2)), + default=Value(3), + output_field=IntegerField(), + ) + ).order_by( + 'user', 'type_priority' # Order by user first, then by priority (REGULAR first) + ).select_related( + 'user', + 'branch', + 'user__yandex_data' + ).prefetch_related( + Prefetch( + 'user__shadcourserecord_set', + queryset=SHADCourseRecord.objects.select_related('semester'), + ), + 'user__onlinecourserecord_set', + Prefetch( + 'user__enrollment_set', + queryset=Enrollment.objects.filter( + is_deleted=False, + grade__in=GradeTypes.satisfactory_grades + ).select_related('course', 'course__meta_course'), + to_attr='prefetched_enrollments' + ), + 'academic_disciplines' + ).distinct('user') @staticmethod def get_meta_courses_data(student_profiles: Iterable[StudentProfile]) -> Tuple[Dict[str, str], List[str], Dict[str, str]]: From 00849c195a21d51a7bfa640ec3d54aee05f5715f Mon Sep 17 00:00:00 2001 From: Sofya Anokhovskaya Date: Thu, 29 May 2025 12:28:07 +0300 Subject: [PATCH 2/5] feat: new filters --- apps/staff/services/diploma_export.py | 39 +++++++++++++++++---------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/apps/staff/services/diploma_export.py b/apps/staff/services/diploma_export.py index 76edd2086..60e66ccfb 100644 --- a/apps/staff/services/diploma_export.py +++ b/apps/staff/services/diploma_export.py @@ -18,11 +18,25 @@ class ElectronicDiplomaExportService: """ Service for exporting student data for electronic diplomas. - - This service handles the preparation and export of student data for electronic diplomas, - including personal information and course grades. """ + @staticmethod + def get_courses_grades(enrollments): + """ + Returns a dictionary mapping course indexes to grade displays for all courses + where there is at least one grade. + """ + result = {} + + if enrollments: + for enrollment in enrollments: + course_index = enrollment.course.meta_course.index + + if course_index: + result[course_index] = enrollment.grade_display.lower() + + return result + @staticmethod def get_student_profiles(site, graduated_year: int) -> QuerySet: """ @@ -48,16 +62,13 @@ def get_student_profiles(site, graduated_year: int) -> QuerySet: 'branch', 'user__yandex_data' ).prefetch_related( - Prefetch( - 'user__shadcourserecord_set', - queryset=SHADCourseRecord.objects.select_related('semester'), - ), - 'user__onlinecourserecord_set', Prefetch( 'user__enrollment_set', queryset=Enrollment.objects.filter( + Q(grade__in=GradeTypes.satisfactory_grades) & Q(grade__ne=GradeTypes.RE_CREDIT), is_deleted=False, - grade__in=GradeTypes.satisfactory_grades + course__main_branch__site_id=site.id, + course__meta_course__index__isnull=False ).select_related('course', 'course__meta_course'), to_attr='prefetched_enrollments' ), @@ -87,14 +98,14 @@ def get_meta_courses_data(student_profiles: Iterable[StudentProfile]) -> Tuple[D meta_courses.append(mc.index) # Generate header and add to headers list - header = f"{mc.index}:evaluation" + header = f"{mc.index}:evaluation" if mc.index else f"{mc.name}:evaluation" courses_headers.append(header) header_to_index[header] = mc.index return meta_courses, courses_headers, header_to_index - @staticmethod - def prepare_student_data(student_profiles: Iterable[StudentProfile], meta_courses: Dict[str, str], + @classmethod + def prepare_student_data(cls, student_profiles: Iterable[StudentProfile], meta_courses: Dict[str, str], graduated_year: int) -> Tuple[List[Dict[str, Any]], Set[str]]: """ Prepare student data for CSV export. @@ -106,7 +117,7 @@ def prepare_student_data(student_profiles: Iterable[StudentProfile], meta_course user = profile.user # Get courses with grades for this student - course_results = profile.get_courses_grades(meta_courses) + course_results = cls.get_courses_grades(profile.user.prefetched_enrollments) courses_with_grades.update(course_results.keys()) # Prepare base data for the student @@ -124,7 +135,7 @@ def prepare_student_data(student_profiles: Iterable[StudentProfile], meta_course profile.diploma_number, '', profile.academic_discipline if profile.academic_discipline else '', - profile.get_passed_courses_total(), + len(course_results), ] student_data.append({ From a6c188a52275bb2244ac87a53cb3b664b9f5a43e Mon Sep 17 00:00:00 2001 From: Sofya Anokhovskaya Date: Thu, 29 May 2025 13:04:33 +0300 Subject: [PATCH 3/5] feat: description --- apps/staff/templates/staff/exports.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/staff/templates/staff/exports.html b/apps/staff/templates/staff/exports.html index 078d72cc2..eb105b112 100644 --- a/apps/staff/templates/staff/exports.html +++ b/apps/staff/templates/staff/exports.html @@ -91,8 +91,12 @@

Будущий выпуск

Выгрузка файла для электронных дипломов

-

Для каждого студента с нужным годом выпуска и статусом Учится Восстановлен будет сгенерирована строчка в выгрузке.
- В случае, если нет какой то информации в профиле студента она останется пустой.

+

Кто будет в выгрузке?
+ Студенты со статусом Будет выпускаться.
+ Если у пользователя и магистратура и просто студент берем просто студента.
+ Студенты со статусом Выпускник, но год выпуска из вуза == год выгрузки.
+ Будут только курсы ШАД.
+ Будут только положительные оценки, кроме Перезачет (без оценки).

{% crispy export_for_electronic_diploma_form %}
From 6b43c884418bc8d3014ab78cc69676461da373a0 Mon Sep 17 00:00:00 2001 From: Sofya Anokhovskaya Date: Thu, 29 May 2025 15:55:31 +0300 Subject: [PATCH 4/5] fix: cr --- apps/staff/services/diploma_export.py | 17 ++++++++--------- apps/staff/templates/staff/exports.html | 11 +++++------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/apps/staff/services/diploma_export.py b/apps/staff/services/diploma_export.py index 60e66ccfb..90dd83e62 100644 --- a/apps/staff/services/diploma_export.py +++ b/apps/staff/services/diploma_export.py @@ -28,12 +28,10 @@ def get_courses_grades(enrollments): """ result = {} - if enrollments: - for enrollment in enrollments: - course_index = enrollment.course.meta_course.index - - if course_index: - result[course_index] = enrollment.grade_display.lower() + for enrollment in enrollments: + course_index = enrollment.course.meta_course.index + if course_index: + result[course_index] = enrollment.grade_display.lower() return result @@ -52,11 +50,12 @@ def get_student_profiles(site, graduated_year: int) -> QuerySet: type_priority=Case( When(type=StudentTypes.REGULAR, then=Value(1)), When(type=StudentTypes.PARTNER, then=Value(2)), - default=Value(3), output_field=IntegerField(), ) ).order_by( 'user', 'type_priority' # Order by user first, then by priority (REGULAR first) + ).distinct( + 'user' ).select_related( 'user', 'branch', @@ -69,11 +68,11 @@ def get_student_profiles(site, graduated_year: int) -> QuerySet: is_deleted=False, course__main_branch__site_id=site.id, course__meta_course__index__isnull=False - ).select_related('course', 'course__meta_course'), + ).select_related('course__meta_course'), to_attr='prefetched_enrollments' ), 'academic_disciplines' - ).distinct('user') + ) @staticmethod def get_meta_courses_data(student_profiles: Iterable[StudentProfile]) -> Tuple[Dict[str, str], List[str], Dict[str, str]]: diff --git a/apps/staff/templates/staff/exports.html b/apps/staff/templates/staff/exports.html index eb105b112..62bf27c6c 100644 --- a/apps/staff/templates/staff/exports.html +++ b/apps/staff/templates/staff/exports.html @@ -91,12 +91,11 @@

Будущий выпуск

Выгрузка файла для электронных дипломов

-

Кто будет в выгрузке?
- Студенты со статусом Будет выпускаться.
- Если у пользователя и магистратура и просто студент берем просто студента.
- Студенты со статусом Выпускник, но год выпуска из вуза == год выгрузки.
- Будут только курсы ШАД.
- Будут только положительные оценки, кроме Перезачет (без оценки).

+

Какие данные попадут в выгрузку
+ Обычные профили студентов и профили студентов совместных программ со статусом "Будет выпускаться" или "Выпускник" (в этом случае проверяется также, что год выпуска из вуза совпадает с текущим)
+ Если у одного пользователя есть несколько подходящих профилей, приоритет отдается обычному профилю
+ Учитываются только записи с оценками "Зачет", "Хорошо" и "Отлично"

+ {% crispy export_for_electronic_diploma_form %}
From 5c74fdd908a9eb25779f812df66a34011a1f9a45 Mon Sep 17 00:00:00 2001 From: Sofya Anokhovskaya Date: Thu, 29 May 2025 15:58:51 +0300 Subject: [PATCH 5/5] feat: doc string --- apps/staff/services/diploma_export.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/staff/services/diploma_export.py b/apps/staff/services/diploma_export.py index 90dd83e62..5e47c6e5c 100644 --- a/apps/staff/services/diploma_export.py +++ b/apps/staff/services/diploma_export.py @@ -39,6 +39,11 @@ def get_courses_grades(enrollments): def get_student_profiles(site, graduated_year: int) -> QuerySet: """ Get student profiles for electronic diplomas export with optimized prefetching. + - Students and partner student with the status Will be graduated. + - If the user has both a master's degree and just a student, we take just a student. + - Students with the status Graduate, but the year of graduation from the university == graduated_year. + - There will only be SHAD (reg) courses. + - There will only be satisfactory_grades, except for Re-credit (no grade). """ return StudentProfile.objects.filter(