mirror of
https://github.com/frappe/lms.git
synced 2026-05-02 13:39:31 +03:00
@@ -71,6 +71,9 @@ jobs:
|
||||
- name: setup requirements
|
||||
working-directory: /home/runner/frappe-bench
|
||||
run: bench setup requirements --dev
|
||||
- name: block endpoints
|
||||
working-directory: /home/runner/frappe-bench
|
||||
run: bench --site frappe.local set-config block_endpoints 1
|
||||
- name: allow tests
|
||||
working-directory: /home/runner/frappe-bench
|
||||
run: bench --site frappe.local set-config allow_tests true
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,152 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("Inter-Thin.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-Thin.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("Inter-ThinItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-ThinItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraLight.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-ExtraLight.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraLightItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-ExtraLightItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("Inter-Light.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-Light.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("Inter-LightItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-LightItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("Inter-Regular.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-Regular.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("Inter-Italic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-Italic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("Inter-Medium.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-Medium.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("Inter-MediumItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-MediumItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("Inter-SemiBold.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-SemiBold.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("Inter-SemiBoldItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-SemiBoldItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("Inter-Bold.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-Bold.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("Inter-BoldItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-BoldItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraBold.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-ExtraBold.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraBoldItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-ExtraBoldItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("Inter-Black.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-Black.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("Inter-BlackItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-BlackItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
@@ -43,7 +43,7 @@
|
||||
<ListRow
|
||||
:row="row"
|
||||
v-for="row in students.data"
|
||||
class="group cursor-pointer"
|
||||
class="group cursor-pointer hover:bg-surface-gray-2 rounded"
|
||||
@click="openStudentProgressModal(row)"
|
||||
>
|
||||
<template #default="{ column, item }">
|
||||
@@ -88,7 +88,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</ListSelectBanner>
|
||||
<div class="mt-4" v-if="students.hasNextPage">
|
||||
<div class="mt-4 flex justify-center" v-if="students.hasNextPage">
|
||||
<Button @click="students.next()">
|
||||
{{ __('Load More') }}
|
||||
</Button>
|
||||
@@ -170,7 +170,7 @@ const studentColumns = [
|
||||
{
|
||||
label: 'Full Name',
|
||||
key: 'full_name',
|
||||
width: '20rem',
|
||||
width: '25rem',
|
||||
icon: 'user',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
<FormControl
|
||||
v-model="profile.open_to"
|
||||
type="select"
|
||||
:options="[' ', 'Opportunities', 'Hiring']"
|
||||
:options="[' ', 'Work', 'Hiring']"
|
||||
:label="__('Open to')"
|
||||
:placeholder="__('Looking for new work or hiring talent?')"
|
||||
/>
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
:size="size"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template v-if="user.open_to === 'Opportunities'" #indicator>
|
||||
<Tooltip :text="__('Open to Opportunities')" placement="right">
|
||||
<template v-if="user.open_to === 'Work'" #indicator>
|
||||
<Tooltip :text="__('Open to Work')" placement="right">
|
||||
<div class="rounded-full bg-surface-green-3 w-fit">
|
||||
<BadgeCheckIcon :class="'text-ink-white ' + checkSize" />
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
@import './assets/Inter/inter.css';
|
||||
@import 'frappe-ui/style.css';
|
||||
@import './styles/codemirror.css';
|
||||
@@ -42,8 +42,8 @@
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<FormControl
|
||||
v-model="openToOpportunities"
|
||||
:label="__('Open to Opportunities')"
|
||||
v-model="openToWork"
|
||||
:label="__('Open to Work')"
|
||||
type="checkbox"
|
||||
@change="updateParticipants()"
|
||||
/>
|
||||
@@ -140,7 +140,7 @@ import UserAvatar from '@/components/UserAvatar.vue'
|
||||
const filters = ref({})
|
||||
const currentCategory = ref('')
|
||||
const nameFilter = ref('')
|
||||
const openToOpportunities = ref(false)
|
||||
const openToWork = ref(false)
|
||||
const hiring = ref(false)
|
||||
const { brand } = sessionStore()
|
||||
const memberCount = ref(0)
|
||||
@@ -197,8 +197,8 @@ const updateFilters = () => {
|
||||
...(nameFilter.value && {
|
||||
member_name: ['like', `%${nameFilter.value}%`],
|
||||
}),
|
||||
...(openToOpportunities.value && {
|
||||
open_to_opportunities: true,
|
||||
...(openToWork.value && {
|
||||
open_to_work: true,
|
||||
}),
|
||||
...(hiring.value && {
|
||||
hiring: true,
|
||||
@@ -211,7 +211,7 @@ const setQueryParams = () => {
|
||||
let filterKeys = {
|
||||
category: currentCategory.value,
|
||||
name: nameFilter.value,
|
||||
'open-to-opportunities': openToOpportunities.value,
|
||||
'open-to-work': openToWork.value,
|
||||
hiring: hiring.value,
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ const setFiltersFromQuery = () => {
|
||||
let queries = new URLSearchParams(location.search)
|
||||
nameFilter.value = queries.get('name') || ''
|
||||
currentCategory.value = queries.get('category') || ''
|
||||
openToOpportunities.value = queries.get('open-to-opportunities') === 'true'
|
||||
openToWork.value = queries.get('open-to-opportunities') === 'true'
|
||||
hiring.value = queries.get('hiring') === 'true'
|
||||
}
|
||||
|
||||
|
||||
@@ -65,8 +65,8 @@
|
||||
<Tooltip
|
||||
v-if="profile.data.open_to"
|
||||
:text="
|
||||
profile.data.open_to === 'Opportunities'
|
||||
? __('Open to Opportunities')
|
||||
profile.data.open_to === 'Work'
|
||||
? __('Open to Work')
|
||||
: __('Hiring')
|
||||
"
|
||||
placement="right"
|
||||
@@ -77,7 +77,7 @@
|
||||
<div
|
||||
class="rounded-full w-fit"
|
||||
:class="
|
||||
profile.data.open_to === 'Opportunities'
|
||||
profile.data.open_to === 'Work'
|
||||
? 'bg-surface-green-3'
|
||||
: 'bg-purple-500'
|
||||
"
|
||||
|
||||
@@ -116,20 +116,30 @@ const debouncedSaveProgress = (scormDetails) => {
|
||||
}
|
||||
|
||||
const saveDataToLMS = (key, value) => {
|
||||
if (key === 'cmi.core.lesson_status') {
|
||||
if (value === 'passed') {
|
||||
isSuccessfullyCompleted.value = true
|
||||
saveProgress({
|
||||
is_complete: isSuccessfullyCompleted.value,
|
||||
scorm_content: '',
|
||||
})
|
||||
} else if (value === 'failed' && courseRestartOnFailure) {
|
||||
saveProgress({
|
||||
is_complete: isSuccessfullyCompleted.value,
|
||||
scorm_content: '',
|
||||
})
|
||||
}
|
||||
} else if (key === 'cmi.suspend_data' && !isSuccessfullyCompleted.value) {
|
||||
const isLessonStatus = key === 'cmi.core.lesson_status' && value === 'passed'
|
||||
const isCompletionStatus =
|
||||
key === 'cmi.completion_status' && value === 'completed'
|
||||
const shouldRestart =
|
||||
(key === 'cmi.core.lesson_status' && value === 'failed') ||
|
||||
(key === 'cmi.completion_status' && value === 'incomplete')
|
||||
|
||||
if (isLessonStatus || isCompletionStatus) {
|
||||
isSuccessfullyCompleted.value = true
|
||||
}
|
||||
|
||||
if (
|
||||
isLessonStatus ||
|
||||
isCompletionStatus ||
|
||||
(shouldRestart && courseRestartOnFailure)
|
||||
) {
|
||||
saveProgress({
|
||||
is_complete: isSuccessfullyCompleted.value,
|
||||
scorm_content: '',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (key === 'cmi.suspend_data' && !isSuccessfullyCompleted.value) {
|
||||
debouncedSaveProgress({
|
||||
is_complete: false,
|
||||
scorm_content: value,
|
||||
|
||||
@@ -21,5 +21,5 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require('@tailwindcss/line-clamp')],
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
@@ -257,12 +257,12 @@
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"mandatory_depends_on": null,
|
||||
"modified": "2025-12-24 12:56:32.110405",
|
||||
"modified": "2025-12-24 12:56:32.110406",
|
||||
"module": null,
|
||||
"name": "User-open_to",
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"options": "\nOpportunities\nHiring",
|
||||
"options": "\nWork\nHiring",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
|
||||
+2
-2
@@ -331,8 +331,8 @@ def get_certification_query(filters):
|
||||
)
|
||||
if field == "member_name":
|
||||
query = query.where(Certificate.member_name.like(value[1]))
|
||||
if field == "open_to_opportunities":
|
||||
query = query.where(User.open_to == "Opportunities")
|
||||
if field == "open_to_work":
|
||||
query = query.where(User.open_to == "Work")
|
||||
if field == "hiring":
|
||||
query = query.where(User.open_to == "Hiring")
|
||||
return query
|
||||
|
||||
@@ -277,14 +277,14 @@ class TestUtils(UnitTestCase):
|
||||
certified_participants_no_match = get_certified_participants(filters=filters)
|
||||
self.assertEqual(len(certified_participants_no_match), 0)
|
||||
|
||||
def test_certified_participants_with_open_to_opportunities(self):
|
||||
filters = {"open_to_opportunities": 1}
|
||||
certified_participants_open_to_oppo = get_certified_participants(filters=filters)
|
||||
self.assertEqual(len(certified_participants_open_to_oppo), 0)
|
||||
def test_certified_participants_with_open_to_work(self):
|
||||
filters = {"open_to_work": 1}
|
||||
certified_participants_open_to_work = get_certified_participants(filters=filters)
|
||||
self.assertEqual(len(certified_participants_open_to_work), 0)
|
||||
|
||||
frappe.db.set_value("User", self.student1.email, "open_to", "Opportunities")
|
||||
certified_participants_open_to_oppo = get_certified_participants(filters=filters)
|
||||
self.assertEqual(len(certified_participants_open_to_oppo), 1)
|
||||
frappe.db.set_value("User", self.student1.email, "open_to", "Work")
|
||||
certified_participants_open_to_work = get_certified_participants(filters=filters)
|
||||
self.assertEqual(len(certified_participants_open_to_work), 1)
|
||||
frappe.db.set_value("User", self.student1.email, "open_to", "")
|
||||
|
||||
def test_certified_participants_with_open_to_hiring(self):
|
||||
|
||||
+2
-1
@@ -114,4 +114,5 @@ lms.patches.v2_0.count_in_program
|
||||
lms.patches.v2_0.fix_scorm_lesson_reference_idx #02-09-2025
|
||||
lms.patches.v2_0.certified_members_to_certifications #05-10-2025
|
||||
lms.patches.v2_0.fix_job_application_resume_urls
|
||||
lms.patches.v2_0.open_to_opportunities
|
||||
lms.patches.v2_0.open_to_opportunities
|
||||
lms.patches.v2_0.open_to_work
|
||||
@@ -0,0 +1,12 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
open_to_field_exists = frappe.db.exists("Custom Field", {"dt": "User", "fieldname": "open_to"})
|
||||
|
||||
if not open_to_field_exists:
|
||||
return
|
||||
|
||||
open_to_opportunities = frappe.get_all("User", {"open_to": "Opportunities"}, ["name"])
|
||||
for user in open_to_opportunities:
|
||||
frappe.db.set_value("User", user.name, "open_to", "Work")
|
||||
+9
-16
@@ -1,6 +1,7 @@
|
||||
import frappe
|
||||
from frappe.tests.test_api import FrappeAPITestCase
|
||||
|
||||
from lms.auth import authenticate
|
||||
from lms.lms.test_utils import TestUtils
|
||||
|
||||
|
||||
@@ -11,24 +12,16 @@ class TestAuth(FrappeAPITestCase):
|
||||
)
|
||||
|
||||
def test_allowed_path(self):
|
||||
site_url = frappe.utils.get_site_url(frappe.local.site)
|
||||
headers = {"Authorization": "Bearer set_test_example_user"}
|
||||
url = site_url + "/api/method/lms.lms.utils.get_courses"
|
||||
response = self.get(
|
||||
url,
|
||||
headers=headers,
|
||||
)
|
||||
self.assertNotEqual(response.json.get("exc_type"), "PermissionError")
|
||||
frappe.form_dict.cmd = "ping"
|
||||
frappe.session.user = self.normal_user.name
|
||||
authenticate()
|
||||
frappe.session.user = "Administrator"
|
||||
|
||||
def test_not_allowed_path(self):
|
||||
site_url = frappe.utils.get_site_url(frappe.local.site)
|
||||
headers = {"Authorization": "Bearer set_test_example_user"}
|
||||
url = site_url + "/api/method/frappe.auth.get_logged_user"
|
||||
response = self.get(
|
||||
url,
|
||||
headers=headers,
|
||||
)
|
||||
self.assertEqual(response.json.get("exc_type"), "PermissionError")
|
||||
frappe.form_dict.cmd = "frappe.auth.get_logged_user"
|
||||
frappe.session.user = self.normal_user.name
|
||||
self.assertRaises(frappe.PermissionError, authenticate)
|
||||
frappe.session.user = "Administrator"
|
||||
|
||||
def tearDown(self):
|
||||
frappe.delete_doc("User", self.normal_user.name)
|
||||
|
||||
Reference in New Issue
Block a user