Add courseSessionId query param to url and switch to it

This commit is contained in:
Daniel Egger 2023-10-04 15:41:46 +02:00
parent 8b4b00170a
commit 6048129507
13 changed files with 137 additions and 66 deletions

View File

@ -3,10 +3,12 @@ import type { CourseSession, DueDate } from "@/types";
import { useCourseSessionsStore } from "@/stores/courseSessions"; import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useTranslation } from "i18next-vue"; import { useTranslation } from "i18next-vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { computed } from "vue";
const props = defineProps<{ const props = defineProps<{
dueDate: DueDate; dueDate: DueDate;
singleLine?: boolean; singleLine?: boolean;
showCourseSession?: boolean;
}>(); }>();
const { t } = useTranslation(); const { t } = useTranslation();
@ -24,6 +26,16 @@ if (!courseSession) {
const isExpert = courseSessionsStore.hasCockpit(courseSession); const isExpert = courseSessionsStore.hasCockpit(courseSession);
const url = isExpert ? props.dueDate.url_expert : props.dueDate.url; const url = isExpert ? props.dueDate.url_expert : props.dueDate.url;
const courseSessionTitle = computed(() => {
if (props.dueDate.course_session) {
return (
courseSessionsStore.getCourseSessionById(props.dueDate.course_session)?.title ??
""
);
}
return "";
});
</script> </script>
<template> <template>
@ -54,7 +66,12 @@ const url = isExpert ? props.dueDate.url_expert : props.dueDate.url;
</a> </a>
</div> </div>
<div class="text-small text-gray-900"> <div class="text-small text-gray-900">
<div>{{ $t("a.Circle") }} «{{ props.dueDate.circle?.title }}»</div> <div>
<span v-if="props.showCourseSession ?? courseSessionTitle">
{{ courseSessionTitle }}:
</span>
{{ $t("a.Circle") }} «{{ props.dueDate.circle?.title }}»
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -9,6 +9,7 @@ const props = defineProps<{
showTopBorder: boolean; showTopBorder: boolean;
showBottomBorder: boolean; showBottomBorder: boolean;
showAllDueDatesLink: boolean; showAllDueDatesLink: boolean;
showCourseSession: boolean;
}>(); }>();
const allDueDates = computed(() => { const allDueDates = computed(() => {
@ -28,7 +29,10 @@ const dueDatesDisplayed = computed(() => {
:key="dueDate.id" :key="dueDate.id"
:class="{ 'first:border-t': props.showTopBorder, 'border-b': true }" :class="{ 'first:border-t': props.showTopBorder, 'border-b': true }"
> >
<DueDateSingle :due-date="dueDate"></DueDateSingle> <DueDateSingle
:due-date="dueDate"
:show-course-session="props.showCourseSession"
></DueDateSingle>
</li> </li>
</ul> </ul>
<div v-if="allDueDates.length === 0">{{ $t("dueDates.noDueDatesAvailable") }}</div> <div v-if="allDueDates.length === 0">{{ $t("dueDates.noDueDatesAvailable") }}</div>

View File

@ -6,6 +6,7 @@
:show-top-border="props.showTopBorder" :show-top-border="props.showTopBorder"
show-all-due-dates-link show-all-due-dates-link
show-bottom-border show-bottom-border
:show-course-session="false"
></DueDatesList> ></DueDatesList>
</div> </div>
</template> </template>

View File

@ -18,7 +18,7 @@ const logout = () => {
userStore.handleLogout(); userStore.handleLogout();
}; };
const selectCourseSession = (courseSession: CourseSession) => { const selectCourseSession = (courseSession: CourseSession) => {
courseSessionsStore.switchCourseSession(courseSession); courseSessionsStore.switchCourseSessionById(courseSession.id);
}; };
const courseSessionsStore = useCourseSessionsStore(); const courseSessionsStore = useCourseSessionsStore();

View File

@ -138,6 +138,7 @@ async function loadAdditionalAppointments() {
:show-all-due-dates-link="false" :show-all-due-dates-link="false"
:max-count="numAppointmentsToShow" :max-count="numAppointmentsToShow"
data-cy="appointments-list" data-cy="appointments-list"
:show-course-session="true"
/> />
<button <button
v-if="canLoadMore" v-if="canLoadMore"

View File

@ -73,6 +73,7 @@ const getNextStepLink = (courseSession: CourseSession) => {
:show-top-border="false" :show-top-border="false"
:show-all-due-dates-link="true" :show-all-due-dates-link="true"
:show-bottom-border="true" :show-bottom-border="true"
:show-course-session="true"
></DueDatesList> ></DueDatesList>
</div> </div>
</div> </div>

View File

@ -17,7 +17,9 @@ export const redirectToLoginIfRequired: NavigationGuard = (to) => {
if (loginRequired(to) && !userStore.loggedIn) { if (loginRequired(to) && !userStore.loggedIn) {
const appEnv = import.meta.env.VITE_APP_ENVIRONMENT || "local"; const appEnv = import.meta.env.VITE_APP_ENVIRONMENT || "local";
const ssoLogin = appEnv.startsWith("prod") || appEnv.startsWith("stage"); const ssoLogin = appEnv.startsWith("prod") || appEnv.startsWith("stage");
return ssoLogin ? `/login?next=${to.fullPath}` : `/login-local?next=${to.fullPath}`; return ssoLogin
? `/login?next=${encodeURIComponent(to.fullPath)}`
: `/login-local?next=${encodeURIComponent(to.fullPath)}`;
} }
}; };
@ -46,15 +48,52 @@ export const expertRequired: NavigationGuard = (to: RouteLocationNormalized) =>
} }
}; };
export async function handleCourseSessions(to: RouteLocationNormalized) { export async function handleCurrentCourseSession(to: RouteLocationNormalized) {
// register after login hooks // register after login hooks
const courseSessionsStore = useCourseSessionsStore(); const userStore = useUserStore();
if (to.params.courseSlug) { if (userStore.loggedIn) {
courseSessionsStore._currentCourseSlug = to.params.courseSlug as string; const courseSessionsStore = useCourseSessionsStore();
} else { if (to.params.courseSlug) {
courseSessionsStore._currentCourseSlug = ""; courseSessionsStore._currentCourseSlug = to.params.courseSlug as string;
} } else {
if (!courseSessionsStore.loaded) { courseSessionsStore._currentCourseSlug = "";
await courseSessionsStore.loadCourseSessionsData(); }
if (!courseSessionsStore.loaded) {
await courseSessionsStore.loadCourseSessionsData();
}
}
}
export async function handleCourseSessionAsQueryParam(to: RouteLocationNormalized) {
/**
* switch to course session with id from query param `courseSessionId` if it
* is present and valid.
*/
// register after login hooks
const userStore = useUserStore();
if (userStore.loggedIn) {
const courseSessionsStore = useCourseSessionsStore();
if (!courseSessionsStore.loaded) {
await courseSessionsStore.loadCourseSessionsData();
}
if (to.query.courseSessionId) {
const { courseSessionId, ...restOfQuery } = to.query;
const switchSuccessful = courseSessionsStore.switchCourseSessionById(
courseSessionId.toString()
);
if (switchSuccessful) {
return {
path: to.path,
query: restOfQuery,
replace: true,
};
} else {
// courseSessionId is invalid for current user -> redirect to home
return {
path: "/",
};
}
}
} }
} }

View File

@ -1,7 +1,8 @@
import DashboardPage from "@/pages/DashboardPage.vue"; import DashboardPage from "@/pages/DashboardPage.vue";
import LoginPage from "@/pages/LoginPage.vue"; import LoginPage from "@/pages/LoginPage.vue";
import { import {
handleCourseSessions, handleCourseSessionAsQueryParam,
handleCurrentCourseSession,
redirectToLoginIfRequired, redirectToLoginIfRequired,
updateLoggedIn, updateLoggedIn,
} from "@/router/guards"; } from "@/router/guards";
@ -214,7 +215,8 @@ router.beforeEach(updateLoggedIn);
router.beforeEach(redirectToLoginIfRequired); router.beforeEach(redirectToLoginIfRequired);
// register after login hooks // register after login hooks
router.beforeEach(handleCourseSessions); router.beforeEach(handleCurrentCourseSession);
router.beforeEach(handleCourseSessionAsQueryParam);
router.beforeEach(addToHistory); router.beforeEach(addToHistory);

View File

@ -91,13 +91,31 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
return undefined; return undefined;
} }
function switchCourseSession(courseSession: CourseSession) { function _switchCourseSession(courseSession: CourseSession) {
log.debug("switchCourseSession", courseSession); log.debug("switchCourseSession", courseSession);
selectedCourseSessionMap.value.set(courseSession.course.slug, courseSession.id); selectedCourseSessionMap.value.set(courseSession.course.slug, courseSession.id);
// Emit event so that the App can re-render with the new courseSession // Emit event so that the App can re-render with the new courseSession
eventBus.emit("switchedCourseSession", courseSession.id); eventBus.emit("switchedCourseSession", courseSession.id);
} }
function getCourseSessionById(courseSessionId: number | string) {
return allCourseSessions.value.find((cs) => {
return courseSessionId.toString() === cs.id.toString();
});
}
function switchCourseSessionById(courseSessionId: number | string) {
const courseSession = allCourseSessions.value.find((cs) => {
return courseSessionId.toString() === cs.id.toString();
});
if (courseSession) {
_switchCourseSession(courseSession);
return true;
} else {
return false;
}
}
function courseSessionForCourse(courseSlug: string) { function courseSessionForCourse(courseSlug: string) {
if (courseSlug) { if (courseSlug) {
const courseSession = selectedCourseSessionForCourse(courseSlug); const courseSession = selectedCourseSessionForCourse(courseSlug);
@ -268,7 +286,8 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
uniqueCourseSessionsByCourse, uniqueCourseSessionsByCourse,
allCurrentCourseSessions, allCurrentCourseSessions,
courseSessionForCourse, courseSessionForCourse,
switchCourseSession, getCourseSessionById,
switchCourseSessionById,
hasCockpit, hasCockpit,
hasCourseSessionPreview, hasCourseSessionPreview,
currentCourseSessionHasCockpit, currentCourseSessionHasCockpit,

View File

@ -37,8 +37,9 @@ class CourseSessionUserAdmin(admin.ModelAdmin):
"user_first_name", "user_first_name",
"course_session", "course_session",
"role", "role",
"created_at", "circles",
"updated_at", # "created_at",
# "updated_at",
] ]
search_fields = [ search_fields = [
"user__first_name", "user__first_name",
@ -66,6 +67,9 @@ class CourseSessionUserAdmin(admin.ModelAdmin):
user_last_name.short_description = "Last Name" user_last_name.short_description = "Last Name"
user_last_name.admin_order_field = "user__last_name" user_last_name.admin_order_field = "user__last_name"
def circles(self, obj):
return ", ".join([c.title for c in obj.expert.all()])
fieldsets = [ fieldsets = [
(None, {"fields": ("user", "course_session", "role")}), (None, {"fields": ("user", "course_session", "role")}),
( (

View File

@ -63,8 +63,10 @@ class CourseSessionAttendanceCourse(models.Model):
) )
if not self.due_date.manual_override_fields: if not self.due_date.manual_override_fields:
self.due_date.url = self.learning_content.get_frontend_url() self.due_date.url = self.learning_content.get_frontend_url(
self.due_date.url_expert = f"/course/{self.due_date.course_session.course.slug}/cockpit/attendance?id={self.learning_content_id}" course_session_id=self.course_session.id
)
self.due_date.url_expert = f"/course/{self.due_date.course_session.course.slug}/cockpit/attendance?id={self.learning_content_id}&courseSessionId={self.course_session.id}"
self.due_date.title = self.learning_content.title self.due_date.title = self.learning_content.title
self.due_date.page = self.learning_content.page_ptr self.due_date.page = self.learning_content.page_ptr
self.due_date.assignment_type_translation_key = ( self.due_date.assignment_type_translation_key = (
@ -123,7 +125,9 @@ class CourseSessionAssignment(models.Model):
if self.learning_content_id: if self.learning_content_id:
title = self.learning_content.title title = self.learning_content.title
page = self.learning_content.page_ptr page = self.learning_content.page_ptr
url = self.learning_content.get_frontend_url() url = self.learning_content.get_frontend_url(
course_session_id=self.course_session.id
)
assignment_type = self.learning_content.assignment_type assignment_type = self.learning_content.assignment_type
assignment_type_translation_keys = { assignment_type_translation_keys = {
AssignmentType.CASEWORK.value: "learningContentTypes.casework", AssignmentType.CASEWORK.value: "learningContentTypes.casework",
@ -131,7 +135,7 @@ class CourseSessionAssignment(models.Model):
AssignmentType.REFLECTION.value: "learningContentTypes.reflection", AssignmentType.REFLECTION.value: "learningContentTypes.reflection",
} }
url_expert = f"/course/{self.course_session.course.slug}/cockpit/assignment/{self.learning_content_id}" url_expert = f"/course/{self.course_session.course.slug}/cockpit/assignment/{self.learning_content_id}?courseSessionId={self.course_session.id}"
if assignment_type in ( if assignment_type in (
AssignmentType.CASEWORK.value, AssignmentType.CASEWORK.value,
@ -212,10 +216,10 @@ class CourseSessionEdoniqTest(models.Model):
) )
if not self.deadline.manual_override_fields: if not self.deadline.manual_override_fields:
self.deadline.url = self.learning_content.get_frontend_url() self.deadline.url = self.learning_content.get_frontend_url(
self.deadline.url_expert = ( course_session_id=self.course_session.id
f"/course/{self.course_session.course.slug}/cockpit/"
) )
self.deadline.url_expert = f"/course/{self.course_session.course.slug}/cockpit?courseSessionId={self.course_session.id}"
self.deadline.title = self.learning_content.title self.deadline.title = self.learning_content.title
self.deadline.page = self.learning_content.page_ptr self.deadline.page = self.learning_content.page_ptr
self.deadline.assignment_type_translation_key = ( self.deadline.assignment_type_translation_key = (

View File

@ -4,56 +4,30 @@ from django.db import migrations, models
def set_url_expert_course_session_assignments(apps): def set_url_expert_course_session_assignments(apps):
Course = apps.get_model("course", "Course") # noqa # need to load concrete model, so that wagtail page has `specific` instance method...
from vbv_lernwelt.course_session.models import CourseSessionAssignment
CourseSessionAssignment = apps.get_model( # noqa
"course_session", "CourseSessionAssignment"
)
for assignment in CourseSessionAssignment.objects.all(): for assignment in CourseSessionAssignment.objects.all():
for due_date in [ # trigger save to update due_date foreign key fields
assignment.submission_deadline, assignment.save()
assignment.evaluation_deadline,
]:
if due_date and due_date.page:
course_slug = due_date.course_session.course.slug
content_id = due_date.page.id
due_date.url_expert = (
f"/course/{course_slug}/cockpit/assignment/{content_id}"
)
due_date.save()
def set_url_expert_course_session_edoniq_test(apps): def set_url_expert_course_session_edoniq_test(apps):
CourseSessionEdoniqTest = apps.get_model( # noqa # need to load concrete model, so that wagtail page has `specific` instance method...
"course_session", "CourseSessionEdoniqTest" from vbv_lernwelt.course_session.models import CourseSessionEdoniqTest
)
for edoniq_test in CourseSessionEdoniqTest.objects.all(): for edoniq_test in CourseSessionEdoniqTest.objects.all():
due_date = edoniq_test.deadline # trigger save to update due_date foreign key fields
edoniq_test.save()
if due_date:
course_slug = due_date.course_session.course.slug
due_date.url_expert = f"/course/{course_slug}/cockpit/"
due_date.save()
def set_url_expert_course_session_attendances(apps): def set_url_expert_course_session_attendances(apps):
Course = apps.get_model("course", "Course") # noqa # need to load concrete model, so that wagtail page has `specific` instance method...
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
CourseSessionAttendanceCourse = apps.get_model( # noqa
"course_session", "CourseSessionAttendanceCourse"
)
for attendance in CourseSessionAttendanceCourse.objects.all(): for attendance in CourseSessionAttendanceCourse.objects.all():
due_date = attendance.due_date # trigger save to update due_date foreign key fields
if due_date and due_date.page: attendance.save()
course_slug = due_date.course_session.course.slug
content_id = due_date.page.id
due_date.url_expert = (
f"/course/{course_slug}/cockpit/attendance?id={content_id}"
)
due_date.save()
def set_url_expert_default(apps, schema_editor): def set_url_expert_default(apps, schema_editor):

View File

@ -268,14 +268,19 @@ class LearningContent(CourseBasePage):
<span style="margin-left: 8px;">{self.get_admin_display_title()}</span> <span style="margin-left: 8px;">{self.get_admin_display_title()}</span>
</span>""" </span>"""
def get_frontend_url(self): def get_frontend_url(self, course_session_id=None):
r = re.compile( r = re.compile(
r"^(?P<coursePart>.+?)-lp-circle-(?P<circlePart>.+?)-lc-(?P<lcPart>.+)$" r"^(?P<coursePart>.+?)-lp-circle-(?P<circlePart>.+?)-lc-(?P<lcPart>.+)$"
) )
m = r.match(self.slug) m = r.match(self.slug)
if m is None: if m is None:
return "ERROR: could not parse slug" return "ERROR: could not parse slug"
return f"/course/{m.group('coursePart')}/learn/{m.group('circlePart')}/{m.group('lcPart')}" url = f"/course/{m.group('coursePart')}/learn/{m.group('circlePart')}/{m.group('lcPart')}"
if course_session_id:
url += f"?courseSessionId={course_session_id}"
return url
def get_parent_circle(self): def get_parent_circle(self):
try: try: