diff --git a/compose/django/supercronic_crontab b/compose/django/supercronic_crontab index 49452de0..daeff803 100644 --- a/compose/django/supercronic_crontab +++ b/compose/django/supercronic_crontab @@ -4,5 +4,8 @@ # Run every 6 hours 0 */6 * * * /usr/local/bin/python /app/manage.py simple_dummy_job +# Run every hour +0 */1 * * * /usr/local/bin/python /app/manage.py edoniq_import_results + # every day at 19:30 30 19 * * * /usr/local/bin/python /app/manage.py send_attendance_course_reminders diff --git a/server/config/settings/base.py b/server/config/settings/base.py index 3872dbdc..a1f174d0 100644 --- a/server/config/settings/base.py +++ b/server/config/settings/base.py @@ -128,6 +128,7 @@ LOCAL_APPS = [ "vbv_lernwelt.assignment", "vbv_lernwelt.duedate", "vbv_lernwelt.importer", + "vbv_lernwelt.edoniq_test", ] # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS diff --git a/server/vbv_lernwelt/assignment/graphql/mutations.py b/server/vbv_lernwelt/assignment/graphql/mutations.py index 683baf74..73b6de7f 100644 --- a/server/vbv_lernwelt/assignment/graphql/mutations.py +++ b/server/vbv_lernwelt/assignment/graphql/mutations.py @@ -101,7 +101,7 @@ class AssignmentCompletionMutation(graphene.Mutation): "evaluation_passed": evaluation_passed, } - ac = update_assignment_completion( + ac, created = update_assignment_completion( copy_task_data=False, initialize_completion=initialize_completion, **assignment_data, @@ -116,6 +116,7 @@ class AssignmentCompletionMutation(graphene.Mutation): assignment_user_id=assignment_user_id, course_session_id=course_session_id, completion_status=completion_status, + created=created, ) return AssignmentCompletionMutation(assignment_completion=ac) diff --git a/server/vbv_lernwelt/assignment/services.py b/server/vbv_lernwelt/assignment/services.py index ad39afc2..c8f5dbc8 100644 --- a/server/vbv_lernwelt/assignment/services.py +++ b/server/vbv_lernwelt/assignment/services.py @@ -28,10 +28,13 @@ def update_assignment_completion( evaluation_user: User | None = None, evaluation_points: float | None = None, evaluation_passed: bool | None = None, + evaluation_max_points: float | None = None, validate_completion_status_change: bool = True, copy_task_data: bool = False, initialize_completion: bool = False, -) -> AssignmentCompletion: + additional_json_data: dict | None = None, + validate_submission_update: bool = True, +) -> tuple[AssignmentCompletion, bool]: """ :param completion_data: should have the following structure: { @@ -56,6 +59,9 @@ def update_assignment_completion( if completion_data is None: completion_data = {} + if additional_json_data is None: + additional_json_data = {} + ac, created = AssignmentCompletion.objects.get_or_create( assignment_user_id=assignment_user.id, assignment_id=assignment.id, @@ -78,17 +84,22 @@ def update_assignment_completion( if validate_completion_status_change: if completion_status == AssignmentCompletionStatus.SUBMITTED: - if ac.completion_status in [ - "SUBMITTED", + completion_status_to_check = [ "EVALUATION_IN_PROGRESS", "EVALUATION_SUBMITTED", - ]: + ] + if validate_submission_update: + completion_status_to_check.append("SUBMITTED") + if ac.completion_status in completion_status_to_check: raise serializers.ValidationError( { "completion_status": f"Cannot update completion status from {ac.completion_status} to SUBMITTED" } ) - elif completion_status == AssignmentCompletionStatus.EVALUATION_SUBMITTED: + elif ( + completion_status == AssignmentCompletionStatus.EVALUATION_SUBMITTED + and validate_submission_update + ): if ac.completion_status == "EVALUATION_SUBMITTED": raise serializers.ValidationError( { @@ -126,14 +137,20 @@ def update_assignment_completion( } ) - ac.evaluation_max_points = assignment.get_max_points() + if evaluation_max_points is None: + ac.evaluation_max_points = assignment.get_max_points() + else: + ac.evaluation_max_points = evaluation_max_points ac.evaluation_points = evaluation_points # if no evaluation_passed is provided, we calculate it from the points - if evaluation_passed is None and evaluation_points is not None: - # if more or equal than 60% of the points are reached, the assignment is passed - # TODO: check with VBV if this is correct - ac.evaluation_passed = (evaluation_points / ac.evaluation_max_points) >= 0.6 + if evaluation_passed is None: + if evaluation_points is not None and ac.evaluation_max_points is not None: + # if more or equal than 60% of the points are reached, the assignment is passed + # TODO: check with VBV if this is correct + ac.evaluation_passed = (evaluation_points / ac.evaluation_max_points) >= 0.6 + else: + ac.evaluation_passed = evaluation_passed if completion_status == AssignmentCompletionStatus.SUBMITTED: ac.submitted_at = timezone.now() @@ -160,6 +177,7 @@ def update_assignment_completion( ) ac.completion_status = completion_status.value + ac.additional_json_data = ac.additional_json_data | additional_json_data # TODO: make more validation of the provided input -> maybe with graphql completion_data = _remove_unknown_entries(assignment, completion_data) @@ -225,7 +243,7 @@ def update_assignment_completion( completion_status=CourseCompletionStatus.SUCCESS.value, ) - return ac + return (ac, created) def _remove_unknown_entries(assignment, completion_data): diff --git a/server/vbv_lernwelt/course/creators/test_course.py b/server/vbv_lernwelt/course/creators/test_course.py index 4582f963..52816437 100644 --- a/server/vbv_lernwelt/course/creators/test_course.py +++ b/server/vbv_lernwelt/course/creators/test_course.py @@ -471,7 +471,11 @@ damit du erfolgreich mit deinem Lernpfad (durch-)starten kannst. ), checkbox_text="Hiermit bestätige ich, dass ich die Anweisungen verstanden und die Redlichkeitserklärung akzeptiert habe.", test_url="https://exam.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1689096897473,2147466097", + edoniq_course_release_id="1689096897473", + edoniq_sequence_id="1688059890497", extended_time_test_url="https://exam2.vbv-afa.ch/e-tutor/v4/user/course/pre_course_object?aid=1689096897473,2147466097", + edoniq_extended_course_release_id="1689096897473", + edoniq_extended_sequence_id="1691151920116", ) LearningSequenceFactory(title="Transfer", parent=circle, icon="it-icon-ls-end") diff --git a/server/vbv_lernwelt/edoniq_test/apps.py b/server/vbv_lernwelt/edoniq_test/apps.py index b88a4f58..b10154d9 100644 --- a/server/vbv_lernwelt/edoniq_test/apps.py +++ b/server/vbv_lernwelt/edoniq_test/apps.py @@ -1,7 +1,7 @@ from django.apps import AppConfig -class LearnpathConfig(AppConfig): +class EdoniqTestConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" name = "vbv_lernwelt.edoniq_test" diff --git a/server/vbv_lernwelt/edoniq_test/management/commands/__init__.py b/server/vbv_lernwelt/edoniq_test/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/edoniq_test/management/commands/edoniq_import_results.py b/server/vbv_lernwelt/edoniq_test/management/commands/edoniq_import_results.py new file mode 100644 index 00000000..99e6c4bc --- /dev/null +++ b/server/vbv_lernwelt/edoniq_test/management/commands/edoniq_import_results.py @@ -0,0 +1,61 @@ +import structlog + +from vbv_lernwelt.core.base import LoggedCommand +from vbv_lernwelt.edoniq_test.result_import.edoniq_sftp_connection import ( + load_edoniq_test_results_csv_data, +) +from vbv_lernwelt.edoniq_test.result_import.services import ( + create_edoniq_csv_test_result, + filter_relevant_rows, + upsert_edoniq_test_result, +) + +logger = structlog.get_logger(__name__) + + +class Command(LoggedCommand): + help = "Imports Test Results from Edoniq" + + def handle(self, *args, **options): + logger.info( + "edoniq_import_results job started", + label="edoniq_import_results", + ) + + stat_results = { + "num_created": 0, + "num_updated": 0, + "num_error": 0, + "num_passed": 0, + "num_failed": 0, + } + + csv_data = load_edoniq_test_results_csv_data() + relevant_rows = filter_relevant_rows(csv_data) + stat_results["num_relevant_edoniq_csv_rows"] = len(relevant_rows) + + for row in relevant_rows: + result_row = create_edoniq_csv_test_result(row) + assignment_completion, created = upsert_edoniq_test_result( + result_row, fail_silently=True + ) + if assignment_completion: + if created: + stat_results["num_created"] += 1 + else: + stat_results["num_updated"] += 1 + if assignment_completion.evaluation_passed: + stat_results["num_passed"] += 1 + else: + stat_results["num_failed"] += 1 + else: + stat_results["num_error"] += 1 + + self.job_log.json_data = stat_results + self.job_log.save() + + logger.info( + "edoniq_import_results job finished", + label="edoniq_import_results", + stat_results=stat_results, + ) diff --git a/server/vbv_lernwelt/edoniq_test/result_import/edoniq_sftp_connection.py b/server/vbv_lernwelt/edoniq_test/result_import/edoniq_sftp_connection.py index 8bc6ae72..d14b69fb 100644 --- a/server/vbv_lernwelt/edoniq_test/result_import/edoniq_sftp_connection.py +++ b/server/vbv_lernwelt/edoniq_test/result_import/edoniq_sftp_connection.py @@ -19,7 +19,7 @@ def _create_edoniq_sftp_client(): logger.warn( "Environment variable not set or empty", env_var=private_key_env_var_name, - label="edoniq_import", + label="edoniq_import_results", ) raise ValueError( f"Environment variable {private_key_env_var_name} not set or empty." @@ -59,5 +59,7 @@ if __name__ == "__main__": csv_data = load_edoniq_test_results_csv_data() csv_reader = csv.reader(StringIO(csv_data), delimiter=";") for i, row in enumerate(csv_reader): - if len(row) > 4 and row[4] == "AG 2023 A" and row[5] == "de_üK1_BA_KN": + # if len(row) > 4 and row[4] == "AG 2023 A" and row[5] == "de_üK1_BA_KN": + # print(row) + if i < 10: print(row) diff --git a/server/vbv_lernwelt/edoniq_test/result_import/services.py b/server/vbv_lernwelt/edoniq_test/result_import/services.py new file mode 100644 index 00000000..1b6fb45c --- /dev/null +++ b/server/vbv_lernwelt/edoniq_test/result_import/services.py @@ -0,0 +1,146 @@ +import csv +from dataclasses import dataclass +from io import StringIO + +import structlog +from django.db.models import Q + +from vbv_lernwelt.assignment.models import AssignmentCompletionStatus +from vbv_lernwelt.assignment.services import update_assignment_completion +from vbv_lernwelt.core.models import User +from vbv_lernwelt.course.models import CourseSession, CourseSessionUser +from vbv_lernwelt.learnpath.models import LearningContentEdoniqTest + +logger = structlog.get_logger(__name__) + + +def get_distinct_sequence_ids(): + distinct_sequence_ids = LearningContentEdoniqTest.objects.values_list( + "edoniq_sequence_id", flat=True + ).distinct() + + distinct_sequence_extended_ids = LearningContentEdoniqTest.objects.values_list( + "edoniq_extended_sequence_id", flat=True + ).distinct() + + # filter out empty values + return { + seq_id + for seq_id in list(distinct_sequence_ids) + list(distinct_sequence_extended_ids) + if seq_id + } + + +def filter_relevant_rows(csv_data): + sequence_ids = get_distinct_sequence_ids() + csv_reader = csv.reader(StringIO(csv_data), delimiter=";") + + # read headers + headers = next(csv_reader) + headers = [header.strip() for header in headers] + + # Read the rest of the lines and create a list of dictionaries + records = [] + for row in csv_reader: + # strip all whitespace around the column entries + row = [item.strip() for item in row] + records.append(dict(zip(headers, row))) + + relevant_rows = [row for row in records if row.get("Sequenz ID") in sequence_ids] + + return relevant_rows + + +@dataclass +class EdoniqCsvTestResult: + user_id: str + user_points: float + max_points: float + passed: bool + edoniq_sequence_id: str + unfinished: bool = False + + +def create_edoniq_csv_test_result(edoniq_csv_row_dict) -> EdoniqCsvTestResult: + user_id = edoniq_csv_row_dict["Benutzer Login"] + user_points = float(edoniq_csv_row_dict["erreichte Punkte absolut"]) + max_points = float(edoniq_csv_row_dict["erreichbares Punktetotal absolut"]) + passed = edoniq_csv_row_dict["Bestanden-/Nicht-Bestanden Status"] == "Bestanden" + unfinished = edoniq_csv_row_dict["Bestanden-/Nicht-Bestanden Status"] == "Begonnen" + edoniq_sequence_id = edoniq_csv_row_dict["Sequenz ID"] + + return EdoniqCsvTestResult( + user_id=user_id, + user_points=user_points, + max_points=max_points, + passed=passed, + edoniq_sequence_id=edoniq_sequence_id, + unfinished=unfinished, + ) + + +def upsert_edoniq_test_result( + edoniq_csv_test_result: EdoniqCsvTestResult, fail_silently=False +): + try: + u = User.objects.get(id=edoniq_csv_test_result.user_id) + + # because the same edoniq test is used in multiple languages, + # we have to consider multiple learning contents + learning_content_qs = LearningContentEdoniqTest.objects.filter( + Q(edoniq_sequence_id=edoniq_csv_test_result.edoniq_sequence_id) + | Q(edoniq_extended_sequence_id=edoniq_csv_test_result.edoniq_sequence_id) + ) + + user_course_session_ids = set( + CourseSessionUser.objects.filter( + user_id=u.id, + course_session__course_id__in=[ + lc.get_course().id for lc in learning_content_qs + ], + ).values_list("course_session__id", flat=True) + ) + + for course_session_id in user_course_session_ids: + course_session = CourseSession.objects.get(id=course_session_id) + + # find the assignment which is attached to this language course + assignment = ( + learning_content_qs.descendant_of(course_session.course.coursepage) + .first() + .content_assignment + ) + + completion_status = AssignmentCompletionStatus.EVALUATION_SUBMITTED + if edoniq_csv_test_result.unfinished: + completion_status = AssignmentCompletionStatus.SUBMITTED + + return update_assignment_completion( + assignment_user=u, + assignment=assignment, + course_session=course_session, + learning_content_page=assignment.find_attached_learning_content(), + completion_data={}, + completion_status=completion_status, + evaluation_user=User.objects.get(username="admin"), + evaluation_points=edoniq_csv_test_result.user_points, + evaluation_passed=edoniq_csv_test_result.passed, + evaluation_max_points=edoniq_csv_test_result.max_points, + additional_json_data={ + "edoniq_sequence_id": edoniq_csv_test_result.edoniq_sequence_id, + }, + validate_submission_update=False, + ) + + except Exception as e: + logger.warning( + "edoniq_test_result_import_fail", + label="edoniq_import_results", + error="user or learning_content not found", + user_id=edoniq_csv_test_result.user_id, + sequence_id=edoniq_csv_test_result.edoniq_sequence_id, + exc_info=True, + ) + if not fail_silently: + raise e + return None, False diff --git a/server/vbv_lernwelt/edoniq_test/tests/edoniq_pruefung_resultate.csv b/server/vbv_lernwelt/edoniq_test/tests/edoniq_pruefung_resultate.csv index ab14dc4d..eedc5f9a 100644 --- a/server/vbv_lernwelt/edoniq_test/tests/edoniq_pruefung_resultate.csv +++ b/server/vbv_lernwelt/edoniq_test/tests/edoniq_pruefung_resultate.csv @@ -1,6 +1,6 @@ Benutzer Login;Benutzer Name;Benutzer Vorname;Korrespondenzsprache;OE;Sequenztitel;Sequenz ID;letztes Datum der Durchführung des Kandidaten;Zeitstempel Beginn;Bearbeitungsdauer;erreichte Punkte absolut ; erreichbares Punktetotal absolut;erreichte Punkte;Bestehensgrenze prozentual;Bestanden-/Nicht-Bestanden Status;Note;prozentualer Durchschnitt der Versuche dieses Kandidaten;prozentualer Durchschnitt aller Kandidaten ; -cc36ad95-d09c-4187-b1d4-689ada3ef497;Sommer;Diego;de;AG 2023 A;de_üK1_BA_KN;1688059890497;06.09.2023;06.09.2023 18:48;00:17:10.4650;21.25 ; 29;73.28;60;Bestanden;;73.28;65.91 -58fdb8e2-881d-48fd-8df9-05f31586e909;Demirel;Beyzanur;de;AG 2023 A;de_üK1_BA_KN;1688059890497;07.09.2023;07.09.2023 19:00;00:17:49.8550;12.23 ; 29;42.16;60;Nicht bestanden;;42.16;65.91 -19d4e374-7300-45cc-a420-bebe184c5c4e;Appert;Mascha;de;AG 2023 A;de_üK1_BA_KN;1688059890497;10.09.2023;10.09.2023 10:48;00:18:22.9710;17.62 ; 29;60.76;60;Bestanden;;60.76;65.91 -29610d7c-ed48-43b8-ab71-ce0e726aada4;Gianformaggio;Ylaria;de;AG 2023 A;de_üK1_KO_Testlauf;1686325621487;10.09.2023;10.09.2023 16:32;00:00:00.0000;0.00 ; 15;0.00;60;Begonnen;;38.11;55.30 \ No newline at end of file +65c73ad0-6d53-43a9-a4a4-64143f27b03a;Student1;Test;de;Test Bern 2022 a;de_üK1_BA_KN;1688059890497;06.09.2023;06.09.2023 18:48;00:17:10.4650;21.25 ; 29;73.28;60;Bestanden;;73.28;65.91 +19c40d94-15cc-4198-aaad-ef707c4b0900;Student2;Test;de;Test Bern 2022 a;de_üK1_BA_KN;1688059890497;07.09.2023;07.09.2023 19:00;00:17:49.8550;12.23 ; 29;42.16;60;Nicht bestanden;;42.16;65.91 +bcf94dba-53bc-474b-a22d-e4af39aa042b;Student3;Test;de;Test Bern 2022 a;de_üK1_BA_KN;1691151920116;10.09.2023;10.09.2023 10:48;00:18:22.9710;0.00 ; 29;60.76;60;Begonnen;;60.76;65.91 +bcf94dba-53bc-474b-a22d-e4af39aa042b;Student3;Test;de;Test Bern 2022 a;AG 2023 A;de_üK1_KO_Testlauf;1686325621487;10.09.2023;10.09.2023 16:32;00:00:00.0000;0.00 ; 15;0.00;60;Begonnen;;38.11;55.30 \ No newline at end of file diff --git a/server/vbv_lernwelt/edoniq_test/tests/test_result_import.py b/server/vbv_lernwelt/edoniq_test/tests/test_result_import.py new file mode 100644 index 00000000..7601eed6 --- /dev/null +++ b/server/vbv_lernwelt/edoniq_test/tests/test_result_import.py @@ -0,0 +1,90 @@ +import os + +from django.test import TestCase + +from vbv_lernwelt.assignment.models import AssignmentCompletion +from vbv_lernwelt.core.constants import ( + TEST_STUDENT1_USER_ID, + TEST_STUDENT2_USER_ID, + TEST_STUDENT3_USER_ID, +) +from vbv_lernwelt.core.create_default_users import create_default_users +from vbv_lernwelt.course.creators.test_course import create_test_course +from vbv_lernwelt.edoniq_test.result_import.services import ( + create_edoniq_csv_test_result, + EdoniqCsvTestResult, + filter_relevant_rows, + get_distinct_sequence_ids, + upsert_edoniq_test_result, +) + + +class EdoniqResultImportTestCase(TestCase): + def setUp(self) -> None: + create_default_users() + create_test_course(with_sessions=True) + + def test_get_distinct_sequence_ids(self): + sequence_ids = get_distinct_sequence_ids() + self.assertEqual(len(sequence_ids), 2) + self.assertSetEqual(sequence_ids, {"1691151920116", "1688059890497"}) + + def test_filter_relevant_rows(self): + csv_file_path = os.path.dirname(__file__) + "/edoniq_pruefung_resultate.csv" + with open( + csv_file_path, + "r", + encoding="utf-8", + ) as file: + csv_data = file.read() + + relevant_rows = filter_relevant_rows(csv_data) + self.assertEqual(len(relevant_rows), 3) + print(relevant_rows) + + self.assertEqual(relevant_rows[0]["Benutzer Login"], TEST_STUDENT1_USER_ID) + self.assertEqual(relevant_rows[1]["Benutzer Login"], TEST_STUDENT2_USER_ID) + self.assertEqual(relevant_rows[2]["Benutzer Login"], TEST_STUDENT3_USER_ID) + + self.assertEqual(relevant_rows[0]["erreichte Punkte absolut"], "21.25") + self.assertEqual(relevant_rows[0]["erreichbares Punktetotal absolut"], "29") + self.assertEqual( + relevant_rows[0]["Bestanden-/Nicht-Bestanden Status"], "Bestanden" + ) + self.assertEqual(relevant_rows[0]["Zeitstempel Beginn"], "06.09.2023 18:48") + + self.assertEqual(relevant_rows[2]["erreichte Punkte absolut"], "0.00") + self.assertEqual(relevant_rows[2]["erreichbares Punktetotal absolut"], "29") + self.assertEqual( + relevant_rows[2]["Bestanden-/Nicht-Bestanden Status"], "Begonnen" + ) + + edoniq_test_result_row = create_edoniq_csv_test_result(relevant_rows[0]) + self.assertEqual(edoniq_test_result_row.user_id, TEST_STUDENT1_USER_ID) + self.assertEqual(edoniq_test_result_row.user_points, 21.25) + self.assertEqual(edoniq_test_result_row.max_points, 29) + self.assertEqual(edoniq_test_result_row.passed, True) + self.assertEqual(edoniq_test_result_row.edoniq_sequence_id, "1688059890497") + + def test_upsert_edoniq_test_results(self): + row = EdoniqCsvTestResult( + user_id=TEST_STUDENT1_USER_ID, + edoniq_sequence_id="1688059890497", + user_points=21.25, + max_points=29, + passed=True, + ) + + _ac, created = upsert_edoniq_test_result(row) + self.assertTrue(created) + + _ac, created = upsert_edoniq_test_result(row) + self.assertFalse(created) + + ac = AssignmentCompletion.objects.get( + assignment_user_id=TEST_STUDENT1_USER_ID, + ) + + self.assertEqual(ac.evaluation_points, 21.25) + self.assertEqual(ac.evaluation_max_points, 29) + self.assertTrue(ac.evaluation_passed) diff --git a/server/vbv_lernwelt/learnpath/migrations/0007_auto_20230929_1455.py b/server/vbv_lernwelt/learnpath/migrations/0007_auto_20230929_1455.py new file mode 100644 index 00000000..15fac6c3 --- /dev/null +++ b/server/vbv_lernwelt/learnpath/migrations/0007_auto_20230929_1455.py @@ -0,0 +1,35 @@ +# Generated by Django 3.2.20 on 2023-09-29 12:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ( + "learnpath", + "0006_alter_learningcontentfeedback_can_user_self_toggle_course_completion", + ), + ] + + operations = [ + migrations.AddField( + model_name="learningcontentedoniqtest", + name="edoniq_extended_course_release_id", + field=models.CharField(blank=True, default="", max_length=255), + ), + migrations.AddField( + model_name="learningcontentedoniqtest", + name="edoniq_course_release_id", + field=models.CharField(blank=True, default="", max_length=255), + ), + migrations.AddField( + model_name="learningcontentedoniqtest", + name="edoniq_extended_sequence_id", + field=models.CharField(blank=True, default="", max_length=255), + ), + migrations.AddField( + model_name="learningcontentedoniqtest", + name="edoniq_sequence_id", + field=models.CharField(blank=True, default="", max_length=255), + ), + ] diff --git a/server/vbv_lernwelt/learnpath/migrations/0008_add_edoniq_sequence_id.py b/server/vbv_lernwelt/learnpath/migrations/0008_add_edoniq_sequence_id.py new file mode 100644 index 00000000..f84afd8f --- /dev/null +++ b/server/vbv_lernwelt/learnpath/migrations/0008_add_edoniq_sequence_id.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.20 on 2023-09-29 14:02 + +from django.db import migrations + + +def update_edoniq_ids(apps, schema_editor): + LearningContentEdoniqTest = apps.get_model("learnpath", "LearningContentEdoniqTest") + LearningContentEdoniqTest.objects.filter( + test_url__icontains="1689096523730" + ).update(edoniq_sequence_id="1688059890497") + LearningContentEdoniqTest.objects.filter( + test_url__icontains="1689096523730" + ).update( + edoniq_sequence_id="1688059890497", + edoniq_course_release_id="1689096523730", + edoniq_extended_sequence_id="1691151920116", + edoniq_extended_course_release_id="1691157696911", + ) + + +class Migration(migrations.Migration): + dependencies = [ + ("learnpath", "0007_auto_20230929_1455"), + ] + + operations = [ + migrations.RunPython(update_edoniq_ids), + ] diff --git a/server/vbv_lernwelt/learnpath/models.py b/server/vbv_lernwelt/learnpath/models.py index a3632b96..ba01ba54 100644 --- a/server/vbv_lernwelt/learnpath/models.py +++ b/server/vbv_lernwelt/learnpath/models.py @@ -342,10 +342,26 @@ class LearningContentEdoniqTest(LearningContent): test_url = models.TextField(blank=True) extended_time_test_url = models.TextField(blank=True) + # Sequenz ID von Edoniq + edoniq_sequence_id = models.CharField(max_length=255, blank=True, default="") + edoniq_extended_sequence_id = models.CharField( + max_length=255, blank=True, default="" + ) + + # Kursfreigaben ID von Edoniq + edoniq_course_release_id = models.CharField(max_length=255, blank=True, default="") + edoniq_extended_course_release_id = models.CharField( + max_length=255, blank=True, default="" + ) + content_panels = LearningContent.content_panels + [ FieldPanel("checkbox_text", classname="Text"), FieldPanel("test_url", classname="Text"), + FieldPanel("edoniq_course_release_id", classname="Text"), + FieldPanel("edoniq_sequence_id", classname="Text"), FieldPanel("extended_time_test_url", classname="Text"), + FieldPanel("edoniq_extended_course_release_id", classname="Text"), + FieldPanel("edoniq_extended_sequence_id", classname="Text"), PageChooserPanel("content_assignment", "assignment.Assignment"), ]