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
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
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