From 47896444a6a5f71bf726e481675fe9f66eda88bd Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 4 Jul 2024 17:38:28 +0200 Subject: [PATCH 01/42] Make `ItDatePicker` component --- .../components/onboarding/PersonalAddress.vue | 38 +----------- .../personalProfile/ProfileEdit.vue | 4 ++ client/src/components/ui/ItDatePicker.vue | 59 +++++++++++++++++++ 3 files changed, 65 insertions(+), 36 deletions(-) create mode 100644 client/src/components/ui/ItDatePicker.vue diff --git a/client/src/components/onboarding/PersonalAddress.vue b/client/src/components/onboarding/PersonalAddress.vue index e40dd405..c4fcbeea 100644 --- a/client/src/components/onboarding/PersonalAddress.vue +++ b/client/src/components/onboarding/PersonalAddress.vue @@ -1,10 +1,9 @@ + + + + From d56c346512fb33f889496f6c5df9b678a6de8381 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 16 Jul 2024 10:58:58 +0200 Subject: [PATCH 02/42] Add more editable profile fields --- .../personalProfile/ProfileEdit.vue | 48 +++++++++- .../personalProfile/ProfileView.vue | 95 ++++++++++++------- client/src/components/ui/ItDatePicker.vue | 1 + client/src/pages/StyleGuidePage.vue | 9 ++ server/vbv_lernwelt/core/models.py | 3 +- 5 files changed, 121 insertions(+), 35 deletions(-) diff --git a/client/src/components/personalProfile/ProfileEdit.vue b/client/src/components/personalProfile/ProfileEdit.vue index 6a928e1b..e668857a 100644 --- a/client/src/components/personalProfile/ProfileEdit.vue +++ b/client/src/components/personalProfile/ProfileEdit.vue @@ -3,6 +3,8 @@ import { useEntities } from "@/services/entities"; import AvatarImage from "@/components/ui/AvatarImage.vue"; import { ref } from "vue"; import { type User, useUserStore } from "@/stores/user"; +import ItDatePicker from "@/components/ui/ItDatePicker.vue"; +import { normalizeSwissPhoneNumber } from "@/utils/phone"; const emit = defineEmits(["cancel", "save"]); @@ -26,6 +28,7 @@ const formData = ref({ birth_date: user.birth_date, organisation: user.organisation, + organisation_detail_name: user.organisation_detail_name, organisation_street: user.organisation_street, organisation_street_number: user.organisation_street_number, organisation_postal_code: user.organisation_postal_code, @@ -35,7 +38,8 @@ const formData = ref({ }); async function save() { - const { country_code, organisation_country_code, ...profileData } = formData.value; + const { country_code, organisation_country_code, phone_number, ...profileData } = + formData.value; const typedProfileData: Partial = { ...profileData }; typedProfileData.country = countries.value.find( @@ -45,6 +49,10 @@ async function save() { (c) => c.country_code === organisation_country_code ); + if (phone_number) { + typedProfileData.phone_number = normalizeSwissPhoneNumber(phone_number); + } + await user.updateUserProfile(typedProfileData); emit("save"); } @@ -130,6 +138,28 @@ async function avatarUpload(e: Event) { disabled class="disabled:bg-gray-50 mb-4 block w-full border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 disabled:cursor-not-allowed disabled:text-gray-500 disabled:ring-gray-200 sm:max-w-sm sm:text-sm sm:leading-6" /> + + +
+ +
+ + +
+ +
+ @@ -268,6 +298,22 @@ async function avatarUpload(e: Event) { {{ $t("a.Firmenanschrift") }} +
+
+ + + +
+
+
{{ user.email }}
+ +
+ + {{ displaySwissPhoneNumber(user.phone_number) }} + + {{ $t("a.Keine Angabe") }} +
+ +
+ + {{ dayjs(user.birth_date).format("DD.MM.YYYY") }} + + {{ $t("a.Keine Angabe") }} +
- +
+ + {{ line }} +
+
+
{{ $t("a.Keine Angabe") }}
@@ -94,9 +119,13 @@ const invoiceAddress = computed(() => { {{ $t("a.Firmenanschrift") }}
- + +
+ + {{ line }} +
+
+
{{ $t("a.Keine Angabe") }}
diff --git a/client/src/components/ui/ItDatePicker.vue b/client/src/components/ui/ItDatePicker.vue index 4ded7f00..4c44f46d 100644 --- a/client/src/components/ui/ItDatePicker.vue +++ b/client/src/components/ui/ItDatePicker.vue @@ -1,6 +1,7 @@ diff --git a/client/src/pages/onboarding/vv/CheckoutAddress.vue b/client/src/pages/onboarding/vv/CheckoutAddress.vue index f8ff17ef..76e5a6ed 100644 --- a/client/src/pages/onboarding/vv/CheckoutAddress.vue +++ b/client/src/pages/onboarding/vv/CheckoutAddress.vue @@ -13,6 +13,12 @@ import DatatransCembraDeviceFingerprint from "@/components/onboarding/DatatransC import { getLocalSessionKey } from "@/statistics"; import log from "loglevel"; import { normalizeSwissPhoneNumber, validatePhoneNumber } from "@/utils/phone"; +import { + ORGANISATION_NO_COMPANY_ID, + ORGANISATION_OTHER_BROKER_ID, + ORGANISATION_OTHER_HEALTH_INSURANCE_ID, + ORGANISATION_OTHER_PRIVATE_INSURANCE_ID, +} from "@/consts"; const props = defineProps({ courseType: { @@ -31,11 +37,14 @@ const userOrganisationName = computed(() => { } // Those IDs do not represent a company - // 1: Other broker - // 2: Other insurance - // 3: Other private insurance - // 31: No company relation - if ([1, 2, 3, 31].includes(user.organisation)) { + if ( + [ + ORGANISATION_OTHER_BROKER_ID, + ORGANISATION_OTHER_HEALTH_INSURANCE_ID, + ORGANISATION_OTHER_PRIVATE_INSURANCE_ID, + ORGANISATION_NO_COMPANY_ID, + ].includes(user.organisation) + ) { return null; } diff --git a/client/src/services/onboarding.ts b/client/src/services/onboarding.ts index 24b59a48..9c59ef48 100644 --- a/client/src/services/onboarding.ts +++ b/client/src/services/onboarding.ts @@ -1,4 +1,9 @@ import { isString, startsWith } from "lodash"; +import { + ORGANISATION_OTHER_BROKER_ID, + ORGANISATION_OTHER_HEALTH_INSURANCE_ID, + ORGANISATION_OTHER_PRIVATE_INSURANCE_ID, +} from "@/consts"; export function profileNextRoute(courseType: string | string[]) { if (courseType === "uk") { @@ -10,3 +15,11 @@ export function profileNextRoute(courseType: string | string[]) { } return ""; } + +export function isOtherOrganisation(orgId: number) { + return [ + ORGANISATION_OTHER_BROKER_ID, + ORGANISATION_OTHER_HEALTH_INSURANCE_ID, + ORGANISATION_OTHER_PRIVATE_INSURANCE_ID, + ].includes(orgId); +} diff --git a/server/vbv_lernwelt/course/consts.py b/server/vbv_lernwelt/course/consts.py index a0dd7b27..a9949458 100644 --- a/server/vbv_lernwelt/course/consts.py +++ b/server/vbv_lernwelt/course/consts.py @@ -28,3 +28,10 @@ UK_COURSE_IDS = [ COURSE_UK_TRAINING_FR, COURSE_UK_TRAINING_IT, ] + + +# Organization IDs +ORGANISATION_OTHER_BROKER_ID = 1 +ORGANISATION_OTHER_HEALTH_INSURANCE_ID = 2 +ORGANISATION_OTHER_PRIVATE_INSURANCE_ID = 3 +ORGANISATION_NO_COMPANY_ID = 31 From aa30dadfd788f78acb1d866030eb3b922ddf59ef Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Tue, 16 Jul 2024 15:20:25 +0200 Subject: [PATCH 04/42] Update cypress tests --- .../personalProfile/ProfileEdit.vue | 4 +- .../personalProfile/ProfileView.vue | 23 ++-- client/src/consts.ts | 2 +- client/src/pages/StyleGuidePage.vue | 8 -- .../src/pages/onboarding/AccountProfile.vue | 2 +- .../personalProfile/PersonalProfilePage.vue | 6 +- cypress/e2e/checkout-vv/checkout.cy.js | 121 +++++++++++++----- cypress/e2e/profile/profile.cy.js | 85 ++++++++++++ cypress/support/commands.js | 11 ++ server/vbv_lernwelt/core/serializers.py | 6 + 10 files changed, 212 insertions(+), 56 deletions(-) create mode 100644 cypress/e2e/profile/profile.cy.js diff --git a/client/src/components/personalProfile/ProfileEdit.vue b/client/src/components/personalProfile/ProfileEdit.vue index e668857a..398e363a 100644 --- a/client/src/components/personalProfile/ProfileEdit.vue +++ b/client/src/components/personalProfile/ProfileEdit.vue @@ -301,7 +301,7 @@ async function avatarUpload(e: Event) {
{{ $t("general.cancel") }} -
diff --git a/client/src/components/personalProfile/ProfileView.vue b/client/src/components/personalProfile/ProfileView.vue index b41779c7..ac2e494c 100644 --- a/client/src/components/personalProfile/ProfileView.vue +++ b/client/src/components/personalProfile/ProfileView.vue @@ -71,17 +71,21 @@ const invoiceAddress = computed(() => {

{{ $t("a.Persönliche Informationen") }}

-
{{ user.first_name }}
+
+ {{ user.first_name }} +
-
{{ user.last_name }}
+
+ {{ user.last_name }} +
-
{{ user.email }}
+
{{ user.email }}
-
+
{{ displaySwissPhoneNumber(user.phone_number) }} @@ -90,7 +94,7 @@ const invoiceAddress = computed(() => { -
+
{{ dayjs(user.birth_date).format("DD.MM.YYYY") }} @@ -99,7 +103,7 @@ const invoiceAddress = computed(() => { -
+
{{ line }} @@ -114,13 +118,14 @@ const invoiceAddress = computed(() => {

{{ $t("a.Geschäftsdaten") }}

-
{{ organisationName }}
+
+ {{ organisationName }} +
- -
+
{{ line }}
diff --git a/client/src/consts.ts b/client/src/consts.ts index 25801247..656714c7 100644 --- a/client/src/consts.ts +++ b/client/src/consts.ts @@ -16,4 +16,4 @@ export const COURSE_MOTORFAHRZEUG_PRUEFUNG_ID = -13; export const ORGANISATION_OTHER_BROKER_ID = 1; export const ORGANISATION_OTHER_HEALTH_INSURANCE_ID = 2; export const ORGANISATION_OTHER_PRIVATE_INSURANCE_ID = 3; -export const ORGANISATION_NO_COMPANY_ID = 31; \ No newline at end of file +export const ORGANISATION_NO_COMPANY_ID = 31; diff --git a/client/src/pages/StyleGuidePage.vue b/client/src/pages/StyleGuidePage.vue index 26f791bb..25ff5db5 100644 --- a/client/src/pages/StyleGuidePage.vue +++ b/client/src/pages/StyleGuidePage.vue @@ -13,7 +13,6 @@ import VerticalBarChart from "@/components/ui/VerticalBarChart.vue"; import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPathCircle.vue"; import logger from "loglevel"; import { reactive, ref } from "vue"; -import VueDatePicker from "@vuepic/vue-datepicker"; import "@vuepic/vue-datepicker/dist/main.css"; const state = reactive({ @@ -36,8 +35,6 @@ const state = reactive({ }, }); -const birtDate = ref("1982-06-15"); - const dropdownData = [ { title: "Option 1", @@ -415,11 +412,6 @@ function log(data: any) { > {{ state.dropdownSelected }} -

Date Picker

-
- -
-

Checkbox

-
+
+ +
diff --git a/client/src/components/learningPath/LearningPathDiagram.vue b/client/src/components/learningPath/LearningPathDiagram.vue index ce0c098e..e91aae0c 100644 --- a/client/src/components/learningPath/LearningPathDiagram.vue +++ b/client/src/components/learningPath/LearningPathDiagram.vue @@ -3,6 +3,7 @@ import LearningPathCircle from "@/pages/learningPath/learningPathPage/LearningPa import { calculateCircleSectorData } from "@/pages/learningPath/learningPathPage/utils"; import { computed } from "vue"; import { useCourseCircleProgress, useCourseDataWithCompletion } from "@/composables"; +import LoadingSpinner from "@/components/ui/LoadingSpinner.vue"; export type DiagramType = "horizontal" | "horizontalSmall" | "singleSmall"; @@ -54,23 +55,25 @@ const { inProgressCirclesCount, circlesCount } = useCourseCircleProgress( From 4d4e202a241c0184fb1ac45f65e275f578b2989a Mon Sep 17 00:00:00 2001 From: Christian Cueni Date: Wed, 24 Jul 2024 09:45:02 +0200 Subject: [PATCH 19/42] Preselect user --- server/vbv_lernwelt/dashboard/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/vbv_lernwelt/dashboard/views.py b/server/vbv_lernwelt/dashboard/views.py index 1cdf533a..e8266145 100644 --- a/server/vbv_lernwelt/dashboard/views.py +++ b/server/vbv_lernwelt/dashboard/views.py @@ -183,7 +183,7 @@ def _create_person_list_with_roles(user): if has_cs_role(cs.roles) and cs.course.configuration.is_uk: course_session_users = CourseSessionUser.objects.filter( course_session=cs.id - ) + ).select_related("user") my_role = user_role(cs.roles) for csu in course_session_users: person_data = result_persons.get( @@ -239,7 +239,6 @@ def _create_person_list_with_roles(user): result_persons[mentor_relation.mentor.id]["course_sessions"].append( course_session_entry ) - return result_persons.values() From c1e1f38a274cd2eea07ebcfde955847733363383 Mon Sep 17 00:00:00 2001 From: Christian Cueni Date: Wed, 24 Jul 2024 11:18:19 +0200 Subject: [PATCH 20/42] Optimize duedates in dashboards --- server/vbv_lernwelt/dashboard/views.py | 41 ++++++++------------------ 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/server/vbv_lernwelt/dashboard/views.py b/server/vbv_lernwelt/dashboard/views.py index e8266145..9d20bab7 100644 --- a/server/vbv_lernwelt/dashboard/views.py +++ b/server/vbv_lernwelt/dashboard/views.py @@ -306,45 +306,28 @@ def get_dashboard_due_dates(request): course_sessions = get_course_sessions_with_roles_for_user(request.user) course_session_ids = [cs.id for cs in course_sessions] - all_due_dates = DueDate.objects.filter( - course_session__id__in=course_session_ids - ) - - # filter only future due dates - due_dates = [] today = date.today() - for due_date in all_due_dates: - # due_dates.append(due_date) - if due_date.end: - if due_date.end.date() >= today: - due_dates.append(due_date) - elif due_date.start: - if due_date.start.date() >= today: - due_dates.append(due_date) - due_dates.sort(key=lambda x: x.start) - - # find course session by id in `course_sessions` + # Fetch future due dates in a single query using Q objects for complex filtering + future_due_dates = DueDate.objects.filter( + Q(course_session_id__in=course_session_ids), + Q(end__gte=today) | Q(start__gte=today) + ).select_related('course_session') result_due_dates = [] - for due_date in due_dates: - data = DueDateSerializer(due_date).data + course_session_map = {cs.id: cs for cs in course_sessions} + + for due_date in sorted(future_due_dates, key=lambda x: x.start): + data = DueDateSerializer(due_date).data + cs = course_session_map.get(due_date.course_session_id) - cs = next( - course_session - for course_session in course_sessions - if course_session.id == due_date.course_session.id - ) if cs: data["course_session"] = _create_course_session_dict( cs, my_role=user_role(cs.roles), user_role="" ) result_due_dates.append(data) - return Response( - status=200, - data=result_due_dates, - ) + return Response(status=200, data=result_due_dates) except PermissionDenied as e: raise e @@ -625,7 +608,7 @@ def _get_course_sessions_with_roles_for_user( csr for csr in get_course_sessions_with_roles_for_user(user) if any(role in allowed_roles for role in csr.roles) - and csr.id in requested_cs_ids + and csr.id in requested_cs_ids ] # noqa return all_cs_roles_for_user From 58908bc5c687c60a900813cfee8ff794cba768dc Mon Sep 17 00:00:00 2001 From: Christian Cueni Date: Wed, 24 Jul 2024 11:37:03 +0200 Subject: [PATCH 21/42] Fix context --- .../dashboard/graphql/types/assignment.py | 50 ++++++++++--------- .../dashboard/graphql/types/competence.py | 3 -- server/vbv_lernwelt/dashboard/views.py | 6 +-- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/server/vbv_lernwelt/dashboard/graphql/types/assignment.py b/server/vbv_lernwelt/dashboard/graphql/types/assignment.py index ba26e3c6..32b91849 100644 --- a/server/vbv_lernwelt/dashboard/graphql/types/assignment.py +++ b/server/vbv_lernwelt/dashboard/graphql/types/assignment.py @@ -1,5 +1,5 @@ import math -from typing import List +from typing import List, Tuple import graphene @@ -148,7 +148,7 @@ def create_record( user_selection_ids: List[str] | None, urql_id_postfix: str = "", context=None, -) -> AssignmentStatisticsRecordType: +) -> Tuple[AssignmentStatisticsRecordType, dict]: if not context: context = {} @@ -172,26 +172,29 @@ def create_record( learning_content = course_session_assignment.learning_content - return AssignmentStatisticsRecordType( - # make sure it's unique, across all types of assignments! - _id=f"{course_session_assignment._meta.model_name}#{course_session_assignment.id}@{urql_id_postfix}", - # noqa - course_session_id=str(course_session_assignment.course_session.id), # noqa - circle_id=circle_id, # noqa - course_session_assignment_id=str(course_session_assignment.id), # noqa - generation=course_session_assignment.course_session.generation, # noqa - assignment_type_translation_key=due_date.assignment_type_translation_key, - # noqa - assignment_title=learning_content.content_assignment.title, # noqa - metrics=get_assignment_completion_metrics( # noqa - course_session=course_session_assignment.course_session, # noqa - assignment=learning_content.content_assignment, # noqa - user_selection_ids=user_selection_ids, # noqa - urql_id_postfix=urql_id_postfix, # noqa - context=context, # noqa + return ( + AssignmentStatisticsRecordType( + # make sure it's unique, across all types of assignments! + _id=f"{course_session_assignment._meta.model_name}#{course_session_assignment.id}@{urql_id_postfix}", + # noqa + course_session_id=str(course_session_assignment.course_session.id), # noqa + circle_id=circle_id, # noqa + course_session_assignment_id=str(course_session_assignment.id), # noqa + generation=course_session_assignment.course_session.generation, # noqa + assignment_type_translation_key=due_date.assignment_type_translation_key, + # noqa + assignment_title=learning_content.content_assignment.title, # noqa + metrics=get_assignment_completion_metrics( # noqa + course_session=course_session_assignment.course_session, # noqa + assignment=learning_content.content_assignment, # noqa + user_selection_ids=user_selection_ids, # noqa + urql_id_postfix=urql_id_postfix, # noqa + context=context, # noqa + ), + details_url=due_date.url_expert, # noqa + deadline=due_date.start, # noqa ), - details_url=due_date.url_expert, # noqa - deadline=due_date.start, # noqa + context, ) @@ -204,7 +207,6 @@ def assignments( ) -> AssignmentsStatisticsType: if urql_id is None: urql_id = str(course_id) - course_sessions = CourseSession.objects.filter( id__in=course_session_selection_ids, ) @@ -215,13 +217,13 @@ def assignments( csets = query_competence_course_session_edoniq_tests(course_sessions, circle_ids) for csa in csas: - record = create_record( + record, context = create_record( csa, user_selection_ids, urql_id_postfix=urql_id, context=context ) records.append(record) for cset in csets: - record = create_record( + record, context = create_record( cset, user_selection_ids, urql_id_postfix=urql_id, context=context ) records.append(record) diff --git a/server/vbv_lernwelt/dashboard/graphql/types/competence.py b/server/vbv_lernwelt/dashboard/graphql/types/competence.py index dc00f167..2cf5d96f 100644 --- a/server/vbv_lernwelt/dashboard/graphql/types/competence.py +++ b/server/vbv_lernwelt/dashboard/graphql/types/competence.py @@ -1,13 +1,10 @@ from typing import List, Tuple import graphene -import structlog from wagtail.models import Page from vbv_lernwelt.course.models import CourseCompletion, CourseCompletionStatus -logger = structlog.get_logger(__name__) - class CompetencePerformanceStatisticsSummaryType(graphene.ObjectType): _id = graphene.ID(required=True) diff --git a/server/vbv_lernwelt/dashboard/views.py b/server/vbv_lernwelt/dashboard/views.py index 9d20bab7..2e64c804 100644 --- a/server/vbv_lernwelt/dashboard/views.py +++ b/server/vbv_lernwelt/dashboard/views.py @@ -311,8 +311,8 @@ def get_dashboard_due_dates(request): # Fetch future due dates in a single query using Q objects for complex filtering future_due_dates = DueDate.objects.filter( Q(course_session_id__in=course_session_ids), - Q(end__gte=today) | Q(start__gte=today) - ).select_related('course_session') + Q(end__gte=today) | Q(start__gte=today), + ).select_related("course_session") result_due_dates = [] course_session_map = {cs.id: cs for cs in course_sessions} @@ -608,7 +608,7 @@ def _get_course_sessions_with_roles_for_user( csr for csr in get_course_sessions_with_roles_for_user(user) if any(role in allowed_roles for role in csr.roles) - and csr.id in requested_cs_ids + and csr.id in requested_cs_ids ] # noqa return all_cs_roles_for_user From fe2b91e619be268095ea9d42edb0fc196b63fc30 Mon Sep 17 00:00:00 2001 From: Christian Cueni Date: Wed, 24 Jul 2024 14:00:29 +0200 Subject: [PATCH 22/42] Add debug middleware --- server/config/settings/base.py | 1 + server/vbv_lernwelt/debugtools/middleware.py | 38 ++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 server/vbv_lernwelt/debugtools/middleware.py diff --git a/server/config/settings/base.py b/server/config/settings/base.py index a4fda6fd..8251e138 100644 --- a/server/config/settings/base.py +++ b/server/config/settings/base.py @@ -201,6 +201,7 @@ MIDDLEWARE = [ "vbv_lernwelt.core.middleware.security.SecurityRequestResponseLoggingMiddleware", "wagtail.contrib.redirects.middleware.RedirectMiddleware", "vbv_lernwelt.core.middleware.auth.UserLoggedInCookieMiddleWare", + # "vbv_lernwelt.debugtools.middleware.QueryCountDebugMiddleware", ] # STATIC diff --git a/server/vbv_lernwelt/debugtools/middleware.py b/server/vbv_lernwelt/debugtools/middleware.py new file mode 100644 index 00000000..fea26453 --- /dev/null +++ b/server/vbv_lernwelt/debugtools/middleware.py @@ -0,0 +1,38 @@ +import time + +import structlog +from django.db import connection, reset_queries + +logger = structlog.get_logger(__name__) + + +class QueryCountDebugMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + if not request.path.startswith("/api/") and not request.path.startswith( + "/server/" + ): + return self.get_response(request) + + reset_queries() + start_queries = len(connection.queries) + start_time = time.time() + + response = self.get_response(request) + + end_queries = len(connection.queries) + end_time = time.time() + + total_queries = end_queries - start_queries + duration = end_time - start_time + + logger.debug( + "query_count_middleware", + request_path=request.path, + queries=total_queries, + duration=duration, + ) + + return response From d750a19d24308159e19db58645a09c07f036e0c3 Mon Sep 17 00:00:00 2001 From: Christian Cueni Date: Thu, 25 Jul 2024 08:44:10 +0200 Subject: [PATCH 23/42] Add migration and admin field --- server/vbv_lernwelt/course/admin.py | 4 ++- ...essionuser_required_attendance_and_more.py | 32 +++++++++++++++++++ server/vbv_lernwelt/course/models.py | 1 + 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 server/vbv_lernwelt/course/migrations/0009_coursesessionuser_required_attendance_and_more.py diff --git a/server/vbv_lernwelt/course/admin.py b/server/vbv_lernwelt/course/admin.py index 4a4741f3..8ec26bb9 100644 --- a/server/vbv_lernwelt/course/admin.py +++ b/server/vbv_lernwelt/course/admin.py @@ -64,6 +64,7 @@ class CourseSessionUserAdmin(admin.ModelAdmin): "course_session", "role", "circles", + "optional_attendance", # "created_at", # "updated_at", ] @@ -76,6 +77,7 @@ class CourseSessionUserAdmin(admin.ModelAdmin): list_filter = [ "role", "course_session", + "optional_attendance", ] raw_id_fields = [ "user", @@ -97,7 +99,7 @@ class CourseSessionUserAdmin(admin.ModelAdmin): return ", ".join([c.title for c in obj.expert.all()]) fieldsets = [ - (None, {"fields": ("user", "course_session", "role")}), + (None, {"fields": ("user", "course_session", "role", "optional_attendance")}), ( "Expert/Trainer", { diff --git a/server/vbv_lernwelt/course/migrations/0009_coursesessionuser_required_attendance_and_more.py b/server/vbv_lernwelt/course/migrations/0009_coursesessionuser_required_attendance_and_more.py new file mode 100644 index 00000000..3c7fe2f8 --- /dev/null +++ b/server/vbv_lernwelt/course/migrations/0009_coursesessionuser_required_attendance_and_more.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.13 on 2024-07-25 05:47 + +from django.db import migrations, models + +import vbv_lernwelt.course.models + + +class Migration(migrations.Migration): + dependencies = [ + ("course", "0008_auto_20240403_1132"), + ] + + operations = [ + migrations.AddField( + model_name="coursesessionuser", + name="optional_attendance", + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name="coursecompletion", + name="completion_status", + field=models.CharField( + choices=[ + ("SUCCESS", "Success"), + ("FAIL", "Fail"), + ("UNKNOWN", "Unknown"), + ], + default=vbv_lernwelt.course.models.CourseCompletionStatus["UNKNOWN"], + max_length=255, + ), + ), + ] diff --git a/server/vbv_lernwelt/course/models.py b/server/vbv_lernwelt/course/models.py index d8b6997a..d85c6a9a 100644 --- a/server/vbv_lernwelt/course/models.py +++ b/server/vbv_lernwelt/course/models.py @@ -283,6 +283,7 @@ class CourseSessionUser(models.Model): expert = models.ManyToManyField( "learnpath.Circle", related_name="expert", blank=True ) + optional_attendance = models.BooleanField(default=False) class Meta: constraints = [ From dec5691d8f7143e83784b44065dc7e39d3394f13 Mon Sep 17 00:00:00 2001 From: Christian Cueni Date: Thu, 25 Jul 2024 09:52:15 +0200 Subject: [PATCH 24/42] Add info to attendance page --- client/src/components/ui/ItPersonRow.vue | 16 ++++++++++++---- client/src/gql/gql.ts | 4 ++-- client/src/gql/graphql.ts | 5 +++-- client/src/gql/schema.graphql | 1 + client/src/graphql/queries.ts | 1 + .../attendanceCheckPage/AttendanceCheckPage.vue | 3 +++ server/vbv_lernwelt/course/graphql/types.py | 2 ++ 7 files changed, 24 insertions(+), 8 deletions(-) diff --git a/client/src/components/ui/ItPersonRow.vue b/client/src/components/ui/ItPersonRow.vue index 98f2792b..addb6356 100644 --- a/client/src/components/ui/ItPersonRow.vue +++ b/client/src/components/ui/ItPersonRow.vue @@ -1,18 +1,26 @@