Refactor document handling

This commit is contained in:
Daniel Egger 2023-10-06 18:06:08 +02:00
parent f75590dd0b
commit 000e963730
10 changed files with 148 additions and 74 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 }\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 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 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 }\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"];
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"];
/**
* 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

@ -326,6 +326,7 @@ type CourseSessionObjectType {
attendance_courses: [CourseSessionAttendanceCourseObjectType]
assignments: [CourseSessionAssignmentObjectType]
edoniq_tests: [CourseSessionEdoniqTestObjectType]
documents: [CircleDocumentObjectType]
users: [CourseSessionUserObjectsType]
}
@ -421,9 +422,9 @@ type CourseSessionAssignmentObjectType {
type CourseSessionEdoniqTestObjectType {
id: ID!
learning_content: LearningContentEdoniqTestObjectType
deadline: DueDateObjectType
course_session_id: ID
learning_content_id: ID
deadline: DueDateObjectType
}
type LearningContentEdoniqTestObjectType implements LearningContentInterface {
@ -442,6 +443,15 @@ type LearningContentEdoniqTestObjectType implements LearningContentInterface {
content: String
}
type CircleDocumentObjectType {
id: UUID!
name: String!
course_session: CourseSessionObjectType!
learning_sequence: LearningSequenceObjectType!
file_name: String
url: String
}
type CourseSessionUserObjectsType {
id: UUID!
role: String

View File

@ -9,6 +9,7 @@ export const AttendanceUserInputType = "AttendanceUserInputType";
export const AttendanceUserObjectType = "AttendanceUserObjectType";
export const AttendanceUserStatus = "AttendanceUserStatus";
export const Boolean = "Boolean";
export const CircleDocumentObjectType = "CircleDocumentObjectType";
export const CircleObjectType = "CircleObjectType";
export const CompetenceCertificateListObjectType = "CompetenceCertificateListObjectType";
export const CompetenceCertificateObjectType = "CompetenceCertificateObjectType";

View File

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

View File

@ -1,11 +1,10 @@
<script setup lang="ts">
import { useCurrentCourseSession } from "@/composables";
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
import { useCockpitStore } from "@/stores/cockpit";
import ItModal from "@/components/ui/ItModal.vue";
import DocumentUploadForm from "@/pages/cockpit/documentPage/DocumentUploadForm.vue";
import { computed, ref, watch } from "vue";
import { useCircleStore } from "@/stores/circle";
import { useTranslation } from "i18next-vue";
import type { CircleDocument, DocumentUploadData } from "@/types";
import dialog from "@/utils/confirm-dialog";
@ -13,18 +12,21 @@ import log from "loglevel";
import { uploadCircleDocument } from "@/services/files";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import DocumentListItem from "@/components/circle/DocumentListItem.vue";
import { useCircleStore } from "@/stores/circle";
const cockpitStore = useCockpitStore();
const courseSession = useCurrentCourseSession();
const circleStore = useCircleStore();
const courseSessionsStore = useCourseSessionsStore();
const courseSessionDetailResults = useCourseSessionDetailQuery();
const circleStore = useCircleStore();
const { t } = useTranslation();
const showUploadModal = ref(false);
const showUploadErrorMessage = ref(false);
const isUploading = ref(false);
const dropdownLearningSequences = computed(() =>
circleStore.circle?.learningSequences.map((sequence) => ({
id: sequence.id,
@ -32,6 +34,14 @@ const dropdownLearningSequences = computed(() =>
}))
);
const circleDocuments = computed(() => {
const documents =
courseSessionDetailResults.courseSessionDetail.value?.documents ?? [];
return documents.filter(
(d) => d.learning_sequence.circle.slug === cockpitStore.currentCircle?.slug
);
});
const deleteDocument = async (doc: CircleDocument) => {
const options = {
title: t("circlePage.documents.deleteModalTitle"),
@ -84,7 +94,7 @@ async function uploadDocument(data: DocumentUploadData) {
<div class="mb-9 flex flex-col lg:flex-row lg:items-center lg:justify-between">
<h2>{{ t("a.Unterlagen für Teilnehmenden") }}</h2>
<ItDropdownSelect
:model-value="cockpitStore.selectedCircle"
:model-value="cockpitStore.currentCircle"
class="mt-4 w-full lg:mt-0 lg:w-96"
:items="cockpitStore.circles"
@update:model-value="cockpitStore.setCurrentCourseCircleFromEvent"
@ -96,23 +106,15 @@ async function uploadDocument(data: DocumentUploadData) {
{{ t("circlePage.documents.action") }}
</button>
<ul
v-if="courseSessionsStore.circleDocuments.length"
class="mt-8 border-t border-t-gray-500"
>
<template
v-for="learningSequence of courseSessionsStore.circleDocuments"
:key="learningSequence.id"
>
<DocumentListItem
v-for="doc of learningSequence.documents"
:key="doc.url"
:subtitle="learningSequence.title"
:can-delete="courseSessionsStore.canUploadCircleDocuments"
:doc="doc"
@delete="deleteDocument(doc)"
/>
</template>
<ul v-if="circleDocuments.length" class="mt-8 border-t border-t-gray-500">
<DocumentListItem
v-for="doc of circleDocuments"
:key="doc.url"
:subtitle="doc.learning_sequence.title"
:can-delete="true"
:doc="doc"
@delete="deleteDocument(doc)"
/>
</ul>
</div>
<ItModal v-model="showUploadModal">

View File

@ -8,28 +8,31 @@
{{ $t("circlePage.documents.userDescription") }}
</div>
</div>
<ul
v-if="courseSessionsStore.circleDocuments.length"
class="mt-8 border-t border-t-gray-500"
>
<template
v-for="learningSequence of courseSessionsStore.circleDocuments"
:key="learningSequence.id"
>
<DocumentListItem
v-for="doc of learningSequence.documents"
:key="doc.url"
:subtitle="learningSequence.title"
:doc="doc"
/>
</template>
<ul v-if="circleDocuments.length" class="mt-8 border-t border-t-gray-500">
<DocumentListItem
v-for="doc of circleDocuments"
:key="doc.url"
:subtitle="doc.learning_sequence.title"
:doc="doc"
/>
</ul>
</div>
</template>
<script setup lang="ts">
import { useCourseSessionsStore } from "@/stores/courseSessions";
import DocumentListItem from "@/components/circle/DocumentListItem.vue";
import { useCourseSessionDetailQuery } from "@/composables";
import { computed } from "vue";
import { useCircleStore } from "@/stores/circle";
const courseSessionsStore = useCourseSessionsStore();
const courseSessionDetailResults = useCourseSessionDetailQuery();
const circleStore = useCircleStore();
const circleDocuments = computed(() => {
const documents =
courseSessionDetailResults.courseSessionDetail.value?.documents ?? [];
return documents.filter(
(d) => d.learning_sequence.circle.slug === circleStore.circle?.slug
);
});
</script>

View File

@ -1,6 +1,5 @@
import { itGetCached, itPost } from "@/fetchHelpers";
import { deleteCircleDocument } from "@/services/files";
import type { CircleDocument, CourseSession, DueDate } from "@/types";
import { itGetCached } from "@/fetchHelpers";
import type { CourseSession, DueDate } from "@/types";
import eventBus from "@/utils/eventBus";
import { useRouteLookups } from "@/utils/route";
import { useLocalStorage } from "@vueuse/core";
@ -184,9 +183,9 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
);
}
function addDocument(document: CircleDocument) {
currentCourseSession.value?.documents.push(document);
}
// function addDocument(document: CircleDocument) {
// currentCourseSession.value?.documents.push(document);
// }
function allDueDates() {
const allDueDatesReturn: DueDate[] = [];
@ -212,25 +211,25 @@ 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
);
}
// 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,
@ -241,9 +240,9 @@ export const useCourseSessionsStore = defineStore("courseSessions", () => {
hasCockpit,
hasCourseSessionPreview,
currentCourseSessionHasCockpit,
addDocument,
startUpload,
removeDocument,
// addDocument,
// startUpload,
// removeDocument,
allDueDates,
// use `useCurrentCourseSession` whenever possible

View File

@ -462,8 +462,11 @@ export interface CircleDocument {
name: string;
file_name: string;
url: string;
course_session: number;
learning_sequence: number;
learning_sequence: {
id: string;
title: string;
circle: CircleLight;
};
}
export interface CourseSessionAttendanceCourse {
@ -566,6 +569,7 @@ export interface CourseSessionDetail {
assignments: CourseSessionAssignment[];
attendance_courses: CourseSessionAttendanceCourse[];
edoniq_tests: CourseSessionEdoniqTest[];
documents: CircleDocument[];
users: CourseSessionUser[];
}

View File

@ -13,6 +13,7 @@ from vbv_lernwelt.course.models import (
CoursePage,
CourseSession,
CourseSessionUser,
CircleDocument,
)
from vbv_lernwelt.course.permissions import has_course_access
from vbv_lernwelt.course_session.graphql.types import (
@ -142,10 +143,34 @@ class CourseSessionUserObjectsType(DjangoObjectType):
return self.expert.all().values("id", "title", "slug", "translation_key")
class CircleDocumentObjectType(DjangoObjectType):
file_name = graphene.String()
url = graphene.String()
class Meta:
model = CircleDocument
fields = [
"id",
"name",
"file_name",
"url",
"course_session",
"learning_sequence",
]
def resolve_file_name(self, info):
return self.file_name
def resolve_url(self, info):
return self.url
class CourseSessionObjectType(DjangoObjectType):
attendance_courses = graphene.List(CourseSessionAttendanceCourseObjectType)
assignments = graphene.List(CourseSessionAssignmentObjectType)
edoniq_tests = graphene.List(CourseSessionEdoniqTestObjectType)
documents = graphene.List(CircleDocumentObjectType)
users = graphene.List(CourseSessionUserObjectsType)
class Meta:
@ -158,7 +183,6 @@ class CourseSessionObjectType(DjangoObjectType):
"title",
"start_date",
"end_date",
"attendance_courses",
"users",
)
@ -171,5 +195,10 @@ class CourseSessionObjectType(DjangoObjectType):
def resolve_edoniq_tests(self, info):
return CourseSessionEdoniqTest.objects.filter(course_session=self)
def resolve_documents(self, info):
return CircleDocument.objects.filter(
course_session=self, file__upload_finished_at__isnull=False
)
def resolve_users(self, info):
return CourseSessionUser.objects.filter(course_session_id=self.id).distinct()