Merged in feature/VBV-696-person-export (pull request #355)

Feature/VBV-696 person export

Approved-by: Elia Bieri
This commit is contained in:
Christian Cueni 2024-07-31 10:00:40 +00:00
commit 8f0c68270b
13 changed files with 654 additions and 189 deletions

View File

@ -6,9 +6,14 @@ import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import { computed, ref, watch } from "vue"; import { computed, ref, watch } from "vue";
import { useTranslation } from "i18next-vue"; import { useTranslation } from "i18next-vue";
import _ from "lodash"; import _ from "lodash";
import type { DashboardPersonCourseSessionType } from "@/services/dashboard"; import {
type DashboardPersonCourseSessionType,
exportPersons,
} from "@/services/dashboard";
import { useRouteQuery } from "@vueuse/router"; import { useRouteQuery } from "@vueuse/router";
import type { DashboardPersonsPageMode } from "@/types"; import type { DashboardPersonsPageMode, StatisticsFilterItem } from "@/types";
import { useUserStore } from "@/stores/user";
import { exportDataAsXls } from "@/utils/export";
log.debug("DashboardPersonsPage created"); log.debug("DashboardPersonsPage created");
@ -28,6 +33,7 @@ type MenuItem = {
}; };
const { t } = useTranslation(); const { t } = useTranslation();
const userStore = useUserStore();
const { loading, dashboardPersons } = useDashboardPersonsDueDates(props.mode); const { loading, dashboardPersons } = useDashboardPersonsDueDates(props.mode);
@ -227,6 +233,32 @@ function personRoleDisplayValue(personCourseSession: DashboardPersonCourseSessio
return ""; return "";
} }
function exportData() {
const courseSessionIdsSet = new Set<string>();
// get all course session ids from users
if (selectedSession.value.id === UNFILTERED) {
for (const person of filteredPersons.value) {
for (const courseSession of person.course_sessions) {
courseSessionIdsSet.add(courseSession.id);
}
}
} else {
courseSessionIdsSet.add(selectedSession.value.id);
}
// construct StatisticsFilterItems for export call
const items: StatisticsFilterItem[] = [];
for (const csId of courseSessionIdsSet) {
items.push({
_id: "",
course_session_id: csId,
generation: "",
circle_id: "",
});
}
exportDataAsXls(items, exportPersons, userStore.language);
}
watch(selectedCourse, () => { watch(selectedCourse, () => {
selectedRegion.value = regions.value[0]; selectedRegion.value = regions.value[0];
}); });
@ -253,7 +285,18 @@ watch(selectedRegion, () => {
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left> <it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>
<span class="inline">{{ $t("general.back") }}</span> <span class="inline">{{ $t("general.back") }}</span>
</router-link> </router-link>
<h2 class="my-4">{{ $t("a.Personen") }}</h2> <div class="mb-10 flex items-center justify-between">
<h2 class="my-4">{{ $t("a.Personen") }}</h2>
<button
v-if="userStore.course_session_experts.length > 0"
class="flex"
data-cy="export-button"
@click="exportData"
>
<it-icon-export></it-icon-export>
<span class="ml inline-block">{{ $t("a.Als Excel exportieren") }}</span>
</button>
</div>
<div class="bg-white px-4 py-2"> <div class="bg-white px-4 py-2">
<section <section
v-if="filtersVisible" v-if="filtersVisible"

View File

@ -221,6 +221,15 @@ export async function exportCompetenceElements(
}); });
} }
export async function exportPersons(
data: XlsExportRequestData,
language: string
): Promise<XlsExportResponseData> {
return await itPost("/api/dashboard/export/persons/", data, {
headers: { "Accept-Language": language },
});
}
export function courseIdForCourseSlug( export function courseIdForCourseSlug(
dashboardConfigs: DashboardCourseConfigType[], dashboardConfigs: DashboardCourseConfigType[],
courseSlug: string courseSlug: string

View File

@ -26,7 +26,7 @@ function getCurrentDate() {
function verifyExportFileExists(fileName) { function verifyExportFileExists(fileName) {
const downloadsFolder = Cypress.config("downloadsFolder"); const downloadsFolder = Cypress.config("downloadsFolder");
cy.readFile( cy.readFile(
path.join(downloadsFolder, `${fileName}_${getCurrentDate()}.xlsx`) path.join(downloadsFolder, `${fileName}_${getCurrentDate()}.xlsx`),
).should("exist"); ).should("exist");
} }
@ -39,7 +39,7 @@ function testExport(url, fileName) {
describe("dashboardExport.cy.js", () => { describe("dashboardExport.cy.js", () => {
beforeEach(() => { beforeEach(() => {
cy.manageCommand( cy.manageCommand(
"cypress_reset --create-assignment-evaluation --create-feedback-responses --create-course-completion-performance-criteria --create-attendance-days" "cypress_reset --create-assignment-evaluation --create-feedback-responses --create-course-completion-performance-criteria --create-attendance-days",
); );
}); });
@ -55,13 +55,17 @@ describe("dashboardExport.cy.js", () => {
it("should download the competence elements export", () => { it("should download the competence elements export", () => {
testExport( testExport(
"/statistic/test-lehrgang/assignment", "/statistic/test-lehrgang/assignment",
"export_kompetenznachweis_elemente" "export_kompetenznachweis_elemente",
); );
}); });
it("should download the feedback export", () => { it("should download the feedback export", () => {
testExport("/statistic/test-lehrgang/feedback", "export_feedback"); testExport("/statistic/test-lehrgang/feedback", "export_feedback");
}); });
it("should download the person export", () => {
testExport("/dashboard/persons", "export_personen");
});
}); });
describe("as trainer", () => { describe("as trainer", () => {
@ -76,12 +80,16 @@ describe("dashboardExport.cy.js", () => {
it("should download the competence elements export", () => { it("should download the competence elements export", () => {
testExport( testExport(
"/statistic/test-lehrgang/assignment", "/statistic/test-lehrgang/assignment",
"export_kompetenznachweis_elemente" "export_kompetenznachweis_elemente",
); );
}); });
it("should download the feedback export", () => { it("should download the feedback export", () => {
testExport("/statistic/test-lehrgang/feedback", "export_feedback"); testExport("/statistic/test-lehrgang/feedback", "export_feedback");
}); });
it("should download the person export", () => {
testExport("/dashboard/persons", "export_personen");
});
}); });
}); });

View File

@ -44,6 +44,7 @@ from vbv_lernwelt.dashboard.views import (
export_attendance_as_xsl, export_attendance_as_xsl,
export_competence_elements_as_xsl, export_competence_elements_as_xsl,
export_feedback_as_xsl, export_feedback_as_xsl,
export_persons_as_xsl,
get_dashboard_config, get_dashboard_config,
get_dashboard_due_dates, get_dashboard_due_dates,
get_dashboard_persons, get_dashboard_persons,
@ -143,6 +144,7 @@ urlpatterns = [
path(r"api/dashboard/export/competence_elements/", export_competence_elements_as_xsl, path(r"api/dashboard/export/competence_elements/", export_competence_elements_as_xsl,
name="export_certificate_as_xsl"), name="export_certificate_as_xsl"),
path(r"api/dashboard/export/feedback/", export_feedback_as_xsl, name="export_feedback_as_xsl"), path(r"api/dashboard/export/feedback/", export_feedback_as_xsl, name="export_feedback_as_xsl"),
path(r"api/dashboard/export/persons/", export_persons_as_xsl, name="export_persons_as_xsl"),
# course # course
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"), path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),

View File

@ -87,7 +87,7 @@ msgstr ""
msgid "Lehrgang-Seite" msgid "Lehrgang-Seite"
msgstr "" msgstr ""
#: vbv_lernwelt/course/models.py:278 #: vbv_lernwelt/dashboard/person_export.py:116
msgid "Teilnehmer" msgid "Teilnehmer"
msgstr "" msgstr ""
@ -111,7 +111,7 @@ msgstr ""
msgid "Versicherungsvermittler-Lehrgang" msgid "Versicherungsvermittler-Lehrgang"
msgstr "" msgstr ""
#: vbv_lernwelt/course/models.py:351 #: vbv_lernwelt/course/models.py:350
msgid "ÜK-Lehrgang" msgid "ÜK-Lehrgang"
msgstr "" msgstr ""
@ -155,10 +155,30 @@ msgstr ""
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
#: vbv_lernwelt/course_session/services/export_attendance.py:138 #: vbv_lernwelt/course_session/services/export_attendance.py:127
msgid "Lehrvertragsnummer" msgid "Lehrvertragsnummer"
msgstr "" msgstr ""
#: vbv_lernwelt/dashboard/person_export.py:16
msgid "export_personen"
msgstr ""
#: vbv_lernwelt/dashboard/person_export.py:68
msgid "Telefon"
msgstr ""
#: vbv_lernwelt/dashboard/person_export.py:69
msgid "Rolle"
msgstr ""
#: vbv_lernwelt/dashboard/person_export.py:118
msgid "Trainer"
msgstr ""
#: vbv_lernwelt/dashboard/person_export.py:120
msgid "Regionenleiter"
msgstr ""
#: vbv_lernwelt/feedback/export.py:19 #: vbv_lernwelt/feedback/export.py:19
msgid "export_feedback" msgid "export_feedback"
msgstr "" msgstr ""

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-07-27 20:59+0200\n" "POT-Creation-Date: 2024-07-30 11:16+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -88,8 +88,9 @@ msgid "Lehrgang-Seite"
msgstr "" msgstr ""
#: vbv_lernwelt/course/models.py:278 #: vbv_lernwelt/course/models.py:278
#: vbv_lernwelt/dashboard/person_export.py:116
msgid "Teilnehmer" msgid "Teilnehmer"
msgstr "" msgstr "Participant"
#: vbv_lernwelt/course/models.py:279 #: vbv_lernwelt/course/models.py:279
msgid "Experte/Trainer" msgid "Experte/Trainer"
@ -120,7 +121,6 @@ msgid "export_anwesenheit"
msgstr "export_presence" msgstr "export_presence"
#: vbv_lernwelt/course_session/services/export_attendance.py:86 #: vbv_lernwelt/course_session/services/export_attendance.py:86
#| msgid "Anwesenheit"
msgid "Optionale Anwesenheit" msgid "Optionale Anwesenheit"
msgstr "Présence facultative" msgstr "Présence facultative"
@ -160,6 +160,26 @@ msgstr "E-mail"
msgid "Lehrvertragsnummer" msgid "Lehrvertragsnummer"
msgstr "Numéro de contrat d'apprentissage" msgstr "Numéro de contrat d'apprentissage"
#: vbv_lernwelt/dashboard/person_export.py:16
msgid "export_personen"
msgstr "export_personnes"
#: vbv_lernwelt/dashboard/person_export.py:68
msgid "Telefon"
msgstr "Téléphone"
#: vbv_lernwelt/dashboard/person_export.py:69
msgid "Rolle"
msgstr "Rôle"
#: vbv_lernwelt/dashboard/person_export.py:118
msgid "Trainer"
msgstr "Formateur / Formatrice"
#: vbv_lernwelt/dashboard/person_export.py:120
msgid "Regionenleiter"
msgstr "Responsable CI"
#: vbv_lernwelt/feedback/export.py:19 #: vbv_lernwelt/feedback/export.py:19
msgid "export_feedback" msgid "export_feedback"
msgstr "export_feedback" msgstr "export_feedback"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-07-27 20:59+0200\n" "POT-Creation-Date: 2024-07-30 11:16+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -88,8 +88,9 @@ msgid "Lehrgang-Seite"
msgstr "" msgstr ""
#: vbv_lernwelt/course/models.py:278 #: vbv_lernwelt/course/models.py:278
#: vbv_lernwelt/dashboard/person_export.py:116
msgid "Teilnehmer" msgid "Teilnehmer"
msgstr "" msgstr "Partecipante"
#: vbv_lernwelt/course/models.py:279 #: vbv_lernwelt/course/models.py:279
msgid "Experte/Trainer" msgid "Experte/Trainer"
@ -120,7 +121,6 @@ msgid "export_anwesenheit"
msgstr "esportazione_presenza" msgstr "esportazione_presenza"
#: vbv_lernwelt/course_session/services/export_attendance.py:86 #: vbv_lernwelt/course_session/services/export_attendance.py:86
#| msgid "Anwesenheit"
msgid "Optionale Anwesenheit" msgid "Optionale Anwesenheit"
msgstr "Presenza opzionale" msgstr "Presenza opzionale"
@ -160,6 +160,28 @@ msgstr "Email"
msgid "Lehrvertragsnummer" msgid "Lehrvertragsnummer"
msgstr "Numero di contratto di tirocinio" msgstr "Numero di contratto di tirocinio"
#: vbv_lernwelt/dashboard/person_export.py:16
#, fuzzy
#| msgid "export_anwesenheit"
msgid "export_personen"
msgstr "esportazione_persone"
#: vbv_lernwelt/dashboard/person_export.py:68
msgid "Telefon"
msgstr "Telefono"
#: vbv_lernwelt/dashboard/person_export.py:69
msgid "Rolle"
msgstr "Ruolo"
#: vbv_lernwelt/dashboard/person_export.py:118
msgid "Trainer"
msgstr "Trainer"
#: vbv_lernwelt/dashboard/person_export.py:120
msgid "Regionenleiter"
msgstr "Responsabile CI"
#: vbv_lernwelt/feedback/export.py:19 #: vbv_lernwelt/feedback/export.py:19
msgid "export_feedback" msgid "export_feedback"
msgstr "esportazione_feedback" msgstr "esportazione_feedback"

View File

@ -0,0 +1,123 @@
from io import BytesIO
from typing import Optional
import structlog
from django.utils.translation import gettext_lazy as _
from openpyxl import Workbook
from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import CourseSession
from vbv_lernwelt.course_session.services.export_attendance import (
add_user_headers,
make_export_filename,
sanitize_sheet_name,
)
from vbv_lernwelt.dashboard.utils import create_person_list_with_roles
PERSONS_EXPORT_FILENAME = _("export_personen")
logger = structlog.get_logger(__name__)
def export_persons(
user: User,
course_session_ids: list[str],
save_as_file: bool = False,
) -> Optional[bytes]:
if not course_session_ids:
return
wb = Workbook()
# remove the first sheet is just easier than keeping track of the active sheet
wb.remove(wb.active)
user_with_roles = create_person_list_with_roles(user, course_session_ids)
course_sessions = CourseSession.objects.filter(id__in=course_session_ids)
for cs in course_sessions:
_create_sheet(
wb,
cs.title,
cs.id,
user_with_roles,
)
if save_as_file:
wb.save(make_export_filename(PERSONS_EXPORT_FILENAME))
else:
output = BytesIO()
wb.save(output)
output.seek(0)
return output.getvalue()
def _create_sheet(
wb: Workbook,
title: str,
cs_id: int,
user_with_roles,
):
sheet = wb.create_sheet(title=sanitize_sheet_name(title))
if len(user_with_roles) == 0:
return sheet
# headers
# common user headers, Circle <title> <learningcontenttitle> bestanden, Circle <title> <learningcontenttitle> Resultat, ...
col_idx = add_user_headers(sheet)
sheet.cell(row=1, column=col_idx, value=str(_("Telefon")))
sheet.cell(row=1, column=col_idx + 1, value=str(_("Rolle")))
_add_rows(sheet, user_with_roles, cs_id)
return sheet
def _add_rows(
sheet,
users,
course_session_id,
):
idx_offset = 0
for row_idx, user in enumerate(users, start=2):
def get_user_cs_by_id(user_cs, cs_id):
return next((cs for cs in user_cs if int(cs.get("id")) == cs_id), None)
user_cs = get_user_cs_by_id(user["course_sessions"], course_session_id)
if not user_cs:
logger.warning(
"User not found in course session",
user_id=user["user_id"],
course_session_id=course_session_id,
)
idx_offset += 1
continue
user_role = _role_as_string(user_cs.get("user_role"))
idx = row_idx - idx_offset
sheet.cell(row=idx, column=1, value=user["first_name"])
sheet.cell(row=idx, column=2, value=user["last_name"])
sheet.cell(row=idx, column=3, value=user["email"])
sheet.cell(row=idx, column=4, value=user.get("Lehrvertragsnummer", ""))
sheet.cell(
row=idx,
column=5,
value=user.get("phone_number", ""),
)
sheet.cell(row=idx, column=6, value=user_role)
def _role_as_string(role):
if role == "MEMBER":
return str(_("Teilnehmer"))
elif role == "EXPERT":
return str(_("Trainer"))
elif role == "SUPERVISOR":
return str(_("Regionenleiter"))
else:
return role

View File

@ -0,0 +1,186 @@
import io
from django.utils.translation import activate
from openpyxl import load_workbook
from vbv_lernwelt.core.constants import (
TEST_STUDENT1_USER_ID,
TEST_STUDENT2_USER_ID,
TEST_STUDENT3_USER_ID,
TEST_TRAINER1_USER_ID,
TEST_TRAINER2_USER_ID,
)
from vbv_lernwelt.core.create_default_users import create_default_users
from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.creators.test_course import create_test_course
from vbv_lernwelt.course.models import CourseSession
from vbv_lernwelt.course_session.tests.test_attendance_export import ExportBaseTestCase
from vbv_lernwelt.dashboard.person_export import export_persons
from vbv_lernwelt.learnpath.models import Circle
class PersonsExportTestCase(ExportBaseTestCase):
def setUp(self):
super().setUp()
create_default_users()
create_test_course(include_vv=False, with_sessions=True)
self.course_session_be = CourseSession.objects.get(title="Test Bern 2022 a")
self.course_session_zh = CourseSession.objects.get(title="Test Zürich 2022 a")
self.circle_fahrzeug = Circle.objects.get(
slug="test-lehrgang-lp-circle-fahrzeug"
)
self.circle_reisen = Circle.objects.get(slug="test-lehrgang-lp-circle-fahrzeug")
self.test_trainer1 = User.objects.get(id=TEST_TRAINER1_USER_ID)
self.test_trainer2 = User.objects.get(id=TEST_TRAINER2_USER_ID)
self.test_student1 = User.objects.get(id=TEST_STUDENT1_USER_ID)
self.test_student2 = User.objects.get(id=TEST_STUDENT2_USER_ID)
self.test_student3 = User.objects.get(id=TEST_STUDENT3_USER_ID)
self.test_student1_row = [
self.test_student1.first_name,
self.test_student1.last_name,
self.test_student1.email,
None,
None,
"Teilnehmer",
]
self.test_student2_row = [
self.test_student2.first_name,
self.test_student2.last_name,
self.test_student2.email,
None,
None,
"Teilnehmer",
]
self.test_student3_row = [
self.test_student3.first_name,
self.test_student3.last_name,
self.test_student3.email,
None,
None,
"Teilnehmer",
]
self.test_trainer1_row = [
self.test_trainer1.first_name,
self.test_trainer1.last_name,
self.test_trainer1.email,
None,
None,
"Trainer",
]
self.test_trainer2_row = [
self.test_trainer2.first_name,
self.test_trainer2.last_name,
self.test_trainer2.email,
None,
None,
"Trainer",
]
def _generate_expected_data(self, rows):
expected_data = [
self._make_header(),
]
for r in rows:
expected_data.append(r)
return expected_data
def _generate_workbook(self, user, course_session_ids):
export_data = io.BytesIO(
export_persons(user, course_session_ids, save_as_file=False)
)
return load_workbook(export_data)
def _make_header(self):
return [
"Vorname",
"Nachname",
"Email",
"Lehrvertragsnummer",
"Telefon",
"Rolle",
]
def test_export_persons(self):
wb = self._generate_workbook(self.test_trainer1, [self.course_session_be.id])
self.assertEqual(len(wb.sheetnames), 1)
self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a")
wb.active = wb["Test Bern 2022 a"]
data = self._generate_expected_data(
[
self.test_student1_row,
self.test_student2_row,
self.test_student3_row,
self.test_trainer1_row,
]
)
self._check_export(wb, data, 4, 6)
wb = self._generate_workbook(self.test_trainer2, [self.course_session_zh.id])
self.assertEqual(len(wb.sheetnames), 1)
self.assertEqual(wb.sheetnames[0], "Test Zürich 2022 a")
wb.active = wb["Test Zürich 2022 a"]
data = self._generate_expected_data(
[self.test_student2_row, self.test_trainer2_row]
)
self._check_export(wb, data, 3, 6)
def test_cannot_export_other_session(self):
wb = self._generate_workbook(self.test_trainer1, [self.course_session_zh.id])
self.assertEqual(len(wb.sheetnames), 1)
self.assertEqual(wb.sheetnames[0], "Test Zürich 2022 a")
wb.active = wb["Test Zürich 2022 a"]
data = self._generate_expected_data([[None] * 6])
self._check_export(wb, data, 1, 6)
def test_export_in_fr(self):
activate("fr")
wb = self._generate_workbook(self.test_trainer1, [self.course_session_be.id])
self.assertEqual(len(wb.sheetnames), 1)
self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a")
wb.active = wb["Test Bern 2022 a"]
header = [
"Prénom",
"Nom de famille",
"E-mail",
"Numéro de contrat d'apprentissage",
"Téléphone",
"Rôle",
]
self.assertEqual([cell.value for cell in wb.active[1]], header)
self.assertEqual(wb.active.cell(row=2, column=6).value, "Participant")
self.assertEqual(
wb.active.cell(row=5, column=6).value, "Formateur / Formatrice"
)
def test_export_in_it(self):
activate("it")
wb = self._generate_workbook(self.test_trainer1, [self.course_session_be.id])
self.assertEqual(len(wb.sheetnames), 1)
self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a")
wb.active = wb["Test Bern 2022 a"]
header = [
"Nome",
"Cognome",
"Email",
"Numero di contratto di tirocinio",
"Telefono",
"Ruolo",
]
self.assertEqual([cell.value for cell in wb.active[1]], header)
self.assertEqual(wb.active.cell(row=2, column=6).value, "Partecipante")
self.assertEqual(wb.active.cell(row=5, column=6).value, "Trainer")

View File

@ -0,0 +1,179 @@
from dataclasses import dataclass
from typing import List, Set
from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
from vbv_lernwelt.learning_mentor.models import LearningMentor
@dataclass(frozen=True)
class CourseSessionWithRoles:
_original: CourseSession
roles: Set[str]
def __getattr__(self, name: str):
# Delegate attribute access to the _original CourseSession object
return getattr(self._original, name)
def save(self, *args, **kwargs):
raise NotImplementedError("This proxy object cannot be saved.")
def get_course_sessions_with_roles_for_user(user: User) -> List[CourseSessionWithRoles]:
result_course_sessions = {}
# participant/member/expert course sessions
csu_qs = CourseSessionUser.objects.filter(user=user).prefetch_related(
"course_session", "course_session__course"
)
for csu in csu_qs:
cs = csu.course_session
# member/expert is mutually exclusive...
cs.roles = {csu.role}
result_course_sessions[cs.id] = cs
# enrich with supervisor course sessions
csg_qs = CourseSessionGroup.objects.filter(supervisor=user).prefetch_related(
"course_session", "course_session__course"
)
for csg in csg_qs:
for cs in csg.course_session.all():
cs.roles = set()
cs = result_course_sessions.get(cs.id, cs)
cs.roles.add("SUPERVISOR")
result_course_sessions[cs.id] = cs
# enrich with mentor course sessions
lm_qs = LearningMentor.objects.filter(mentor=user).prefetch_related(
"course_session", "course_session__course"
)
for lm in lm_qs:
cs = lm.course_session
cs.roles = set()
cs = result_course_sessions.get(cs.id, cs)
cs.roles.add("LEARNING_MENTOR")
result_course_sessions[cs.id] = cs
return [
CourseSessionWithRoles(cs, cs.roles) for cs in result_course_sessions.values()
]
def has_cs_role(roles: Set[str]) -> bool:
return bool(roles & {"SUPERVISOR", "EXPERT", "MEMBER"})
def user_role(roles: Set[str]) -> str:
if "SUPERVISOR" in roles:
return "SUPERVISOR"
if "EXPERT" in roles:
return "EXPERT"
if "MEMBER" in roles:
return "MEMBER"
return "LEARNING_MENTOR"
def create_course_session_dict(course_session_object, my_role, user_role):
return {
"id": str(course_session_object.id),
"session_title": course_session_object.title,
"course_id": str(course_session_object.course.id),
"course_title": course_session_object.course.title,
"course_slug": course_session_object.course.slug,
"region": course_session_object.region,
"generation": course_session_object.generation,
"my_role": my_role,
"user_role": user_role,
"is_uk": course_session_object.course.configuration.is_uk,
"is_vv": course_session_object.course.configuration.is_vv,
}
def create_person_list_with_roles(
user, course_session_ids=None, include_private_data=False
):
def create_user_dict(user_object):
user_data = {
"user_id": user_object.id,
"first_name": user_object.first_name,
"last_name": user_object.last_name,
"email": user_object.email,
"avatar_url_small": user_object.avatar_url_small,
"avatar_url": user_object.avatar_url,
"course_sessions": [],
}
if include_private_data:
user_data["phone_number"] = user_object.phone_number
user_data["Lehrvertragsnummer"] = user_object.additional_json_data.get(
"Lehrvertragsnummer", ""
)
return user_data
course_sessions = get_course_sessions_with_roles_for_user(user)
result_persons = {}
for cs in course_sessions:
if has_cs_role(cs.roles) and cs.course.configuration.is_uk:
course_session_users = CourseSessionUser.objects.filter(
course_session=cs.id
).select_related("user")
my_role = user_role(cs.roles)
for csu in course_session_users:
person_data = result_persons.get(
csu.user.id, create_user_dict(csu.user)
)
person_data["course_sessions"].append(
create_course_session_dict(cs, my_role, csu.role)
)
result_persons[csu.user.id] = person_data
# add persons where request.user is mentor
for cs in course_sessions:
if "LEARNING_MENTOR" in cs.roles:
lm = LearningMentor.objects.filter(
mentor=user, course_session=cs.id
).first()
for participant in lm.participants.all():
course_session_entry = create_course_session_dict(
cs,
"LEARNING_MENTOR",
"LEARNING_MENTEE",
)
if participant.user.id not in result_persons:
person_data = create_user_dict(participant.user)
person_data["course_sessions"] = [course_session_entry]
result_persons[participant.user.id] = person_data
else:
# user is already in result_persons
result_persons[participant.user.id]["course_sessions"].append(
course_session_entry
)
# add persons where request.user is mentee
mentor_relation_qs = LearningMentor.objects.filter(
participants__user=user
).prefetch_related("mentor", "course_session")
for mentor_relation in mentor_relation_qs:
cs = mentor_relation.course_session
course_session_entry = create_course_session_dict(
cs,
"LEARNING_MENTEE",
"LEARNING_MENTOR",
)
if mentor_relation.mentor.id not in result_persons:
person_data = create_user_dict(mentor_relation.mentor)
person_data["course_sessions"] = [course_session_entry]
result_persons[mentor_relation.mentor.id] = person_data
else:
# user is already in result_persons
result_persons[mentor_relation.mentor.id]["course_sessions"].append(
course_session_entry
)
return result_persons.values()

View File

@ -2,7 +2,7 @@ import base64
from dataclasses import asdict, dataclass from dataclasses import asdict, dataclass
from datetime import date from datetime import date
from enum import Enum from enum import Enum
from typing import List, Set, Tuple from typing import List, Tuple
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponse from django.http import HttpResponse
@ -24,25 +24,27 @@ from vbv_lernwelt.competence.services import (
query_competence_course_session_edoniq_tests, query_competence_course_session_edoniq_tests,
) )
from vbv_lernwelt.core.models import User from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import ( from vbv_lernwelt.course.models import CourseConfiguration, CourseSessionUser
CourseConfiguration,
CourseSession,
CourseSessionUser,
)
from vbv_lernwelt.course.views import logger from vbv_lernwelt.course.views import logger
from vbv_lernwelt.course_session.services.export_attendance import ( from vbv_lernwelt.course_session.services.export_attendance import (
ATTENDANCE_EXPORT_FILENAME, ATTENDANCE_EXPORT_FILENAME,
export_attendance, export_attendance,
make_export_filename, make_export_filename,
) )
from vbv_lernwelt.course_session_group.models import CourseSessionGroup from vbv_lernwelt.dashboard.person_export import export_persons, PERSONS_EXPORT_FILENAME
from vbv_lernwelt.dashboard.utils import (
CourseSessionWithRoles,
create_course_session_dict,
create_person_list_with_roles,
get_course_sessions_with_roles_for_user,
user_role,
)
from vbv_lernwelt.duedate.models import DueDate from vbv_lernwelt.duedate.models import DueDate
from vbv_lernwelt.duedate.serializers import DueDateSerializer from vbv_lernwelt.duedate.serializers import DueDateSerializer
from vbv_lernwelt.feedback.export import ( from vbv_lernwelt.feedback.export import (
export_feedback_with_circle_restriction, export_feedback_with_circle_restriction,
FEEDBACK_EXPORT_FILE_NAME, FEEDBACK_EXPORT_FILE_NAME,
) )
from vbv_lernwelt.learning_mentor.models import LearningMentor
from vbv_lernwelt.learnpath.models import Circle from vbv_lernwelt.learnpath.models import Circle
from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback
@ -65,19 +67,6 @@ class RoleKeyType(Enum):
TRAINER = "Trainer" TRAINER = "Trainer"
@dataclass(frozen=True)
class CourseSessionWithRoles:
_original: CourseSession
roles: Set[str]
def __getattr__(self, name: str):
# Delegate attribute access to the _original CourseSession object
return getattr(self._original, name)
def save(self, *args, **kwargs):
raise NotImplementedError("This proxy object cannot be saved.")
@dataclass(frozen=True) @dataclass(frozen=True)
class CourseConfig: class CourseConfig:
course_id: str course_id: str
@ -92,156 +81,6 @@ class CourseConfig:
session_to_continue_id: str | None session_to_continue_id: str | None
def get_course_sessions_with_roles_for_user(user: User) -> List[CourseSessionWithRoles]:
result_course_sessions = {}
# participant/member/expert course sessions
csu_qs = CourseSessionUser.objects.filter(user=user).prefetch_related(
"course_session", "course_session__course"
)
for csu in csu_qs:
cs = csu.course_session
# member/expert is mutually exclusive...
cs.roles = {csu.role}
result_course_sessions[cs.id] = cs
# enrich with supervisor course sessions
csg_qs = CourseSessionGroup.objects.filter(supervisor=user).prefetch_related(
"course_session", "course_session__course"
)
for csg in csg_qs:
for cs in csg.course_session.all():
cs.roles = set()
cs = result_course_sessions.get(cs.id, cs)
cs.roles.add("SUPERVISOR")
result_course_sessions[cs.id] = cs
# enrich with mentor course sessions
lm_qs = LearningMentor.objects.filter(mentor=user).prefetch_related(
"course_session", "course_session__course"
)
for lm in lm_qs:
cs = lm.course_session
cs.roles = set()
cs = result_course_sessions.get(cs.id, cs)
cs.roles.add("LEARNING_MENTOR")
result_course_sessions[cs.id] = cs
return [
CourseSessionWithRoles(cs, cs.roles) for cs in result_course_sessions.values()
]
def has_cs_role(roles: Set[str]) -> bool:
return bool(roles & {"SUPERVISOR", "EXPERT", "MEMBER"})
def user_role(roles: Set[str]) -> str:
if "SUPERVISOR" in roles:
return "SUPERVISOR"
if "EXPERT" in roles:
return "EXPERT"
if "MEMBER" in roles:
return "MEMBER"
return "LEARNING_MENTOR"
def _create_course_session_dict(course_session_object, my_role, user_role):
return {
"id": str(course_session_object.id),
"session_title": course_session_object.title,
"course_id": str(course_session_object.course.id),
"course_title": course_session_object.course.title,
"course_slug": course_session_object.course.slug,
"region": course_session_object.region,
"generation": course_session_object.generation,
"my_role": my_role,
"user_role": user_role,
"is_uk": course_session_object.course.configuration.is_uk,
"is_vv": course_session_object.course.configuration.is_vv,
}
def _create_person_list_with_roles(user):
def create_user_dict(user_object):
return {
"user_id": user_object.id,
"first_name": user_object.first_name,
"last_name": user_object.last_name,
"email": user_object.email,
"avatar_url_small": user_object.avatar_url_small,
"avatar_url": user_object.avatar_url,
"course_sessions": [],
}
course_sessions = get_course_sessions_with_roles_for_user(user)
result_persons = {}
for cs in course_sessions:
if has_cs_role(cs.roles) and cs.course.configuration.is_uk:
course_session_users = CourseSessionUser.objects.filter(
course_session=cs.id
).select_related("user")
my_role = user_role(cs.roles)
for csu in course_session_users:
person_data = result_persons.get(
csu.user.id, create_user_dict(csu.user)
)
person_data["course_sessions"].append(
_create_course_session_dict(cs, my_role, csu.role)
)
result_persons[csu.user.id] = person_data
# add persons where request.user is mentor
for cs in course_sessions:
if "LEARNING_MENTOR" in cs.roles:
lm = LearningMentor.objects.filter(
mentor=user, course_session=cs.id
).first()
for participant in lm.participants.all():
course_session_entry = _create_course_session_dict(
cs,
"LEARNING_MENTOR",
"LEARNING_MENTEE",
)
if participant.user.id not in result_persons:
person_data = create_user_dict(participant.user)
person_data["course_sessions"] = [course_session_entry]
result_persons[participant.user.id] = person_data
else:
# user is already in result_persons
result_persons[participant.user.id]["course_sessions"].append(
course_session_entry
)
# add persons where request.user is mentee
mentor_relation_qs = LearningMentor.objects.filter(
participants__user=user
).prefetch_related("mentor", "course_session")
for mentor_relation in mentor_relation_qs:
cs = mentor_relation.course_session
course_session_entry = _create_course_session_dict(
cs,
"LEARNING_MENTEE",
"LEARNING_MENTOR",
)
if mentor_relation.mentor.id not in result_persons:
person_data = create_user_dict(mentor_relation.mentor)
person_data["course_sessions"] = [course_session_entry]
result_persons[mentor_relation.mentor.id] = person_data
else:
# user is already in result_persons
result_persons[mentor_relation.mentor.id]["course_sessions"].append(
course_session_entry
)
return result_persons.values()
def _persons_list_add_competence_metrics(persons): def _persons_list_add_competence_metrics(persons):
course_session_ids = {cs["id"] for p in persons for cs in p["course_sessions"]} course_session_ids = {cs["id"] for p in persons for cs in p["course_sessions"]}
competence_assignments = query_competence_course_session_assignments( competence_assignments = query_competence_course_session_assignments(
@ -284,7 +123,7 @@ def _persons_list_add_competence_metrics(persons):
@api_view(["GET"]) @api_view(["GET"])
def get_dashboard_persons(request): def get_dashboard_persons(request):
try: try:
persons = list(_create_person_list_with_roles(request.user)) persons = list(create_person_list_with_roles(request.user))
if request.GET.get("with_competence_metrics", "") == "true": if request.GET.get("with_competence_metrics", "") == "true":
persons = _persons_list_add_competence_metrics(persons) persons = _persons_list_add_competence_metrics(persons)
@ -322,7 +161,7 @@ def get_dashboard_due_dates(request):
cs = course_session_map.get(due_date.course_session_id) cs = course_session_map.get(due_date.course_session_id)
if cs: if cs:
data["course_session"] = _create_course_session_dict( data["course_session"] = create_course_session_dict(
cs, my_role=user_role(cs.roles), user_role="" cs, my_role=user_role(cs.roles), user_role=""
) )
result_due_dates.append(data) result_due_dates.append(data)
@ -577,6 +416,20 @@ def export_feedback_as_xsl(request):
return _make_excel_response(data, FEEDBACK_EXPORT_FILE_NAME) return _make_excel_response(data, FEEDBACK_EXPORT_FILE_NAME)
@api_view(["POST"])
def export_persons_as_xsl(request):
requested_course_session_ids = request.data.get("courseSessionIds", [])
course_sessions_with_roles = _get_permitted_courses_sessions_for_user(
request.user, requested_course_session_ids
) # noqa
data = export_persons(
request.user,
[cswr.id for cswr in course_sessions_with_roles],
)
return _make_excel_response(data, PERSONS_EXPORT_FILENAME)
def _get_permitted_courses_sessions_for_user( def _get_permitted_courses_sessions_for_user(
user: User, requested_coursesession_ids: List[str] user: User, requested_coursesession_ids: List[str]
) -> List[CourseSessionWithRoles]: ) -> List[CourseSessionWithRoles]: