diff --git a/client/src/pages/onboarding/vv/CheckoutAddress.vue b/client/src/pages/onboarding/vv/CheckoutAddress.vue index 76e5a6ed..ba309415 100644 --- a/client/src/pages/onboarding/vv/CheckoutAddress.vue +++ b/client/src/pages/onboarding/vv/CheckoutAddress.vue @@ -265,11 +265,10 @@ const executePayment = async () => {

{{ - $t("a.Fehler bei der Zahlung. Bitte versuche es erneut oder kontaktiere uns") - }}: - - vermittler@vbv-afa.ch - + $t( + "a.Fehler bei der Zahlung. Bitte versuche es erneut oder wähle eine andere Zahlungsmethode." + ) + }}

{{ $t("a.Adresse") }}

diff --git a/server/config/settings/base.py b/server/config/settings/base.py index 031cb063..de7744aa 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/competence/services.py b/server/vbv_lernwelt/competence/services.py index 0f5cf691..a04bedc6 100644 --- a/server/vbv_lernwelt/competence/services.py +++ b/server/vbv_lernwelt/competence/services.py @@ -18,6 +18,11 @@ def query_competence_course_session_assignments(course_session_ids, circle_ids=N ], learning_content__content_assignment__competence_certificate__isnull=False, learning_content__live=True, + ).select_related( + "submission_deadline", + "learning_content", + "course_session", + "learning_content__content_assignment", ): if circle_ids and csa.learning_content.get_circle().id not in circle_ids: continue @@ -36,6 +41,11 @@ def query_competence_course_session_edoniq_tests(course_session_ids, circle_ids= course_session_id__in=course_session_ids, learning_content__content_assignment__competence_certificate__isnull=False, learning_content__live=True, + ).select_related( + "deadline", + "learning_content", + "course_session", + "learning_content__content_assignment", ): if circle_ids and cset.learning_content.get_circle().id not in circle_ids: continue diff --git a/server/vbv_lernwelt/dashboard/graphql/types/assignment.py b/server/vbv_lernwelt/dashboard/graphql/types/assignment.py index f2d36caa..1c614e72 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 @@ -110,15 +110,23 @@ def get_assignment_completion_metrics( assignment: vbv_lernwelt.assignment.models.Assignment, user_selection_ids: List[str] | None, urql_id_postfix: str = "", + context=None, ) -> AssignmentCompletionMetricsType: + if not context: + context = {} + csu_qs = CourseSessionUser.objects.filter( course_session_id=course_session.id, role=CourseSessionUser.Role.MEMBER ) - if user_selection_ids: - csu_qs = csu_qs.filter(user_id__in=user_selection_ids) + key = f"CourseSessionUsers_{course_session.id}" + if not key in context: + if user_selection_ids: + csu_qs = csu_qs.filter(user_id__in=user_selection_ids) - course_session_users = csu_qs.values_list("user", flat=True) + context[key] = list(csu_qs.values_list("user", flat=True)) + + course_session_users = context[key] assignment_completions = AssignmentCompletion.objects.filter( completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value, @@ -166,37 +174,59 @@ def create_record( course_session_assignment: CourseSessionAssignment | CourseSessionEdoniqTest, user_selection_ids: List[str] | None, urql_id_postfix: str = "", -) -> AssignmentStatisticsRecordType: - if isinstance(course_session_assignment, CourseSessionAssignment): - due_date = course_session_assignment.submission_deadline - else: - due_date = course_session_assignment.deadline + context=None, +) -> Tuple[AssignmentStatisticsRecordType, dict]: + if not context: + context = {} + + assignment_type = ( + "CourseSessionAssignment" + if isinstance(course_session_assignment, CourseSessionAssignment) + else "CourseSessionEdoniqTest" + ) + due_date = ( + course_session_assignment.submission_deadline + if assignment_type == "CourseSessionAssignment" + else course_session_assignment.deadline + ) + + key = f"{assignment_type}_{course_session_assignment.learning_content.id}" + + if not key in context: + context[key] = course_session_assignment.learning_content.get_circle().id + + circle_id = context[key] learning_content = course_session_assignment.learning_content competence_certificate = learning_content.content_assignment.competence_certificate - 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 - course_session_title=course_session_assignment.course_session.title, # noqa - circle_id=learning_content.get_circle().id, # noqa - course_session_assignment_id=str(course_session_assignment.id), # noqa - generation=course_session_assignment.course_session.generation, # noqa - region=course_session_assignment.course_session.region, # noqa - assignment_type_translation_key=due_date.assignment_type_translation_key, # noqa - competence_certificate_id=str(competence_certificate.id), # noqa - competence_certificate_title=competence_certificate.title, # noqa - assignment_title=learning_content.content_assignment.title, # noqa - learning_content_id=str(learning_content.id), # 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 + 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 + course_session_title=course_session_assignment.course_session.title, # noqa + circle_id=circle_id, # noqa + course_session_assignment_id=str(course_session_assignment.id), # noqa + generation=course_session_assignment.course_session.generation, # noqa + region=course_session_assignment.course_session.region, # noqa + assignment_type_translation_key=due_date.assignment_type_translation_key, # noqa + competence_certificate_id=str(competence_certificate.id), # noqa + competence_certificate_title=competence_certificate.title, # noqa + assignment_title=learning_content.content_assignment.title, # noqa + learning_content_id=str(learning_content.id), # 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, ) @@ -209,28 +239,26 @@ def assignments( ) -> AssignmentsStatisticsType: if urql_id is None: urql_id = str(course_id) - course_sessions = CourseSession.objects.filter( id__in=course_session_selection_ids, ) records: List[AssignmentStatisticsRecordType] = [] + context = {} - for course_session in course_sessions: - for csa in query_competence_course_session_assignments( - [course_session.id], circle_ids - ): - record = create_record(csa, user_selection_ids, urql_id_postfix=urql_id) - records.append(record) + csas = query_competence_course_session_assignments(course_sessions, circle_ids) + csets = query_competence_course_session_edoniq_tests(course_sessions, circle_ids) - for cset in query_competence_course_session_edoniq_tests( - [course_session.id], circle_ids - ): - record = create_record( - course_session_assignment=cset, - user_selection_ids=user_selection_ids, - urql_id_postfix=urql_id, - ) - records.append(record) + for csa in csas: + record, context = create_record( + csa, user_selection_ids, urql_id_postfix=urql_id, context=context + ) + records.append(record) + + for cset in csets: + record, context = create_record( + cset, user_selection_ids, urql_id_postfix=urql_id, context=context + ) + records.append(record) return AssignmentsStatisticsType( _id=urql_id, # noqa diff --git a/server/vbv_lernwelt/dashboard/graphql/types/competence.py b/server/vbv_lernwelt/dashboard/graphql/types/competence.py index 4df3984e..2265ab5a 100644 --- a/server/vbv_lernwelt/dashboard/graphql/types/competence.py +++ b/server/vbv_lernwelt/dashboard/graphql/types/competence.py @@ -42,25 +42,23 @@ def competences( completions = CourseCompletion.objects.filter( course_session_id__in=course_session_selection_ids, page_type="competence.PerformanceCriteria", - ).prefetch_related("course_session", "page") + ).select_related("course_session", "page") if user_selection_ids is not None: completions = completions.filter(user_id__in=user_selection_ids) - competence_records = {} + page_ids = completions.values_list("page_id", flat=True).distinct() + pages = Page.objects.filter(id__in=page_ids).specific() + learning_units = {page.id: page.specific.learning_unit for page in pages} - unique_page_ids = {completion.page.id for completion in completions} - learning_units = { - page_id: Page.objects.get(id=page_id).specific.learning_unit - for page_id in unique_page_ids - } circles = { lu.id: c for lu in learning_units.values() - if (lu is not None and (c := lu.get_circle()) is not None) - and (circle_ids is None or c.id in circle_ids) + if lu and (c := lu.get_circle()) and (circle_ids is None or c.id in circle_ids) } + competence_records = {} + for completion in completions: learning_unit = learning_units.get(completion.page.id) diff --git a/server/vbv_lernwelt/dashboard/views.py b/server/vbv_lernwelt/dashboard/views.py index 95c85a7b..d17b7300 100644 --- a/server/vbv_lernwelt/dashboard/views.py +++ b/server/vbv_lernwelt/dashboard/views.py @@ -188,7 +188,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( @@ -250,7 +250,6 @@ def _create_person_list_with_roles(user): result_persons[mentor_relation.agent.id]["course_sessions"].append( course_session_entry ) - return result_persons.values() @@ -318,45 +317,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 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