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 { useTranslation } from "i18next-vue";
import dayjs from "dayjs";
import { computed } from "vue";
const props = defineProps<{
dueDate: DueDate;
singleLine?: boolean;
showCourseSession?: boolean;
}>();
const { t } = useTranslation();
@ -24,6 +26,16 @@ if (!courseSession) {
const isExpert = courseSessionsStore.hasCockpit(courseSession);
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>
<template>
@ -54,7 +66,12 @@ const url = isExpert ? props.dueDate.url_expert : props.dueDate.url;
</a>
</div>
<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>

View File

@ -9,6 +9,7 @@ const props = defineProps<{
showTopBorder: boolean;
showBottomBorder: boolean;
showAllDueDatesLink: boolean;
showCourseSession: boolean;
}>();
const allDueDates = computed(() => {
@ -28,7 +29,10 @@ const dueDatesDisplayed = computed(() => {
:key="dueDate.id"
: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>
</ul>
<div v-if="allDueDates.length === 0">{{ $t("dueDates.noDueDatesAvailable") }}</div>

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,9 @@ export const redirectToLoginIfRequired: NavigationGuard = (to) => {
if (loginRequired(to) && !userStore.loggedIn) {
const appEnv = import.meta.env.VITE_APP_ENVIRONMENT || "local";
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
const courseSessionsStore = useCourseSessionsStore();
if (to.params.courseSlug) {
courseSessionsStore._currentCourseSlug = to.params.courseSlug as string;
} else {
courseSessionsStore._currentCourseSlug = "";
}
if (!courseSessionsStore.loaded) {
await courseSessionsStore.loadCourseSessionsData();
const userStore = useUserStore();
if (userStore.loggedIn) {
const courseSessionsStore = useCourseSessionsStore();
if (to.params.courseSlug) {
courseSessionsStore._currentCourseSlug = to.params.courseSlug as string;
} else {
courseSessionsStore._currentCourseSlug = "";
}
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 LoginPage from "@/pages/LoginPage.vue";
import {
handleCourseSessions,
handleCourseSessionAsQueryParam,
handleCurrentCourseSession,
redirectToLoginIfRequired,
updateLoggedIn,
} from "@/router/guards";
@ -214,7 +215,8 @@ router.beforeEach(updateLoggedIn);
router.beforeEach(redirectToLoginIfRequired);
// register after login hooks
router.beforeEach(handleCourseSessions);
router.beforeEach(handleCurrentCourseSession);
router.beforeEach(handleCourseSessionAsQueryParam);
router.beforeEach(addToHistory);

View File

@ -91,13 +91,31 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
return undefined;
}
function switchCourseSession(courseSession: CourseSession) {
function _switchCourseSession(courseSession: CourseSession) {
log.debug("switchCourseSession", courseSession);
selectedCourseSessionMap.value.set(courseSession.course.slug, courseSession.id);
// Emit event so that the App can re-render with the new courseSession
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) {
if (courseSlug) {
const courseSession = selectedCourseSessionForCourse(courseSlug);
@ -268,7 +286,8 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
uniqueCourseSessionsByCourse,
allCurrentCourseSessions,
courseSessionForCourse,
switchCourseSession,
getCourseSessionById,
switchCourseSessionById,
hasCockpit,
hasCourseSessionPreview,
currentCourseSessionHasCockpit,

View File

@ -37,8 +37,9 @@ class CourseSessionUserAdmin(admin.ModelAdmin):
"user_first_name",
"course_session",
"role",
"created_at",
"updated_at",
"circles",
# "created_at",
# "updated_at",
]
search_fields = [
"user__first_name",
@ -66,6 +67,9 @@ class CourseSessionUserAdmin(admin.ModelAdmin):
user_last_name.short_description = "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 = [
(None, {"fields": ("user", "course_session", "role")}),
(

View File

@ -63,8 +63,10 @@ class CourseSessionAttendanceCourse(models.Model):
)
if not self.due_date.manual_override_fields:
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}"
self.due_date.url = self.learning_content.get_frontend_url(
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.page = self.learning_content.page_ptr
self.due_date.assignment_type_translation_key = (
@ -123,7 +125,9 @@ class CourseSessionAssignment(models.Model):
if self.learning_content_id:
title = self.learning_content.title
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_translation_keys = {
AssignmentType.CASEWORK.value: "learningContentTypes.casework",
@ -131,7 +135,7 @@ class CourseSessionAssignment(models.Model):
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 (
AssignmentType.CASEWORK.value,
@ -212,10 +216,10 @@ class CourseSessionEdoniqTest(models.Model):
)
if not self.deadline.manual_override_fields:
self.deadline.url = self.learning_content.get_frontend_url()
self.deadline.url_expert = (
f"/course/{self.course_session.course.slug}/cockpit/"
self.deadline.url = self.learning_content.get_frontend_url(
course_session_id=self.course_session.id
)
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.page = self.learning_content.page_ptr
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):
Course = apps.get_model("course", "Course") # noqa
CourseSessionAssignment = apps.get_model( # noqa
"course_session", "CourseSessionAssignment"
)
# need to load concrete model, so that wagtail page has `specific` instance method...
from vbv_lernwelt.course_session.models import CourseSessionAssignment
for assignment in CourseSessionAssignment.objects.all():
for due_date in [
assignment.submission_deadline,
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()
# trigger save to update due_date foreign key fields
assignment.save()
def set_url_expert_course_session_edoniq_test(apps):
CourseSessionEdoniqTest = apps.get_model( # noqa
"course_session", "CourseSessionEdoniqTest"
)
# need to load concrete model, so that wagtail page has `specific` instance method...
from vbv_lernwelt.course_session.models import CourseSessionEdoniqTest
for edoniq_test in CourseSessionEdoniqTest.objects.all():
due_date = edoniq_test.deadline
if due_date:
course_slug = due_date.course_session.course.slug
due_date.url_expert = f"/course/{course_slug}/cockpit/"
due_date.save()
# trigger save to update due_date foreign key fields
edoniq_test.save()
def set_url_expert_course_session_attendances(apps):
Course = apps.get_model("course", "Course") # noqa
CourseSessionAttendanceCourse = apps.get_model( # noqa
"course_session", "CourseSessionAttendanceCourse"
)
# need to load concrete model, so that wagtail page has `specific` instance method...
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
for attendance in CourseSessionAttendanceCourse.objects.all():
due_date = attendance.due_date
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/attendance?id={content_id}"
)
due_date.save()
# trigger save to update due_date foreign key fields
attendance.save()
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>"""
def get_frontend_url(self):
def get_frontend_url(self, course_session_id=None):
r = re.compile(
r"^(?P<coursePart>.+?)-lp-circle-(?P<circlePart>.+?)-lc-(?P<lcPart>.+)$"
)
m = r.match(self.slug)
if m is None:
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):
try: