Merge branch 'develop' into fix/course-deletion

This commit is contained in:
Jannat Patel
2026-01-19 15:32:51 +05:30
committed by GitHub
44 changed files with 1137 additions and 846 deletions

57
lms/auth.py Normal file
View File

@@ -0,0 +1,57 @@
import frappe
ALLOWED_PATHS = [
"/api/method/ping",
"/api/method/login",
"/api/method/logout",
"/api/method/frappe.core.doctype.communication.email.mark_email_as_seen",
"/api/method/frappe.realtime.get_user_info",
"/api/method/frappe.realtime.can_subscribe_doc",
"/api/method/frappe.realtime.can_subscribe_doctype",
"/api/method/frappe.realtime.has_permission",
"/api/method/frappe.integrations.oauth2.authorize",
"/api/method/frappe.integrations.oauth2.approve",
"/api/method/frappe.integrations.oauth2.get_token",
"/api/method/frappe.integrations.oauth2.openid_profile",
"/api/method/frappe.website.doctype.web_page_view.web_page_view.make_view_log",
"/api/method/upload_file",
"/api/method/frappe.search.web_search",
"/api/method/frappe.email.queue.unsubscribe",
"/api/method/frappe.website.doctype.web_form.web_form.accept",
"/api/method/frappe.core.doctype.user.user.test_password_strength",
"/api/method/frappe.core.doctype.user.user.update_password",
"/api/method/frappe.utils.telemetry.pulse.client.is_enabled",
"/api/method/frappe.client.get_value",
"/api/method/frappe.client.get_count",
"/api/method/frappe.client.get",
"/api/method/frappe.client.insert",
"/api/method/frappe.client.set_value",
"/api/method/frappe.client.delete",
"/api/method/frappe.client.get_list",
"/api/method/frappe.client.rename_doc",
"/api/method/frappe.onboarding.get_onboarding_status",
"/api/method/frappe.utils.print_format.download_pdf",
"/api/method/frappe.desk.search.search_link",
"/api/method/frappe.core.doctype.communication.email.make",
]
def authenticate():
if frappe.form_dict.cmd:
path = f"/api/method/{frappe.form_dict.cmd}"
else:
path = frappe.request.path
user_type = frappe.db.get_value("User", frappe.session.user, "user_type")
if user_type == "System User":
return
if not path.startswith("/api/"):
return
print("path", path)
if path.startswith("/lms") or path.startswith("/api/method/lms."):
return
if path in ALLOWED_PATHS:
return
frappe.throw(f"Access not allowed for this URL: {path}", frappe.PermissionError)

View File

@@ -262,3 +262,4 @@ add_to_apps_screen = [
]
sqlite_search = ["lms.sqlite.LearningSearch"]
auth_hooks = ["lms.auth.authenticate"]

View File

@@ -2043,3 +2043,9 @@ def get_upcoming_batches():
limit=4,
pluck="name",
)
@frappe.whitelist()
def delete_programming_exercise(exercise):
frappe.db.delete("LMS Programming Exercise Submission", {"exercise": exercise})
frappe.db.delete("LMS Programming Exercise", exercise)

View File

@@ -5,7 +5,7 @@
from frappe.tests import UnitTestCase
from frappe.utils import add_days, format_time, getdate
from lms.lms.doctype.course_evaluator.course_evaluator import get_schedule
from lms.lms.doctype.course_evaluator.course_evaluator import get_schedule, get_schedule_range_end_date
from lms.lms.test_utils import TestUtils
@@ -37,7 +37,7 @@ class TestCourseEvaluator(UnitTestCase):
def test_schedule_dates(self):
schedule = get_schedule(self.batch.courses[0].course, self.batch.name)
first_date = self.calculated_first_date_of_schedule()
last_date = self.calculated_last_date_of_schedule(first_date)
last_date = self.calculated_last_date_of_schedule()
self.assertEqual(getdate(schedule[0].get("date")), first_date)
self.assertEqual(getdate(schedule[-1].get("date")), last_date)
@@ -51,17 +51,10 @@ class TestCourseEvaluator(UnitTestCase):
first_date = add_days(today, offset_wednesday)
return first_date
def calculated_last_date_of_schedule(self, first_date):
last_day = add_days(getdate(), 56)
offset_monday = (0 - last_day.weekday() + 7) % 7 # 0 for Monday
offset_wednesday = (2 - last_day.weekday() + 7) % 7 # 2 for Wednesday
if offset_monday < offset_wednesday and offset_monday <= 4:
last_day = add_days(last_day, offset_monday)
elif offset_wednesday <= 4:
last_day = add_days(last_day, offset_wednesday)
else:
last_day = add_days(last_day, min(offset_monday, offset_wednesday) + 7)
def calculated_last_date_of_schedule(self):
last_day = getdate(get_schedule_range_end_date(getdate(), self.batch.name))
while last_day.weekday() not in (0, 2):
last_day = add_days(last_day, -1)
return last_day

