From 57cd5fe871921e9320eff5a9606eb388c0f58a39 Mon Sep 17 00:00:00 2001 From: Christian Cueni Date: Wed, 16 Aug 2023 16:15:49 +0200 Subject: [PATCH 1/2] Match existing Teilnehmer with multiple parameters --- client/src/locales/de/translation.json | 2 +- client/src/pages/DashboardPage.vue | 4 ++- server/vbv_lernwelt/importer/services.py | 29 +++++++++++++++++-- .../vbv_lernwelt/importer/tests/__init__.py | 0 .../importer/tests/test_import_students.py | 1 + .../importer/tests/test_t2l_sync.py | 4 +++ 6 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 server/vbv_lernwelt/importer/tests/__init__.py diff --git a/client/src/locales/de/translation.json b/client/src/locales/de/translation.json index 55a8ebec..dd5b1340 100644 --- a/client/src/locales/de/translation.json +++ b/client/src/locales/de/translation.json @@ -269,7 +269,7 @@ "selfEvaluationNo": "@:selfEvaluation: Muss ich nochmals anschauen.", "selfEvaluationYes": "@:selfEvaluation: Ich kann das.", "steps": "Schritt {{current}} von {{max}}", - "title": "@:selfEvaluation.selfEvaluation {{title}}", + "title": "Selbsteinschätzung {{title}}", "yes": "Ja, ich kann das" }, "settings": { diff --git a/client/src/pages/DashboardPage.vue b/client/src/pages/DashboardPage.vue index f7ca56d5..bd246037 100644 --- a/client/src/pages/DashboardPage.vue +++ b/client/src/pages/DashboardPage.vue @@ -91,7 +91,9 @@ const getNextStepLink = (courseSession: CourseSession) => {
{{ $t("uk.contact.address") }}
- uk.contact.email + + uek-support@vbv-afa.ch +

diff --git a/server/vbv_lernwelt/importer/services.py b/server/vbv_lernwelt/importer/services.py index 30b35e9a..2cd64419 100644 --- a/server/vbv_lernwelt/importer/services.py +++ b/server/vbv_lernwelt/importer/services.py @@ -185,6 +185,7 @@ def create_or_update_user( last_name: str = "", sso_id: str = None, contract_number: str = "", + date_of_birth: str = "", ): logger.debug( "create_or_update_user", @@ -200,9 +201,13 @@ def create_or_update_user( if user_qs.exists(): user = user_qs.first() + # use the ID from DBPLAP2 (Lehrvertragsnummer, firstname, lastname, date of birth) if not user and contract_number: user_qs = User.objects.filter( - additional_json_data__Lehrvertragsnummer=contract_number + first_name=first_name, + last_name=last_name, + additional_json_data__Lehrvertragsnummer=contract_number, + additional_json_data__Geburtsdatum=date_of_birth, ) if user_qs.exists(): user = user_qs.first() @@ -502,6 +507,8 @@ def import_students_from_excel(filename: str): "Name", "Sprache", "Durchführungen", + "Lehrvertragsnummer", + "Geburtsdatum", ], ) create_or_update_student(data) @@ -514,11 +521,14 @@ def create_or_update_student(data: Dict[str, Any]): label="import", ) + date_of_birth = _get_date_of_birth(data) + user = create_or_update_user( email=data["Email"].lower(), first_name=data["Vorname"], last_name=data["Name"], contract_number=data.get("Lehrvertragsnummer", ""), + date_of_birth=date_of_birth, ) user.language = data["Sprache"] @@ -535,6 +545,16 @@ def create_or_update_student(data: Dict[str, Any]): csu.save() +def _get_date_of_birth(data: Dict[str, Any]) -> str: + date_of_birth = data.get("Geburtsdatum", None) + if date_of_birth is None: + return "" + elif date_of_birth is date or date_of_birth is datetime: + return date_of_birth.strftime("%d.%m.%Y") + elif type(date_of_birth) is str: + return date_of_birth + + def sync_students_from_t2l_excel(filename: str): workbook = load_workbook(filename=filename) sheet = workbook.active @@ -546,10 +566,13 @@ def sync_students_from_t2l_excel(filename: str): def sync_students_from_t2l(data): - # ignore errors + date_of_birth = _get_date_of_birth(data) try: user = User.objects.get( - additional_json_data__Lehrvertragsnummer=data["Lehrvertragsnummer"] + first_name=data["Vorname"], + last_name=data["Name"], + additional_json_data__Lehrvertragsnummer=data["Lehrvertragsnummer"], + additional_json_data__Geburtsdatum=date_of_birth, ) except User.DoesNotExist: return diff --git a/server/vbv_lernwelt/importer/tests/__init__.py b/server/vbv_lernwelt/importer/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/importer/tests/test_import_students.py b/server/vbv_lernwelt/importer/tests/test_import_students.py index e9597c25..d771d89d 100644 --- a/server/vbv_lernwelt/importer/tests/test_import_students.py +++ b/server/vbv_lernwelt/importer/tests/test_import_students.py @@ -51,6 +51,7 @@ class CreateOrUpdateStudentTestCase(TestCase): "Durchführungen": "DE 2023 A", "Lehrvertragsnummer": "1234", "Tel. Privat": "079 593 83 43", + "Geburtsdatum": "01.01.2000", } def test_create_student(self): diff --git a/server/vbv_lernwelt/importer/tests/test_t2l_sync.py b/server/vbv_lernwelt/importer/tests/test_t2l_sync.py index cc889a80..c800a52e 100644 --- a/server/vbv_lernwelt/importer/tests/test_t2l_sync.py +++ b/server/vbv_lernwelt/importer/tests/test_t2l_sync.py @@ -32,6 +32,7 @@ class SyncT2lTestCase(TestCase): "Durchführungen": "DE 2023 A", "Lehrvertragsnummer": "1234", "Tel. Privat": "079 593 83 43", + "Geburtsdatum": "01.01.2000", } create_or_update_student(self.user_dict) @@ -44,6 +45,7 @@ class SyncT2lTestCase(TestCase): "Durchführungen": "DE 2023 A", "Lehrvertragsnummer": "1234", "Tel. Privat": "079 593 83 65", + "Geburtsdatum": "01.01.2000", } sync_students_from_t2l(user_dict) @@ -65,6 +67,7 @@ class SyncT2lTestCase(TestCase): "Lehrvertragsnummer": "1234", "Tel. Privat": "079 593 83 43", "Firma": "VBV", + "Geburtsdatum": "01.01.2000", } sync_students_from_t2l(user_dict) @@ -84,6 +87,7 @@ class SyncT2lTestCase(TestCase): "Durchführungen": "DE 2023 B", "Lehrvertragsnummer": "1234", "Tel. Privat": "079 593 83 43", + "Geburtsdatum": "01.01.2000", } sync_students_from_t2l(user_dict) From 3447de19ca12d397106a95bea992eee7be52cd54 Mon Sep 17 00:00:00 2001 From: Christian Cueni Date: Thu, 17 Aug 2023 09:02:53 +0200 Subject: [PATCH 2/2] Add trainer and combined export --- server/config/urls.py | 8 +++- .../edoniq_test/tests/test_edoniq_export.py | 33 +++++++++++++++++ server/vbv_lernwelt/edoniq_test/views.py | 35 ++++++++++++++++-- .../tests/Schulungen_Teilnehmende.xlsx | Bin 17344 -> 18236 bytes .../vbv_lernwelt/templates/admin/index.html | 6 ++- 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/server/config/urls.py b/server/config/urls.py index 4268109e..0c60936a 100644 --- a/server/config/urls.py +++ b/server/config/urls.py @@ -37,7 +37,11 @@ from vbv_lernwelt.course.views import ( request_course_completion, request_course_completion_for_user, ) -from vbv_lernwelt.edoniq_test.views import export_students, export_trainers +from vbv_lernwelt.edoniq_test.views import ( + export_students, + export_students_and_trainers, + export_trainers, +) from vbv_lernwelt.feedback.views import ( get_expert_feedbacks_for_course, get_feedback_for_circle, @@ -148,6 +152,8 @@ urlpatterns = [ # edoniq test path(r'api/core/edoniq-test/export-users/', export_students, name='edoniq_export_students'), path(r'api/core/edoniq-test/export-trainers/', export_trainers, name='edoniq_export_trainers'), + path(r'api/core/edoniq-test/export-users-trainers/', export_students_and_trainers, + name='edoniq_export_students_and_trainers'), # importer path( diff --git a/server/vbv_lernwelt/edoniq_test/tests/test_edoniq_export.py b/server/vbv_lernwelt/edoniq_test/tests/test_edoniq_export.py index fae09e0e..893ca698 100644 --- a/server/vbv_lernwelt/edoniq_test/tests/test_edoniq_export.py +++ b/server/vbv_lernwelt/edoniq_test/tests/test_edoniq_export.py @@ -6,7 +6,9 @@ from vbv_lernwelt.core.admin import User from vbv_lernwelt.core.create_default_users import create_default_users from vbv_lernwelt.course.consts import COURSE_TEST_ID from vbv_lernwelt.course.creators.test_course import create_test_course +from vbv_lernwelt.course.models import CourseSession, CourseSessionUser from vbv_lernwelt.edoniq_test.views import ( + fetch_course_session_all_users, fetch_course_session_users, generate_export_response, ) @@ -36,6 +38,37 @@ class EdoniqUserExportTestCase(TestCase): users = fetch_course_session_users([COURSE_TEST_ID]) self.assertEqual(len(users), 2) + def test_fetch_course_session_trainers(self): + users = fetch_course_session_users( + [COURSE_TEST_ID], role=CourseSessionUser.Role.EXPERT + ) + self.assertEqual(len(users), 1) + + def test_remove_eiger_versicherungen(self): + user1 = User.objects.get(email="test-student1@example.com") + user1.email = "some@eiger-versicherungen.ch" + user1.save() + users = fetch_course_session_users([COURSE_TEST_ID]) + self.assertEqual(len(users), 1) + + def test_export_students_and_trainers(self): + users = fetch_course_session_all_users([COURSE_TEST_ID]) + self.assertEqual(len(users), 3) + + def test_deduplicates_users(self): + trainer1 = User.objects.get(email="test-trainer1@example.com") + cs_zrh = CourseSession.objects.get( + title="Test Zürich 2022 a", + ) + _csu = CourseSessionUser.objects.create( + course_session=cs_zrh, + user=trainer1, + ) + users = fetch_course_session_all_users([COURSE_TEST_ID]) + for u in users: + print(u.edoniq_role, u.email) + self.assertEqual(len(users), 3) + def test_response_csv(self): users = fetch_course_session_users([COURSE_TEST_ID]) response = generate_export_response(users) diff --git a/server/vbv_lernwelt/edoniq_test/views.py b/server/vbv_lernwelt/edoniq_test/views.py index 1fe5cf44..30f65398 100644 --- a/server/vbv_lernwelt/edoniq_test/views.py +++ b/server/vbv_lernwelt/edoniq_test/views.py @@ -1,8 +1,10 @@ import csv from datetime import date +from itertools import chain from typing import List from django.contrib.admin.views.decorators import staff_member_required +from django.db.models import CharField, Value from django.http import HttpResponse from vbv_lernwelt.core.models import User @@ -18,26 +20,53 @@ def export_students(request): return generate_export_response(course_session_users) +@staff_member_required def export_trainers(request): course_session_users = fetch_course_session_users( UK_COURSE_IDS, role=CourseSessionUser.Role.EXPERT ) - return generate_export_response(course_session_users, role="Trainer") + return generate_export_response(course_session_users) + + +@staff_member_required +def export_students_and_trainers(request): + course_session_users = fetch_course_session_all_users(UK_COURSE_IDS) + return generate_export_response(course_session_users) def fetch_course_session_users(courses: List[int], role=CourseSessionUser.Role.MEMBER): + if role == CourseSessionUser.Role.EXPERT: + edoniq_role = "Trainer" + else: + edoniq_role = "Lernende" + # if users should be exported per course session, remove the distinct() call return ( User.objects.filter( coursesessionuser__course_session__course__id__in=courses, coursesessionuser__role=role, ) + .exclude(email__contains="eiger-versicherungen.ch") # exclude test users + .exclude(email__contains="assurance.ch") .order_by("email") + .annotate(edoniq_role=Value(edoniq_role, output_field=CharField())) .distinct() ) -def generate_export_response(cs_users: List[User], role="Lernende") -> HttpResponse: +def fetch_course_session_all_users(courses: List[int]): + course_session_users = fetch_course_session_users(courses) + course_session_trainers = fetch_course_session_users( + courses, role=CourseSessionUser.Role.EXPERT + ) + combined_queryset = chain(course_session_users, course_session_trainers) + + # use email as key to remove duplicates. Maybe we can use the sso id in the future, but it is not set for all users + unique_dict = {obj.email: obj for obj in combined_queryset} + return list(unique_dict.values()) + + +def generate_export_response(cs_users: List[User]) -> HttpResponse: response = HttpResponse(content_type="text/csv") response[ "Content-Disposition" @@ -71,7 +100,7 @@ def generate_export_response(cs_users: List[User], role="Lernende") -> HttpRespo cs_user.language, cs_user.email, cs_user.additional_json_data.get("Geburtsdatum", ""), - role, + cs_user.edoniq_role, cs_user.additional_json_data.get("Firmenname", ""), cs_user.additional_json_data.get("Lehrvertragsnummer", ""), cs_user.coursesessionuser_set.first().course_session.import_id, diff --git a/server/vbv_lernwelt/importer/tests/Schulungen_Teilnehmende.xlsx b/server/vbv_lernwelt/importer/tests/Schulungen_Teilnehmende.xlsx index 743518fa7b85533a3b3696ef4a44089c673328b9..b79f00e000a3d9dbffc70854736c690e7088a2f2 100644 GIT binary patch delta 9464 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 TL>w9~LJ0jR4MJU!eB}Ipi*}b2 delta 8570 zcmZWv2UrtZ)24$+5hNgpA|bTUAxKkdLhrpd0qN3|&eBT|G(_oLdT-LCh^QEP5fBiO zj`SkZ;SYMh@80|Ud7jywbKaSG&zW;J+1*J?Jnq{lTnbGUd;%IA5)u*|vWy-5Y6>}A zf=i`V4_5^5kMgUgo={_f6T4}+Vp2j|GXjIMzHA@%j zcldb2NA2z9i5XE4zG3IUq1S$AuIxq{wMNZtZ&rfNz4qn4Y|z-YWPa#z&M@`LO2|Fg z>H#vO?2g8*Q4m`NLcoxYZW>|aYMzk+8bQ<`W6kT8##S?Y6D#Mm&r)VGKxy}49!`bn z_Xxe2IT+A)Z{FMlw{%av|FpE{mHM>y#~IWp>CGMAnx`syXVxfJq+H#jTKye6-_xR0 zzaoutqswLKs6K+AuclXhbMWy$QB zATI@OL&UhRIFOdN27j4&bk?>#zV7~lX9gs2AAnCh;rKN2CZc2>l9JT*+%~`; z&5d?Q>0Rv#Y~)K?g@|aBPC5IM@vAKDoYj<-WUD7FM(q;o&Btyw3wjmH+NG`1ys~L( zSvj|PEHR>dqWro#-XhyA-WYzaKSgtdwiGUOF6QV~{jESVFfRzd>`+(DP|~)a@|67V2}h&n#vz zRVttFzZJ1Nt0K-tr#J0~-?MR2?+sJVUv$wr4R4;#a|p`4NqkapD~#<@i{$K2LSb~ zbWi`q)3ZLZUxj2}I6E^z=Vyg=$0weVU7nNsfL*=X5-?X5ST$yCupzZj9>_l)^kn>~ zix0f)Gqw#luL2(WJ>Ku;&BXba-5;ADJfE6&y7zi?FWsfGb&bNC1>N|RZ(wh|8vW?? z8`5>Z{^bHD30tj{jAMz`pY@T0s-wrNUZV=C)f>j2J0hTgjq6!i_jN&Exxl)HY9B~I zz!Mz-`G^tS7MX}CUDFL3eYII((0I{y8nU5?Bo*mhM3RPN(h{(T2FXUSs%Bh^aofL- zA~6BjsAt!R!BiiEAk?$#d~k5t*-ORn1`#;8_Y4ksH$A{n9r31}D#q>LDvHD#V5gql zWP*dgo}m@P-*ld-t&9r4`(_27BLRRYmRu?#$}TtyP7$jRG7jcyCha zv>6WZX+ab0_v{=`t#P`Ug9!nOdL`31 zDpYi=dmWl=mD-&rK4XN_O}*ed@-yNI;|bfS&YfDAJB-uK0!+wTuk?)&4&K6}+r%^Y zM!5Ndrm;{*dKHh;jhV0}Hmxz0#)l7uM>)BG(PBn@vdSUm++JuLl*qu_@*=I`6(k&o z;|0DXHcDU;ysm*}yhWXIWbd1vzaFawx|k z&0G{sKa9FFhKL+-jncg(JlV{DZUhG7ci|9(GAP3mic{oeg@>|pqDS9rhTEy^S$2@B z-9(DgD@(JbhCplzP|>O|dk$Ga0h*)wU$AP2mA7>{BvJU78dVmnL)7xmmHaq$5VA{iZ1#VL8{s) zMvNAUv73tOIG&+}MIHag_F#SFRwD4xyQ^-C#9rS*n-!!~mIubXgon4iA_{!8m;Qx~ z66q4RRpBs$`cNiW7H0`Iyww4(k_^4~nQ%rctSu`@G@uXmg^t;7Yz!>y`5=|kW#OKR z`Z7Wn!gNCi6=0(ZPreyi_&jYyBQ7ecUN~SpyU>N}ABsgBh- zXal6q7!Ok&wlk%XuZ3xx(h3Z3VulmA*jS(mj4B9fn-~&Lx!@Zfp2xU58oxpw5By@i z&f!bRDkX;@<~Sl@ZI{MWYI@+aW@Rr3y*MRewa8xZ<~YJvJ`Ycr9sG#FG3|@#imS4J zig;ADLK<69y~0D=+L_B#T?C-Em&_iAd%cQYp@G_R;1V;CV=`HAW#5SVHkk(BcHe>_6+rUh!s3+ zViXi%6^VlD5?tL)ApsH>gs2+3;VaO7E3s5D?yjGViE?(Cj2Jdu$V502lBW3=swtLA z5AFI%_EK)?&88z_Bra3jM@mCxOcey(x{!z2SbAHq7ij0(P?@Kj zYNe+eNkAtN3p8|y?s~^cZ1nE|P>J2@lO-C5-eC)klh%bOSg3|q)X(wJ9rcWECpo3Io4ymTa*2v|6XLxJp`+)g zN}VKaH+|1%5QgrKNt*6z~{OYY%)#0k8f-4 zMV2zud8oa5slqSs`Z=bEgh*eN0KfFEJp!=;&3#!a!!-^wZX$SK*>MxpQ3$%;QK)yb z4jW!i8ZN5$9URoeaO$Bpp9H9ADr`m}lNDW~8MLU#98x|d_6woDZh95@cIuG_`4;T- zuoSU~%wsRt%7@T|aUqbBK{1Kx{9kOVn*Xi@zvRV6rcym@X7o`crB zEVSYpfSLGy5~%-5K!d%!#szc17=1Y0l=VOG49w){5^lS$g)%LZL3mUXU2gF3isMiE znUUoIBhHH~U0*yr)!~;A2=f78_^*4V71w{<^)KD4FmR93j^ya*(x=0NdNy1Zo%(GK z^}DDC&P}htU~oE59%|u6FhbUVzB{M%lYZOZ;Y;Cg#I{+bD#6UJ$;k5L=tiXTqSKdd zkv@G%W^fa!Bz7CrTnZRG2RL$JLzV}Sn;aEPbhE=DqQXtG!R`j4Qb#yY)6lC8B!TL7Ls7m61)*j&^3T2baX=(l7mD-bg8IPmGP)_ zP{nxUI)nu&&^wTDspt;)Hu{DtffRCRt~A*2jx`jlmn~EB7N$)|5N_!ralK>m2{eHP z3YOKOPs=G*)D4L&!W)5uu2F>MZ@>vGIF#@)SS37DP*6?-RftEfL0I4my|azSHC3$G zf35|fOid7Qdq=KJ(&zs`uVPW&@?3CJ9Wr|Gv;IBM9SuT;Oj5NyUB&N&W3RD4uKH>S z4>$sTI3iQ-1BGXj!tQ}mmx`aiy~^!W2o}r5E^jUv40{dIi1?(F6Im!bat}o96QYNm ztAg&Oz{3mb=ZdcZzR=Ixy7sAsva)&?!3jhW81fdVU}EfOvyRD^voq} z!KcBoq5ihli=W$r1Ps%aHwR}mEEw*uVSH`-rjAsqrLs^MmmJmoC+9uW6R}K7LQHv_ zdG(Rs`k62TfcWuL&VkiOxi|ojsFbq@zE17=s>S_$S8sF3m;X49?DSRXuYfg0+BP=Q zxNj0)W`NdlMcbv?uTR)oGA;ry_5;S~_x%-0cSMLEnGZapXcZS{EI1l!5L})}=|ZnL z1pSn`>(xjXPjLWVxxkS!dZ@Y1X_C*p;Iak*_`>i1Ff~t_Dx7>=01g9oh}m1a4lDVj zg3NTsvs;`lfLU%_8vh&0niwv1n9She z6cCp9QA?(la-ERU%>3kO=&N`}n5g)cP?Y-SY41>2n3N~?7TwFSd!9c9nkwEuSEg&B z40@$lY5ToV$4X-O$eCdh_!0_R(l3l4WjwyK&N|BbN|~N4s6^EbYI)u@Hviq8q{}&; z>ZOR;NU;C*OsC!GFD2p#p6v)&$U*yeD^#&ASG8dLCCqs;C0RYM0x$!I+7e=z=nfl2tu;A%HH~p z!SK$&;2~abMqzE(S(rgBYk1piCkFb#UK@8m?D{aYOFxs;>+vZL2(e&p!}#t6yO-^p zLz5@>v%zFnQLt4O)L+l?&2>~Lc(1CqXu~;{s84c6GDS1&d&w(YMzy&swD+&74CV>> zy;t@A_+Tgrto`C=GEfIwju#vps(4YaZY?qT)KH83=hY)bo6q-_POY}h@0~uI+_7qC zgi@@_r?^qm>(ZJc(i3klk{@WHH#s?CjD7NV8TQMB*!vh{6RDc)vGk#}8vMW%-PP&SF3ne) zKI!m-%~v|ZmNA1AnpAAZpZWb+?yqlj4ciV|#lDO)oRvREWbO0WT#+~~oYz>&3IW4SulRkZ~=_80aB?;#P;(R~Sw6A^yHtSC$v+ zm+__q3W;8Fd<2!R&dUi2L3N|vc5g}TXAiV@X9sFr4W)^EpS@m_u<-1CKjse69 zOskZxNjURGq=a)4QkCacBK;%Qq_yO#7{wg+-8@FG;<4{wp2uSjcyF4u^<&MSAe5Jy zR$KJ5MVo-~@p?DQ#YJ}h*deRP&!Rw|@T6VXkI3pR6WNYsmx*(^r;@6vc?2V`HKElH-DE<=w!R1|{-_BLxn~4CP}YsCwKt%CDs~nP(zDSJRFoCoetLyCqkyA-Gt_}!txPcfkCthDaXjwhVAd_4JBv=?SpboQI(A0Y#2ovRZ+ zW((&5z3p0jso?H(b>hR@?oc0ffrFX@4;O!aYx7TU(}?B6_X%i6UE^U?DHq?g@bAj< z4UHwY&Fruh&Z{^<_5H}lbc`L3skdIe4bJsm`0-9n+zlm4OlRM9r;r4s2GIa%Gl|n> zfr14VGX07fZy;2YS|pjK^ttMsd^YPXxYnuwWVr`T^*ePT)i`mFDi>46hAF^uX!YUl zRC`sr`v9v~sp_t_8MYww@SD87pIdWIX1*tRe@(c=HxGO7H@Eza4`|m*N{C=n3J|9? z@egFQ$OA@5wcC!C!Hq#d{_gi7e$1YH8Ig~CF!WVf)E0sZ>kfwV71(#YOEui(@_cMS zMcxl-=4mobX7=NS{*_JeV?rd?A=`zj9g=M2Y>~ijr`+(Q3EZldZ}+e}RLe(?>nBP^ zQW}v1r~h#sG?udYRx@A#6X;vEZEf=q+L&HAKntF(hD?>BT`x?HWM})%Vhd*-eYO`RE9*?q%A6O*2hK z?P{1W)uV3y6~ADQpsd5JkqYDm?%;FT8FJ-mYo-mbV0~$u+IH_%6T`gtI54nXY%UNI zad8*pxh|A9*w|3hf9>mf+S{QP_jsBiuPI}`j!gD(rEImhPcP@;j!_MwRY5w;?;pOp zts^5sJMBt`0-vS8NBkN8+Lt7VjFVvu=eOY6kOD96S7EN|`@| zY`#Ni7<@>q!rNg*&cR+~*_rT2@^%k3P4AW>eHmL3i*{qk zF|ak2c`&Cly*@|J~@PQ(jz6mkqdrJ)~8u4d-J zMF3FE!AX8t28)E4%a9marBuak?yXbnwzU3d1yP#!aYBgq%uUmtXK zWyS5aJ>-@y;LOa}e|E*0CXZ5K%bn2aglphSJAV8-`?{wrl9u;wo05HDfW72d*A0wO zf;pEw@tv-pY6F7bkEoi>JS#(Il6u{Yi2w98a1hUox~l7TqdPa&0)@2#)VqgYV!R41Y3Ou2J_45e&vSlUf>Ku z;zqh)v%He3CPC`WoSy~qS$!k+q2T2d+A<*qM=zYHrJogkH=jmyaJgySu{U^Ce()r+ zdfsFuB{r-q4BR&;#58Ob7WV?#`D#`QxNrYVM^>54pVBz^#>CZY88NjP7ks)mg3<@d zQ^Uyb84C|i#rtN1+9Kn2%A9m5VQMvZOottz@J9#v;gjvKi#xaENDWBcYhkw0=LmWB)a}a#_x6f7pZN&Z}?YDomOh$cX#c% z)aqYd>oUTBELmm6{wUV6B7c+&S@<7iQC5xOvR?o9Lk(J5PV~-Y*FUdKntzn@ad6sh zp_Apnzm5L6X{i2XM8}Svlw%^fjC7eIG%lPLuZIIo55N6~Spv?0cN>Z}fHVJT2VmQ_ zT<9HsT66`R_P5{cXA@a;FxJn4`*%M%KPkEqPWBtCv99<2jT!M^u{&r@1lgZBmIwws z9bR+*;;*ZnPxU*P-5r6cQY;&c T^nV$>?xCp^ScxnpFDd>HpfTh- diff --git a/server/vbv_lernwelt/templates/admin/index.html b/server/vbv_lernwelt/templates/admin/index.html index ffdf0dc3..564c0c92 100644 --- a/server/vbv_lernwelt/templates/admin/index.html +++ b/server/vbv_lernwelt/templates/admin/index.html @@ -31,9 +31,13 @@

Export Edoniq Teilnehmer

Teilnehmer exportieren -

Export Edoniq Trainer

+

Export Edoniq Trainer

Trainer exportieren +

Export Edoniq Teilnehmer und Trainer

+ Teilnehmer und Trainer exportieren + +

Reset

{% csrf_token %}