From 7a7caec2194b2ca48bd2c233d8ba1ed73178b834 Mon Sep 17 00:00:00 2001 From: Christian Cueni Date: Tue, 4 Jun 2024 13:02:26 +0200 Subject: [PATCH] wip: Add assignment completion tests --- env_secrets/local_chrigu.env | Bin 2593 -> 2746 bytes server/vbv_lernwelt/assignment/export.py | 20 +- .../test_assignment_completions_export.py | 172 ++++++++++++++++-- .../course/creators/test_course.py | 7 + .../tests/test_attendance_export.py | 40 ++-- 5 files changed, 205 insertions(+), 34 deletions(-) diff --git a/env_secrets/local_chrigu.env b/env_secrets/local_chrigu.env index 431043ffe4399d1f0a764a1b986c6d2f9f682c76..23475a25faa8084c1204de1ebf541da650ad45f5 100644 GIT binary patch literal 2746 zcmV;r3Ptq*M@dveQdv+`05A$#V^2!X$+C1f>HOb(zq<9s>e5Do5u&Mm1EUIKWU$Mg zoXZrITHtR=5}y5Jvu@m ziXX!(x}JASJxE5!Vtf#{i?FcS_?|4Vfir)JJLQP<<0#NY;bL3&V@&|*JDIb>ghs7m0n$HM{b*y68C)A7mfKxQAg1G+8KGM@ z;kAOlPn`o)u&n(+g`O@nSsdf#ud9fuLHO^YW2}Ff+o~lGmHmL2BG`gU__A!uyr>TJ zY^dYW7mEQ)k`}dYz-}d!R6NtgqfZMFYJf{3mzW~+ad@T~n9b)EUfFqDey!_EVFSx` z$&i%yd@j@PyqV1q4uVS0odzCZe_DeCwKY58jOhAKpwuTmStS;IfA3@=#^A0&2h&n^ z7`7aShvMCNM%(_E(A1dc?vbXhrRoVf9bd>=S)2e5YZTe+gCwN8gWukU>_PGZXEFlm zxhOAKWT#wh4AN{FFJ!y#)Zc{*;kn<;Ix=Qll&c39w%-h> zTYzzt16c-D^RxM_JmXV-t>nP#iU|-gooI0deuMd<*c!@w!2+>?E=(0yl}Cz1L*YA2 zFGK8z8v_SxYNnHVr+#nmWakoZ##p3B-L_8}srxr^y&1_|ixUwD)!u->3M!+?+he00 zjyM*#({&#!3IN*x`rx;TON{;jA$sPrYvn(5U!gyF(w}huUaWS^@MwH0UARDR5%WEg zV<`hdJG83`hSGakcu1muz8|d_+I`t{=j7&M=2SoUmAU=LGPl@UFr+<6s`%^)Px?klEn&I6-6@@ggEAs6t;kgbRg;_&%Msf2Zv8hn>V+Q$s+X-AnEQVqU!BGO}!$FhORs>U;Vl0#TV<%(+}KV)yi zE#YqxK;iA$$pavmX{@}BAx$?3i25x{r2e}JQo>2*y^8luc7&OKAKnIjLp+*NZl-#Z zTD8Lb7_Wbf?b+RYhc=1K2Pd6PKx9SVlIP+aB3%r7%FW^IoJW{)C;vR1U_h%?_VHj< zC$@&@4_?mQ8I+v!|Gm#?YvOOoKYaS{h%BNac>(&Vaayy#Xs~tf069%0Q5oCccy z8Q^6qrR2j6d%k~3b*Bw3=imk-c+C*Mdsbn0lB12ypY+M<)E2*^)#ZNnSO#Ja?xkH& zD1>3;(c3(syT>9%B<*C!uE4;aM=Wju_B1?;oY$mc8v0K0iaL;AtTUgOT0avGXS^P%T;+9Pb9hdp_ybY9vY2ft{;S zb8Lm?W!O7q=XK6DMrHwW=FO%XHSAH5m$%}pYdHATMn7Wt8F~|8JfM^7luzN1dSIkQ zBUMOVp$0sEV-_ouPWgJXv5pY$eMd&Z8_vRQFssCJQMX49Xvk0R+Wq;7cUUzgI}L|t zH*dw&h*)6#OxAPeQJFO=dUKLO%E|!a$BG(A2yXjuw|}@Od2`GTFW)0uJrPBWyse2m zTM;S239?j)A%Ng$Y*l&to6+MwKPc7Cz%l}x=nM}4QReSBf>JtQ1qXIX0;og0GK6|v zCv(yfjQG&|nl2%i9Ey7ohTmi)da1h%l@jB%?ow+~FUYkCf@+IzN{DjN!r1y&Nx>i&@qL;ug6DbHPyOofT@ zLx0!|(0Q5~=uiKgRq*^rmiu`sqvkLT_LXw{OCZuRMRmqXbwOSNT;ysttRh|ckl4&A zuAn81@VdxITo!CBS;E>M>K*q4QXGXZ{pynpB> zaX0=rYA6jDEmSQ}B0%#eAle9v_t4(X56=Y1^;#cF7dy6=jt7TS6jf<`9x9SbLbd)0 z#Lf{>7B0=t{l=g&pYBLoCIoDmSKGWEQz%X^a(^#xAK9}I?h(4-7#Oj=1dE4_k2~pr zlO`2Pp%sn|!-$+Vr!aDpk3w zcZ}Z{D2LlBmr3QiFEC{r7|Ci1I9Xi)>!zD!3E)WU%5P z+lDl&^hVl51KW^UKM%l|Ob=nFzClOK1$V-WLRiUd7}hd0&B2|!Hs-{z&he&15W#Z% zFDS8_edjM}EQQc^$tdgm?a|6Oy!E6j>ev_9%1}K`c<^V@Ymyd(vJK}eRpxlvr7-;Z zist&bq>!`Fw~}d_5Txs}ITw z;DRxVUKt(e@UdM1hORy7Y|tVE17JZQ(xEXs?Xiik6}yO#@jMYdJoywhHW=QLg>zsd z+cCx^?E$!6YV|Bwkme7=x$ufcXcz~qae1Mssb+L@x!O}lHBjWXa1tL_>`|d`w6>u8 zNeso*rVf|Z!*)@apZ%!(dOT4T?IBoh|8RbAsgnGIlYXuOuT z`Q{8(aEgKEwR$B6ZJ-4+=!_eM>@@phZA*3f(R87JVd+c#N0mTZnAcPYEI?3ocXMh{ AzyJUM literal 2593 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 DnjH~# diff --git a/server/vbv_lernwelt/assignment/export.py b/server/vbv_lernwelt/assignment/export.py index fe845274..39ff9247 100644 --- a/server/vbv_lernwelt/assignment/export.py +++ b/server/vbv_lernwelt/assignment/export.py @@ -76,12 +76,24 @@ def export_competence_certificates( }, label="assignment_export", ) + + # handle the case where there are no competence certificate elements for the course session + try: + cces = grouped_cce[course_session_title] + except KeyError: + cces = [] + + try: + acs = grouped_ac[course_session_title] + except KeyError: + acs = [] + _create_sheet( wb, course_session_title, cs_users, - grouped_cce[course_session_title], - grouped_ac[course_session_title], + cces, + acs, circle_ids, ) @@ -105,7 +117,7 @@ def _create_sheet( ): sheet = wb.create_sheet(title=sanitize_sheet_name(title)) - if len(users) == 0: + if len(users) == 0 or len(competence_certificate_element) == 0: return sheet # headers @@ -121,7 +133,7 @@ def _create_sheet( if circle_ids and circle.id not in circle_ids: continue - col_prefix = f'Circle "{circle.title}" {cse.learning_content.title} ' + col_prefix = f'Circle "{circle.title}" {cse.learning_content.title}' sheet.cell( row=1, diff --git a/server/vbv_lernwelt/assignment/tests/test_assignment_completions_export.py b/server/vbv_lernwelt/assignment/tests/test_assignment_completions_export.py index 10f6da0f..ee21e875 100644 --- a/server/vbv_lernwelt/assignment/tests/test_assignment_completions_export.py +++ b/server/vbv_lernwelt/assignment/tests/test_assignment_completions_export.py @@ -1,5 +1,8 @@ -from django.test import TestCase +import io +from openpyxl import load_workbook + +from vbv_lernwelt.assignment.export import export_competence_certificates from vbv_lernwelt.assignment.models import Assignment from vbv_lernwelt.assignment.services import update_assignment_completion from vbv_lernwelt.core.constants import TEST_STUDENT1_USER_ID, TEST_STUDENT2_USER_ID @@ -7,28 +10,32 @@ 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.models import ( + CourseSessionAssignment, + CourseSessionEdoniqTest, +) +from vbv_lernwelt.course_session.tests.test_attendance_export import ExportBaseTextCase -class AttendanceExportTestCase(TestCase): +class AssignmentCompletionExportTestCase(ExportBaseTextCase): def setUp(self): create_default_users() self.course = 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.attendance_course_be = ( - self.course_session_be.coursesessionattendancecourse_set.first() - ) - some = ( + self.casework = ( self.course.coursepage.get_descendants() .exact_type(Assignment) .filter(assignment__assignment_type="CASEWORK") + .first() + .specific ) - self.assignment = ( + self.edoniq_test = ( self.course.coursepage.get_descendants() .exact_type(Assignment) - .filter(assignment__assignment_type="CASEWORK") + .filter(assignment__assignment_type="EDONIQ_TEST") .first() .specific ) @@ -44,13 +51,156 @@ class AttendanceExportTestCase(TestCase): test_student3 = User.objects.get(email="test-student3@example.com") + # Bern assignments update_assignment_completion( assignment_user=self.test_student1, - assignment=self.assignment, + assignment=self.casework, course_session=self.course_session_be, completion_data={}, evaluation_points=20, ) - def test_attendance_export_single_cs(self): - self.assertTrue(True) + update_assignment_completion( + assignment_user=self.test_student1, + assignment=self.edoniq_test, + course_session=self.course_session_be, + completion_data={}, + evaluation_points=14, + evaluation_passed=False, + ) + + update_assignment_completion( + assignment_user=self.test_student2, + assignment=self.edoniq_test, + course_session=self.course_session_be, + completion_data={}, + evaluation_points=24, + evaluation_passed=True, + ) + + self.expected_data_be = [ + self._make_header(), + [ + self.test_student1.first_name, + self.test_student1.last_name, + self.test_student1.email, + self.test_student1.additional_json_data["Lehrvertragsnummer"], + "Nicht bestanden", + 58, + "Bestanden", + 83, + ], + [ + self.test_student2.first_name, + self.test_student2.last_name, + self.test_student2.email, + self.test_student2.additional_json_data["Lehrvertragsnummer"], + "Bestanden", + 100, + "Keine Daten", + "Keine Daten", + ], + [ + test_student3.first_name, + test_student3.last_name, + test_student3.email, + None, + "Keine Daten", + "Keine Daten", + "Keine Daten", + "Keine Daten", + ], + ] + + def _generate_workbook(self, course_session_ids): + export_data = io.BytesIO( + export_competence_certificates(course_session_ids, save_as_file=False) + ) + return load_workbook(export_data) + + def _make_header( + self, + ): + casework_assignment = CourseSessionAssignment.objects.filter( + course_session__id=self.course_session_be.id, + learning_content__content_assignment__competence_certificate__isnull=False, + ).first() + + edoniq_assignment = CourseSessionEdoniqTest.objects.filter( + course_session__id=self.course_session_be.id, + learning_content__content_assignment__competence_certificate__isnull=False, + ).first() + + return [ + "Vorname", + "Nachname", + "Email", + "Lehrvertragsnummer", + f'Circle "{self.edoniq_test.get_attached_circle_title()}" {edoniq_assignment.learning_content.title} bestanden', + f'Circle "{self.edoniq_test.get_attached_circle_title()}" {edoniq_assignment.learning_content.title} Resultat %', + f'Circle "{self.casework.get_attached_circle_title()}" {casework_assignment.learning_content.title} bestanden', + f'Circle "{self.casework.get_attached_circle_title()}" {casework_assignment.learning_content.title} Resultat %', + ] + + def test_export_single_cs(self): + wb = self._generate_workbook([self.course_session_be.id]) + self.assertEqual(len(wb.sheetnames), 1) + self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a") + + self._check_export(wb, self.expected_data_be, 4, 8) + + def test_export_only_kn_elements(self): + self.edoniq_test.competence_certificate = None + self.edoniq_test.save() + + expected_data = [] + for row in self.expected_data_be: + expected_data.append( + [cell for i, cell in enumerate(row) if (i != 4 and i != 5)] + ) + + wb = self._generate_workbook([self.course_session_be.id]) + self._check_export(wb, expected_data, 4, 6) + + def test_export_multiple_cs(self): + update_assignment_completion( + assignment_user=self.test_student2, + assignment=self.casework, + course_session=self.course_session_zh, + completion_data={}, + evaluation_points=18, + ) + + update_assignment_completion( + assignment_user=self.test_student2, + assignment=self.edoniq_test, + course_session=self.course_session_zh, + completion_data={}, + evaluation_points=22, + evaluation_passed=False, + ) + + expected_data = [ + [cell for i, cell in enumerate(self._make_header()) if (i != 4 and i != 5)], + [ + self.test_student2.first_name, + self.test_student2.last_name, + self.test_student2.email, + self.test_student2.additional_json_data["Lehrvertragsnummer"], + "Bestanden", + 75, + ], + ] + + wb = self._generate_workbook( + [self.course_session_be.id, self.course_session_zh.id] + ) + self.assertEqual(len(wb.sheetnames), 2) + self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a") + self.assertEqual(wb.sheetnames[1], "Test Zürich 2022 a") + + self._check_export(wb, self.expected_data_be, 4, 5) + + wb.active = wb["Test Zürich 2022 a"] + + self._check_export(wb, expected_data, 2, 5) diff --git a/server/vbv_lernwelt/course/creators/test_course.py b/server/vbv_lernwelt/course/creators/test_course.py index 968be54e..7b6c9bd4 100644 --- a/server/vbv_lernwelt/course/creators/test_course.py +++ b/server/vbv_lernwelt/course/creators/test_course.py @@ -282,6 +282,13 @@ def create_test_course( ) csac.due_date.save() + _csa = CourseSessionAssignment.objects.create( + course_session=cs_zurich, + learning_content=LearningContentAssignment.objects.get( + slug=f"{course.slug}-lp-circle-fahrzeug-lc-überprüfen-einer-motorfahrzeug-versicherungspolice" + ), + ) + region1 = CourseSessionGroup.objects.create( name="Region 1", course=course, diff --git a/server/vbv_lernwelt/course_session/tests/test_attendance_export.py b/server/vbv_lernwelt/course_session/tests/test_attendance_export.py index 49f1c085..40d82517 100644 --- a/server/vbv_lernwelt/course_session/tests/test_attendance_export.py +++ b/server/vbv_lernwelt/course_session/tests/test_attendance_export.py @@ -11,7 +11,16 @@ from vbv_lernwelt.course.models import CourseSession from vbv_lernwelt.course_session.services.export_attendance import export_attendance -class AttendanceExportTestCase(TestCase): +class ExportBaseTextCase(TestCase): + def _check_export(self, wb, expected_data, max_row, max_col): + for row in wb.active.iter_rows(max_col=max_col, max_row=max_row): + for cell in row: + self.assertEqual( + cell.value, expected_data[row[0].row - 1][row.index(cell)] + ) + + +class AttendanceExportTestCase(ExportBaseTextCase): def setUp(self): create_default_users() create_test_course(include_vv=False, with_sessions=True) @@ -76,29 +85,20 @@ class AttendanceExportTestCase(TestCase): return load_workbook(export_data) def _make_header(self, csac): - return ( - [ - "Vorname", - "Nachname", - "Email", - "Lehrvertragsnummer", - f"Anwesenheit {csac.get_circle().title} {csac.attendance_course_zh.due_date.start.strftime('%d.%m.%Y')}", - ], - ) - - def _check_attendance_export(self, wb, expected_data, max_row, max_col): - for row in wb.active.iter_rows(max_col=max_col, max_row=max_row): - for cell in row: - self.assertEqual( - cell.value, expected_data[row[0].row - 1][row.index(cell)] - ) + return [ + "Vorname", + "Nachname", + "Email", + "Lehrvertragsnummer", + f"Anwesenheit {csac.get_circle().title} {csac.attendance_course_zh.due_date.start.strftime('%d.%m.%Y')}", + ] def test_attendance_export_single_cs(self): wb = self._generate_workbook([self.course_session_be.id]) self.assertEqual(len(wb.sheetnames), 1) self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a") - self._check_attendance_export(wb, self.expected_data_be, 4, 5) + self._check_export(wb, self.expected_data_be, 4, 5) def test_attendance_export_multiple_cs(self): self.attendance_course_zh.attendance_user_list = [ @@ -131,5 +131,7 @@ class AttendanceExportTestCase(TestCase): self.assertEqual(wb.sheetnames[0], "Test Bern 2022 a") self.assertEqual(wb.sheetnames[1], "Test Zürich 2022 a") + self._check_export(wb, self.expected_data_be, 4, 5) + wb.active = wb["Test Zürich 2022 a"] - self._check_attendance_export(wb, expected_data_zh, 2, 5) + self._check_export(wb, expected_data_zh, 2, 5)