chore: merge conflicts

This commit is contained in:
Jannat Patel
2024-05-13 11:16:13 +05:30
29 changed files with 615 additions and 462 deletions

View File

@@ -4,30 +4,45 @@ on:
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
push: push:
branches: [ main ] branches: [ main, develop ]
jobs: jobs:
linters:
name: Semantic Commits commit-lint:
name: 'Semantic Commits'
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with:
fetch-depth: 200
- uses: actions/setup-node@v4
with:
node-version: 18
check-latest: true
- name: Set up Python - name: Check commit titles
uses: actions/setup-python@v4 run: |
npm install @commitlint/cli @commitlint/config-conventional
npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}
linters:
name: Semgrep Rules
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: with:
python-version: '3.10' python-version: '3.10'
cache: pip
- name: Install and Run Pre-commit
uses: pre-commit/action@v2.0.3
- name: Download Semgrep rules - name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- name: Download semgrep
run: pip install semgrep
- name: Run Semgrep rules - name: Run Semgrep rules
run: semgrep ci --config ./frappe-semgrep-rules/rules run: |
pip install semgrep
semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness

Submodule frappe-ui updated: c5faaae38e...5c0513c2df

View File

@@ -21,7 +21,7 @@
"chart.js": "^4.4.1", "chart.js": "^4.4.1",
"dayjs": "^1.11.6", "dayjs": "^1.11.6",
"feather-icons": "^4.28.0", "feather-icons": "^4.28.0",
"frappe-ui": "^0.1.50", "frappe-ui": "^0.1.54",
"lucide-vue-next": "^0.309.0", "lucide-vue-next": "^0.309.0",
"markdown-it": "^14.0.0", "markdown-it": "^14.0.0",
"pinia": "^2.0.33", "pinia": "^2.0.33",

View File

@@ -19,7 +19,7 @@
<ListView <ListView
:columns="getCoursesColumns()" :columns="getCoursesColumns()"
:rows="courses.data" :rows="courses.data"
row-key="name" row-key="batch_course"
:options="{ showTooltip: false }" :options="{ showTooltip: false }"
> >
<ListHeader <ListHeader
@@ -49,7 +49,10 @@
<ListSelectBanner> <ListSelectBanner>
<template #actions="{ unselectAll, selections }"> <template #actions="{ unselectAll, selections }">
<div class="flex gap-2"> <div class="flex gap-2">
<Button variant="ghost" @click="removeCourses(selections)"> <Button
variant="ghost"
@click="removeCourses(selections, unselectAll)"
>
<Trash2 class="h-4 w-4 stroke-1.5" /> <Trash2 class="h-4 w-4 stroke-1.5" />
</Button> </Button>
</div> </div>
@@ -133,11 +136,13 @@ const removeCourse = createResource({
}, },
}) })
const removeCourses = (selections) => { const removeCourses = (selections, unselectAll) => {
selections.forEach(async (course) => { selections.forEach(async (course) => {
removeCourse.submit({ course }) removeCourse.submit({ course })
await setTimeout(1000)
}) })
courses.reload() setTimeout(() => {
courses.reload()
unselectAll()
}, 1000)
} }
</script> </script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div v-if="batch.data" class="shadow rounded-md p-5" style="width: 300px"> <div v-if="batch.data" class="shadow rounded-md p-5 lg:w-72">
<Badge <Badge
v-if="batch.data.seat_count && seats_left > 0" v-if="batch.data.seat_count && seats_left > 0"
theme="green" theme="green"

View File