View File

@@ -7,6 +7,7 @@ from frappe.email.doctype.email_template.email_template import get_email_templat
from frappe.model.document import Document
from frappe.model.naming import make_autoname
from frappe.utils import nowdate
from frappe.utils.telemetry import capture
class LMSCertificate(Document):
@@ -17,6 +18,10 @@ class LMSCertificate(Document):
self.name = make_autoname("hash", self.doctype)
def after_insert(self):
self.send_certification_email()
capture("certificate_issued", "lms")
def send_certification_email(self):
outgoing_email_account = frappe.get_cached_value(
"Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name"
)

View File

@@ -33,6 +33,9 @@ class LMSQuiz(Document):
frappe.throw(_("Rows {0} have the duplicate questions.").format(frappe.bold(comma_and(rows))))
def validate_limit(self):
if not self.shuffle_questions and self.limit_questions_to:
self.limit_questions_to = 0
if self.limit_questions_to and cint(self.limit_questions_to) >= len(self.questions):
frappe.throw(_("Limit cannot be greater than or equal to the number of questions in the quiz."))

View File

@@ -24,6 +24,8 @@ from frappe.utils import (
pretty_date,
rounded,
)
from pypika import Case
from pypika import functions as fn
from lms.lms.md import find_macros
@@ -419,7 +421,7 @@ def get_batch_details_for_notification(topic):
users = []
batch_title = frappe.db.get_value("LMS Batch", topic.reference_docname, "title")
subject = _("New comment in batch {0}").format(batch_title)
link = f"/lms/batches/{topic.reference_docname}"
link = f"/lms/batches/{topic.reference_docname}#discussions"
instructors = frappe.db.get_all(
"Course Instructor",
{"parenttype": "LMS Batch", "parent": topic.reference_docname},
@@ -588,32 +590,24 @@ def get_chart_date_range(from_date, to_date):
def get_chart_filters(doctype, chart, datefield, from_date, to_date):
version = get_frappe_version()
if version.startswith("16."):
filters = [([chart.document_type, "docstatus", "<", 2])]
filters = filters + json.loads(chart.filters_json)
filters.append([doctype, datefield, ">=", from_date])
filters.append([doctype, datefield, "<=", to_date])
else:
if version.startswith("15.") or version.startswith("14."):
filters = [([chart.document_type, "docstatus", "<", 2, False])]
filters = filters + json.loads(chart.filters_json)
filters.append([doctype, datefield, ">=", from_date, False])
filters.append([doctype, datefield, "<=", to_date, False])
else:
filters = [([chart.document_type, "docstatus", "<", 2])]
filters = filters + json.loads(chart.filters_json)
filters.append([doctype, datefield, ">=", from_date])
filters.append([doctype, datefield, "<=", to_date])
return filters
def get_chart_details(doctype, datefield, value_field, chart, from_date, to_date):
filters = get_chart_filters(doctype, chart, datefield, from_date, to_date)
version = get_frappe_version()
if version.startswith("16."):
return frappe.db.get_all(
doctype,
fields=[datefield, {"SUM": value_field}, {"COUNT": "*"}],
filters=filters,
group_by=datefield,
order_by=datefield,
as_list=True,
)
else:
if version.startswith("15.") or version.startswith("14."):
return frappe.db.get_all(
doctype,
fields=[f"{datefield} as _unit", f"SUM({value_field})", "COUNT(*)"],
@@ -622,6 +616,15 @@ def get_chart_details(doctype, datefield, value_field, chart, from_date, to_date
order_by="_unit asc",
as_list=True,
)
else:
return frappe.db.get_all(
doctype,
fields=[datefield, {"SUM": value_field}, {"COUNT": "*"}],
filters=filters,
group_by=datefield,
order_by=datefield,
as_list=True,
)
@frappe.whitelist(allow_guest=True)
@@ -1354,20 +1357,129 @@ def get_exercise_details(assessment, member):
@frappe.whitelist()
def get_batch_students(batch):
def get_batch_assessment_count(batch):
if not frappe.db.exists("LMS Batch", batch):
frappe.throw(_("The specified batch does not exist."))
return frappe.db.count("LMS Assessment", {"parent": batch})
@frappe.whitelist()
def get_batch_students(filters, offset=0, limit_start=0, limit_page_length=None, limit=None):
# limit_start and limit_page_length are used for backward compatibility
start = limit_start or offset
page_length = limit_page_length or limit
batch = filters.get("batch")
if not batch:
return []
students = []
students_list = frappe.get_all(
"LMS Batch Enrollment", filters={"batch": batch}, fields=["member", "name"]
"LMS Batch Enrollment",
filters={"batch": batch},
fields=["member", "name"],
offset=start,
limit=page_length,
order_by="creation desc",
)
for student in students_list:
details = get_batch_student_details(student)
calculate_student_progress(batch, details)
students.append(details)
students = sorted(students, key=lambda x: x.progress, reverse=True)
return students
def get_course_completion_stats(batch):
"""Get completion counts per course in batch"""
BatchCourse = frappe.qb.DocType("Batch Course")
BatchEnrollment = frappe.qb.DocType("LMS Batch Enrollment")
Enrollment = frappe.qb.DocType("LMS Enrollment")
rows = (
frappe.qb.from_(BatchCourse)
.left_join(BatchEnrollment)
.on(BatchEnrollment.batch == BatchCourse.parent)
.left_join(Enrollment)
.on((Enrollment.course == BatchCourse.course) & (Enrollment.member == BatchEnrollment.member))
.where(BatchCourse.parent == batch)
.groupby(BatchCourse.course, BatchCourse.title)
.select(
BatchCourse.title,
fn.Count(Case().when(Enrollment.progress == 100, Enrollment.member)).distinct().as_("completed"),
)
).run(as_dict=True)
return [{"task": row.title, "value": row.completed or 0} for row in rows]
def get_assignment_pass_stats(batch):
"""Get pass counts per assignment in batch"""
Assessment = frappe.qb.DocType("LMS Assessment")
Assignment = frappe.qb.DocType("LMS Assignment")
BatchEnrollment = frappe.qb.DocType("LMS Batch Enrollment")
Submission = frappe.qb.DocType("LMS Assignment Submission")
rows = (
frappe.qb.from_(Assessment)
.join(Assignment)
.on(Assignment.name == Assessment.assessment_name)
.left_join(BatchEnrollment)
.on(BatchEnrollment.batch == Assessment.parent)
.left_join(Submission)
.on(
(Submission.assignment == Assessment.assessment_name)
& (Submission.member == BatchEnrollment.member)
)
.where((Assessment.parent == batch) & (Assessment.assessment_type == "LMS Assignment"))
.groupby(Assessment.assessment_name, Assignment.title)
.select(
Assignment.title,
fn.Count(Case().when(Submission.status == "Pass", Submission.member)).distinct().as_("passed"),
)
).run(as_dict=True)
return [{"task": row.title, "value": row.passed or 0} for row in rows]
def get_quiz_pass_stats(batch):
"""Get pass counts per quiz in batch"""
Assessment = frappe.qb.DocType("LMS Assessment")
Quiz = frappe.qb.DocType("LMS Quiz")
BatchEnrollment = frappe.qb.DocType("LMS Batch Enrollment")
Submission = frappe.qb.DocType("LMS Quiz Submission")
rows = (
frappe.qb.from_(Assessment)
.join(Quiz)
.on(Quiz.name == Assessment.assessment_name)
.left_join(BatchEnrollment)
.on(BatchEnrollment.batch == Assessment.parent)
.left_join(Submission)
.on((Submission.quiz == Assessment.assessment_name) & (Submission.member == BatchEnrollment.member))
.where((Assessment.parent == batch) & (Assessment.assessment_type == "LMS Quiz"))
.groupby(Assessment.assessment_name, Quiz.title)
.select(
Quiz.title,
fn.Count(Case().when(Submission.percentage >= Submission.passing_percentage, Submission.member))
.distinct()
.as_("passed"),
)
).run(as_dict=True)
return [{"task": row.title, "value": row.passed or 0} for row in rows]
@frappe.whitelist()
def get_batch_chart_data(batch):
"""Get completion counts per course and assessment"""
if not frappe.db.exists("LMS Batch", batch):
frappe.throw(_("The specified batch does not exist."))
return get_course_completion_stats(batch) + get_assignment_pass_stats(batch) + get_quiz_pass_stats(batch)
def get_batch_student_details(student):
details = frappe.db.get_value(
"User",
@@ -1410,8 +1522,11 @@ def calculate_course_progress(batch_courses, details):
details.courses = frappe._dict()
for course in batch_courses:
progress = frappe.db.get_value(
"LMS Enrollment", {"course": course.course, "member": details.email}, "progress"
progress = (
frappe.db.get_value(
"LMS Enrollment", {"course": course.course, "member": details.email}, "progress"
)
or 0
)
details.courses[course.title] = progress
course_progress.append(progress)

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: jannat@frappe.io\n"
"POT-Creation-Date: 2026-01-13 05:34+0000\n"
"PO-Revision-Date: 2026-01-13 19:44\n"
"PO-Revision-Date: 2026-01-14 19:44\n"
"Last-Translator: jannat@frappe.io\n"
"Language-Team: Russian\n"
"MIME-Version: 1.0\n"
@@ -2412,7 +2412,7 @@ msgstr "Регистрация на программу {0}"
#. Label of the enrollment_from_batch (Link) field in DocType 'LMS Enrollment'
#: lms/lms/doctype/lms_enrollment/lms_enrollment.json
msgid "Enrollment from Batch"
msgstr ""
msgstr "Зачисление из группы"
#: lms/lms/doctype/lms_batch_enrollment/lms_batch_enrollment.py:57
msgid "Enrollment in this batch is restricted. Please contact the Administrator."
@@ -2730,7 +2730,7 @@ msgstr "Тип файла"
#: frontend/src/components/AssessmentPlugin.vue:54
msgid "Filter assignments by course"
msgstr ""
msgstr "Фильтруйте задания по курсу"
#: frontend/src/components/Settings/Transactions/TransactionList.vue:15
msgid "Filter by Billing Name"
@@ -3031,7 +3031,7 @@ msgstr "Подсвеченный текст"
#: frontend/src/pages/CertifiedParticipants.vue:52
#: frontend/src/pages/Profile.vue:70 lms/fixtures/custom_field.json
msgid "Hiring"
msgstr ""
msgstr "Набор персонала"
#: frontend/src/pages/Home/Home.vue:5 frontend/src/pages/Home/Home.vue:154
msgid "Home"
@@ -3062,7 +3062,7 @@ msgstr "Я недоступен"
#: frontend/src/pages/Billing.vue:181
msgid "I consent to my personal information being stored for invoicing"
msgstr ""
msgstr "Я даю согласие на хранение моих персональных данных для выставления счетов"
#: frontend/src/pages/QuizForm.vue:340
msgid "ID"
@@ -3900,7 +3900,7 @@ msgstr "Самая длинная серия"
#: frontend/src/components/Modals/EditProfile.vue:95
msgid "Looking for new work or hiring talent?"
msgstr ""
msgstr "Ищете новую работу или нанимаете талантливых сотрудников?"
#: lms/templates/emails/payment_reminder.html:23
msgid "Looking forward to seeing you enrolled!"
@@ -4065,7 +4065,7 @@ msgstr "Участник"
#. Label of the member_consent (Check) field in DocType 'LMS Payment'
#: lms/lms/doctype/lms_payment/lms_payment.json
msgid "Member Consent"
msgstr ""
msgstr "Согласие участника"
#. Label of the member_count (Int) field in DocType 'LMS Program'
#: lms/lms/doctype/lms_program/lms_program.json
@@ -4696,7 +4696,7 @@ msgstr "Откройте "
#: frontend/src/components/Modals/EditProfile.vue:94
#: lms/fixtures/custom_field.json
msgid "Open to"
msgstr ""
msgstr "Открыто для"
#: frontend/src/components/UserAvatar.vue:11
#: frontend/src/pages/CertifiedParticipants.vue:46
@@ -5054,7 +5054,7 @@ msgstr "Запишитесь на этот курс, чтобы просмотр
#: frontend/src/pages/Billing.vue:99
msgid "Please ensure that the billing name you enter is correct, as it will be used on your invoice."
msgstr ""
msgstr "Пожалуйста, убедитесь, что указанное вами имя плательщика верное, так как оно будет использовано в вашем счете."
#: frontend/src/components/Quiz.vue:16
msgid "Please ensure that you complete all the questions in {0} minutes."
@@ -5132,11 +5132,11 @@ msgstr "Пожалуйста, хорошо подготовьтесь и при
#: frontend/src/pages/Billing.vue:194
msgid "Please provide your consent to proceed with the payment"
msgstr ""
msgstr "Пожалуйста, подтвердите свое согласие на продолжение оплаты"
#: frontend/src/pages/Billing.vue:337
msgid "Please provide your consent to proceed with the payment."
msgstr ""
msgstr "Пожалуйста, подтвердите свое согласие на продолжение оплаты."
#: frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue:139
msgid "Please run the code to execute the test cases."
@@ -5989,7 +5989,7 @@ msgstr "Выберите тест"
#: frontend/src/components/AssessmentPlugin.vue:41
#: frontend/src/components/AssessmentPlugin.vue:49
msgid "Select an Assignment"
msgstr ""
msgstr "Выберите задание"
#: frontend/src/components/ContactUsEmail.vue:33
#: frontend/src/pages/JobApplications.vue:115
@@ -7796,7 +7796,7 @@ msgstr "{0} не найден"
#: frontend/src/pages/Jobs.vue:33
msgid "{0} {1} Jobs"
msgstr ""
msgstr "{0} {1} Работа"
#. Count format of shortcut in the LMS Workspace
#: lms/lms/workspace/lms/lms.json

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: jannat@frappe.io\n"
"POT-Creation-Date: 2026-01-13 05:34+0000\n"
"PO-Revision-Date: 2026-01-13 19:44\n"
"PO-Revision-Date: 2026-01-14 19:44\n"
"Last-Translator: jannat@frappe.io\n"
"Language-Team: Serbian (Cyrillic)\n"
"MIME-Version: 1.0\n"
@@ -2412,7 +2412,7 @@ msgstr "Упис у програм {0}"
#. Label of the enrollment_from_batch (Link) field in DocType 'LMS Enrollment'
#: lms/lms/doctype/lms_enrollment/lms_enrollment.json
msgid "Enrollment from Batch"
msgstr ""
msgstr "Упис из групе"
#: lms/lms/doctype/lms_batch_enrollment/lms_batch_enrollment.py:57
msgid "Enrollment in this batch is restricted. Please contact the Administrator."
@@ -2730,7 +2730,7 @@ msgstr "Врста фајла"
#: frontend/src/components/AssessmentPlugin.vue:54
msgid "Filter assignments by course"
msgstr ""
msgstr "Филтрирај задатке према обуци"
#: frontend/src/components/Settings/Transactions/TransactionList.vue:15
msgid "Filter by Billing Name"
@@ -3031,7 +3031,7 @@ msgstr "Истакнути текст"
#: frontend/src/pages/CertifiedParticipants.vue:52
#: frontend/src/pages/Profile.vue:70 lms/fixtures/custom_field.json
msgid "Hiring"
msgstr ""
msgstr "Запошљавање"
#: frontend/src/pages/Home/Home.vue:5 frontend/src/pages/Home/Home.vue:154
msgid "Home"
@@ -3062,7 +3062,7 @@ msgstr "Нисам доступан"
#: frontend/src/pages/Billing.vue:181
msgid "I consent to my personal information being stored for invoicing"
msgstr ""
msgstr "Слажем се да се моји лични подаци чувају за фактурисање"
#: frontend/src/pages/QuizForm.vue:340
msgid "ID"
@@ -3900,7 +3900,7 @@ msgstr "Најдужи низ дана"
#: frontend/src/components/Modals/EditProfile.vue:95
msgid "Looking for new work or hiring talent?"
msgstr ""
msgstr "Тражите нови посао или запошљавате таленте?"
#: lms/templates/emails/payment_reminder.html:23
msgid "Looking forward to seeing you enrolled!"
@@ -4065,7 +4065,7 @@ msgstr "Члан"
#. Label of the member_consent (Check) field in DocType 'LMS Payment'
#: lms/lms/doctype/lms_payment/lms_payment.json
msgid "Member Consent"
msgstr ""
msgstr "Сагласност члана"
#. Label of the member_count (Int) field in DocType 'LMS Program'
#: lms/lms/doctype/lms_program/lms_program.json
@@ -4696,7 +4696,7 @@ msgstr "Отвори "
#: frontend/src/components/Modals/EditProfile.vue:94
#: lms/fixtures/custom_field.json
msgid "Open to"
msgstr ""
msgstr "Отворен за"
#: frontend/src/components/UserAvatar.vue:11
#: frontend/src/pages/CertifiedParticipants.vue:46
@@ -5054,7 +5054,7 @@ msgstr "Молимо Вас да се упишете на ову обуку да
#: frontend/src/pages/Billing.vue:99
msgid "Please ensure that the billing name you enter is correct, as it will be used on your invoice."
msgstr ""
msgstr "Молимо Вас да проверите да ли је назив за фактурисање које уносите тачно, јер ће бити коришћено на Вашој фактури."
#: frontend/src/components/Quiz.vue:16
msgid "Please ensure that you complete all the questions in {0} minutes."
@@ -5132,11 +5132,11 @@ msgstr "Молимо Вас да се добро припремите и сти
#: frontend/src/pages/Billing.vue:194
msgid "Please provide your consent to proceed with the payment"
msgstr ""
msgstr "Молимо Вас да дате сагласност како бисте наставили са плаћањем"
#: frontend/src/pages/Billing.vue:337
msgid "Please provide your consent to proceed with the payment."
msgstr ""
msgstr "Молимо Вас да дате сагласност како бисте наставили са плаћањем."
#: frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue:139
msgid "Please run the code to execute the test cases."
@@ -5989,7 +5989,7 @@ msgstr "Изаберите квиз"
#: frontend/src/components/AssessmentPlugin.vue:41
#: frontend/src/components/AssessmentPlugin.vue:49
msgid "Select an Assignment"
msgstr ""
msgstr "Изаберите задатак"
#: frontend/src/components/ContactUsEmail.vue:33
#: frontend/src/pages/JobApplications.vue:115
@@ -7796,7 +7796,7 @@ msgstr "{0} није пронађено"
#: frontend/src/pages/Jobs.vue:33
msgid "{0} {1} Jobs"
msgstr ""
msgstr "{0} {1} послова"
#. Count format of shortcut in the LMS Workspace
#: lms/lms/workspace/lms/lms.json

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: jannat@frappe.io\n"
"POT-Creation-Date: 2026-01-13 05:34+0000\n"
"PO-Revision-Date: 2026-01-13 19:44\n"
"PO-Revision-Date: 2026-01-14 19:44\n"
"Last-Translator: jannat@frappe.io\n"
"Language-Team: Serbian (Latin)\n"
"MIME-Version: 1.0\n"
@@ -2412,7 +2412,7 @@ msgstr "Upis u program {0}"
#. Label of the enrollment_from_batch (Link) field in DocType 'LMS Enrollment'
#: lms/lms/doctype/lms_enrollment/lms_enrollment.json
msgid "Enrollment from Batch"
msgstr ""
msgstr "Upis iz grupe"
#: lms/lms/doctype/lms_batch_enrollment/lms_batch_enrollment.py:57
msgid "Enrollment in this batch is restricted. Please contact the Administrator."
@@ -2730,7 +2730,7 @@ msgstr "Vrsta fajla"
#: frontend/src/components/AssessmentPlugin.vue:54
msgid "Filter assignments by course"
msgstr ""
msgstr "Filtriraj zadatke prema obuci"
#: frontend/src/components/Settings/Transactions/TransactionList.vue:15
msgid "Filter by Billing Name"
@@ -3031,7 +3031,7 @@ msgstr "Istaknuti tekst"
#: frontend/src/pages/CertifiedParticipants.vue:52
#: frontend/src/pages/Profile.vue:70 lms/fixtures/custom_field.json
msgid "Hiring"
msgstr ""
msgstr "Zapošljavanje"
#: frontend/src/pages/Home/Home.vue:5 frontend/src/pages/Home/Home.vue:154
msgid "Home"
@@ -3062,7 +3062,7 @@ msgstr "Nisam dostupan"
#: frontend/src/pages/Billing.vue:181
msgid "I consent to my personal information being stored for invoicing"
msgstr ""
msgstr "Slažem se da se moji lični podaci čuvaju za fakturisanje"
#: frontend/src/pages/QuizForm.vue:340
msgid "ID"
@@ -3900,7 +3900,7 @@ msgstr "Najduži niz dana"
#: frontend/src/components/Modals/EditProfile.vue:95
msgid "Looking for new work or hiring talent?"
msgstr ""
msgstr "Tražite novi posao ili zapošljavate talente?"
#: lms/templates/emails/payment_reminder.html:23
msgid "Looking forward to seeing you enrolled!"
@@ -4065,7 +4065,7 @@ msgstr "Član"
#. Label of the member_consent (Check) field in DocType 'LMS Payment'
#: lms/lms/doctype/lms_payment/lms_payment.json
msgid "Member Consent"
msgstr ""
msgstr "Saglasnost člana"
#. Label of the member_count (Int) field in DocType 'LMS Program'
#: lms/lms/doctype/lms_program/lms_program.json
@@ -4696,7 +4696,7 @@ msgstr "Otvori "
#: frontend/src/components/Modals/EditProfile.vue:94
#: lms/fixtures/custom_field.json
msgid "Open to"
msgstr ""
msgstr "Otvoren za"
#: frontend/src/components/UserAvatar.vue:11
#: frontend/src/pages/CertifiedParticipants.vue:46
@@ -5054,7 +5054,7 @@ msgstr "Molimo Vas da se upišete na ovu obuku da biste pristupili lekciji"
#: frontend/src/pages/Billing.vue:99
msgid "Please ensure that the billing name you enter is correct, as it will be used on your invoice."
msgstr ""
msgstr "Molimo Vas da proverite da li je naziv za fakturisanje koje unosite tačno, jer će biti korišćeno na Vašoj fakturi."
#: frontend/src/components/Quiz.vue:16
msgid "Please ensure that you complete all the questions in {0} minutes."
@@ -5132,11 +5132,11 @@ msgstr "Molimo Vas da se dobro pripremite i stignete na vreme za ocenjivanje."
#: frontend/src/pages/Billing.vue:194
msgid "Please provide your consent to proceed with the payment"
msgstr ""
msgstr "Molimo Vas da date saglasnost kako biste nastavili sa plaćanjem"
#: frontend/src/pages/Billing.vue:337
msgid "Please provide your consent to proceed with the payment."
msgstr ""
msgstr "Molimo Vas da date saglasnost kako biste nastavili sa plaćanjem."
#: frontend/src/pages/ProgrammingExercises/ProgrammingExerciseSubmission.vue:139
msgid "Please run the code to execute the test cases."
@@ -5989,7 +5989,7 @@ msgstr "Izaberite kviz"
#: frontend/src/components/AssessmentPlugin.vue:41
#: frontend/src/components/AssessmentPlugin.vue:49
msgid "Select an Assignment"
msgstr ""
msgstr "Izaberite zadatak"
#: frontend/src/components/ContactUsEmail.vue:33
#: frontend/src/pages/JobApplications.vue:115
@@ -7796,7 +7796,7 @@ msgstr "{0} nije pronađeno"
#: frontend/src/pages/Jobs.vue:33
msgid "{0} {1} Jobs"
msgstr ""
msgstr "{0} {1} poslova"
#. Count format of shortcut in the LMS Workspace
#: lms/lms/workspace/lms/lms.json

36
lms/test_auth.py Normal file
View File

@@ -0,0 +1,36 @@
import frappe
from frappe.tests import UnitTestCase
from frappe.tests.test_api import FrappeAPITestCase
from lms.auth import authenticate
from lms.lms.test_utils import TestUtils
class TestAuth(FrappeAPITestCase):
def setUp(self):
self.normal_user = TestUtils.create_user(
self, "normal-user@example.com", "Normal", "User", ["LMS Student"]
)
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")
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")
def tearDown(self):
frappe.delete_doc("User", self.normal_user.name)