Document handling via REST

This commit is contained in:
Daniel Egger 2023-10-10 13:11:13 +02:00
parent a1f2c8cd12
commit 49a3fa99e1
20 changed files with 137 additions and 181 deletions

View File

@ -20,7 +20,7 @@ const documents = {
"\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n }\n assignment_user {\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n }\n }\n": types.AssignmentCompletionQueryDocument,
"\n query courseQuery($courseId: ID!) {\n course(id: $courseId) {\n id\n slug\n title\n category_name\n learning_path {\n id\n }\n }\n }\n": types.CourseQueryDocument,
"\n query competenceCertificateQuery($courseSlug: String!, $courseSessionId: ID!) {\n competence_certificate_list(course_slug: $courseSlug) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n completion(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_passed\n }\n learning_content {\n title\n id\n slug\n content_type\n frontend_url\n circle {\n ...CoursePageFields\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateQueryDocument,
"\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n documents {\n id\n name\n file_name\n url\n learning_sequence {\n id\n title\n circle {\n id\n slug\n title\n }\n }\n }\n }\n }\n": types.CourseSessionDetailDocument,
"\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n": types.CourseSessionDetailDocument,
"\n mutation SendFeedbackMutation(\n $courseSessionId: ID!\n $learningContentId: ID!\n $data: GenericScalar!\n $submitted: Boolean\n ) {\n send_feedback(\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n data: $data\n submitted: $submitted\n ) {\n feedback_response {\n id\n data\n submitted\n }\n errors {\n field\n messages\n }\n }\n }\n": types.SendFeedbackMutationDocument,
};
@ -69,7 +69,7 @@ export function graphql(source: "\n query competenceCertificateQuery($courseSlu
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n documents {\n id\n name\n file_name\n url\n learning_sequence {\n id\n title\n circle {\n id\n slug\n title\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n documents {\n id\n name\n file_name\n url\n learning_sequence {\n id\n title\n circle {\n id\n slug\n title\n }\n }\n }\n }\n }\n"];
export function graphql(source: "\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n"): (typeof documents)["\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/

File diff suppressed because one or more lines are too long

View File

@ -204,21 +204,6 @@ export const COURSE_SESSION_DETAIL_QUERY = graphql(`
}
}
}
documents {
id
name
file_name
url
learning_sequence {
id
title
circle {
id
slug
title
}
}
}
}
}
`);

View File

@ -1,22 +0,0 @@
<script setup lang="ts">
import * as log from "loglevel";
import { onMounted } from "vue";
import { useCourseSessionDetailQuery } from "@/composables";
const props = defineProps<{
courseSlug: string;
}>();
onMounted(async () => {
log.debug("TestCourseSessionComposablePage mounted");
});
// const courseSession = useCurrentCourseSession();
const queryResult = useCourseSessionDetailQuery("-1");
</script>
<template>
<h1>Hello World</h1>
<pre>{{ queryResult.courseSessionDetail }}</pre>
</template>

View File

@ -1,5 +1,4 @@
<script setup lang="ts">
import { useCockpitStore } from "@/stores/cockpit";
import { useCompetenceStore } from "@/stores/competence";
import { useLearningPathStore } from "@/stores/learningPath";
import * as log from "loglevel";
@ -16,7 +15,6 @@ const props = defineProps<{
log.debug("CockpitUserProfilePage created", props.userId);
const cockpitStore = useCockpitStore();
const competenceStore = useCompetenceStore();
const learningPathStore = useLearningPathStore();

View File

@ -47,7 +47,7 @@ const submittables = computed(() => {
return [];
}
return learningPath.circles
.filter((circle) => props.selectedCircle == circle.id)
.filter((circle) => props.selectedCircle.toString() == circle.id.toString())
.flatMap((circle) => {
const learningContents = circle.flatLearningContents.filter(
(lc) =>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
import { useCurrentCourseSession } from "@/composables";
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import { useCockpitStore } from "@/stores/cockpit";
import ItModal from "@/components/ui/ItModal.vue";
@ -9,7 +9,11 @@ import { useTranslation } from "i18next-vue";
import type { CircleDocument, DocumentUploadData } from "@/types";
import dialog from "@/utils/confirm-dialog";
import log from "loglevel";
import { uploadCircleDocument } from "@/services/files";
import {
deleteCircleDocument,
fetchCourseSessionDocuments,
uploadCircleDocument,
} from "@/services/files";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import DocumentListItem from "@/components/circle/DocumentListItem.vue";
import { useCircleStore } from "@/stores/circle";
@ -19,7 +23,6 @@ const courseSession = useCurrentCourseSession();
const circleStore = useCircleStore();
const courseSessionsStore = useCourseSessionsStore();
const courseSessionDetailResults = useCourseSessionDetailQuery();
const { t } = useTranslation();
@ -27,8 +30,25 @@ const showUploadModal = ref(false);
const showUploadErrorMessage = ref(false);
const isUploading = ref(false);
const circleDocumentsResultData = ref<CircleDocument[]>([]);
let courseSessionDocumentsUrl = "";
async function fetchDocuments() {
const result = await fetchCourseSessionDocuments(courseSession.value?.id);
if (result.length > 0) {
circleDocumentsResultData.value = result;
} else {
circleDocumentsResultData.value = [];
}
}
onMounted(async () => {
log.debug("DocumentPage mounted");
if (courseSession.value?.id) {
courseSessionDocumentsUrl = `/api/core/document/list/${courseSession.value?.id}/`;
}
await fetchDocuments();
});
watch(
@ -48,14 +68,12 @@ watch(
const dropdownLearningSequences = computed(() =>
circleStore.circle?.learningSequences.map((sequence) => ({
id: sequence.id,
name: `${sequence.title} ${sequence.id}`,
name: `${sequence.title}`,
}))
);
const circleDocuments = computed(() => {
const documents =
courseSessionDetailResults.courseSessionDetail.value?.documents ?? [];
return documents.filter(
return circleDocumentsResultData.value.filter(
(d) => d.learning_sequence.circle.slug === cockpitStore.currentCircle?.slug
);
});
@ -67,7 +85,10 @@ const deleteDocument = async (doc: CircleDocument) => {
};
try {
await dialog.confirm(options);
courseSessionsStore.removeDocument(doc.id);
await deleteCircleDocument(doc.id, courseSessionDocumentsUrl);
circleDocumentsResultData.value = circleDocumentsResultData.value.filter(
(d) => d.id !== doc.id
);
} catch (e) {
log.debug("rejected");
}
@ -81,11 +102,13 @@ async function uploadDocument(data: DocumentUploadData) {
if (!courseSessionsStore.currentCourseSession) {
throw new Error("No course session found");
}
const newDocument = await uploadCircleDocument(
await uploadCircleDocument(
data,
courseSessionsStore.currentCourseSession.id
courseSessionsStore.currentCourseSession.id,
courseSessionDocumentsUrl
);
courseSessionsStore.addDocument(newDocument);
await fetchDocuments();
showUploadModal.value = false;
isUploading.value = false;
} catch (error) {

View File

@ -21,18 +21,33 @@
<script setup lang="ts">
import DocumentListItem from "@/components/circle/DocumentListItem.vue";
import { useCourseSessionDetailQuery } from "@/composables";
import { computed } from "vue";
import { useCurrentCourseSession } from "@/composables";
import { computed, onMounted, ref } from "vue";
import { useCircleStore } from "@/stores/circle";
import type { CircleDocument } from "@/types";
import { fetchCourseSessionDocuments } from "@/services/files";
const courseSessionDetailResults = useCourseSessionDetailQuery();
const courseSession = useCurrentCourseSession();
const circleStore = useCircleStore();
const circleDocumentsResultData = ref<CircleDocument[]>([]);
async function fetchDocuments() {
const result = await fetchCourseSessionDocuments(courseSession.value?.id);
if (result.length > 0) {
circleDocumentsResultData.value = result;
} else {
circleDocumentsResultData.value = [];
}
}
const circleDocuments = computed(() => {
const documents =
courseSessionDetailResults.courseSessionDetail.value?.documents ?? [];
return documents.filter(
return circleDocumentsResultData.value.filter(
(d) => d.learning_sequence.circle.slug === circleStore.circle?.slug
);
});
onMounted(async () => {
await fetchDocuments();
});
</script>

View File

@ -98,11 +98,6 @@ const router = createRouter({
},
],
},
{
path: "/course/:courseSlug/test-composable",
component: () => import("../pages/TestCourseSessionComposablePage.vue"),
props: true,
},
{
path: "/course/:courseSlug/learn",
component: () =>

View File

@ -1,6 +1,6 @@
import { itDelete, itFetch, itPost } from "@/fetchHelpers";
import { bustItGetCache, itDelete, itFetch, itGetCached, itPost } from "@/fetchHelpers";
import { getCookieValue } from "@/router/guards";
import type { CircleDocument, DocumentUploadData } from "@/types";
import type { DocumentUploadData } from "@/types";
type FileData = {
fields: Record<string, string>;
@ -73,8 +73,9 @@ function handleUpload(url: string, options: RequestInit) {
export async function uploadCircleDocument(
data: DocumentUploadData,
courseSessionId: number
): Promise<CircleDocument> {
courseSessionId: number,
bustCacheUrlKey = ""
) {
if (data.file === null) {
throw new Error("No file selected");
}
@ -82,22 +83,25 @@ export async function uploadCircleDocument(
const startData = await startFileUpload(data, courseSessionId);
await uploadFile(startData, data.file);
const response = await itPost(`/api/core/file/finish/`, {
const response = itPost(`/api/core/file/finish/`, {
file_id: startData.file_id,
});
const newDocument: CircleDocument = {
id: startData.id,
name: data.name,
file_name: data.file.name,
url: response.url,
course_session: courseSessionId,
learning_sequence: data.learningSequence.id,
};
if (bustCacheUrlKey) {
bustItGetCache(bustCacheUrlKey);
}
return Promise.resolve(newDocument);
return response;
}
export async function deleteCircleDocument(documentId: string) {
return itDelete(`/api/core/document/${documentId}/`);
export async function deleteCircleDocument(documentId: string, bustCacheUrlKey = "") {
const result = itDelete(`/api/core/document/${documentId}/`);
if (bustCacheUrlKey) {
bustItGetCache(bustCacheUrlKey);
}
return result;
}
export async function fetchCourseSessionDocuments(courseSessionId: number) {
return itGetCached(`/api/core/document/list/${courseSessionId}/`);
}

View File

@ -8,7 +8,6 @@ import type {
CompetenceProfilePage,
PerformanceCriteria,
} from "@/types";
import i18next from "i18next";
import _ from "lodash";
import cloneDeep from "lodash/cloneDeep";
import groupBy from "lodash/groupBy";
@ -17,9 +16,6 @@ import { defineStore } from "pinia";
export type CompetenceStoreState = {
competenceProfilePages: Map<string, CompetenceProfilePage>;
selectedCircle: { id: string; name: string };
availableCircles: { id: string; name: string }[];
circles: CircleLight[];
};
@ -28,8 +24,6 @@ export const useCompetenceStore = defineStore({
state: () => {
return {
competenceProfilePages: new Map<string, CompetenceProfilePage>(),
selectedCircle: { id: "all", name: `Circle: ${i18next.t("Alle")}` },
availableCircles: [],
circles: [],
} as CompetenceStoreState;
},
@ -52,13 +46,7 @@ export const useCompetenceStore = defineStore({
};
},
criteriaByCompetence(competence: CompetencePage) {
return competence.children.filter((criteria) => {
if (this.selectedCircle.id != "all") {
return criteria.circle.translation_key === this.selectedCircle.id;
}
return competence.children;
});
return competence.children;
},
competenceProfilePage(userId: string | undefined = undefined) {
if (!userId) {
@ -88,14 +76,10 @@ export const useCompetenceStore = defineStore({
["asc"]
);
if (this.selectedCircle.id !== "all") {
criteria = criteria.filter(
(c) => c.circle.translation_key === this.selectedCircle.id
);
}
if (circleId) {
criteria = criteria.filter((c) => circleId === c.circle.id);
criteria = criteria.filter(
(c) => circleId.toString() === c.circle.id.toString()
);
}
return criteria;
@ -116,12 +100,7 @@ export const useCompetenceStore = defineStore({
if (competenceProfilePage?.children.length) {
return _.orderBy(
competenceProfilePage.children.filter((competence) => {
let criteria = competence.children;
if (this.selectedCircle.id != "all") {
criteria = criteria.filter((criteria) => {
return criteria.circle.translation_key === this.selectedCircle.id;
});
}
const criteria = competence.children;
return criteria.length > 0;
}),
["competence_id"],
@ -159,10 +138,6 @@ export const useCompetenceStore = defineStore({
this.competenceProfilePages.set(userId, cloneDeep(competenceProfilePage));
this.circles = competenceProfilePage.circles;
const circles = competenceProfilePage.circles.map((c: CircleLight) => {
return { id: c.translation_key, name: `Circle: ${c.title}` };
});
this.availableCircles = [{ id: "all", name: "Circle: Alle" }, ...circles];
await this.parseCompletionData(userId);

View File

@ -146,35 +146,6 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
return Boolean(isCourseExpert && (inLearningPath() || inCompetenceProfile()));
});
// const canUploadCircleDocuments = computed(() => {
// const userStore = useUserStore();
// return (
// circleExperts.value.filter((expert) => expert.user_id === userStore.id).length > 0
// );
// });
// const circleDocuments = computed(() => {
// const circleStore = useCircleStore();
//
// return (
// circleStore.circle?.learningSequences
// .map((ls) => ({ id: ls.id, title: ls.title, documents: [] }))
// .map((ls: { id: number; title: string; documents: CircleDocument[] }) => {
// if (currentCourseSession.value === undefined) {
// return ls;
// }
//
// for (const document of currentCourseSession.value.documents) {
// if (document.learning_sequence === ls.id) {
// ls.documents.push(document);
// }
// }
// return ls;
// })
// .filter((ls) => ls.documents.length > 0) || []
// );
// });
function hasCockpit(courseSession: CourseSession) {
const userStore = useUserStore();
return (
@ -183,10 +154,6 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
);
}
// function addDocument(document: CircleDocument) {
// currentCourseSession.value?.documents.push(document);
// }
function allDueDates() {
const allDueDatesReturn: DueDate[] = [];
@ -211,26 +178,6 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
});
}
// async function startUpload() {
// log.debug("loadCourseSessionsData called");
// allCourseSessions.value = await itPost(`/api/core/file/start`, {
// file_type: "image/png",
// file_name: "test.png",
// });
// }
//
// async function removeDocument(documentId: string) {
// await deleteCircleDocument(documentId);
//
// if (currentCourseSession.value === undefined) {
// return;
// }
//
// currentCourseSession.value.documents = currentCourseSession.value?.documents.filter(
// (d) => d.id !== documentId
// );
// }
return {
uniqueCourseSessionsByCourse,
allCurrentCourseSessions,
@ -240,9 +187,6 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
hasCockpit,
hasCourseSessionPreview,
currentCourseSessionHasCockpit,
// addDocument,
// startUpload,
// removeDocument,
allDueDates,
// use `useCurrentCourseSession` whenever possible

View File

@ -569,7 +569,6 @@ export interface CourseSessionDetail {
assignments: CourseSessionAssignment[];
attendance_courses: CourseSessionAttendanceCourse[];
edoniq_tests: CourseSessionEdoniqTest[];
documents: CircleDocument[];
users: CourseSessionUser[];
}

View File

@ -31,12 +31,12 @@ from vbv_lernwelt.course.views import (
document_direct_upload,
document_upload_finish,
document_upload_start,
get_course_session_users,
get_course_sessions,
mark_course_completion_view,
request_course_completion,
request_course_completion_for_user,
)
from vbv_lernwelt.course_session.views import get_course_session_documents
from vbv_lernwelt.edoniq_test.views import (
export_students,
export_students_and_trainers,
@ -90,6 +90,10 @@ urlpatterns = [
path('server/documents/', include(wagtaildocs_urls)),
path('server/pages/', include(wagtail_urls)),
# core
re_path(r"server/core/icons/$", generate_web_component_icons,
name="generate_web_component_icons"),
# user management
path("sso/", include("vbv_lernwelt.sso.urls")),
re_path(r'api/core/me/$', me_user_view, name='me_user_view'),
@ -104,10 +108,6 @@ urlpatterns = [
re_path(r"api/notify/email_notification_settings/$", email_notification_settings,
name='email_notification_settings'),
# core
re_path(r"server/core/icons/$", generate_web_component_icons,
name="generate_web_component_icons"),
# course
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
# path(r"api/course/sessions/<signed_int:course_session_id>/users/",
@ -139,6 +139,9 @@ urlpatterns = [
name='file_upload_finish'),
path(r"api/core/document/local/<str:file_id>/", document_direct_upload,
name='file_upload_local'),
path(r'api/core/document/list/<str:course_session_id>/',
get_course_session_documents,
name='get_course_session_documents'),
# feedback
path(r'api/core/feedback/<str:course_session_id>/summary/',

View File

@ -8,22 +8,22 @@ from graphql import GraphQLError
from rest_framework.exceptions import PermissionDenied
from vbv_lernwelt.course.models import (
CircleDocument,
Course,
CourseBasePage,
CoursePage,
CourseSession,
CourseSessionUser,
CircleDocument,
)
from vbv_lernwelt.course.permissions import has_course_access
from vbv_lernwelt.course_session.graphql.types import (
CourseSessionAttendanceCourseObjectType,
CourseSessionAssignmentObjectType,
CourseSessionAttendanceCourseObjectType,
CourseSessionEdoniqTestObjectType,
)
from vbv_lernwelt.course_session.models import (
CourseSessionAttendanceCourse,
CourseSessionAssignment,
CourseSessionAttendanceCourse,
CourseSessionEdoniqTest,
)
from vbv_lernwelt.learnpath.graphql.types import LearningPathObjectType

View File

@ -322,6 +322,9 @@ class CircleDocument(models.Model):
"learnpath.LearningSequence", on_delete=models.CASCADE
)
def get_circle(self):
return self.learning_sequence.get_circle()
@property
def url(self) -> str:
return self.file.url

View File

@ -89,6 +89,8 @@ class CourseSessionSerializer(serializers.ModelSerializer):
class CircleDocumentSerializer(serializers.ModelSerializer):
learning_sequence = serializers.SerializerMethodField()
class Meta:
model = CircleDocument
fields = [
@ -100,6 +102,20 @@ class CircleDocumentSerializer(serializers.ModelSerializer):
"learning_sequence",
]
def get_learning_sequence(self, obj):
ls = obj.learning_sequence
circle = ls.get_circle()
return {
"title": ls.title,
"id": ls.id,
"slug": ls.slug,
"circle": {
"title": circle.title,
"id": circle.id,
"slug": circle.slug,
},
}
class DocumentUploadStartInputSerializer(serializers.Serializer):
file_name = serializers.CharField()

View File

@ -3,8 +3,8 @@ from graphene_django import DjangoObjectType
from vbv_lernwelt.course.permissions import is_course_session_expert
from vbv_lernwelt.course_session.models import (
CourseSessionAttendanceCourse,
CourseSessionAssignment,
CourseSessionAttendanceCourse,
CourseSessionEdoniqTest,
)
from vbv_lernwelt.course_session.services.attendance import AttendanceUserStatus

View File

@ -1,3 +1,21 @@
from django.shortcuts import render
from rest_framework.decorators import api_view
from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response
# Create your views here.
from vbv_lernwelt.course.models import CircleDocument
from vbv_lernwelt.course.permissions import has_course_session_access
from vbv_lernwelt.course.serializers import CircleDocumentSerializer
@api_view(["GET"])
def get_course_session_documents(request, course_session_id):
if not has_course_session_access(request.user, course_session_id):
raise PermissionDenied()
circle_documents = CircleDocument.objects.filter(
course_session_id=course_session_id
)
return Response(
status=200, data=CircleDocumentSerializer(circle_documents, many=True).data
)

View File

@ -6,12 +6,12 @@ from vbv_lernwelt.learnpath.graphql.types import (
LearningContentAssignmentObjectType,
LearningContentAttendanceCourseObjectType,
LearningContentDocumentListObjectType,
LearningContentEdoniqTestObjectType,
LearningContentFeedbackObjectType,
LearningContentLearningModuleObjectType,
LearningContentMediaLibraryObjectType,
LearningContentPlaceholderObjectType,
LearningContentRichTextObjectType,
LearningContentEdoniqTestObjectType,
LearningContentVideoObjectType,
LearningPathObjectType,
)