@@ -52,7 +52,10 @@
<ListSelectBanner> <ListSelectBanner>
<template #actions="{ unselectAll, selections }"> <template #actions="{ unselectAll, selections }">
<div class="flex gap-2"> <div class="flex gap-2">
<Button variant="ghost" @click="removeStudents(selections)"> <Button
variant="ghost"
@click="removeStudents(selections, unselectAll)"
>
<Trash2 class="h-4 w-4 stroke-1.5" /> <Trash2 class="h-4 w-4 stroke-1.5" />
</Button> </Button>
</div> </div>
@@ -142,11 +145,13 @@ const removeStudent = createResource({
}, },
}) })
const removeStudents = (selections) => { const removeStudents = (selections, unselectAll) => {
selections.forEach(async (student) => { selections.forEach(async (student) => {
removeStudent.submit({ student }) removeStudent.submit({ student })
await setTimeout(1000)
}) })
students.reload() setTimeout(() => {
students.reload()
unselectAll()
}, 500)
} }
</script> </script>

View File

@@ -7,7 +7,7 @@
<div <div
class="course-image" class="course-image"
:class="{ 'default-image': !course.image }" :class="{ 'default-image': !course.image }"
:style="{ backgroundImage: 'url(' + encodeURI(course.image) + ')' }" :style="{ backgroundImage: 'url(\'' + encodeURI(course.image) + '\')' }"
> >
<div class="flex relative top-4 left-4 w-fit flex-wrap"> <div class="flex relative top-4 left-4 w-fit flex-wrap">
<Badge <Badge

View File

