Add real courses data, update importer

Make course optional on import

Add importer in admin

Use new trainer format in xls

Import trainers via admin interface

Add participant import

Update tests

Update url, handle error

Refactor importer

Add json field

Fix tests

Add update test
This commit is contained in:
Christian Cueni 2023-07-13 08:38:36 +02:00
parent 65d527d894
commit 12977b01cc
13 changed files with 378 additions and 134 deletions

View File

@ -122,6 +122,7 @@ LOCAL_APPS = [
"vbv_lernwelt.notify", "vbv_lernwelt.notify",
"vbv_lernwelt.assignment", "vbv_lernwelt.assignment",
"vbv_lernwelt.duedate", "vbv_lernwelt.duedate",
"vbv_lernwelt.importer",
] ]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS

View File

@ -42,6 +42,10 @@ from vbv_lernwelt.feedback.views import (
get_expert_feedbacks_for_course, get_expert_feedbacks_for_course,
get_feedback_for_circle, get_feedback_for_circle,
) )
from vbv_lernwelt.importer.views import (
coursesessions_students_import,
coursesessions_trainers_import,
)
from vbv_lernwelt.notify.views import email_notification_settings from vbv_lernwelt.notify.views import email_notification_settings
from wagtail import urls as wagtail_urls from wagtail import urls as wagtail_urls
from wagtail.admin import urls as wagtailadmin_urls from wagtail.admin import urls as wagtailadmin_urls
@ -143,6 +147,18 @@ urlpatterns = [
# edoniq test # edoniq test
path(r'api/core/edoniq-test/export-users/', export_students, name='edoniq_export_students'), path(r'api/core/edoniq-test/export-users/', export_students, name='edoniq_export_students'),
# importer
path(
r"server/importer/coursesession-trainer-import/",
coursesessions_trainers_import,
name="coursesessions_trainers_import",
),
path(
r"server/importer/coursesession-students-import/",
coursesessions_students_import,
name="coursesessions_students_import",
),
# testing and debug # testing and debug
path('server/raise_error/', path('server/raise_error/',
user_passes_test(lambda u: u.is_superuser, login_url='/login/')( user_passes_test(lambda u: u.is_superuser, login_url='/login/')(

View File

@ -23,6 +23,7 @@ class UserAdmin(auth_admin.UserAdmin):
}, },
), ),
(_("Important dates"), {"fields": ("last_login", "date_joined")}), (_("Important dates"), {"fields": ("last_login", "date_joined")}),
(_("Additional data"), {"fields": ("additional_json_data",)}),
) )
list_display = [ list_display = [
"username", "username",

View File

@ -79,7 +79,7 @@ from vbv_lernwelt.feedback.creators.create_demo_feedback import create_feedback
from vbv_lernwelt.importer.services import ( from vbv_lernwelt.importer.services import (
import_course_sessions_from_excel, import_course_sessions_from_excel,
import_students_from_excel, import_students_from_excel,
import_trainers_from_excel, import_trainers_from_excel_for_training,
) )
from vbv_lernwelt.learnpath.create_vv_new_learning_path import ( from vbv_lernwelt.learnpath.create_vv_new_learning_path import (
create_vv_new_learning_path, create_vv_new_learning_path,
@ -547,12 +547,13 @@ def create_course_training_de():
print(current_dir) print(current_dir)
course = Course.objects.get(id=COURSE_UK_TRAINING) course = Course.objects.get(id=COURSE_UK_TRAINING)
import_course_sessions_from_excel( import_course_sessions_from_excel(
course,
f"{current_dir}/../../../importer/tests/Schulungen_Durchfuehrung_Trainer.xlsx", f"{current_dir}/../../../importer/tests/Schulungen_Durchfuehrung_Trainer.xlsx",
course=course,
restrict_language="de",
) )
import_trainers_from_excel( import_trainers_from_excel_for_training(
course,
f"{current_dir}/../../../importer/tests/Schulungen_Durchfuehrung_Trainer.xlsx", f"{current_dir}/../../../importer/tests/Schulungen_Durchfuehrung_Trainer.xlsx",
course=course,
) )
import_students_from_excel( import_students_from_excel(
f"{current_dir}/../../../importer/tests/Schulungen_Teilnehmende.xlsx", f"{current_dir}/../../../importer/tests/Schulungen_Teilnehmende.xlsx",
@ -636,13 +637,13 @@ def create_course_training_fr():
print(current_dir) print(current_dir)
course = Course.objects.get(id=COURSE_UK_TRAINING_FR) course = Course.objects.get(id=COURSE_UK_TRAINING_FR)
import_course_sessions_from_excel( import_course_sessions_from_excel(
course,
f"{current_dir}/../../../importer/tests/Schulungen_Durchfuehrung_Trainer.xlsx", f"{current_dir}/../../../importer/tests/Schulungen_Durchfuehrung_Trainer.xlsx",
language="fr", restrict_language="fr",
course=course,
) )
import_trainers_from_excel( import_trainers_from_excel_for_training(
course,
f"{current_dir}/../../../importer/tests/Schulungen_Durchfuehrung_Trainer.xlsx", f"{current_dir}/../../../importer/tests/Schulungen_Durchfuehrung_Trainer.xlsx",
course=course,
language="fr", language="fr",
) )
import_students_from_excel( import_students_from_excel(
@ -723,13 +724,13 @@ def create_course_training_it():
print(current_dir) print(current_dir)
course = Course.objects.get(id=COURSE_UK_TRAINING_IT) course = Course.objects.get(id=COURSE_UK_TRAINING_IT)
import_course_sessions_from_excel( import_course_sessions_from_excel(
course,
f"{current_dir}/../../../importer/tests/Schulungen_Durchfuehrung_Trainer.xlsx", f"{current_dir}/../../../importer/tests/Schulungen_Durchfuehrung_Trainer.xlsx",
language="it", restrict_language="it",
course=course,
) )
import_trainers_from_excel( import_trainers_from_excel_for_training(
course,
f"{current_dir}/../../../importer/tests/Schulungen_Durchfuehrung_Trainer.xlsx", f"{current_dir}/../../../importer/tests/Schulungen_Durchfuehrung_Trainer.xlsx",
course=course,
language="it", language="it",
) )
import_students_from_excel( import_students_from_excel(

View File

@ -1,9 +1,10 @@
from typing import Any, Dict from typing import Any, Dict, List
import structlog import structlog
from openpyxl.reader.excel import load_workbook from openpyxl.reader.excel import load_workbook
from vbv_lernwelt.core.models import User from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.consts import COURSE_UK, COURSE_UK_FR, COURSE_UK_IT
from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
from vbv_lernwelt.importer.utils import ( from vbv_lernwelt.importer.utils import (
@ -15,9 +16,25 @@ from vbv_lernwelt.learnpath.models import Circle, LearningContentAttendanceCours
logger = structlog.get_logger(__name__) logger = structlog.get_logger(__name__)
CIRCLE_NAMES = {
"Kickoff": {"de": "Kickoff", "fr": "Lancement", "it": "Introduzione"},
"Basis": {"de": "Basis", "fr": "Base", "it": "Base"},
"Fahrzeug": {"de": "Fahrzeug", "fr": "Véhicule", "it": "Veicolo"},
"Haushalt Teil 1": {"de": "Haushalt Teil 1", "fr": "Habitat partie 1", "it": ""},
"Haushalt Teil 2": {"de": "Haushalt Teil 2", "fr": "Haushalt Teil 2", "it": ""},
}
class DataImportError(Exception):
pass
def create_or_update_user( def create_or_update_user(
email: str, first_name: str = "", last_name: str = "", sso_id: str = None email: str,
first_name: str = "",
last_name: str = "",
sso_id: str = None,
contract_number: str = "",
): ):
logger.debug( logger.debug(
"create_or_update_user", "create_or_update_user",
@ -33,6 +50,13 @@ def create_or_update_user(
if user_qs.exists(): if user_qs.exists():
user = user_qs.first() user = user_qs.first()
if not user and contract_number:
user_qs = User.objects.filter(
additional_json_data__Lehrvertragsnummer=contract_number
)
if user_qs.exists():
user = user_qs.first()
if not user: if not user:
user_qs = User.objects.filter(email=email) user_qs = User.objects.filter(email=email)
if user_qs.exists(): if user_qs.exists():
@ -53,25 +77,41 @@ def create_or_update_user(
return user return user
def import_course_sessions_from_excel(course: Course, filename: str, language="de"): def import_course_sessions_from_excel(
filename: str, course: Course = None, restrict_language=None
):
workbook = load_workbook(filename=filename) workbook = load_workbook(filename=filename)
sheet = workbook["Schulungen Durchführung"] sheet = workbook["Schulungen Durchführung"]
tuple_list = calc_header_tuple_list_from_pyxl_sheet(sheet) tuple_list = calc_header_tuple_list_from_pyxl_sheet(sheet)
for row in tuple_list: for row in tuple_list:
data = dict(row)
validate_row_data(data, ["Klasse", "ID", "Generation", "Region", "Sprache"])
language = data["Sprache"].strip()
# this can be removed when the training import (create_course_training_xx) is no longer used
if restrict_language and language != restrict_language:
continue
if not course:
course = get_uk_course(language)
create_or_update_course_session( create_or_update_course_session(
course, dict(row), language=language, circles=["Fahrzeug"] course, data, language, circle_keys=["Kickoff", "Basis", "Fahrzeug"]
) )
def create_or_update_course_session( def create_or_update_course_session(
course: Course, data: Dict[str, Any], language="de", circles=None course: Course,
data: Dict[str, Any],
language: str,
circle_keys=None,
): ):
""" """
:param data: the following keys are required to process the data: Generation, Region, Klasse :param data: the following keys are required to process the data: Generation, Region, Klasse
:return: :return:
""" """
logger.debug( logger.debug(
"create_or_update_course_session", "create_or_update_course_session",
course=course.title, course=course.title,
@ -79,10 +119,9 @@ def create_or_update_course_session(
label="import", label="import",
) )
if circles is None: if not circle_keys:
circles = [] circle_keys = []
# TODO: validation
group = data["Klasse"].strip() group = data["Klasse"].strip()
import_id = data["ID"].strip() import_id = data["ID"].strip()
@ -91,12 +130,8 @@ def create_or_update_course_session(
title = f"{region} {generation} {group}" title = f"{region} {generation} {group}"
if not import_id.lower().startswith(language.lower()):
# FIXME: language check depends on import_id format for now...
return None
cs, _created = CourseSession.objects.get_or_create( cs, _created = CourseSession.objects.get_or_create(
import_id=import_id, group=group, course=course title=title, course=course, import_id=import_id
) )
cs.additional_json_data["import_data"] = data cs.additional_json_data["import_data"] = data
@ -109,29 +144,26 @@ def create_or_update_course_session(
cs.import_id = import_id cs.import_id = import_id
cs.save() cs.save()
for circle in circles: for circle in circle_keys:
attendance_course_lp_qs = None circle_name = CIRCLE_NAMES[circle][language]
if language == "de":
attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter(
slug=f"{course.slug}-lp-circle-{circle.lower()}-lc-präsenzkurs-{circle.lower()}"
)
elif language == "fr": attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter(
# todo: this is a hack remove me slug=f"{course.slug}-lp-circle-{circle_name.lower()}-lc-präsenzkurs-{circle_name.lower()}"
attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter( )
slug=f"{course.slug}-lp-circle-véhicule-lc-cours-de-présence-véhicule-à-moteur"
)
elif language == "it":
# todo: this is a hack remove me
attendance_course_lp_qs = LearningContentAttendanceCourse.objects.filter(
slug=f"{course.slug}-lp-circle-veicolo-lc-corso-di-presenza-veicolo"
)
if attendance_course_lp_qs and attendance_course_lp_qs.exists(): if attendance_course_lp_qs and attendance_course_lp_qs.exists():
# reset existing data
# TODO: Is this save? stuff shouldn't get lost
CourseSessionAttendanceCourse.objects.filter(
course_session=cs, learning_content=attendance_course_lp_qs.first()
).delete()
location = f"{data[f'{circle} Raum']}, {data[f'{circle} Standort']}, {data[f'{circle} Adresse']}"
csa = CourseSessionAttendanceCourse.objects.create( csa = CourseSessionAttendanceCourse.objects.create(
course_session=cs, course_session=cs,
learning_content=attendance_course_lp_qs.first(), learning_content=attendance_course_lp_qs.first(),
location=data[f"{circle} Raum"], location=location,
trainer="", trainer="",
) )
csa.due_date.start = try_parse_datetime(data[f"{circle} Start"])[1] csa.due_date.start = try_parse_datetime(data[f"{circle} Start"])[1]
@ -141,19 +173,50 @@ def create_or_update_course_session(
return cs return cs
def import_trainers_from_excel(course: Course, filename: str, language="de"): def validate_row_data(data: Dict[str, any], required_headers: List[str]):
for header in required_headers:
some = str(data.get(header, "")).strip()
if str(data.get(header, "")).strip() in ["", "None"]:
logger.debug(
"validate_row_data_missing_header",
data={"data": data, "header": header},
label="import",
)
raise DataImportError(f"Missing or empty value for header {header}")
def get_uk_course(language: str) -> Course:
if language == "fr":
course_id = COURSE_UK_FR
elif language == "it":
course_id = COURSE_UK_IT
else:
course_id = COURSE_UK
return Course.objects.get(id=course_id)
def import_trainers_from_excel_for_training(
filename: str, language="de", course: Course = None
):
workbook = load_workbook(filename=filename) workbook = load_workbook(filename=filename)
sheet = workbook["Schulungen Trainer"] sheet = workbook["Schulungen Trainer"]
tuple_list = calc_header_tuple_list_from_pyxl_sheet(sheet) tuple_list = calc_header_tuple_list_from_pyxl_sheet(sheet)
for row in tuple_list: for row in tuple_list:
create_or_update_trainer(course, dict(row), language=language) data = dict(row)
validate_row_data(
data, ["Email", "Vorname", "Name", "Sprache", "Klasse", "Generation"]
)
create_or_update_trainer(course, data, language=language)
def create_or_update_trainer(course: Course, data: Dict[str, Any], language="de"): def create_or_update_trainer(course: Course, data: Dict[str, Any], language="de"):
course_title = course.title if course else "None"
logger.debug( logger.debug(
"create_or_update_trainer", "create_or_update_trainer",
course=course.title, course=course_title,
data=data, data=data,
label="import", label="import",
) )
@ -163,62 +226,65 @@ def create_or_update_trainer(course: Course, data: Dict[str, Any], language="de"
first_name=data["Vorname"], first_name=data["Vorname"],
last_name=data["Name"], last_name=data["Name"],
) )
user.language = data["Sprache"]
user.save()
groups = [g.strip() for g in data["Klasse"].strip().split(",")] group = data["Klasse"].strip()
# general expert handling # general expert handling
for group in groups: import_id = f"{data['Generation'].strip()} {group}"
import_id = f"{data['Generation'].strip()} {group}" course_session = CourseSession.objects.filter(
course_session = CourseSession.objects.filter( import_id=import_id,
group=group,
).first()
if course_session:
csu, _created = CourseSessionUser.objects.get_or_create(
course_session_id=course_session.id, user_id=user.id
)
csu.role = CourseSessionUser.Role.EXPERT
csu.save()
else:
logger.warning(
"create_or_update_trainer: course_session not found",
import_id=import_id, import_id=import_id,
group=group, group=group,
).first() label="import",
if course_session: )
csu, _created = CourseSessionUser.objects.get_or_create(
course_session_id=course_session.id, user_id=user.id if not course and not course_session:
) logger.warning(
csu.role = CourseSessionUser.Role.EXPERT "create_or_update_trainer: course_session and course are None",
csu.save() import_id=import_id,
else: group=group,
logger.warning( label="import",
"create_or_update_trainer: course_session not found", )
import_id=import_id, return
group=group,
label="import",
)
if not course: if not course:
return course = course_session.course
# circle expert handling # circle expert handling
circle_data = parse_circle_group_string(data["Circles"]) circle_data = parse_circle_group_string(data["Circles"])
for circle_string in circle_data: for circle_key in circle_data:
parts = circle_string.split("(", 1)
circle_name = parts[0].strip()
groups = [g.strip() for g in parts[1].rstrip(")").strip().split(",")] circle_name = CIRCLE_NAMES[circle_key][language]
# FIXME: hardcoded translation
if language == "fr" and circle_name == "Fahrzeug":
circle_name = "Véhicule"
# print(circle_name, groups) # print(circle_name, groups)
for group in groups: import_id = f"{data['Generation'].strip()} {group}"
import_id = f"{data['Generation'].strip()} {group}" course_session = CourseSession.objects.filter(
course_session = CourseSession.objects.filter( import_id=import_id, group=group
import_id=import_id, group=group ).first()
).first() circle = Circle.objects.filter(
circle = Circle.objects.filter( slug=f"{course.slug}-lp-circle-{circle_name.lower()}"
slug=f"{course.slug}-lp-circle-{circle_name.lower()}" ).first()
).first()
if course_session and circle: if course_session and circle:
csu = CourseSessionUser.objects.filter( csu = CourseSessionUser.objects.filter(
course_session_id=course_session.id, user_id=user.id course_session_id=course_session.id, user_id=user.id
).first() ).first()
if csu: if csu:
csu.expert.add(circle) csu.expert.add(circle)
csu.save() csu.save()
def import_students_from_excel(filename: str): def import_students_from_excel(filename: str):
@ -227,7 +293,18 @@ def import_students_from_excel(filename: str):
tuple_list = calc_header_tuple_list_from_pyxl_sheet(sheet) tuple_list = calc_header_tuple_list_from_pyxl_sheet(sheet)
for row in tuple_list: for row in tuple_list:
create_or_update_student(dict(row)) data = dict(row)
validate_row_data(
data,
[
"Email",
"Vorname",
"Name",
"Sprache",
"Durchführungen",
],
)
create_or_update_student(data)
def create_or_update_student(data: Dict[str, Any]): def create_or_update_student(data: Dict[str, Any]):
@ -241,16 +318,18 @@ def create_or_update_student(data: Dict[str, Any]):
email=data["Email"].lower(), email=data["Email"].lower(),
first_name=data["Vorname"], first_name=data["Vorname"],
last_name=data["Name"], last_name=data["Name"],
contract_number=data.get("Lehrvertragsnummer", ""),
) )
# TODO: handle language user.language = data["Sprache"]
user.additional_json_data = user.additional_json_data | data
user.save()
# general expert handling # general expert handling
import_ids = [i.strip() for i in data["Durchführungen"].split(",")] import_id = data["Durchführungen"]
for import_id in import_ids: course_session = CourseSession.objects.filter(import_id=import_id).first()
course_session = CourseSession.objects.filter(import_id=import_id).first() if course_session:
if course_session: csu, _created = CourseSessionUser.objects.get_or_create(
csu, _created = CourseSessionUser.objects.get_or_create( course_session_id=course_session.id, user_id=user.id
course_session_id=course_session.id, user_id=user.id )
) csu.save()
csu.save()

View File

@ -0,0 +1,12 @@
{% extends "admin/index.html" %}
{% block content %}
<div id="content-main">
<div class="content">
<h1>Die Daten konnten nicht importiert werden</h1>
<p>Folgender Fehler ist aufgetreten:</p>
<pre>{{ error }}</pre>
</div>
</div>
{% endblock %}

View File

@ -6,7 +6,11 @@ from openpyxl.reader.excel import load_workbook
from vbv_lernwelt.course.creators.test_course import create_test_course from vbv_lernwelt.course.creators.test_course import create_test_course
from vbv_lernwelt.course.models import CourseSession from vbv_lernwelt.course.models import CourseSession
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
from vbv_lernwelt.importer.services import create_or_update_course_session from vbv_lernwelt.importer.services import (
create_or_update_course_session,
DataImportError,
validate_row_data,
)
from vbv_lernwelt.importer.utils import calc_header_tuple_list_from_pyxl_sheet from vbv_lernwelt.importer.utils import calc_header_tuple_list_from_pyxl_sheet
test_dir = os.path.dirname(os.path.abspath(__file__)) test_dir = os.path.dirname(os.path.abspath(__file__))
@ -26,10 +30,10 @@ class ImportCourseSessionTestCase(TestCase):
for row in tuple_list: for row in tuple_list:
print(row) print(row)
create_or_update_course_session( create_or_update_course_session(
self.course, dict(row), language="de", circles=["Fahrzeug"] self.course, dict(row), language="de", circle_keys=["Fahrzeug"]
) )
self.assertEqual(CourseSession.objects.count(), 3) self.assertEqual(CourseSession.objects.count(), 6)
class CreateOrUpdateCourseSessionTestCase(TestCase): class CreateOrUpdateCourseSessionTestCase(TestCase):
@ -41,6 +45,7 @@ class CreateOrUpdateCourseSessionTestCase(TestCase):
("ID", "DE 2023 A"), ("ID", "DE 2023 A"),
("Generation", 2023), ("Generation", 2023),
("Region", "Deutschschweiz"), ("Region", "Deutschschweiz"),
("Sprache", "de"),
("Klasse", "A"), ("Klasse", "A"),
("Fahrzeug Start", "06.06.2023, 13:30"), ("Fahrzeug Start", "06.06.2023, 13:30"),
("Fahrzeug Ende", "06.06.2023, 15:00"), ("Fahrzeug Ende", "06.06.2023, 15:00"),
@ -54,7 +59,9 @@ class CreateOrUpdateCourseSessionTestCase(TestCase):
data = dict(row) data = dict(row)
cs = create_or_update_course_session(self.course, data, circles=["Fahrzeug"]) cs = create_or_update_course_session(
self.course, data, language="de", circle_keys=["Fahrzeug"]
)
self.assertEqual(cs.import_id, "DE 2023 A") self.assertEqual(cs.import_id, "DE 2023 A")
self.assertEqual(cs.title, "Deutschschweiz 2023 A") self.assertEqual(cs.title, "Deutschschweiz 2023 A")
@ -82,6 +89,7 @@ class CreateOrUpdateCourseSessionTestCase(TestCase):
("ID", "DE 2023"), ("ID", "DE 2023"),
("Generation", 2023), ("Generation", 2023),
("Region", "Deutschschweiz"), ("Region", "Deutschschweiz"),
("Sprache", "de"),
("Klasse", "A"), ("Klasse", "A"),
("Fahrzeug Start", "06.06.2023, 13:30"), ("Fahrzeug Start", "06.06.2023, 13:30"),
("Fahrzeug Ende", "06.06.2023, 15:00"), ("Fahrzeug Ende", "06.06.2023, 15:00"),
@ -95,7 +103,9 @@ class CreateOrUpdateCourseSessionTestCase(TestCase):
data = dict(row) data = dict(row)
cs = create_or_update_course_session(self.course, data, circles=["Fahrzeug"]) cs = create_or_update_course_session(
self.course, data, language="de", circle_keys=["Fahrzeug"]
)
self.assertEqual(1, CourseSession.objects.count()) self.assertEqual(1, CourseSession.objects.count())
@ -112,3 +122,32 @@ class CreateOrUpdateCourseSessionTestCase(TestCase):
self.assertEqual( self.assertEqual(
attendance_course.due_date.end.isoformat(), "2023-06-06T13:00:00+00:00" attendance_course.due_date.end.isoformat(), "2023-06-06T13:00:00+00:00"
) )
def test_raise_exception_if_header_is_missing(self):
data = [
("ID", "DE 2023"),
]
with self.assertRaises(DataImportError):
validate_row_data(
dict(data),
[
"ID",
"Generation",
],
)
def test_raise_exception_if_required_field_is_empty(self):
data = [
("ID", "DE 2023"),
("Generation", ""),
]
with self.assertRaises(DataImportError):
validate_row_data(
dict(data),
[
"ID",
"Generation",
],
)

View File

@ -1,5 +1,4 @@
import os import os
from datetime import datetime
from django.test import TestCase from django.test import TestCase
from openpyxl.reader.excel import load_workbook from openpyxl.reader.excel import load_workbook
@ -44,33 +43,56 @@ class CreateOrUpdateStudentTestCase(TestCase):
group="A", group="A",
) )
def test_create_student(self): self.user_dict = {
row = [ "Name": "Rascher",
("Name", "Rascher"), "Vorname": "Barbara",
("Vorname", "Barbara"), "Email": "barbara.rascher@vbv-afa.ch",
("Email", "barbara.rascher@vbv-afa.ch"), "Sprache": "de",
("Sprache", "de"), "Durchführungen": "DE 2023 A",
("Durchführungen", "DE 2023 A"), "Lehrvertragsnummer": "1234",
("Datum", datetime(2023, 9, 6, 0, 0)), "Tel. Privat": "079 593 83 43",
(None, "VBV"), }
(None, None),
(None, None),
(None, None),
(None, None),
]
create_or_update_student(dict(row)) def test_create_student(self):
create_or_update_student(self.user_dict)
self.assertEqual(
CourseSessionUser.objects.filter(
user__email=self.user_dict["Email"]
).count(),
1,
)
csu = CourseSessionUser.objects.filter(
course_session=self.course_session_a,
).first()
self.assertEqual(csu.role, CourseSessionUser.Role.MEMBER)
self.assertEqual(csu.user.email, self.user_dict["Email"])
self.assertEqual(csu.user.additional_json_data, self.user_dict)
def test_update_student(self):
create_or_update_student(self.user_dict)
self.user_dict["Email"] = "br@vbv.ch"
create_or_update_student(self.user_dict)
self.assertEqual( self.assertEqual(
CourseSessionUser.objects.filter( CourseSessionUser.objects.filter(
user__email="barbara.rascher@vbv-afa.ch" user__email="barbara.rascher@vbv-afa.ch"
).count(), ).count(),
0,
)
self.assertEqual(
CourseSessionUser.objects.filter(
user__additional_json_data__Lehrvertragsnummer=self.user_dict["Lehrvertragsnummer"]
).count(),
1, 1,
) )
csu = CourseSessionUser.objects.get( csu = CourseSessionUser.objects.filter(
course_session=self.course_session_a, course_session=self.course_session_a,
) ).first()
self.assertEqual(csu.role, CourseSessionUser.Role.MEMBER) self.assertEqual(csu.user.additional_json_data, self.user_dict)
self.assertEqual(csu.user.email, "barbara.rascher@vbv-afa.ch")

View File

@ -58,19 +58,29 @@ class CreateOrUpdateTrainerTestCase(TestCase):
) )
def test_create_trainer(self): def test_create_trainer(self):
row = [ rows = [
("Name", "Hänni"), [
("Vorname", "Fabienne"), ("Name", "Hänni"),
("Email", "fabienne.haenni@vbv-afa.ch"), ("Vorname", "Fabienne"),
("Sprache", "de"), ("Email", "fabienne.haenni@vbv-afa.ch"),
("Generation", "DE 2023"), ("Sprache", "de"),
("Klasse", "A, B"), ("Generation", "DE 2023"),
("Circles", "Fahrzeug (A, B), Reisen (A), KMU (B)"), ("Klasse", "A"),
("Status Referenten", "ok"), ("Circles", "Fahrzeug, Haushalt Teil 1"),
(None, "Schulung D"), ],
[
("Name", "Hänni"),
("Vorname", "Fabienne"),
("Email", "fabienne.haenni@vbv-afa.ch"),
("Sprache", "de"),
("Generation", "DE 2023"),
("Klasse", "B"),
("Circles", "Fahrzeug, Haushalt Teil 2"),
],
] ]
create_or_update_trainer(self.course, dict(row)) for row in rows:
create_or_update_trainer(self.course, dict(row))
self.assertEqual( self.assertEqual(
CourseSessionUser.objects.filter( CourseSessionUser.objects.filter(

View File

@ -0,0 +1,48 @@
from typing import Callable
from django.contrib import messages
from django.contrib.admin.views.decorators import staff_member_required
from django.shortcuts import redirect, render
from vbv_lernwelt.importer.services import (
import_course_sessions_from_excel,
import_students_from_excel,
import_trainers_from_excel_for_training,
)
@staff_member_required
def coursesessions_trainers_import(request):
return handle_import(
request,
"Die Durchführungen und Trainer wurden erfolgreich importiert!",
import_training_and_trainer,
)
def import_training_and_trainer(excel_file: str):
import_course_sessions_from_excel(
excel_file,
)
import_trainers_from_excel_for_training(excel_file)
@staff_member_required
def coursesessions_students_import(request):
return handle_import(
request,
"Die Teilnehmer wurden erflogreich importiert!",
import_students_from_excel,
)
def handle_import(request, success_msg: str, importer: Callable[[str], None]):
if request.method == "POST" and request.FILES["excel_file"]:
excel_file = request.FILES["excel_file"]
try:
importer(excel_file)
except Exception as e:
return render(request, "admin/importer/error.html", {"error": str(e)})
messages.info(request, success_msg)
return redirect("admin:index")

View File

@ -6,6 +6,21 @@
<div class="content"> <div class="content">
<h1>Export üK</h1> <h1>Export üK</h1>
<h1>Excel Import üK</h1>
<h2>Durchführungen und Trainer</h2>
<form method="post" enctype="multipart/form-data" action="/server/importer/coursesession-trainer-import/">
{% csrf_token %}
<input type="file" name="excel_file" accept=".xlsx, .xls">
<input type="submit" value="Durchführungen und Trainer importieren">
</form>
<h2>Teilnehmer</h2>
<form method="post" enctype="multipart/form-data" action="/server/importer/coursesession-students-import/">
{% csrf_token %}
<input type="file" name="excel_file" accept=".xlsx, .xls">
<input type="submit" value="Teilnehmer importieren">
</form>
<h2>Edoniq Teilnehmer</h2> <h2>Edoniq Teilnehmer</h2>
<a href="{% url 'edoniq_export_students' %}" class="btn btn-primary">Teilnehmer exportieren</a> <a href="{% url 'edoniq_export_students' %}" class="btn btn-primary">Teilnehmer exportieren</a>