Merged develop into feature/VBV-757-clone-course-data-for-completion
This commit is contained in:
commit
180958bd42
|
|
@ -8,7 +8,12 @@
|
||||||
</h3>
|
</h3>
|
||||||
<h4 class="grid-in-subtitle">{{ subtitle }}</h4>
|
<h4 class="grid-in-subtitle">{{ subtitle }}</h4>
|
||||||
<div class="flex items-center justify-end gap-x-4 grid-in-icons">
|
<div class="flex items-center justify-end gap-x-4 grid-in-icons">
|
||||||
<a v-if="canDelete" class="flex cursor-pointer" @click="emit('delete')">
|
<a
|
||||||
|
v-if="canDelete"
|
||||||
|
class="flex cursor-pointer"
|
||||||
|
data-cy="document-delete-button"
|
||||||
|
@click="emit('delete')"
|
||||||
|
>
|
||||||
<it-icon-delete class="h-8 w-8" />
|
<it-icon-delete class="h-8 w-8" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,11 @@
|
||||||
import DocumentListItem from "@/components/circle/DocumentListItem.vue";
|
import DocumentListItem from "@/components/circle/DocumentListItem.vue";
|
||||||
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
import ItDropdownSelect from "@/components/ui/ItDropdownSelect.vue";
|
||||||
import ItModal from "@/components/ui/ItModal.vue";
|
import ItModal from "@/components/ui/ItModal.vue";
|
||||||
import { useCourseData, useCurrentCourseSession } from "@/composables";
|
import {
|
||||||
|
useCourseData,
|
||||||
|
useCourseSessionDetailQuery,
|
||||||
|
useCurrentCourseSession,
|
||||||
|
} from "@/composables";
|
||||||
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
|
import { useExpertCockpitPageData } from "@/pages/cockpit/cockpitPage/composables";
|
||||||
import DocumentUploadForm from "@/pages/cockpit/documentPage/DocumentUploadForm.vue";
|
import DocumentUploadForm from "@/pages/cockpit/documentPage/DocumentUploadForm.vue";
|
||||||
import {
|
import {
|
||||||
|
|
@ -12,6 +16,7 @@ import {
|
||||||
} from "@/services/files";
|
} from "@/services/files";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
import { useExpertCockpitStore } from "@/stores/expertCockpit";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
import type { CircleDocument, DocumentUploadData } from "@/types";
|
import type { CircleDocument, DocumentUploadData } from "@/types";
|
||||||
import dialog from "@/utils/confirm-dialog";
|
import dialog from "@/utils/confirm-dialog";
|
||||||
import { useTranslation } from "i18next-vue";
|
import { useTranslation } from "i18next-vue";
|
||||||
|
|
@ -21,6 +26,9 @@ import { computed, onMounted, ref, watch } from "vue";
|
||||||
const cockpitStore = useExpertCockpitStore();
|
const cockpitStore = useExpertCockpitStore();
|
||||||
const courseSession = useCurrentCourseSession();
|
const courseSession = useCurrentCourseSession();
|
||||||
const courseSessionsStore = useCourseSessionsStore();
|
const courseSessionsStore = useCourseSessionsStore();
|
||||||
|
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const courseData = useCourseData(courseSession.value?.course.slug);
|
const courseData = useCourseData(courseSession.value?.course.slug);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
@ -71,6 +79,19 @@ const circleDocuments = computed(() => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const canEditDocuments = computed(() => {
|
||||||
|
const circleExperts = courseSessionDetailResult.filterCircleExperts(
|
||||||
|
cockpitStore.currentCircle?.slug || ""
|
||||||
|
);
|
||||||
|
// hack-ish way to check if the user is an expert for the circle
|
||||||
|
// supervistors are not allowd to edit documents if they are not experts in the circle
|
||||||
|
return circleExperts.some(
|
||||||
|
(expert) =>
|
||||||
|
expert.user_id === userStore.id &&
|
||||||
|
expert.id.indexOf("as-ephemeral-supervisor") === -1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const deleteDocument = async (doc: CircleDocument) => {
|
const deleteDocument = async (doc: CircleDocument) => {
|
||||||
const options = {
|
const options = {
|
||||||
title: t("circlePage.documents.deleteModalTitle"),
|
title: t("circlePage.documents.deleteModalTitle"),
|
||||||
|
|
@ -134,18 +155,22 @@ async function uploadDocument(data: DocumentUploadData) {
|
||||||
@update:model-value="cockpitStore.setCurrentCourseCircleFromEvent"
|
@update:model-value="cockpitStore.setCurrentCourseCircleFromEvent"
|
||||||
></ItDropdownSelect>
|
></ItDropdownSelect>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white p-6">
|
<div class="bg-white p-6">
|
||||||
<button class="btn-primary text-xl" @click="showUploadModal = true">
|
<button
|
||||||
|
v-if="canEditDocuments"
|
||||||
|
class="btn-primary mb-6 text-xl"
|
||||||
|
data-cy="document-upload-button"
|
||||||
|
@click="showUploadModal = true"
|
||||||
|
>
|
||||||
{{ t("circlePage.documents.action") }}
|
{{ t("circlePage.documents.action") }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul v-if="circleDocuments.length" class="mt-8 border-t border-t-gray-500">
|
<ul v-if="circleDocuments.length" class="border-t border-t-gray-500">
|
||||||
<DocumentListItem
|
<DocumentListItem
|
||||||
v-for="doc of circleDocuments"
|
v-for="doc of circleDocuments"
|
||||||
:key="doc.url"
|
:key="doc.url"
|
||||||
:subtitle="doc.learning_sequence.title"
|
:subtitle="doc.learning_sequence.title"
|
||||||
:can-delete="true"
|
:can-delete="canEditDocuments"
|
||||||
:doc="doc"
|
:doc="doc"
|
||||||
@delete="deleteDocument(doc)"
|
@delete="deleteDocument(doc)"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,11 @@
|
||||||
{{ $t("circlePage.documents.userDescription") }}
|
{{ $t("circlePage.documents.userDescription") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul v-if="circleDocuments.length" class="mt-8 border-t border-t-gray-500">
|
<ul
|
||||||
|
v-if="circleDocuments.length"
|
||||||
|
class="mt-8 border-t border-t-gray-500"
|
||||||
|
data-cy="circle-page-documents"
|
||||||
|
>
|
||||||
<DocumentListItem
|
<DocumentListItem
|
||||||
v-for="doc of circleDocuments"
|
v-for="doc of circleDocuments"
|
||||||
:key="doc.url"
|
:key="doc.url"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { login } from "../helpers";
|
||||||
|
|
||||||
|
describe("cockpitDocuments.cy.js", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.manageCommand("cypress_reset");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Cockpit Document list", () => {
|
||||||
|
it("Trainer sees document mutation buttons", () => {
|
||||||
|
login("test-trainer1@example.com", "test");
|
||||||
|
cy.visit("/course/test-lehrgang/cockpit/documents");
|
||||||
|
|
||||||
|
cy.get('[data-cy="document-upload-button"]').should("exist");
|
||||||
|
cy.get('[data-cy="document-delete-button"]').should("exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Supervisor does not see document mutation buttons", () => {
|
||||||
|
login("test-supervisor1@example.com", "test");
|
||||||
|
cy.visit("/course/test-lehrgang/cockpit/documents");
|
||||||
|
|
||||||
|
cy.get('[data-cy="document-upload-button"]').should("not.exist");
|
||||||
|
cy.get('[data-cy="document-delete-button"]').should("not.exist");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Preview", () => {
|
||||||
|
it("Supervisor sees documents list", () => {
|
||||||
|
login("test-supervisor1@example.com", "test");
|
||||||
|
cy.visit("/course/test-lehrgang/learn/fahrzeug");
|
||||||
|
|
||||||
|
cy.get('[data-cy="circle-page-documents"]').should("exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Berufsbildner sees document list", () => {
|
||||||
|
login("test-berufsbildner1@example.com", "test");
|
||||||
|
cy.visit("/course/test-lehrgang/learn/fahrzeug");
|
||||||
|
|
||||||
|
cy.get('[data-cy="circle-page-documents"]').should("exist");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -2,6 +2,7 @@ from collections import deque
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from dateutil.relativedelta import MO, TH, TU, WE, relativedelta
|
from dateutil.relativedelta import MO, TH, TU, WE, relativedelta
|
||||||
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
from wagtail.rich_text import RichText
|
from wagtail.rich_text import RichText
|
||||||
|
|
@ -48,6 +49,7 @@ from vbv_lernwelt.core.utils import safe_deque_popleft
|
||||||
from vbv_lernwelt.course.consts import COURSE_TEST_ID
|
from vbv_lernwelt.course.consts import COURSE_TEST_ID
|
||||||
from vbv_lernwelt.course.factories import CoursePageFactory
|
from vbv_lernwelt.course.factories import CoursePageFactory
|
||||||
from vbv_lernwelt.course.models import (
|
from vbv_lernwelt.course.models import (
|
||||||
|
CircleDocument,
|
||||||
Course,
|
Course,
|
||||||
CourseCategory,
|
CourseCategory,
|
||||||
CourseConfiguration,
|
CourseConfiguration,
|
||||||
|
|
@ -63,12 +65,14 @@ from vbv_lernwelt.course_session.models import (
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
||||||
from vbv_lernwelt.feedback.services import update_feedback_response
|
from vbv_lernwelt.feedback.services import update_feedback_response
|
||||||
|
from vbv_lernwelt.files.models import UploadFile
|
||||||
from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation
|
from vbv_lernwelt.learning_mentor.models import AgentParticipantRelation
|
||||||
from vbv_lernwelt.learnpath.models import (
|
from vbv_lernwelt.learnpath.models import (
|
||||||
Circle,
|
Circle,
|
||||||
LearningContentAssignment,
|
LearningContentAssignment,
|
||||||
LearningContentAttendanceCourse,
|
LearningContentAttendanceCourse,
|
||||||
LearningContentEdoniqTest,
|
LearningContentEdoniqTest,
|
||||||
|
LearningSequence,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.learnpath.tests.learning_path_factories import (
|
from vbv_lernwelt.learnpath.tests.learning_path_factories import (
|
||||||
CircleFactory,
|
CircleFactory,
|
||||||
|
|
@ -187,6 +191,27 @@ def create_test_course(
|
||||||
)
|
)
|
||||||
csac.due_date.save()
|
csac.due_date.save()
|
||||||
|
|
||||||
|
# create fake doc (will be uploaded to aws)
|
||||||
|
ls = LearningSequence.objects.get(
|
||||||
|
title="Vorbereitung",
|
||||||
|
slug="test-lehrgang-lp-circle-fahrzeug-ls-vorbereitung",
|
||||||
|
)
|
||||||
|
fake_file = SimpleUploadedFile(
|
||||||
|
"fake_file.txt", b"file_content_here", content_type="text/plain"
|
||||||
|
)
|
||||||
|
pseudo_file = UploadFile.objects.create(
|
||||||
|
file=fake_file,
|
||||||
|
original_file_name="Test Dokument",
|
||||||
|
file_name="Test Dokument",
|
||||||
|
file_type="txt",
|
||||||
|
)
|
||||||
|
CircleDocument.objects.create(
|
||||||
|
file=pseudo_file,
|
||||||
|
name="Test Dokument)",
|
||||||
|
course_session=cs_bern,
|
||||||
|
learning_sequence=ls,
|
||||||
|
)
|
||||||
|
|
||||||
if include_vv:
|
if include_vv:
|
||||||
csac = CourseSessionAttendanceCourse.objects.create(
|
csac = CourseSessionAttendanceCourse.objects.create(
|
||||||
course_session=cs_bern,
|
course_session=cs_bern,
|
||||||
|
|
|
||||||
|
|
@ -86,16 +86,10 @@ class CourseSessionSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
course = serializers.SerializerMethodField()
|
course = serializers.SerializerMethodField()
|
||||||
actions = serializers.SerializerMethodField()
|
actions = serializers.SerializerMethodField()
|
||||||
user_roles = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
def get_course(self, obj):
|
def get_course(self, obj):
|
||||||
return CourseSerializer(obj.course).data
|
return CourseSerializer(obj.course).data
|
||||||
|
|
||||||
def get_user_roles(self, obj):
|
|
||||||
if hasattr(obj, "roles"):
|
|
||||||
return list(obj.roles)
|
|
||||||
return []
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CourseSession
|
model = CourseSession
|
||||||
fields = [
|
fields = [
|
||||||
|
|
@ -107,7 +101,6 @@ class CourseSessionSerializer(serializers.ModelSerializer):
|
||||||
"start_date",
|
"start_date",
|
||||||
"end_date",
|
"end_date",
|
||||||
"actions",
|
"actions",
|
||||||
"user_roles",
|
|
||||||
]
|
]
|
||||||
read_only_fields = ["actions"]
|
read_only_fields = ["actions"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,14 @@ from rest_framework.response import Response
|
||||||
|
|
||||||
from vbv_lernwelt.course.models import CircleDocument
|
from vbv_lernwelt.course.models import CircleDocument
|
||||||
from vbv_lernwelt.course.serializers import CircleDocumentSerializer
|
from vbv_lernwelt.course.serializers import CircleDocumentSerializer
|
||||||
from vbv_lernwelt.iam.permissions import has_course_session_access
|
from vbv_lernwelt.iam.permissions import (
|
||||||
|
has_course_session_document_access,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@api_view(["GET"])
|
@api_view(["GET"])
|
||||||
def get_course_session_documents(request, course_session_id):
|
def get_course_session_documents(request, course_session_id):
|
||||||
if not has_course_session_access(request.user, course_session_id):
|
if not has_course_session_document_access(request.user, course_session_id):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
circle_documents = CircleDocument.objects.filter(
|
circle_documents = CircleDocument.objects.filter(
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,21 @@ def has_course_session_access(user, course_session_id: int):
|
||||||
).exists()
|
).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def has_course_session_document_access(user, course_session_id: int):
|
||||||
|
if user.is_superuser:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return (
|
||||||
|
CourseSessionUser.objects.filter(
|
||||||
|
course_session_id=course_session_id, user=user
|
||||||
|
).exists()
|
||||||
|
or is_course_session_berufsbildner(user, course_session_id)
|
||||||
|
or CourseSessionGroup.objects.filter(
|
||||||
|
course_session=course_session_id, supervisor=user.id
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def has_course_session_preview(user, course_session_id: int):
|
def has_course_session_preview(user, course_session_id: int):
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from vbv_lernwelt.course.creators.test_utils import (
|
||||||
|
create_course,
|
||||||
|
create_course_session,
|
||||||
|
create_user,
|
||||||
|
)
|
||||||
|
from vbv_lernwelt.course.models import CourseSessionUser
|
||||||
|
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
||||||
|
from vbv_lernwelt.iam.permissions import (
|
||||||
|
has_course_session_document_access,
|
||||||
|
)
|
||||||
|
from vbv_lernwelt.learning_mentor.models import (
|
||||||
|
AgentParticipantRelation,
|
||||||
|
AgentParticipantRoleType,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionsTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.course, _ = create_course("Test Course")
|
||||||
|
self.course_session = create_course_session(
|
||||||
|
course=self.course, title="Test Session"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.other_course_session = create_course_session(
|
||||||
|
course=self.course, title="Other Session"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.user = create_user("user")
|
||||||
|
|
||||||
|
def test_regionenleiter_has_course_session_document_access(self):
|
||||||
|
# GIVEN
|
||||||
|
csg = CourseSessionGroup.objects.create(name="Test Group", course=self.course)
|
||||||
|
csg.course_session.add(self.course_session)
|
||||||
|
csg.supervisor.add(self.user)
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
has_access = has_course_session_document_access(
|
||||||
|
self.user, self.course_session.id
|
||||||
|
)
|
||||||
|
|
||||||
|
some = CourseSessionGroup.objects.filter(
|
||||||
|
course_session=self.course_session.id, supervisor=self.user.id
|
||||||
|
)
|
||||||
|
print(some)
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertTrue(has_access)
|
||||||
|
|
||||||
|
def test_regionenleiter_has_no_course_session_document_access(self):
|
||||||
|
# GIVEN
|
||||||
|
csg = CourseSessionGroup.objects.create(name="Test Group", course=self.course)
|
||||||
|
csg.course_session.add(self.other_course_session)
|
||||||
|
csg.supervisor.add(self.user)
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
has_access = has_course_session_document_access(
|
||||||
|
self.user, self.course_session.id
|
||||||
|
)
|
||||||
|
|
||||||
|
some = CourseSessionGroup.objects.filter(
|
||||||
|
course_session=self.course_session.id, supervisor=self.user.id
|
||||||
|
)
|
||||||
|
print(some)
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertFalse(has_access)
|
||||||
|
|
||||||
|
def test_expert_has_course_session_document_access(self):
|
||||||
|
# GIVEN
|
||||||
|
_csu = CourseSessionUser.objects.create(
|
||||||
|
course_session=self.course_session,
|
||||||
|
user=self.user,
|
||||||
|
role=CourseSessionUser.Role.EXPERT,
|
||||||
|
)
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
has_access = has_course_session_document_access(
|
||||||
|
self.user, self.course_session.id
|
||||||
|
)
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertTrue(has_access)
|
||||||
|
|
||||||
|
def test_expert_has_no_course_session_document_access(self):
|
||||||
|
# GIVEN
|
||||||
|
_csu = CourseSessionUser.objects.create(
|
||||||
|
course_session=self.course_session,
|
||||||
|
user=self.user,
|
||||||
|
role=CourseSessionUser.Role.EXPERT,
|
||||||
|
)
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
has_access = has_course_session_document_access(
|
||||||
|
self.user, self.other_course_session.id
|
||||||
|
)
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertFalse(has_access)
|
||||||
|
|
||||||
|
def test_member_has_course_session_document_access(self):
|
||||||
|
# GIVEN
|
||||||
|
_csu = CourseSessionUser.objects.create(
|
||||||
|
course_session=self.course_session,
|
||||||
|
user=self.user,
|
||||||
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
|
)
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
has_access = has_course_session_document_access(
|
||||||
|
self.user, self.course_session.id
|
||||||
|
)
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertTrue(has_access)
|
||||||
|
|
||||||
|
def test_member_has_no_course_session_document_access(self):
|
||||||
|
# GIVEN
|
||||||
|
_csu = CourseSessionUser.objects.create(
|
||||||
|
course_session=self.course_session,
|
||||||
|
user=self.user,
|
||||||
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
|
)
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
has_access = has_course_session_document_access(
|
||||||
|
self.user, self.other_course_session.id
|
||||||
|
)
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertFalse(has_access)
|
||||||
|
|
||||||
|
def test_berufsbildner_has_course_session_document_access(self):
|
||||||
|
# GIVEN
|
||||||
|
member = create_user("member")
|
||||||
|
_csu = CourseSessionUser.objects.create(
|
||||||
|
course_session=self.course_session,
|
||||||
|
user=member,
|
||||||
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
|
)
|
||||||
|
|
||||||
|
AgentParticipantRelation.objects.create(
|
||||||
|
agent=self.user,
|
||||||
|
participant=_csu,
|
||||||
|
role=AgentParticipantRoleType.BERUFSBILDNER.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
has_access = has_course_session_document_access(
|
||||||
|
self.user, self.course_session.id
|
||||||
|
)
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertTrue(has_access)
|
||||||
|
|
||||||
|
def test_berufsbildner_has_no_course_session_document_access(self):
|
||||||
|
# GIVEN
|
||||||
|
member = create_user("member")
|
||||||
|
_csu = CourseSessionUser.objects.create(
|
||||||
|
course_session=self.other_course_session,
|
||||||
|
user=member,
|
||||||
|
role=CourseSessionUser.Role.MEMBER,
|
||||||
|
)
|
||||||
|
|
||||||
|
AgentParticipantRelation.objects.create(
|
||||||
|
agent=self.user,
|
||||||
|
participant=_csu,
|
||||||
|
role=AgentParticipantRoleType.BERUFSBILDNER.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
# WHEN
|
||||||
|
has_access = has_course_session_document_access(
|
||||||
|
self.user, self.course_session.id
|
||||||
|
)
|
||||||
|
|
||||||
|
# THEN
|
||||||
|
self.assertFalse(has_access)
|
||||||
Loading…
Reference in New Issue