@@ -29,15 +29,42 @@
<script setup> <script setup>
import { getSidebarLinks } from '../utils' import { getSidebarLinks } from '../utils'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { computed, inject } from 'vue' import { computed } from 'vue'
import { sessionStore } from '@/stores/session' import { sessionStore } from '@/stores/session'
import { usersStore } from '@/stores/user'
import { LogOut, LogIn, UserRound } from 'lucide-vue-next'
const { logout, user } = sessionStore() const { logout, user, username } = sessionStore()
let { isLoggedIn } = sessionStore() let { isLoggedIn } = sessionStore()
const router = useRouter() const router = useRouter()
let { userResource } = usersStore()
const tabs = computed(() => { const tabs = computed(() => {
return getSidebarLinks() let links = getSidebarLinks()
if (user) {
links.push({
label: 'Profile',
icon: UserRound,
activeFor: [
'Profile',
'ProfileAbout',
'ProfileCertification',
'ProfileEvaluator',
'ProfileRoles',
],
})
links.push({
label: 'Log out',
icon: LogOut,
})
} else {
links.push({
label: 'Log in',
icon: LogIn,
})
}
return links
}) })
let isActive = (tab) => { let isActive = (tab) => {
@@ -50,6 +77,13 @@ const handleClick = (tab) => {
logout.submit().then(() => { logout.submit().then(() => {
isLoggedIn = false isLoggedIn = false
}) })
else if (tab.label == 'Profile')
router.push({
name: 'Profile',
params: {
username: userResource.data?.username,
},
})
else router.push({ name: tab.to }) else router.push({ name: tab.to })
} }

View File

@@ -38,7 +38,8 @@
</template> </template>
<script setup> <script setup>
import { Dialog, Input, TextEditor, createResource } from 'frappe-ui' import { Dialog, Input, TextEditor, createResource } from 'frappe-ui'
import { reactive, defineModel } from 'vue' import { reactive, defineModel, computed } from 'vue'
import { showToast } from '@/utils'
const topics = defineModel('reloadTopics') const topics = defineModel('reloadTopics')
@@ -93,6 +94,14 @@ const submitTopic = (close) => {
topicResource.submit( topicResource.submit(
{}, {},
{ {
validate() {
if (!topic.title) {
return 'Title cannot be empty.'
}
if (!topic.reply) {
return 'Reply cannot be empty.'
}
},
onSuccess(data) { onSuccess(data) {
replyResource.submit( replyResource.submit(
{ {
@@ -108,6 +117,9 @@ const submitTopic = (close) => {
} }
) )
}, },
onError(err) {
showToast('Error', err.message, 'x')
},
} }
) )
} }

View File

@@ -57,13 +57,23 @@
import LMSLogo from '@/components/Icons/LMSLogo.vue' import LMSLogo from '@/components/Icons/LMSLogo.vue'
import { sessionStore } from '@/stores/session' import { sessionStore } from '@/stores/session'
import { Dropdown, createResource } from 'frappe-ui' import { Dropdown, createResource } from 'frappe-ui'
import { ChevronDown, LogIn, LogOut, User } from 'lucide-vue-next' import {
ChevronDown,
LogIn,
LogOut,
User,
ArrowRightLeft,
} from 'lucide-vue-next'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { convertToTitleCase } from '../utils' import { convertToTitleCase } from '../utils'
import { onMounted, inject } from 'vue' import { onMounted, inject } from 'vue'
import { usersStore } from '@/stores/user' import { usersStore } from '@/stores/user'
const router = useRouter() const router = useRouter()
const { logout } = sessionStore()
let { userResource } = usersStore()
let { isLoggedIn } = sessionStore()
const props = defineProps({ const props = defineProps({
isCollapsed: { isCollapsed: {
type: Boolean, type: Boolean,
@@ -80,10 +90,6 @@ const branding = createResource({
}, },
}) })
const { logout } = sessionStore()
let { userResource } = usersStore()
let { isLoggedIn } = sessionStore()
const userDropdownOptions = [ const userDropdownOptions = [
{ {
icon: User, icon: User,
@@ -95,6 +101,19 @@ const userDropdownOptions = [
return isLoggedIn return isLoggedIn
}, },
}, },
{
icon: ArrowRightLeft,
label: 'Switch to Desk',
onClick: () => {
window.location.href = '/app'
},
condition: () => {
let cookies = new URLSearchParams(document.cookie.split('; ').join('&'))
let system_user = cookies.get('system_user')
if (system_user === 'yes') return true
else return false
},
},
{ {
icon: LogOut, icon: LogOut,
label: 'Log out', label: 'Log out',

View File

@@ -244,7 +244,7 @@ const newBatch = createResource({
return { return {
doc: { doc: {
doctype: 'LMS Batch', doctype: 'LMS Batch',
meta_image: batch.image.file_url, meta_image: batch.image?.file_url,
...batch, ...batch,
}, },
} }
@@ -279,7 +279,7 @@ const editBatch = createResource({
doctype: 'LMS Batch', doctype: 'LMS Batch',
name: props.batchName, name: props.batchName,
fieldname: { fieldname: {
meta_image: batch.image.file_url, meta_image: batch.image?.file_url,
...batch, ...batch,
}, },
} }

View File

@@ -11,17 +11,23 @@
<div class="my-3"> <div class="my-3">
{{ batch.data.description }} {{ batch.data.description }}
</div> </div>
<div class="flex items-center justify-between w-1/2"> <div
class="flex flex-col gap-2 lg:gap-0 lg:flex-row lg:items-center justify-between lg:w-1/2"
>
<div class="flex items-center"> <div class="flex items-center">
<BookOpen class="h-4 w-4 text-gray-700 mr-2" /> <BookOpen class="h-4 w-4 text-gray-700 mr-2" />
<span> {{ batch.data?.courses?.length }} {{ __('Courses') }} </span> <span> {{ batch.data?.courses?.length }} {{ __('Courses') }} </span>
</div> </div>
<span v-if="batch.data.courses">&middot;</span> <span class="hidden lg:block" v-if="batch.data.courses"
>&middot;</span
>
<DateRange <DateRange
:startDate="batch.data.start_date" :startDate="batch.data.start_date"
:endDate="batch.data.end_date" :endDate="batch.data.end_date"
/> />
<span v-if="batch.data.start_date">&middot;</span> <span class="hidden lg:block" v-if="batch.data.start_date"
>&middot;</span
>
<div class="flex items-center"> <div class="flex items-center">
<Clock class="h-4 w-4 text-gray-700 mr-2" /> <Clock class="h-4 w-4 text-gray-700 mr-2" />
<span> <span>
@@ -31,14 +37,14 @@
</div> </div>
</div> </div>
</div> </div>
<div class="grid grid-cols-[60%,20%] gap-20 mt-10"> <div class="grid lg:grid-cols-[60%,20%] gap-4 lg:gap-20 mt-10">
<div class=""> <div class="order-2 lg:order-none">
<div <div
class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-gray-300 prose-th:border-gray-300 prose-td:relative prose-th:relative prose-th:bg-gray-100 prose-sm max-w-none !whitespace-normal mt-6" class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-gray-300 prose-th:border-gray-300 prose-td:relative prose-th:relative prose-th:bg-gray-100 prose-sm max-w-none !whitespace-normal mt-6"
v-html="batch.data.batch_details" v-html="batch.data.batch_details"
></div> ></div>
</div> </div>
<div> <div class="order-1 lg:order-none">
<BatchOverlay :batch="batch" /> <BatchOverlay :batch="batch" />
</div> </div>
</div> </div>
@@ -48,7 +54,7 @@
{{ __('Courses') }} {{ __('Courses') }}
</div> </div>
</div> </div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8 mt-5"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mt-5">
<div <div
v-if="batch.data.courses" v-if="batch.data.courses"
v-for="course in courses.data" v-for="course in courses.data"

View File

@@ -3,9 +3,22 @@
class="sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5" class="sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5"
> >
<Breadcrumbs :items="breadcrumbs" /> <Breadcrumbs :items="breadcrumbs" />
<div>
<FormControl
type="text"
placeholder="Search Participants"
v-model="searchQuery"
@input="participants.reload()"
>
<template #prefix>
<Search class="w-4" name="search" />
</template>
</FormControl>
</div>
</header> </header>
<div class="grid grid-cols-3 gap-4 m-5">
<div v-for="participant in participants.data"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 m-5">
<div v-if="participants.data" v-for="participant in participants.data">
<router-link <router-link
:to="{ :to="{
name: 'Profile', name: 'Profile',
@@ -38,14 +51,23 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { Breadcrumbs, createResource } from 'frappe-ui' import { Breadcrumbs, FormControl, createResource } from 'frappe-ui'
import { computed } from 'vue' import { ref, computed } from 'vue'
import UserAvatar from '@/components/UserAvatar.vue' import UserAvatar from '@/components/UserAvatar.vue'
import { Search } from 'lucide-vue-next'
const searchQuery = ref('')
const participants = createResource({ const participants = createResource({
url: 'lms.lms.api.get_certified_participants', url: 'lms.lms.api.get_certified_participants',
method: 'GET',
cache: ['certified_participants'],
makeParams() {
return {
search_query: searchQuery.value,
}
},
auto: true, auto: true,
cache: ['certified-participants'],
}) })
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {

View File

@@ -43,7 +43,7 @@
<div <div
v-show="openInstructorEditor" v-show="openInstructorEditor"
id="instructor-notes" id="instructor-notes"
class="py-3" class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-gray-300 prose-th:border-gray-300 prose-td:relative prose-th:relative prose-th:bg-gray-100 prose-sm max-w-none !whitespace-normal mt-6 py-3"
></div> ></div>
</div> </div>
</div> </div>
@@ -52,7 +52,10 @@
<label class="block font-medium text-gray-600 mb-1"> <label class="block font-medium text-gray-600 mb-1">
{{ __('Content') }} {{ __('Content') }}
</label> </label>
<div id="content" class="py-3"></div> <div
id="content"
class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-gray-300 prose-th:border-gray-300 prose-td:relative prose-th:relative prose-th:bg-gray-100 prose-sm max-w-none !whitespace-normal mt-6 py-3"
></div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -35,8 +35,8 @@
</EditCoverImage> </EditCoverImage>
</div> </div>
</div> </div>
<div class="mx-auto -mt-4 max-w-4xl translate-x-0 sm:px-5"> <div class="mx-auto -mt-10 md:-mt-4 max-w-4xl translate-x-0 px-5">
<div class="flex items-center"> <div class="flex flex-col md:flex-row items-center">
<div> <div>
<img <img
v-if="profile.data.user_image" v-if="profile.data.user_image"
@@ -57,7 +57,11 @@
{{ profile.data.headline }} {{ profile.data.headline }}
</div> </div>
</div> </div>
<Button v-if="isSessionUser()" class="ml-auto" @click="editProfile()"> <Button
v-if="isSessionUser()"
class="mt-3 sm:mt-0 md:ml-auto"
@click="editProfile()"
>
<template #prefix> <template #prefix>
<Edit class="w-4 h-4 stroke-1.5 text-gray-700" /> <Edit class="w-4 h-4 stroke-1.5 text-gray-700" />
</template> </template>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="mt-7"> <div class="mt-7 mb-10">
<h2 class="mb-3 text-lg font-semibold text-gray-900"> <h2 class="mb-3 text-lg font-semibold text-gray-900">
{{ __('About') }} {{ __('About') }}
</h2> </h2>

View File

@@ -1,9 +1,9 @@
<template> <template>
<div class="mt-7"> <div class="mt-7 mb-10">
<h2 class="mb-3 text-lg font-semibold text-gray-900"> <h2 class="mb-3 text-lg font-semibold text-gray-900">
{{ __('Certificates') }} {{ __('Certificates') }}
</h2> </h2>
<div class="grid grid-cols-3 gap-4"> <div class="grid grod-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div <div
v-for="certificate in certificates.data" v-for="certificate in certificates.data"
:key="certificate.name" :key="certificate.name"
@@ -34,13 +34,9 @@ const props = defineProps({
}) })
const certificates = createResource({ const certificates = createResource({
url: 'frappe.client.get_list', url: 'lms.lms.api.get_certificates',
params: { params: {
doctype: 'LMS Certificate', member: props.profile.data.name,
fields: ['name', 'course', 'course_title', 'issue_date', 'template'],
filters: {
member: props.profile.data.name,
},
}, },
auto: true, auto: true,
}) })

View File

@@ -5,7 +5,9 @@
</h2> </h2>
<div class=""> <div class="">
<div class="grid grid-cols-4 gap-4 text-sm text-gray-700 mb-4"> <div
class="grid grid-cols-3 md:grid-cols-4 gap-4 text-sm text-gray-700 mb-4"
>
<div> <div>
{{ __('Day') }} {{ __('Day') }}
</div> </div>
@@ -20,7 +22,7 @@
<div <div
v-if="evaluator.data" v-if="evaluator.data"
v-for="slot in evaluator.data.slots.schedule" v-for="slot in evaluator.data.slots.schedule"
class="grid grid-cols-4 gap-4 mb-4 group" class="grid grid-cols-3 md:grid-cols-4 gap-4 mb-4 group"
> >
<FormControl <FormControl
type="select" type="select"
@@ -44,7 +46,10 @@
/> />
</div> </div>
<div class="grid grid-cols-4 gap-4 mb-4" v-show="showSlotsTemplate"> <div
class="grid grid-cols-3 md:grod-cols-4 gap-4 mb-4"
v-show="showSlotsTemplate"
>
<FormControl <FormControl
type="select" type="select"
:options="days" :options="days"
@@ -74,7 +79,7 @@
<h2 class="mb-4 text-lg font-semibold text-gray-900"> <h2 class="mb-4 text-lg font-semibold text-gray-900">
{{ __('I am unavailable') }} {{ __('I am unavailable') }}
</h2> </h2>
<div class="grid grid-cols-4 gap-4"> <div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<FormControl <FormControl
type="date" type="date"
:label="__('From')" :label="__('From')"

View File

@@ -3,7 +3,9 @@
<h2 class="mb-3 text-lg font-semibold text-gray-900"> <h2 class="mb-3 text-lg font-semibold text-gray-900">
{{ __('Settings') }} {{ __('Settings') }}
</h2> </h2>
<div class="flex justify-between w-3/4 mt-5"> <div
class="flex flex-col md:flex-row gap-4 md:gap-0 justify-between w-3/4 mt-5"
>
<FormControl <FormControl
:label="__('Moderator')" :label="__('Moderator')"
v-model="moderator" v-model="moderator"

View File

@@ -6,7 +6,7 @@
<Breadcrumbs class="h-7" :items="breadcrumbs" /> <Breadcrumbs class="h-7" :items="breadcrumbs" />
</header> </header>
<div v-if="chartDetails.data" class="p-5"> <div v-if="chartDetails.data" class="p-5">
<div class="grid grid-cols-2 lg:grid-cols-5 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4">
<div class="flex items-center shadow py-2 px-3 rounded-md"> <div class="flex items-center shadow py-2 px-3 rounded-md">
<div class="p-2 rounded-md bg-gray-100 mr-3"> <div class="p-2 rounded-md bg-gray-100 mr-3">
<BookOpen class="w-18 h-18 stroke-1.5 text-gray-700" /> <BookOpen class="w-18 h-18 stroke-1.5 text-gray-700" />

File diff suppressed because it is too large Load Diff

View File

@@ -330,12 +330,13 @@ def get_evaluator_details(evaluator):
@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def get_certified_participants(): def get_certified_participants(search_query=""):
LMSCertificate = DocType("LMS Certificate") LMSCertificate = DocType("LMS Certificate")
participants = ( participants = (
frappe.qb.from_(LMSCertificate) frappe.qb.from_(LMSCertificate)
.select(LMSCertificate.member) .select(LMSCertificate.member)
.distinct() .distinct()
.where(LMSCertificate.member_name.like(f"%{search_query}%"))
.where(LMSCertificate.published == 1) .where(LMSCertificate.published == 1)
.orderby(LMSCertificate.creation, order=frappe.qb.desc) .orderby(LMSCertificate.creation, order=frappe.qb.desc)
.run(as_dict=1) .run(as_dict=1)
@@ -355,9 +356,8 @@ def get_certified_participants():
courses = [] courses = []
for course in course_names: for course in course_names:
courses.append(frappe.db.get_value("LMS Course", course, "title")) courses.append(frappe.db.get_value("LMS Course", course, "title"))
details.courses = courses details["courses"] = courses
participant_details.append(details) participant_details.append(details)
return participant_details return participant_details
@@ -374,3 +374,14 @@ def get_assigned_badges(member):
badge.update( badge.update(
frappe.db.get_value("LMS Badge", badge.badge, ["name", "title", "image"]) frappe.db.get_value("LMS Badge", badge.badge, ["name", "title", "image"])
) )
return assigned_badges
def get_certificates(member):
"""Get certificates for a member."""
return frappe.get_all(
"LMS Certificate",
filters={"member": member},
fields=["name", "course", "course_title", "issue_date", "template"],
order_by="creation desc",
)

View File

@@ -76,8 +76,7 @@
{ {
"fieldname": "video_link", "fieldname": "video_link",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Video Embed Link", "label": "Video Embed Link"
"reqd": 1
}, },
{ {
"fieldname": "short_introduction", "fieldname": "short_introduction",
@@ -305,7 +304,6 @@
"write": 1 "write": 1
} }
], ],
"search_fields": "title, tags",
"show_title_field_in_link": 1, "show_title_field_in_link": 1,
"sort_field": "creation", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",

View File

@@ -1524,10 +1524,12 @@ def get_question_details(question):
@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def get_batch_courses(batch): def get_batch_courses(batch):
courses = [] courses = []
course_list = frappe.get_all("Batch Course", {"parent": batch}, pluck="course") course_list = frappe.get_all("Batch Course", {"parent": batch}, ["name", "course"])
for course in course_list: for course in course_list:
courses.append(get_course_details(course)) details = get_course_details(course.course)
details.batch_course = course.name
courses.append(details)
return courses return courses

View File

@@ -9,8 +9,9 @@
"label": "Enrollments" "label": "Enrollments"
} }
], ],
"content": "[{\"id\":\"jNO4sdKxHu\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Get Started</b></span>\",\"col\":12}},{\"id\":\"5s0qRBc4rY\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/courses\\\" draggable=\\\"false\\\">Visit LMS Portal</a>\",\"col\":4}},{\"id\":\"lGMuNLpmv-\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/courses/new-course/edit\\\">Create a Course</a>\",\"col\":4}},{\"id\":\"3TVyc9AkPy\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/app/web-page/new-web-page-1\\\">Setup a Home Page</a>\",\"col\":4}},{\"id\":\"9zcbqpu2gm\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/app/lms-settings/LMS%20Settings\\\">LMS Setting</a>\",\"col\":4}},{\"id\":\"0ATmnKmXjc\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"https://docs.frappelms.com\\\">Documentation</a>\",\"col\":4}},{\"id\":\"7tGB2TYPmn\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"https://frappe.school/courses/introducing-frappe-lms\\\">Video Tutorials</a>\",\"col\":4}},{\"id\":\"C128a4abjX\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"5q4sPiv2ci\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Signups\",\"col\":6}},{\"id\":\"8NSaRaEV5u\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Enrollments\",\"col\":6}},{\"id\":\"kMuzko0uAU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"iuvIOHmztI\",\"type\":\"header\",\"data\":{\"text\":\"<span style=\\\"font-size: 18px;\\\"><b>Statistics</b></span>\",\"col\":12}},{\"id\":\"l0VTd66Uy2\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Users\",\"col\":4}},{\"id\":\"wAWZin1KKk\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course\",\"col\":4}},{\"id\":\"RLrIlFx0Hd\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Enrollments\",\"col\":4}},{\"id\":\"OuhWkhCQmq\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course Completed\",\"col\":4}},{\"id\":\"3g8QmNqUXG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Certificate\",\"col\":4}},{\"id\":\"EZsdsujs8N\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Evaluation\",\"col\":4}},{\"id\":\"s-nfsFQbGV\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"jeOBWBzHEa\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Master</b></span>\",\"col\":12}},{\"id\":\"sVhgfS5GIh\",\"type\":\"card\",\"data\":{\"card_name\":\"Course Data\",\"col\":4}},{\"id\":\"Iea0snm4Fg\",\"type\":\"card\",\"data\":{\"card_name\":\"Course Stats\",\"col\":4}},{\"id\":\"bZB7RqOl6a\",\"type\":\"card\",\"data\":{\"card_name\":\"Certification\",\"col\":4}}]", "content": "[{\"id\":\"jNO4sdKxHu\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Get Started</b></span>\",\"col\":12}},{\"id\":\"5s0qRBc4rY\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/lms/courses\\\">Visit LMS Portal</a>\",\"col\":4}},{\"id\":\"lGMuNLpmv-\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/courses/new-course/edit\\\">Create a Course</a>\",\"col\":4}},{\"id\":\"3TVyc9AkPy\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/app/web-page/new-web-page-1\\\">Setup a Home Page</a>\",\"col\":4}},{\"id\":\"9zcbqpu2gm\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"/app/lms-settings/LMS%20Settings\\\">LMS Setting</a>\",\"col\":4}},{\"id\":\"0ATmnKmXjc\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"https://docs.frappelms.com\\\">Documentation</a>\",\"col\":4}},{\"id\":\"7tGB2TYPmn\",\"type\":\"paragraph\",\"data\":{\"text\":\"<a href=\\\"https://frappe.school/courses/introducing-frappe-lms\\\">Video Tutorials</a>\",\"col\":4}},{\"id\":\"C128a4abjX\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"5q4sPiv2ci\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Signups\",\"col\":6}},{\"id\":\"8NSaRaEV5u\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Enrollments\",\"col\":6}},{\"id\":\"kMuzko0uAU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"iuvIOHmztI\",\"type\":\"header\",\"data\":{\"text\":\"<span style=\\\"font-size: 18px;\\\"><b>Statistics</b></span>\",\"col\":12}},{\"id\":\"l0VTd66Uy2\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Users\",\"col\":4}},{\"id\":\"wAWZin1KKk\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course\",\"col\":4}},{\"id\":\"RLrIlFx0Hd\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Enrollments\",\"col\":4}},{\"id\":\"OuhWkhCQmq\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course Completed\",\"col\":4}},{\"id\":\"3g8QmNqUXG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Certificate\",\"col\":4}},{\"id\":\"EZsdsujs8N\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Evaluation\",\"col\":4}},{\"id\":\"s-nfsFQbGV\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"jeOBWBzHEa\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Master</b></span>\",\"col\":12}},{\"id\":\"sVhgfS5GIh\",\"type\":\"card\",\"data\":{\"card_name\":\"Course Data\",\"col\":4}},{\"id\":\"Iea0snm4Fg\",\"type\":\"card\",\"data\":{\"card_name\":\"Course Stats\",\"col\":4}},{\"id\":\"bZB7RqOl6a\",\"type\":\"card\",\"data\":{\"card_name\":\"Certification\",\"col\":4}}]",
"creation": "2021-10-21 17:20:01.358903", "creation": "2021-10-21 17:20:01.358903",
"custom_blocks": [],
"docstatus": 0, "docstatus": 0,
"doctype": "Workspace", "doctype": "Workspace",
"hide_custom": 0, "hide_custom": 0,
@@ -144,7 +145,7 @@
"type": "Link" "type": "Link"
} }
], ],
"modified": "2023-05-11 15:41:25.514443", "modified": "2024-05-09 14:44:08.590606",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS", "name": "LMS",

