Refactor urls for courses, learning paths and competence profile

This commit is contained in:
Daniel Egger 2022-12-02 11:10:12 +01:00
parent cb9505b54c
commit 00d2527b29
16 changed files with 80 additions and 40 deletions

View File

@ -35,13 +35,22 @@ function toggleNav() {
state.showMenu = !state.showMenu; state.showMenu = !state.showMenu;
} }
function isInRoutePath(checkPaths: string[]) { function inCourse() {
log.debug("isInRoutePath", checkPaths, route.path); return route.path.startsWith("/course/");
return checkPaths.some((path) => route.path.startsWith(path));
} }
function inCourse() { function inLearningPath() {
return isInRoutePath(["/learn", "/competence"]); const regex = new RegExp("/course/[^/]+/learn");
return regex.test(route.path);
}
function inCompetenceProfile() {
const regex = new RegExp("/course/[^/]+/competence");
return regex.test(route.path);
}
function inMediaLibrary() {
return route.path.startsWith("/media/");
} }
function handleDropdownSelect(data: DropdownData) { function handleDropdownSelect(data: DropdownData) {
@ -149,7 +158,7 @@ const profileDropdownData: DropdownListItem[] = [
v-if="inCourse() && courseSessionsStore.courseSessionForRoute" v-if="inCourse() && courseSessionsStore.courseSessionForRoute"
:to="courseSessionsStore.courseSessionForRoute.learning_path_url" :to="courseSessionsStore.courseSessionForRoute.learning_path_url"
class="nav-item" class="nav-item"
:class="{ 'nav-item--active': isInRoutePath(['/learn']) }" :class="{ 'nav-item--active': inLearningPath() }"
> >
{{ $t("general.learningPath") }} {{ $t("general.learningPath") }}
</router-link> </router-link>
@ -158,7 +167,7 @@ const profileDropdownData: DropdownListItem[] = [
v-if="inCourse() && courseSessionsStore.courseSessionForRoute" v-if="inCourse() && courseSessionsStore.courseSessionForRoute"
:to="courseSessionsStore.courseSessionForRoute.competence_url" :to="courseSessionsStore.courseSessionForRoute.competence_url"
class="nav-item" class="nav-item"
:class="{ 'nav-item--active': isInRoutePath(['/competence']) }" :class="{ 'nav-item--active': inCompetenceProfile() }"
> >
KompetenzNavi KompetenzNavi
</router-link> </router-link>
@ -173,7 +182,7 @@ const profileDropdownData: DropdownListItem[] = [
<router-link <router-link
to="/media/versicherungsvermittlerin-media" to="/media/versicherungsvermittlerin-media"
class="nav-item" class="nav-item"
:class="{ 'nav-item--active': isInRoutePath(['/media']) }" :class="{ 'nav-item--active': inMediaLibrary() }"
data-cy="medialibrary-link" data-cy="medialibrary-link"
> >
{{ $t("mediaLibrary.title") }} {{ $t("mediaLibrary.title") }}

View File

@ -17,7 +17,11 @@ const learningPathData = ref<LearningPath | undefined>(undefined);
const learningPathStore = useLearningPathStore(); const learningPathStore = useLearningPathStore();
learningPathStore learningPathStore
.loadLearningPath(props.learningPathUrl.replace("/learn/", ""), false, false) .loadLearningPath(
props.learningPathUrl.replace(/\/learn$/, "-lp").replace(/^\/course\//, ""),
false,
false
)
.then((data) => { .then((data) => {
learningPathData.value = data; learningPathData.value = data;
}); });

View File

@ -6,16 +6,17 @@ import { onMounted } from "vue";
log.debug("CompetenceParentPage created"); log.debug("CompetenceParentPage created");
const props = defineProps<{ const props = defineProps<{
competenceProfilePageSlug: string; courseSlug: string;
}>(); }>();
const competenceStore = useCompetenceStore(); const competenceStore = useCompetenceStore();
onMounted(async () => { onMounted(async () => {
log.debug("CompetencesView mounted", props.competenceProfilePageSlug); log.debug("CompetencesView mounted", props.courseSlug);
try { try {
await competenceStore.loadCompetenceProfilePage(props.competenceProfilePageSlug); const competencePageSlug = props.courseSlug + "-competence";
await competenceStore.loadCompetenceProfilePage(competencePageSlug);
} catch (error) { } catch (error) {
log.error(error); log.error(error);
} }

View File

@ -16,7 +16,7 @@ const route = useRoute();
log.debug("CircleView.vue created", route); log.debug("CircleView.vue created", route);
const props = defineProps<{ const props = defineProps<{
learningPathSlug: string; courseSlug: string;
circleSlug: string; circleSlug: string;
}>(); }>();
@ -35,10 +35,10 @@ const duration = computed(() => {
}); });
onMounted(async () => { onMounted(async () => {
log.debug("CircleView.vue mounted", props.learningPathSlug, props.circleSlug); log.debug("CircleView.vue mounted", props.courseSlug, props.circleSlug);
try { try {
await circleStore.loadCircle(props.learningPathSlug, props.circleSlug); await circleStore.loadCircle(props.courseSlug, props.circleSlug);
if (route.hash.startsWith("#ls-") || route.hash.startsWith("#lu-")) { if (route.hash.startsWith("#ls-") || route.hash.startsWith("#lu-")) {
const slugEnd = route.hash.replace("#", ""); const slugEnd = route.hash.replace("#", "");
@ -86,7 +86,7 @@ onMounted(async () => {
<div class="flex flex-col lg:flex-row"> <div class="flex flex-col lg:flex-row">
<div class="flex-initial lg:w-128 px-4 py-4 lg:px-8 lg:pt-4 bg-white"> <div class="flex-initial lg:w-128 px-4 py-4 lg:px-8 lg:pt-4 bg-white">
<router-link <router-link
:to="`/learn/${props.learningPathSlug}`" :to="`/course/${props.courseSlug}/learn`"
class="btn-text inline-flex items-center px-3 py-4" class="btn-text inline-flex items-center px-3 py-4"
data-cy="back-to-learning-path-button" data-cy="back-to-learning-path-button"
> >

View File

@ -9,7 +9,7 @@ import { onMounted, reactive, watch } from "vue";
log.debug("LearningContentView created"); log.debug("LearningContentView created");
const props = defineProps<{ const props = defineProps<{
learningPathSlug: string; courseSlug: string;
circleSlug: string; circleSlug: string;
contentSlug: string; contentSlug: string;
}>(); }>();
@ -24,7 +24,7 @@ const circleStore = useCircleStore();
const loadLearningContent = async () => { const loadLearningContent = async () => {
try { try {
state.learningContent = await circleStore.loadLearningContent( state.learningContent = await circleStore.loadLearningContent(
props.learningPathSlug, props.courseSlug,
props.circleSlug, props.circleSlug,
props.contentSlug props.contentSlug
); );
@ -38,7 +38,7 @@ watch(
async () => { async () => {
log.debug( log.debug(
"LearningContentView props.contentSlug changed", "LearningContentView props.contentSlug changed",
props.learningPathSlug, props.courseSlug,
props.circleSlug, props.circleSlug,
props.contentSlug props.contentSlug
); );
@ -49,7 +49,7 @@ watch(
onMounted(async () => { onMounted(async () => {
log.debug( log.debug(
"LearningContentView mounted", "LearningContentView mounted",
props.learningPathSlug, props.courseSlug,
props.circleSlug, props.circleSlug,
props.contentSlug props.contentSlug
); );

View File

@ -12,7 +12,7 @@ import type { LearningPath } from "@/services/learningPath";
log.debug("LearningPathView created"); log.debug("LearningPathView created");
const props = defineProps<{ const props = defineProps<{
learningPathSlug: string; courseSlug: string;
}>(); }>();
const learningPathStore = useLearningPathStore(); const learningPathStore = useLearningPathStore();
@ -22,7 +22,7 @@ onMounted(async () => {
log.debug("LearningPathView mounted"); log.debug("LearningPathView mounted");
try { try {
await learningPathStore.loadLearningPath(props.learningPathSlug); await learningPathStore.loadLearningPath(props.courseSlug + "-lp");
} catch (error) { } catch (error) {
log.error(error); log.error(error);
} }
@ -49,7 +49,7 @@ const createContinueUrl = (learningPath: LearningPath): [string, boolean] => {
<Teleport to="body"> <Teleport to="body">
<LearningPathViewVertical <LearningPathViewVertical
:show="learningPathStore.page === 'OVERVIEW'" :show="learningPathStore.page === 'OVERVIEW'"
:learning-path-slug="props.learningPathSlug" :learning-path-slug="props.learningPathSlug + '-lp'"
@closemodal="learningPathStore.page = 'INDEX'" @closemodal="learningPathStore.page = 'INDEX'"
/> />
</Teleport> </Teleport>

View File

@ -11,7 +11,7 @@ import { onMounted, reactive } from "vue";
log.debug("LearningUnitSelfEvaluationView created"); log.debug("LearningUnitSelfEvaluationView created");
const props = defineProps<{ const props = defineProps<{
learningPathSlug: string; courseSlug: string;
circleSlug: string; circleSlug: string;
learningUnitSlug: string; learningUnitSlug: string;
}>(); }>();
@ -26,14 +26,14 @@ const state: { learningUnit?: LearningUnit } = reactive({});
onMounted(async () => { onMounted(async () => {
log.debug( log.debug(
"LearningUnitSelfEvaluationView mounted", "LearningUnitSelfEvaluationView mounted",
props.learningPathSlug, props.courseSlug,
props.circleSlug, props.circleSlug,
props.learningUnitSlug props.learningUnitSlug
); );
try { try {
state.learningUnit = await circleStore.loadSelfEvaluation( state.learningUnit = await circleStore.loadSelfEvaluation(
props.learningPathSlug, props.courseSlug,
props.circleSlug, props.circleSlug,
props.learningUnitSlug props.learningUnitSlug
); );

View File

@ -53,7 +53,7 @@ const router = createRouter({
], ],
}, },
{ {
path: "/competence/:competenceProfilePageSlug", path: "/course/:courseSlug/competence",
props: true, props: true,
component: () => import("@/pages/competence/CompetenceParentPage.vue"), component: () => import("@/pages/competence/CompetenceParentPage.vue"),
children: [ children: [
@ -77,27 +77,27 @@ const router = createRouter({
], ],
}, },
{ {
path: "/learn/:learningPathSlug", path: "/course/:courseSlug/learn",
component: () => import("../pages/learningPath/LearningPathPage.vue"), component: () => import("../pages/learningPath/LearningPathPage.vue"),
props: true, props: true,
}, },
{ {
path: "/learn/:learningPathSlug/:circleSlug", path: "/course/:courseSlug/learn/:circleSlug",
component: () => import("../pages/learningPath/CirclePage.vue"), component: () => import("../pages/learningPath/CirclePage.vue"),
props: true, props: true,
}, },
{ {
path: "/learn/:learningPathSlug/:circleSlug/evaluate/:learningUnitSlug", path: "/course/:courseSlug/learn/:circleSlug/evaluate/:learningUnitSlug",
component: () => import("../pages/learningPath/SelfEvaluationPage.vue"), component: () => import("../pages/learningPath/SelfEvaluationPage.vue"),
props: true, props: true,
}, },
{ {
path: "/learn/:learningPathSlug/:circleSlug/:contentSlug", path: "/course/:courseSlug/learn/:circleSlug/:contentSlug",
component: () => import("../pages/learningPath/LearningContentPage.vue"), component: () => import("../pages/learningPath/LearningContentPage.vue"),
props: true, props: true,
}, },
{ {
path: "/learn/:learningPathSlug/cockpit", path: "/course/:courseSlug/cockpit",
component: () => import("../pages/cockpit/CockpitPage.vue"), component: () => import("../pages/cockpit/CockpitPage.vue"),
props: true, props: true,
}, },

View File

@ -37,8 +37,9 @@ export const useCircleStore = defineStore({
}, },
getters: {}, getters: {},
actions: { actions: {
async loadCircle(learningPathSlug: string, circleSlug: string): Promise<Circle> { async loadCircle(courseSlug: string, circleSlug: string): Promise<Circle> {
this.circle = undefined; this.circle = undefined;
const learningPathSlug = courseSlug + "-lp";
const learningPathStore = useLearningPathStore(); const learningPathStore = useLearningPathStore();
await learningPathStore.loadLearningPath(learningPathSlug); await learningPathStore.loadLearningPath(learningPathSlug);
if (learningPathStore.learningPath) { if (learningPathStore.learningPath) {
@ -54,11 +55,11 @@ export const useCircleStore = defineStore({
return this.circle; return this.circle;
}, },
async loadLearningContent( async loadLearningContent(
learningPathSlug: string, courseSlug: string,
circleSlug: string, circleSlug: string,
learningContentSlug: string learningContentSlug: string
) { ) {
const circle = await this.loadCircle(learningPathSlug, circleSlug); const circle = await this.loadCircle(courseSlug, circleSlug);
const result = circle.flatLearningContents.find((learningContent) => { const result = circle.flatLearningContents.find((learningContent) => {
return learningContent.slug.endsWith(learningContentSlug); return learningContent.slug.endsWith(learningContentSlug);
}); });
@ -70,11 +71,11 @@ export const useCircleStore = defineStore({
return result; return result;
}, },
async loadSelfEvaluation( async loadSelfEvaluation(
learningPathSlug: string, courseSlug: string,
circleSlug: string, circleSlug: string,
learningUnitSlug: string learningUnitSlug: string
) { ) {
const circle = await this.loadCircle(learningPathSlug, circleSlug); const circle = await this.loadCircle(courseSlug, circleSlug);
const learningUnit = circle.flatLearningUnits.find((child) => { const learningUnit = circle.flatLearningUnits.find((child) => {
return child.slug.endsWith(learningUnitSlug); return child.slug.endsWith(learningUnitSlug);
}); });

View File

@ -30,7 +30,7 @@ class CompetenceProfilePage(CourseBasePage):
super(CompetenceProfilePage, self).full_clean(*args, **kwargs) super(CompetenceProfilePage, self).full_clean(*args, **kwargs)
def get_frontend_url(self): def get_frontend_url(self):
return f"/competence/{self.slug}" return f"/course/{self.get_parent().slug}/competence"
class CompetencePage(CourseBasePage): class CompetencePage(CourseBasePage):

View File

@ -80,6 +80,8 @@ def create_test_course_with_categories(apps=None, schema_editor=None):
parent=site.root_page, parent=site.root_page,
course=course, course=course,
) )
course.slug = course_page.slug
course.save()
def create_test_learning_path(user=None, skip_locales=True): def create_test_learning_path(user=None, skip_locales=True):

View File

@ -57,3 +57,5 @@ def create_versicherungsvermittlerin_with_categories(
parent=site.root_page, parent=site.root_page,
course=course, course=course,
) )
course.slug = course_page.slug
course.save()

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2022-12-02 09:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0005_alter_coursesessionuser_expert'),
]
operations = [
migrations.AddField(
model_name='course',
name='slug',
field=models.SlugField(allow_unicode=True, blank=True, max_length=255, unique=True, verbose_name='Slug'),
),
]

View File

@ -14,6 +14,9 @@ class Course(models.Model):
category_name = models.CharField( category_name = models.CharField(
_("Kategorie-Name"), max_length=255, default="Kategorie" _("Kategorie-Name"), max_length=255, default="Kategorie"
) )
slug = models.SlugField(
_("Slug"), max_length=255, unique=True, blank=True, allow_unicode=True
)
class Meta: class Meta:
verbose_name = _("Lehrgang") verbose_name = _("Lehrgang")

View File

@ -65,7 +65,7 @@ class CourseSessionSerializer(serializers.ModelSerializer):
def get_experts(self, obj): def get_experts(self, obj):
expert_relations = CourseSessionUser.objects.filter( expert_relations = CourseSessionUser.objects.filter(
expert__in=Circle.objects.descendant_of(obj.course.coursepage) expert__in=Circle.objects.descendant_of(obj.course.coursepage)
) ).distinct()
expert_result = [] expert_result = []
for er in expert_relations: for er in expert_relations:
for circle in er.expert.all(): for circle in er.expert.all():

View File

@ -43,7 +43,7 @@ class LearningPath(CourseBasePage):
return f"{self.title}" return f"{self.title}"
def get_frontend_url(self): def get_frontend_url(self):
return f"/learn/{self.slug}" return f"/course/{self.get_parent().slug}/learn"
class Topic(CourseBasePage): class Topic(CourseBasePage):