fix: api permissions
This commit is contained in:
@@ -26,8 +26,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col overflow-y-auto">
|
<div class="flex flex-col overflow-y-auto">
|
||||||
<div class="p-5">
|
<div class="p-5 space-y-5">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between">
|
||||||
<div class="font-semibold text-ink-gray-9">
|
<div class="font-semibold text-ink-gray-9">
|
||||||
{{ __('Submission') }}
|
{{ __('Submission') }}
|
||||||
</div>
|
</div>
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
!['Pass', 'Fail'].includes(submissionResource.doc?.status) &&
|
!['Pass', 'Fail'].includes(submissionResource.doc?.status) &&
|
||||||
submissionResource.doc?.owner == user.data?.name
|
submissionResource.doc?.owner == user.data?.name
|
||||||
"
|
"
|
||||||
class="bg-surface-blue-2 text-ink-blue-2 p-3 rounded-md leading-5 text-sm mb-4"
|
class="bg-surface-blue-2 text-ink-blue-2 p-3 rounded-md leading-5 text-sm"
|
||||||
>
|
>
|
||||||
{{ __("You've successfully submitted the assignment.") }}
|
{{ __("You've successfully submitted the assignment.") }}
|
||||||
{{
|
{{
|
||||||
@@ -63,12 +63,17 @@
|
|||||||
}}
|
}}
|
||||||
{{ __('Feel free to make edits to your submission if needed.') }}
|
{{ __('Feel free to make edits to your submission if needed.') }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showUploader()">
|
<div v-if="showUploader()" class="border rounded-lg p-3">
|
||||||
<div class="text-xs text-ink-gray-5 mt-1 mb-2">
|
<div class="font-semibold mb-2">
|
||||||
{{ __('Add your assignment as {0}').format(assignment.data.type) }}
|
{{ __('Upload Assignment') }}
|
||||||
|
</div>
|
||||||
|
<div class="text-ink-gray-5 text-sm mt-1 mb-4">
|
||||||
|
{{
|
||||||
|
__('You can only upload {0} files').format(assignment.data.type)
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
<FileUploader
|
<FileUploader
|
||||||
v-if="!submissionFile"
|
v-if="!submissionResource.doc?.assignment_attachment"
|
||||||
:fileTypes="getType()"
|
:fileTypes="getType()"
|
||||||
:uploadArgs="{
|
:uploadArgs="{
|
||||||
private: true,
|
private: true,
|
||||||
@@ -87,21 +92,24 @@
|
|||||||
</template>
|
</template>
|
||||||
</FileUploader>
|
</FileUploader>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div class="flex text-ink-gray-7">
|
<div class="flex items-center text-ink-gray-7">
|
||||||
<div class="border self-start rounded-md p-2 mr-2">
|
|
||||||
<FileText class="h-5 w-5 stroke-1.5" />
|
|
||||||
</div>
|
|
||||||
<a
|
<a
|
||||||
:href="submissionFile.file_url"
|
:href="submissionResource.doc.assignment_attachment"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="flex flex-col cursor-pointer !no-underline"
|
class="cursor-pointer !no-underline text-sm leading-5"
|
||||||
>
|
>
|
||||||
<span class="text-sm leading-5">
|
<div class="flex items-center">
|
||||||
{{ submissionFile.file_name }}
|
<div class="border rounded-md p-2 mr-2">
|
||||||
</span>
|
<FileText class="h-5 w-5 stroke-1.5" />
|
||||||
<span class="text-sm text-ink-gray-5 mt-1">
|
</div>
|
||||||
{{ getFileSize(submissionFile.file_size) }}
|
<span>
|
||||||
</span>
|
{{
|
||||||
|
submissionResource.doc.assignment_attachment
|
||||||
|
.split('/')
|
||||||
|
.pop()
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<X
|
<X
|
||||||
v-if="canModifyAssignment"
|
v-if="canModifyAssignment"
|
||||||
@@ -142,13 +150,13 @@
|
|||||||
user.data?.name == submissionResource.doc?.owner &&
|
user.data?.name == submissionResource.doc?.owner &&
|
||||||
submissionResource.doc?.comments
|
submissionResource.doc?.comments
|
||||||
"
|
"
|
||||||
class="mt-8 p-3 bg-surface-blue-2 rounded-md"
|
class="mt-8 p-3 border rounded-lg"
|
||||||
>
|
>
|
||||||
<div class="text-sm text-ink-gray-5 font-medium mb-2">
|
<div class="text-ink-gray-5 mb-4">
|
||||||
{{ __('Comments by Evaluator') }}:
|
{{ __('Comments by Evaluator') }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="leading-5 text-ink-gray-9"
|
class="leading-6 text-ink-gray-9"
|
||||||
v-html="submissionResource.doc.comments"
|
v-html="submissionResource.doc.comments"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -204,10 +212,8 @@ import {
|
|||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { computed, inject, onMounted, onBeforeUnmount, ref, watch } from 'vue'
|
import { computed, inject, onMounted, onBeforeUnmount, ref, watch } from 'vue'
|
||||||
import { FileText, X } from 'lucide-vue-next'
|
import { FileText, X } from 'lucide-vue-next'
|
||||||
import { getFileSize } from '@/utils'
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const submissionFile = ref(null)
|
|
||||||
const answer = ref(null)
|
const answer = ref(null)
|
||||||
const comments = ref(null)
|
const comments = ref(null)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -266,9 +272,7 @@ const newSubmission = createResource({
|
|||||||
assignment: props.assignmentID,
|
assignment: props.assignmentID,
|
||||||
member: user.data?.name,
|
member: user.data?.name,
|
||||||
}
|
}
|
||||||
if (showUploader()) {
|
if (!showUploader()) {
|
||||||
doc.assignment_attachment = submissionFile.value.file_url
|
|
||||||
} else {
|
|
||||||
doc.answer = answer.value
|
doc.answer = answer.value
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -277,19 +281,6 @@ const newSubmission = createResource({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const imageResource = createResource({
|
|
||||||
url: 'lms.lms.api.get_file_info',
|
|
||||||
makeParams(values) {
|
|
||||||
return {
|
|
||||||
file_url: values.image,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
auto: false,
|
|
||||||
onSuccess(data) {
|
|
||||||
submissionFile.value = data
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const submissionResource = createDocumentResource({
|
const submissionResource = createDocumentResource({
|
||||||
doctype: 'LMS Assignment Submission',
|
doctype: 'LMS Assignment Submission',
|
||||||
name: props.submissionName,
|
name: props.submissionName,
|
||||||
@@ -302,11 +293,6 @@ const submissionResource = createDocumentResource({
|
|||||||
|
|
||||||
watch(submissionResource, () => {
|
watch(submissionResource, () => {
|
||||||
if (submissionResource.doc) {
|
if (submissionResource.doc) {
|
||||||
if (submissionResource.doc.assignment_attachment) {
|
|
||||||
imageResource.reload({
|
|
||||||
image: submissionResource.doc.assignment_attachment,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (submissionResource.doc.answer) {
|
if (submissionResource.doc.answer) {
|
||||||
answer.value = submissionResource.doc.answer
|
answer.value = submissionResource.doc.answer
|
||||||
}
|
}
|
||||||
@@ -315,7 +301,10 @@ watch(submissionResource, () => {
|
|||||||
}
|
}
|
||||||
if (submissionResource.isDirty) {
|
if (submissionResource.isDirty) {
|
||||||
isDirty.value = true
|
isDirty.value = true
|
||||||
} else if (showUploader() && !submissionFile.value) {
|
} else if (
|
||||||
|
showUploader() &&
|
||||||
|
!submissionResource.doc.assignment_attachment
|
||||||
|
) {
|
||||||
isDirty.value = true
|
isDirty.value = true
|
||||||
} else if (!showUploader() && !answer.value) {
|
} else if (!showUploader() && !answer.value) {
|
||||||
isDirty.value = true
|
isDirty.value = true
|
||||||
@@ -325,11 +314,17 @@ watch(submissionResource, () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(submissionFile, () => {
|
watch(
|
||||||
if (props.submissionName == 'new' && submissionFile.value) {
|
() => submissionResource.doc,
|
||||||
isDirty.value = true
|
() => {
|
||||||
|
if (
|
||||||
|
props.submissionName == 'new' &&
|
||||||
|
submissionResource.doc?.assignment_attachment
|
||||||
|
) {
|
||||||
|
isDirty.value = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
const submitAssignment = () => {
|
const submitAssignment = () => {
|
||||||
if (props.submissionName != 'new') {
|
if (props.submissionName != 'new') {
|
||||||
@@ -341,13 +336,13 @@ const submitAssignment = () => {
|
|||||||
submissionResource.setValue.submit(
|
submissionResource.setValue.submit(
|
||||||
{
|
{
|
||||||
...submissionResource.doc,
|
...submissionResource.doc,
|
||||||
assignment_attachment: submissionFile.value?.file_url,
|
|
||||||
evaluator: evaluator,
|
evaluator: evaluator,
|
||||||
comments: comments.value,
|
comments: comments.value,
|
||||||
answer: answer.value,
|
answer: answer.value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
|
isDirty.value = false
|
||||||
toast.success(__('Changes saved successfully'))
|
toast.success(__('Changes saved successfully'))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -388,7 +383,7 @@ const addNewSubmission = () => {
|
|||||||
|
|
||||||
const saveSubmission = (file) => {
|
const saveSubmission = (file) => {
|
||||||
isDirty.value = true
|
isDirty.value = true
|
||||||
submissionFile.value = file
|
submissionResource.doc.assignment_attachment = file.file_url
|
||||||
}
|
}
|
||||||
|
|
||||||
const markLessonProgress = () => {
|
const markLessonProgress = () => {
|
||||||
@@ -439,7 +434,7 @@ const validateFile = (file) => {
|
|||||||
|
|
||||||
const removeSubmission = () => {
|
const removeSubmission = () => {
|
||||||
isDirty.value = true
|
isDirty.value = true
|
||||||
submissionFile.value = null
|
submissionResource.doc.assignment_attachment = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const canGradeSubmission = computed(() => {
|
const canGradeSubmission = computed(() => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="batch.data" class="border-2 rounded-md p-5 lg:w-72">
|
<div v-if="batch.data" class="border-2 rounded-md p-5 lg:w-72">
|
||||||
<div
|
<div
|
||||||
v-if="batch.data.seat_count && seats_left > 0"
|
v-if="batch.data.seat_count && batch.data.seats_left > 0"
|
||||||
class="text-sm bg-green-100 text-green-700 px-2 py-1 rounded-md"
|
class="text-sm bg-green-100 text-green-700 px-2 py-1 rounded-md"
|
||||||
:class="
|
:class="
|
||||||
batch.data.amount || batch.data.courses.length
|
batch.data.amount || batch.data.courses.length
|
||||||
@@ -9,16 +9,16 @@
|
|||||||
: 'w-fit mb-4'
|
: 'w-fit mb-4'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ seats_left }}
|
{{ batch.data.seats_left }}
|
||||||
<span v-if="seats_left > 1">
|
<span v-if="batch.data.seats_left > 1">
|
||||||
{{ __('Seats Left') }}
|
{{ __('Seats Left') }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="seats_left == 1">
|
<span v-else-if="batch.data.seats_left == 1">
|
||||||
{{ __('Seat Left') }}
|
{{ __('Seat Left') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="batch.data.seat_count && seats_left <= 0"
|
v-else-if="batch.data.seat_count && batch.data.seats_left <= 0"
|
||||||
class="text-xs bg-red-100 text-red-700 float-right px-2 py-0.5 rounded-md"
|
class="text-xs bg-red-100 text-red-700 float-right px-2 py-0.5 rounded-md"
|
||||||
>
|
>
|
||||||
{{ __('Sold Out') }}
|
{{ __('Sold Out') }}
|
||||||
@@ -54,6 +54,7 @@
|
|||||||
{{ batch.data.timezone }}
|
{{ batch.data.timezone }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!readOnlyMode">
|
<div v-if="!readOnlyMode">
|
||||||
<router-link
|
<router-link
|
||||||
v-if="canAccessBatch"
|
v-if="canAccessBatch"
|
||||||
@@ -190,15 +191,10 @@ const enrollInBatch = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const seats_left = computed(() => {
|
|
||||||
if (props.batch.data?.seat_count) {
|
|
||||||
return props.batch.data?.seat_count - props.batch.data?.students?.length
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
const isStudent = computed(() => {
|
const isStudent = computed(() => {
|
||||||
return props.batch.data?.students?.includes(user.data?.name)
|
return user.data
|
||||||
|
? props.batch.data?.students?.includes(user.data?.name)
|
||||||
|
: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const isModerator = computed(() => {
|
const isModerator = computed(() => {
|
||||||
@@ -218,6 +214,9 @@ const isInstructor = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const canAccessBatch = computed(() => {
|
const canAccessBatch = computed(() => {
|
||||||
|
if (!user.data) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return isModerator.value || isStudent.value || isEvaluator.value
|
return isModerator.value || isStudent.value || isEvaluator.value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,12 @@
|
|||||||
<img
|
<img
|
||||||
v-if="type == 'image'"
|
v-if="type == 'image'"
|
||||||
:src="modelValue"
|
:src="modelValue"
|
||||||
class="border rounded-md w-44 h-auto"
|
:class="[
|
||||||
|
'border object-cover',
|
||||||
|
shape === 'circle'
|
||||||
|
? 'w-20 h-20 rounded-full'
|
||||||
|
: 'w-44 h-auto rounded-md',
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
<video v-else controls class="border rounded-md w-44 h-auto">
|
<video v-else controls class="border rounded-md w-44 h-auto">
|
||||||
<source :src="modelValue" />
|
<source :src="modelValue" />
|
||||||
@@ -72,6 +77,7 @@ const props = withDefaults(
|
|||||||
description?: string
|
description?: string
|
||||||
type?: 'image' | 'video'
|
type?: 'image' | 'video'
|
||||||
required?: boolean
|
required?: boolean
|
||||||
|
shape?: 'square' | 'circle'
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
modelValue: '',
|
modelValue: '',
|
||||||
@@ -79,6 +85,7 @@ const props = withDefaults(
|
|||||||
description: '',
|
description: '',
|
||||||
type: 'image',
|
type: 'image',
|
||||||
required: true,
|
required: true,
|
||||||
|
shape: 'square',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog
|
<Dialog
|
||||||
|
v-model="show"
|
||||||
:options="{
|
:options="{
|
||||||
size: '3xl',
|
size: '3xl',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #body-header>
|
<template #body-header>
|
||||||
<div class="flex items-center mb-5">
|
<div class="flex items-center justify-between mb-5">
|
||||||
<div class="text-2xl font-semibold leading-6 text-ink-gray-9">
|
<div class="text-2xl font-semibold leading-6 text-ink-gray-9">
|
||||||
{{ __('Edit Profile') }}
|
{{ __('Edit Profile') }}
|
||||||
</div>
|
</div>
|
||||||
<Badge v-if="isDirty" class="ml-4" theme="orange">
|
<div class="space-x-2">
|
||||||
{{ __('Not Saved') }}
|
<Badge v-if="isDirty" theme="orange">
|
||||||
</Badge>
|
{{ __('Not Saved') }}
|
||||||
|
</Badge>
|
||||||
|
<div class="pb-5 float-right">
|
||||||
|
<Button variant="solid" @click="saveProfile()">
|
||||||
|
{{ __('Save') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #body-content>
|
<template #body-content>
|
||||||
@@ -19,52 +27,13 @@
|
|||||||
<div class="grid grid-cols-2 gap-10">
|
<div class="grid grid-cols-2 gap-10">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<Uploader
|
||||||
<div class="text-xs text-ink-gray-5 mb-1">
|
v-model="profile.image"
|
||||||
{{ __('Profile Image') }}
|
:label="__('Profile Image')"
|
||||||
</div>
|
:required="true"
|
||||||
<FileUploader
|
shape="circle"
|
||||||
v-if="!profile.image"
|
/>
|
||||||
:fileTypes="['image/*']"
|
|
||||||
:validateFile="validateFile"
|
|
||||||
@success="(file) => saveImage(file)"
|
|
||||||
>
|
|
||||||
<template
|
|
||||||
v-slot="{ file, progress, uploading, openFileSelector }"
|
|
||||||
>
|
|
||||||
<div class="mb-4">
|
|
||||||
<Button @click="openFileSelector" :loading="uploading">
|
|
||||||
{{
|
|
||||||
uploading
|
|
||||||
? `Uploading ${progress}%`
|
|
||||||
: 'Upload a profile image'
|
|
||||||
}}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</FileUploader>
|
|
||||||
<div v-else class="mb-4">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<img
|
|
||||||
:src="profile.image?.file_url"
|
|
||||||
class="object-cover h-[50px] w-[50px] rounded-full border-4 border-white object-cover"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="text-base flex flex-col ml-2">
|
|
||||||
<span>
|
|
||||||
{{ profile.image?.file_name }}
|
|
||||||
</span>
|
|
||||||
<span class="text-sm text-ink-gray-4 mt-1">
|
|
||||||
{{ getFileSize(profile.image?.file_size) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<X
|
|
||||||
@click="removeImage()"
|
|
||||||
class="bg-surface-gray-3 rounded-md cursor-pointer stroke-1.5 w-5 h-5 p-1 ml-4"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<FormControl
|
<FormControl
|
||||||
v-model="profile.first_name"
|
v-model="profile.first_name"
|
||||||
:label="__('First Name')"
|
:label="__('First Name')"
|
||||||
@@ -115,13 +84,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #actions="{ close }">
|
|
||||||
<div class="pb-5 float-right">
|
|
||||||
<Button variant="solid" @click="saveProfile(close)">
|
|
||||||
{{ __('Save') }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -131,15 +93,14 @@ import {
|
|||||||
createResource,
|
createResource,
|
||||||
Dialog,
|
Dialog,
|
||||||
FormControl,
|
FormControl,
|
||||||
FileUploader,
|
|
||||||
TextEditor,
|
TextEditor,
|
||||||
toast,
|
toast,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { ref, reactive, watch } from 'vue'
|
import { ref, reactive, watch } from 'vue'
|
||||||
import { X } from 'lucide-vue-next'
|
import { sanitizeHTML } from '@/utils'
|
||||||
import { getFileSize, sanitizeHTML } from '@/utils'
|
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
|
|
||||||
|
const show = defineModel()
|
||||||
const reloadProfile = defineModel('reloadProfile')
|
const reloadProfile = defineModel('reloadProfile')
|
||||||
const hasLanguageChanged = ref(false)
|
const hasLanguageChanged = ref(false)
|
||||||
const isDirty = ref(false)
|
const isDirty = ref(false)
|
||||||
@@ -163,19 +124,6 @@ const profile = reactive({
|
|||||||
twitter: '',
|
twitter: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const imageResource = createResource({
|
|
||||||
url: 'lms.lms.api.get_file_info',
|
|
||||||
makeParams(values) {
|
|
||||||
return {
|
|
||||||
file_url: values.image,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
auto: false,
|
|
||||||
onSuccess(data) {
|
|
||||||
profile.image = data
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const updateProfile = createResource({
|
const updateProfile = createResource({
|
||||||
url: 'frappe.client.set_value',
|
url: 'frappe.client.set_value',
|
||||||
makeParams(values) {
|
makeParams(values) {
|
||||||
@@ -183,7 +131,7 @@ const updateProfile = createResource({
|
|||||||
doctype: 'User',
|
doctype: 'User',
|
||||||
name: props.profile.data.name,
|
name: props.profile.data.name,
|
||||||
fieldname: {
|
fieldname: {
|
||||||
user_image: profile.image?.file_url || null,
|
user_image: profile.image || null,
|
||||||
...profile,
|
...profile,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -193,13 +141,13 @@ const updateProfile = createResource({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const saveProfile = (close) => {
|
const saveProfile = () => {
|
||||||
profile.bio = sanitizeHTML(profile.bio)
|
profile.bio = sanitizeHTML(profile.bio)
|
||||||
updateProfile.submit(
|
updateProfile.submit(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
close()
|
show.value = false
|
||||||
reloadProfile.value.reload()
|
reloadProfile.value.reload()
|
||||||
if (hasLanguageChanged.value) {
|
if (hasLanguageChanged.value) {
|
||||||
hasLanguageChanged.value = false
|
hasLanguageChanged.value = false
|
||||||
@@ -213,21 +161,6 @@ const saveProfile = (close) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateFile = (file) => {
|
|
||||||
let extension = file.name.split('.').pop().toLowerCase()
|
|
||||||
if (!['jpg', 'jpeg', 'png'].includes(extension)) {
|
|
||||||
return 'Only image file is allowed.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveImage = (file) => {
|
|
||||||
profile.image = file
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeImage = () => {
|
|
||||||
profile.image = null
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => profile,
|
() => profile,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
@@ -240,7 +173,7 @@ watch(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (profile.image?.file_url !== props.profile.data.user_image) {
|
if (profile.image !== props.profile.data.user_image) {
|
||||||
isDirty.value = true
|
isDirty.value = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -262,7 +195,7 @@ watch(
|
|||||||
profile.linkedin = newVal.linkedin
|
profile.linkedin = newVal.linkedin
|
||||||
profile.github = newVal.github
|
profile.github = newVal.github
|
||||||
profile.twitter = newVal.twitter
|
profile.twitter = newVal.twitter
|
||||||
if (newVal.user_image) imageResource.submit({ image: newVal.user_image })
|
profile.image = newVal.user_image
|
||||||
isDirty.value = false
|
isDirty.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,8 +186,9 @@ const openProfile = (username: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteEvaluator = (evaluator: string) => {
|
const deleteEvaluator = (evaluator: string) => {
|
||||||
call('lms.lms.api.delete_evaluator', {
|
call('frappe.client.delete', {
|
||||||
evaluator: evaluator,
|
doctype: 'Course Evaluator',
|
||||||
|
name: evaluator,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success(__('Evaluator deleted successfully'))
|
toast.success(__('Evaluator deleted successfully'))
|
||||||
|
|||||||
@@ -269,12 +269,13 @@ const iconProps = {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setUpOnboarding()
|
setUpOnboarding()
|
||||||
addKeyboardShortcut()
|
addKeyboardShortcut()
|
||||||
|
updateSidebarLinks()
|
||||||
socket.on('publish_lms_notifications', (data) => {
|
socket.on('publish_lms_notifications', (data) => {
|
||||||
unreadNotifications.reload()
|
unreadNotifications.reload()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const setSidebarLinks = () => {
|
const updateSidebarLinksVisibility = () => {
|
||||||
sidebarSettings.reload(
|
sidebarSettings.reload(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
@@ -591,10 +592,18 @@ watch(userResource, async () => {
|
|||||||
await programs.reload()
|
await programs.reload()
|
||||||
setUpOnboarding()
|
setUpOnboarding()
|
||||||
}
|
}
|
||||||
sidebarLinks.value = getSidebarLinks()
|
updateSidebarLinks()
|
||||||
setSidebarLinks()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(settingsStore.settings, () => {
|
||||||
|
updateSidebarLinks()
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateSidebarLinks = () => {
|
||||||
|
sidebarLinks.value = getSidebarLinks()
|
||||||
|
updateSidebarLinksVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
const redirectToWebsite = () => {
|
const redirectToWebsite = () => {
|
||||||
window.open('https://frappe.io/learning', '_blank')
|
window.open('https://frappe.io/learning', '_blank')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ import {
|
|||||||
import { computed, inject, onMounted, ref } from 'vue'
|
import { computed, inject, onMounted, ref } from 'vue'
|
||||||
import { GraduationCap } from 'lucide-vue-next'
|
import { GraduationCap } from 'lucide-vue-next'
|
||||||
import { sessionStore } from '../stores/session'
|
import { sessionStore } from '../stores/session'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import EmptyState from '@/components/EmptyState.vue'
|
import EmptyState from '@/components/EmptyState.vue'
|
||||||
import UserAvatar from '@/components/UserAvatar.vue'
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
|
|
||||||
@@ -145,8 +146,14 @@ const hiring = ref(false)
|
|||||||
const { brand } = sessionStore()
|
const { brand } = sessionStore()
|
||||||
const memberCount = ref(0)
|
const memberCount = ref(0)
|
||||||
const dayjs = inject('$dayjs')
|
const dayjs = inject('$dayjs')
|
||||||
|
const user = inject('$user')
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
if (!user.data) {
|
||||||
|
router.push({ name: 'Courses' })
|
||||||
|
return
|
||||||
|
}
|
||||||
setFiltersFromQuery()
|
setFiltersFromQuery()
|
||||||
updateParticipants()
|
updateParticipants()
|
||||||
})
|
})
|
||||||
@@ -171,7 +178,7 @@ const categories = createListResource({
|
|||||||
doctype: 'LMS Certificate',
|
doctype: 'LMS Certificate',
|
||||||
url: 'lms.lms.api.get_certification_categories',
|
url: 'lms.lms.api.get_certification_categories',
|
||||||
cache: ['certification_categories'],
|
cache: ['certification_categories'],
|
||||||
auto: true,
|
auto: user.data ? true : false,
|
||||||
transform(data) {
|
transform(data) {
|
||||||
data.unshift({ label: __(' '), value: ' ' })
|
data.unshift({ label: __(' '), value: ' ' })
|
||||||
return data
|
return data
|
||||||
|
|||||||
@@ -326,6 +326,7 @@
|
|||||||
@updateNotes="updateNotes"
|
@updateNotes="updateNotes"
|
||||||
/>
|
/>
|
||||||
<VideoStatistics
|
<VideoStatistics
|
||||||
|
v-if="showStatsDialog"
|
||||||
v-model="showStatsDialog"
|
v-model="showStatsDialog"
|
||||||
:lessonName="lesson.data?.name"
|
:lessonName="lesson.data?.name"
|
||||||
:lessonTitle="lesson.data?.title"
|
:lessonTitle="lesson.data?.title"
|
||||||
@@ -870,6 +871,7 @@ const scrollDiscussionsIntoView = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateNotes = () => {
|
const updateNotes = () => {
|
||||||
|
if (!user.data) return
|
||||||
notes.update({
|
notes.update({
|
||||||
filters: {
|
filters: {
|
||||||
lesson: lesson.data?.name,
|
lesson: lesson.data?.name,
|
||||||
|
|||||||
@@ -185,10 +185,9 @@ const unReadNotifications = createListResource({
|
|||||||
doctype: 'Notification Log',
|
doctype: 'Notification Log',
|
||||||
url: 'lms.lms.api.get_notifications',
|
url: 'lms.lms.api.get_notifications',
|
||||||
filters: {
|
filters: {
|
||||||
for_user: user.data?.name,
|
|
||||||
read: 0,
|
read: 0,
|
||||||
},
|
},
|
||||||
auto: true,
|
auto: user.data ? true : false,
|
||||||
cache: 'Unread Notifications',
|
cache: 'Unread Notifications',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -196,18 +195,17 @@ const readNotifications = createListResource({
|
|||||||
doctype: 'Notification Log',
|
doctype: 'Notification Log',
|
||||||
url: 'lms.lms.api.get_notifications',
|
url: 'lms.lms.api.get_notifications',
|
||||||
filters: {
|
filters: {
|
||||||
for_user: user.data?.name,
|
|
||||||
read: 1,
|
read: 1,
|
||||||
},
|
},
|
||||||
auto: true,
|
auto: user.data ? true : false,
|
||||||
cache: 'Read Notifications',
|
cache: 'Read Notifications',
|
||||||
})
|
})
|
||||||
|
|
||||||
const markAsRead = createResource({
|
const markAsRead = createResource({
|
||||||
url: 'lms.lms.api.mark_as_read',
|
url: 'frappe.desk.doctype.notification_log.notification_log.mark_as_read',
|
||||||
makeParams(values) {
|
makeParams(values) {
|
||||||
return {
|
return {
|
||||||
name: values.name,
|
docname: values.name,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
@@ -217,7 +215,7 @@ const markAsRead = createResource({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const markAllAsRead = createResource({
|
const markAllAsRead = createResource({
|
||||||
url: 'lms.lms.api.mark_all_as_read',
|
url: 'frappe.desk.doctype.notification_log.notification_log.mark_all_as_read',
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
unReadNotifications.reload()
|
unReadNotifications.reload()
|
||||||
readNotifications.reload()
|
readNotifications.reload()
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ export const sessionStore = defineStore('lms-session', () => {
|
|||||||
let _sessionUser = cookies.get('user_id')
|
let _sessionUser = cookies.get('user_id')
|
||||||
if (_sessionUser === 'Guest') {
|
if (_sessionUser === 'Guest') {
|
||||||
_sessionUser = null
|
_sessionUser = null
|
||||||
|
} else {
|
||||||
|
userResource.reload()
|
||||||
}
|
}
|
||||||
return _sessionUser
|
return _sessionUser
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export const usersStore = defineStore('lms-users', () => {
|
|||||||
window.location.href = '/login'
|
window.location.href = '/login'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
auto: true,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const allUsers = createResource({
|
const allUsers = createResource({
|
||||||
|
|||||||
@@ -490,6 +490,9 @@ const getSidebarItems = () => {
|
|||||||
icon: 'GraduationCap',
|
icon: 'GraduationCap',
|
||||||
to: 'CertifiedParticipants',
|
to: 'CertifiedParticipants',
|
||||||
activeFor: ['CertifiedParticipants'],
|
activeFor: ['CertifiedParticipants'],
|
||||||
|
condition: () => {
|
||||||
|
return userResource?.data
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Jobs',
|
label: 'Jobs',
|
||||||
|
|||||||
215
lms/lms/api.py
215
lms/lms/api.py
@@ -35,10 +35,14 @@ from lms.lms.utils import (
|
|||||||
get_course_details,
|
get_course_details,
|
||||||
get_instructors,
|
get_instructors,
|
||||||
get_lesson_count,
|
get_lesson_count,
|
||||||
|
has_course_instructor_role,
|
||||||
|
has_evaluator_role,
|
||||||
|
has_moderator_role,
|
||||||
|
has_student_role,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist()
|
||||||
def get_user_info():
|
def get_user_info():
|
||||||
if frappe.session.user == "Guest":
|
if frappe.session.user == "Guest":
|
||||||
return None
|
return None
|
||||||
@@ -222,7 +226,6 @@ def get_chart_details():
|
|||||||
return details
|
return details
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_file_info(file_url):
|
def get_file_info(file_url):
|
||||||
"""Get file info for the given file URL."""
|
"""Get file info for the given file URL."""
|
||||||
file_info = frappe.db.get_value(
|
file_info = frappe.db.get_value(
|
||||||
@@ -234,17 +237,20 @@ def get_file_info(file_url):
|
|||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
def get_branding():
|
def get_branding():
|
||||||
"""Get branding details."""
|
"""Get branding details."""
|
||||||
website_settings = frappe.get_single("Website Settings")
|
fields = ["app_name"]
|
||||||
image_fields = ["banner_image", "footer_logo", "favicon"]
|
image_fields = ["banner_image", "footer_logo", "favicon", "app_logo"]
|
||||||
|
fields = fields + image_fields
|
||||||
|
settings = frappe._dict()
|
||||||
|
|
||||||
for field in image_fields:
|
for field in fields:
|
||||||
if website_settings.get(field):
|
value = frappe.get_cached_value("Website Settings", None, field)
|
||||||
file_info = get_file_info(website_settings.get(field))
|
if field in image_fields and value:
|
||||||
website_settings.update({field: json.loads(json.dumps(file_info))})
|
file_info = get_file_info(value)
|
||||||
|
settings.update({field: json.loads(json.dumps(file_info))})
|
||||||
else:
|
else:
|
||||||
website_settings.update({field: None})
|
settings.update({field: value})
|
||||||
|
|
||||||
return website_settings
|
return settings
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -284,7 +290,7 @@ def get_evaluator_details(evaluator):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist()
|
||||||
def get_certified_participants(filters=None, start=0, page_length=100):
|
def get_certified_participants(filters=None, start=0, page_length=100):
|
||||||
query = get_certification_query(filters)
|
query = get_certification_query(filters)
|
||||||
query = query.orderby("issue_date", order=frappe.qb.desc).offset(start).limit(page_length)
|
query = query.orderby("issue_date", order=frappe.qb.desc).offset(start).limit(page_length)
|
||||||
@@ -338,14 +344,14 @@ def get_certification_query(filters):
|
|||||||
return query
|
return query
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist()
|
||||||
def get_count_of_certified_members(filters=None):
|
def get_count_of_certified_members(filters=None):
|
||||||
query = get_certification_query(filters)
|
query = get_certification_query(filters)
|
||||||
result = query.run(as_dict=True)
|
result = query.run(as_dict=True)
|
||||||
return len(result) or 0
|
return len(result) or 0
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist()
|
||||||
def get_certification_categories():
|
def get_certification_categories():
|
||||||
categories = []
|
categories = []
|
||||||
seen = set()
|
seen = set()
|
||||||
@@ -367,20 +373,6 @@ def get_certification_categories():
|
|||||||
return categories
|
return categories
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_assigned_badges(member):
|
|
||||||
assigned_badges = frappe.get_all(
|
|
||||||
"LMS Badge Assignment",
|
|
||||||
{"member": member},
|
|
||||||
["badge"],
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
for badge in assigned_badges:
|
|
||||||
badge.update(frappe.db.get_value("LMS Badge", badge.badge, ["name", "title", "image"]))
|
|
||||||
return assigned_badges
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_all_users():
|
def get_all_users():
|
||||||
frappe.only_for(["Moderator", "Course Creator", "Batch Evaluator"])
|
frappe.only_for(["Moderator", "Course Creator", "Batch Evaluator"])
|
||||||
@@ -395,28 +387,13 @@ def get_all_users():
|
|||||||
return {user.name: user for user in users}
|
return {user.name: user for user in users}
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def mark_as_read(name):
|
|
||||||
doc = frappe.get_doc("Notification Log", name)
|
|
||||||
doc.read = 1
|
|
||||||
doc.save(ignore_permissions=True)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def mark_all_as_read():
|
|
||||||
notifications = frappe.get_all(
|
|
||||||
"Notification Log", {"for_user": frappe.session.user, "read": 0}, pluck="name"
|
|
||||||
)
|
|
||||||
|
|
||||||
for notification in notifications:
|
|
||||||
mark_as_read(notification)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
def get_sidebar_settings():
|
def get_sidebar_settings():
|
||||||
lms_settings = frappe.get_single("LMS Settings")
|
lms_settings = frappe.get_single("LMS Settings")
|
||||||
sidebar_items = frappe._dict()
|
if not lms_settings.allow_guest_access:
|
||||||
|
return []
|
||||||
|
|
||||||
|
sidebar_items = frappe._dict()
|
||||||
items = [
|
items = [
|
||||||
"courses",
|
"courses",
|
||||||
"batches",
|
"batches",
|
||||||
@@ -445,6 +422,7 @@ def get_sidebar_settings():
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_sidebar_item(webpage, icon):
|
def update_sidebar_item(webpage, icon):
|
||||||
|
frappe.only_for("Moderator")
|
||||||
filters = {
|
filters = {
|
||||||
"web_page": webpage,
|
"web_page": webpage,
|
||||||
"parenttype": "LMS Settings",
|
"parenttype": "LMS Settings",
|
||||||
@@ -463,6 +441,7 @@ def update_sidebar_item(webpage, icon):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def delete_sidebar_item(webpage):
|
def delete_sidebar_item(webpage):
|
||||||
|
frappe.only_for("Moderator")
|
||||||
return frappe.db.delete(
|
return frappe.db.delete(
|
||||||
"LMS Sidebar Item",
|
"LMS Sidebar Item",
|
||||||
{
|
{
|
||||||
@@ -476,6 +455,10 @@ def delete_sidebar_item(webpage):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def delete_lesson(lesson, chapter):
|
def delete_lesson(lesson, chapter):
|
||||||
|
course = frappe.db.get_value("Course Chapter", chapter, "course")
|
||||||
|
if not can_modify_course(course):
|
||||||
|
frappe.throw(_("You do not have permission to delete this lesson."), frappe.PermissionError)
|
||||||
|
|
||||||
# Delete Reference
|
# Delete Reference
|
||||||
chapter = frappe.get_doc("Course Chapter", chapter)
|
chapter = frappe.get_doc("Course Chapter", chapter)
|
||||||
chapter.lessons = [row for row in chapter.lessons if row.lesson != lesson]
|
chapter.lessons = [row for row in chapter.lessons if row.lesson != lesson]
|
||||||
@@ -490,8 +473,11 @@ def delete_lesson(lesson, chapter):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_lesson_index(lesson, sourceChapter, targetChapter, idx):
|
def update_lesson_index(lesson, sourceChapter, targetChapter, idx):
|
||||||
hasMoved = sourceChapter == targetChapter
|
course = frappe.db.get_value("Course Chapter", sourceChapter, "course")
|
||||||
|
if not can_modify_course(course):
|
||||||
|
frappe.throw(_("You do not have permission to modify this lesson."), frappe.PermissionError)
|
||||||
|
|
||||||
|
hasMoved = sourceChapter == targetChapter
|
||||||
update_source_chapter(lesson, sourceChapter, idx, hasMoved)
|
update_source_chapter(lesson, sourceChapter, idx, hasMoved)
|
||||||
if not hasMoved:
|
if not hasMoved:
|
||||||
update_target_chapter(lesson, targetChapter, idx)
|
update_target_chapter(lesson, targetChapter, idx)
|
||||||
@@ -550,6 +536,10 @@ def update_index(lessons, chapter):
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_chapter_index(chapter, course, idx):
|
def update_chapter_index(chapter, course, idx):
|
||||||
"""Update the index of a chapter within a course"""
|
"""Update the index of a chapter within a course"""
|
||||||
|
|
||||||
|
if not can_modify_course(course):
|
||||||
|
frappe.throw(_("You do not have permission to modify this chapter."), frappe.PermissionError)
|
||||||
|
|
||||||
chapters = frappe.get_all(
|
chapters = frappe.get_all(
|
||||||
"Chapter Reference",
|
"Chapter Reference",
|
||||||
{"parent": course},
|
{"parent": course},
|
||||||
@@ -566,26 +556,9 @@ def update_chapter_index(chapter, course, idx):
|
|||||||
frappe.db.set_value("Chapter Reference", {"chapter": chapter_name, "parent": course}, "idx", i + 1)
|
frappe.db.set_value("Chapter Reference", {"chapter": chapter_name, "parent": course}, "idx", i + 1)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
|
||||||
def get_categories(doctype, filters):
|
|
||||||
categoryOptions = []
|
|
||||||
|
|
||||||
categories = frappe.get_all(
|
|
||||||
doctype,
|
|
||||||
filters,
|
|
||||||
pluck="category",
|
|
||||||
)
|
|
||||||
categories = list(set(categories))
|
|
||||||
|
|
||||||
for category in categories:
|
|
||||||
if category:
|
|
||||||
categoryOptions.append({"label": category, "value": category})
|
|
||||||
|
|
||||||
return categoryOptions
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_members(start=0, search=""):
|
def get_members(start=0, search=""):
|
||||||
|
frappe.only_for(["Moderator"])
|
||||||
filters = {"enabled": 1, "name": ["not in", ["Administrator", "Guest"]]}
|
filters = {"enabled": 1, "name": ["not in", ["Administrator", "Guest"]]}
|
||||||
or_filters = {}
|
or_filters = {}
|
||||||
|
|
||||||
@@ -652,6 +625,7 @@ def save_evaluation_details(
|
|||||||
"""
|
"""
|
||||||
Save evaluation details for a member against a course.
|
Save evaluation details for a member against a course.
|
||||||
"""
|
"""
|
||||||
|
frappe.only_for(["Batch Evaluator", "Moderator"])
|
||||||
evaluation = frappe.db.exists("LMS Certificate Evaluation", {"member": member, "course": course})
|
evaluation = frappe.db.exists("LMS Certificate Evaluation", {"member": member, "course": course})
|
||||||
|
|
||||||
details = {
|
details = {
|
||||||
@@ -695,6 +669,7 @@ def save_certificate_details(
|
|||||||
"""
|
"""
|
||||||
Save certificate details for a member against a course.
|
Save certificate details for a member against a course.
|
||||||
"""
|
"""
|
||||||
|
frappe.only_for(["Batch Evaluator", "Moderator"])
|
||||||
certificate = frappe.db.exists("LMS Certificate", {"member": member, "course": course})
|
certificate = frappe.db.exists("LMS Certificate", {"member": member, "course": course})
|
||||||
|
|
||||||
details = {
|
details = {
|
||||||
@@ -729,16 +704,9 @@ def delete_documents(doctype, documents):
|
|||||||
frappe.delete_doc(doctype, doc)
|
frappe.delete_doc(doctype, doc)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
|
||||||
def get_count(doctype, filters):
|
|
||||||
return frappe.db.count(
|
|
||||||
doctype,
|
|
||||||
filters=filters,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_payment_gateway_details(payment_gateway):
|
def get_payment_gateway_details(payment_gateway):
|
||||||
|
frappe.only_for("Moderator")
|
||||||
gateway = frappe.get_doc("Payment Gateway", payment_gateway)
|
gateway = frappe.get_doc("Payment Gateway", payment_gateway)
|
||||||
|
|
||||||
if gateway.gateway_controller is None:
|
if gateway.gateway_controller is None:
|
||||||
@@ -794,6 +762,7 @@ def get_transformed_fields(meta, data=None):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_new_gateway_fields(doctype):
|
def get_new_gateway_fields(doctype):
|
||||||
|
frappe.only_for("Moderator")
|
||||||
try:
|
try:
|
||||||
meta = frappe.get_meta(doctype).fields
|
meta = frappe.get_meta(doctype).fields
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -824,6 +793,18 @@ def update_course_statistics():
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_announcements(batch):
|
def get_announcements(batch):
|
||||||
|
roles = frappe.get_roles()
|
||||||
|
is_batch_student = frappe.db.exists(
|
||||||
|
"LMS Batch Enrollment", {"batch": batch, "member": frappe.session.user}
|
||||||
|
)
|
||||||
|
is_moderator = "Moderator" in roles
|
||||||
|
is_evaluator = "Batch Evaluator" in roles
|
||||||
|
|
||||||
|
if not (is_batch_student or is_moderator or is_evaluator):
|
||||||
|
frappe.throw(
|
||||||
|
_("You do not have permission to access announcements for this batch."), frappe.PermissionError
|
||||||
|
)
|
||||||
|
|
||||||
communications = frappe.get_all(
|
communications = frappe.get_all(
|
||||||
"Communication",
|
"Communication",
|
||||||
filters={
|
filters={
|
||||||
@@ -848,8 +829,21 @@ def get_announcements(batch):
|
|||||||
return communications
|
return communications
|
||||||
|
|
||||||
|
|
||||||
|
def can_modify_course(course):
|
||||||
|
is_instructor = frappe.db.exists(
|
||||||
|
"Course Instructor",
|
||||||
|
{"instructor": frappe.session.user, "parent": course, "parenttype": "LMS Course"},
|
||||||
|
)
|
||||||
|
if not (has_moderator_role() or is_instructor):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def delete_course(course):
|
def delete_course(course):
|
||||||
|
if not can_modify_course(course):
|
||||||
|
frappe.throw(_("You do not have permission to delete this course."), frappe.PermissionError)
|
||||||
|
|
||||||
frappe.db.delete("LMS Enrollment", {"course": course})
|
frappe.db.delete("LMS Enrollment", {"course": course})
|
||||||
frappe.db.delete("LMS Course Progress", {"course": course})
|
frappe.db.delete("LMS Course Progress", {"course": course})
|
||||||
frappe.db.set_value("LMS Quiz", {"course": course}, "course", None)
|
frappe.db.set_value("LMS Quiz", {"course": course}, "course", None)
|
||||||
@@ -882,8 +876,25 @@ def delete_course(course):
|
|||||||
frappe.delete_doc("LMS Course", course)
|
frappe.delete_doc("LMS Course", course)
|
||||||
|
|
||||||
|
|
||||||
|
def can_modify_batch(batch):
|
||||||
|
is_instructor = frappe.db.exists(
|
||||||
|
"Course Instructor",
|
||||||
|
{
|
||||||
|
"instructor": frappe.session.user,
|
||||||
|
"parent": batch,
|
||||||
|
"parenttype": "LMS Batch",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if not (has_moderator_role() or is_instructor):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def delete_batch(batch):
|
def delete_batch(batch):
|
||||||
|
if not can_modify_batch(batch):
|
||||||
|
frappe.throw(_("You do not have permission to delete this batch."), frappe.PermissionError)
|
||||||
|
|
||||||
frappe.db.delete("LMS Batch Enrollment", {"batch": batch})
|
frappe.db.delete("LMS Batch Enrollment", {"batch": batch})
|
||||||
frappe.db.delete("Batch Course", {"parent": batch, "parenttype": "LMS Batch"})
|
frappe.db.delete("Batch Course", {"parent": batch, "parenttype": "LMS Batch"})
|
||||||
frappe.db.delete("LMS Assessment", {"parent": batch, "parenttype": "LMS Batch"})
|
frappe.db.delete("LMS Assessment", {"parent": batch, "parenttype": "LMS Batch"})
|
||||||
@@ -927,6 +938,9 @@ def give_discussions_permission():
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def upsert_chapter(title, course, is_scorm_package, scorm_package, name=None):
|
def upsert_chapter(title, course, is_scorm_package, scorm_package, name=None):
|
||||||
|
if not can_modify_course(course):
|
||||||
|
frappe.throw(_("You do not have permission to modify this chapter."), frappe.PermissionError)
|
||||||
|
|
||||||
values = frappe._dict({"title": title, "course": course, "is_scorm_package": is_scorm_package})
|
values = frappe._dict({"title": title, "course": course, "is_scorm_package": is_scorm_package})
|
||||||
|
|
||||||
if is_scorm_package:
|
if is_scorm_package:
|
||||||
@@ -1049,6 +1063,10 @@ def add_lesson(title, chapter, course, idx):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def delete_chapter(chapter):
|
def delete_chapter(chapter):
|
||||||
|
course = frappe.db.get_value("Course Chapter", chapter, "course")
|
||||||
|
if not can_modify_course(course):
|
||||||
|
frappe.throw(_("You do not have permission to delete this chapter."), frappe.PermissionError)
|
||||||
|
|
||||||
chapterInfo = frappe.db.get_value(
|
chapterInfo = frappe.db.get_value(
|
||||||
"Course Chapter", chapter, ["is_scorm_package", "scorm_package_path"], as_dict=True
|
"Course Chapter", chapter, ["is_scorm_package", "scorm_package_path"], as_dict=True
|
||||||
)
|
)
|
||||||
@@ -1091,9 +1109,9 @@ def mark_lesson_progress(course, chapter_number, lesson_number):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_heatmap_data(member=None, base_days=200):
|
def get_heatmap_data(member, base_days=200):
|
||||||
if not member:
|
if not (has_course_instructor_role() or has_moderator_role() or has_evaluator_role()):
|
||||||
member = frappe.session.user
|
frappe.throw(_("You do not have permission to access heatmap data."), frappe.PermissionError)
|
||||||
|
|
||||||
base_date, start_date, number_of_days, days = calculate_date_ranges(base_days)
|
base_date, start_date, number_of_days, days = calculate_date_ranges(base_days)
|
||||||
date_count = initialize_date_count(days)
|
date_count = initialize_date_count(days)
|
||||||
@@ -1206,6 +1224,8 @@ def get_week_difference(start_date, current_date):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_notifications(filters):
|
def get_notifications(filters):
|
||||||
|
filters = frappe._dict(filters or {})
|
||||||
|
filters.for_user = frappe.session.user
|
||||||
notifications = frappe.get_all(
|
notifications = frappe.get_all(
|
||||||
"Notification Log",
|
"Notification Log",
|
||||||
filters,
|
filters,
|
||||||
@@ -1305,9 +1325,8 @@ def get_lms_settings():
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def cancel_evaluation(evaluation):
|
def cancel_evaluation(evaluation):
|
||||||
evaluation = frappe._dict(evaluation)
|
evaluation = frappe._dict(evaluation)
|
||||||
|
|
||||||
if evaluation.member != frappe.session.user:
|
if evaluation.member != frappe.session.user:
|
||||||
return
|
frappe.throw(_("You do not have permission to cancel this evaluation."), frappe.PermissionError)
|
||||||
|
|
||||||
frappe.db.set_value("LMS Certificate Request", evaluation.name, "status", "Cancelled")
|
frappe.db.set_value("LMS Certificate Request", evaluation.name, "status", "Cancelled")
|
||||||
events = frappe.get_all(
|
events = frappe.get_all(
|
||||||
@@ -1405,16 +1424,6 @@ def add_an_evaluator(email):
|
|||||||
return evaluator
|
return evaluator
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def delete_evaluator(evaluator):
|
|
||||||
frappe.only_for("Moderator")
|
|
||||||
if not frappe.db.exists("Course Evaluator", evaluator):
|
|
||||||
frappe.throw(_("Evaluator does not exist."))
|
|
||||||
|
|
||||||
frappe.db.delete("Has Role", {"parent": evaluator, "role": "Batch Evaluator"})
|
|
||||||
frappe.db.delete("Course Evaluator", evaluator)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def capture_user_persona(responses):
|
def capture_user_persona(responses):
|
||||||
frappe.only_for("System Manager")
|
frappe.only_for("System Manager")
|
||||||
@@ -1447,6 +1456,7 @@ def get_meta_info(type, route):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_meta_info(meta_type, route, meta_tags):
|
def update_meta_info(meta_type, route, meta_tags):
|
||||||
|
frappe.only_for(["Course Creator", "Batch Evaluator", "Moderator"])
|
||||||
validate_meta_data_permissions(meta_type)
|
validate_meta_data_permissions(meta_type)
|
||||||
validate_meta_tags(meta_tags)
|
validate_meta_tags(meta_tags)
|
||||||
|
|
||||||
@@ -1547,6 +1557,10 @@ def make_new_exercise_submission(exercise, code, test_cases):
|
|||||||
|
|
||||||
|
|
||||||
def update_exercise_submission(submission, code, test_cases):
|
def update_exercise_submission(submission, code, test_cases):
|
||||||
|
member = frappe.db.get_value("LMS Programming Exercise Submission", submission, "member")
|
||||||
|
if member != frappe.session.user:
|
||||||
|
frappe.throw(_("You do not have permission to update this submission."), frappe.PermissionError)
|
||||||
|
|
||||||
update_test_cases(test_cases, submission)
|
update_test_cases(test_cases, submission)
|
||||||
status = get_exercise_status(test_cases)
|
status = get_exercise_status(test_cases)
|
||||||
frappe.db.set_value("LMS Programming Exercise Submission", submission, {"status": status, "code": code})
|
frappe.db.set_value("LMS Programming Exercise Submission", submission, {"status": status, "code": code})
|
||||||
@@ -1619,6 +1633,11 @@ def track_new_watch_time(lesson, video):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_course_progress_distribution(course):
|
def get_course_progress_distribution(course):
|
||||||
|
if not can_modify_course(course):
|
||||||
|
frappe.throw(
|
||||||
|
_("You do not have permission to access this course's progress data."), frappe.PermissionError
|
||||||
|
)
|
||||||
|
|
||||||
all_progress = frappe.get_all(
|
all_progress = frappe.get_all(
|
||||||
"LMS Enrollment",
|
"LMS Enrollment",
|
||||||
{
|
{
|
||||||
@@ -1723,9 +1742,6 @@ def get_profile_details(username):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_streak_info():
|
def get_streak_info():
|
||||||
if frappe.session.user == "Guest":
|
|
||||||
return {}
|
|
||||||
|
|
||||||
all_dates = fetch_activity_dates(frappe.session.user)
|
all_dates = fetch_activity_dates(frappe.session.user)
|
||||||
streak, longest_streak = calculate_streaks(all_dates)
|
streak, longest_streak = calculate_streaks(all_dates)
|
||||||
current_streak = calculate_current_streak(all_dates, streak)
|
current_streak = calculate_current_streak(all_dates, streak)
|
||||||
@@ -1794,8 +1810,6 @@ def calculate_current_streak(all_dates, streak):
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_my_live_classes():
|
def get_my_live_classes():
|
||||||
my_live_classes = []
|
my_live_classes = []
|
||||||
if frappe.session.user == "Guest":
|
|
||||||
return my_live_classes
|
|
||||||
|
|
||||||
batches = frappe.get_all(
|
batches = frappe.get_all(
|
||||||
"LMS Batch Enrollment",
|
"LMS Batch Enrollment",
|
||||||
@@ -1840,8 +1854,6 @@ def get_my_live_classes():
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_created_courses():
|
def get_created_courses():
|
||||||
created_courses = []
|
created_courses = []
|
||||||
if frappe.session.user == "Guest":
|
|
||||||
return created_courses
|
|
||||||
|
|
||||||
CourseInstructor = frappe.qb.DocType("Course Instructor")
|
CourseInstructor = frappe.qb.DocType("Course Instructor")
|
||||||
Course = frappe.qb.DocType("LMS Course")
|
Course = frappe.qb.DocType("LMS Course")
|
||||||
@@ -1869,8 +1881,6 @@ def get_created_courses():
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_created_batches():
|
def get_created_batches():
|
||||||
created_batches = []
|
created_batches = []
|
||||||
if frappe.session.user == "Guest":
|
|
||||||
return created_batches
|
|
||||||
|
|
||||||
CourseInstructor = frappe.qb.DocType("Course Instructor")
|
CourseInstructor = frappe.qb.DocType("Course Instructor")
|
||||||
Batch = frappe.qb.DocType("LMS Batch")
|
Batch = frappe.qb.DocType("LMS Batch")
|
||||||
@@ -1898,9 +1908,6 @@ def get_created_batches():
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_admin_live_classes():
|
def get_admin_live_classes():
|
||||||
if frappe.session.user == "Guest":
|
|
||||||
return []
|
|
||||||
|
|
||||||
CourseInstructor = frappe.qb.DocType("Course Instructor")
|
CourseInstructor = frappe.qb.DocType("Course Instructor")
|
||||||
LMSLiveClass = frappe.qb.DocType("LMS Live Class")
|
LMSLiveClass = frappe.qb.DocType("LMS Live Class")
|
||||||
|
|
||||||
@@ -1931,9 +1938,6 @@ def get_admin_live_classes():
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_admin_evals():
|
def get_admin_evals():
|
||||||
if frappe.session.user == "Guest":
|
|
||||||
return []
|
|
||||||
|
|
||||||
evals = frappe.get_all(
|
evals = frappe.get_all(
|
||||||
"LMS Certificate Request",
|
"LMS Certificate Request",
|
||||||
{
|
{
|
||||||
@@ -1963,9 +1967,6 @@ def get_admin_evals():
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_my_courses():
|
def get_my_courses():
|
||||||
my_courses = []
|
my_courses = []
|
||||||
if frappe.session.user == "Guest":
|
|
||||||
return my_courses
|
|
||||||
|
|
||||||
courses = get_my_latest_courses()
|
courses = get_my_latest_courses()
|
||||||
|
|
||||||
if not len(courses):
|
if not len(courses):
|
||||||
@@ -2017,9 +2018,6 @@ def get_popular_courses():
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_my_batches():
|
def get_my_batches():
|
||||||
my_batches = []
|
my_batches = []
|
||||||
if frappe.session.user == "Guest":
|
|
||||||
return my_batches
|
|
||||||
|
|
||||||
batches = get_my_latest_batches()
|
batches = get_my_latest_batches()
|
||||||
|
|
||||||
if not len(batches):
|
if not len(batches):
|
||||||
@@ -2060,5 +2058,6 @@ def get_upcoming_batches():
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def delete_programming_exercise(exercise):
|
def delete_programming_exercise(exercise):
|
||||||
|
frappe.only_for(["Moderator", "Course Creator"])
|
||||||
frappe.db.delete("LMS Programming Exercise Submission", {"exercise": exercise})
|
frappe.db.delete("LMS Programming Exercise Submission", {"exercise": exercise})
|
||||||
frappe.db.delete("LMS Programming Exercise", exercise)
|
frappe.db.delete("LMS Programming Exercise", exercise)
|
||||||
|
|||||||
@@ -1179,6 +1179,8 @@ def get_batch_details(batch):
|
|||||||
if batch_details.seat_count:
|
if batch_details.seat_count:
|
||||||
batch_details.seats_left = batch_details.seat_count - len(batch_students)
|
batch_details.seats_left = batch_details.seat_count - len(batch_students)
|
||||||
|
|
||||||
|
print(batch_details.seat_count, len(batch_students))
|
||||||
|
|
||||||
return batch_details
|
return batch_details
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user