View File

@@ -86,4 +86,5 @@ lms.patches.v1_0.change_jobs_url #19-01-2024
lms.patches.v1_0.custom_perm_for_discussions #14-01-2024 lms.patches.v1_0.custom_perm_for_discussions #14-01-2024
lms.patches.v1_0.rename_evaluator_role lms.patches.v1_0.rename_evaluator_role
lms.patches.v1_0.change_navbar_urls lms.patches.v1_0.change_navbar_urls
lms.patches.v1_0.set_published_on lms.patches.v1_0.set_published_on
lms.patches.v2_0.fix_progress_percentage

View File

@@ -2,5 +2,7 @@ import frappe
def execute(): def execute():
if frappe.db.exists("Role", "Class Evaluator"): if frappe.db.exists("Role", "Class Evaluator") and not frappe.db.exists(
"Role", "Batch Evaluator"
):
frappe.rename_doc("Role", "Class Evaluator", "Batch Evaluator") frappe.rename_doc("Role", "Class Evaluator", "Batch Evaluator")

View File

@@ -0,0 +1,10 @@
import frappe
from lms.lms.utils import get_course_progress
def execute():
enrollments = frappe.get_all("LMS Enrollment", fields=["name", "course", "member"])
for enrollment in enrollments:
progress = get_course_progress(enrollment.course, enrollment.member)
frappe.db.set_value("LMS Enrollment", enrollment.name, "progress", progress)

View File

@@ -97,7 +97,7 @@ def get_meta(app_path):
as_dict=True, as_dict=True,
) )
return { return {
"title": job_opening.title, "title": job_opening.job_title,
"image": job_opening.company_logo, "image": job_opening.company_logo,
"description": job_opening.company_name, "description": job_opening.company_name,
"keywords": "Job Openings, Jobs, Vacancies", "keywords": "Job Openings, Jobs, Vacancies",