From 04ed7f412f81d13c6f54416cc2f03be9de425a38 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 22 Aug 2023 18:34:42 +0530 Subject: [PATCH] feat: class billing --- lms/hooks.py | 7 +- .../doctype/class_student/class_student.json | 25 +- lms/lms/doctype/lms_class/lms_class.json | 50 ++-- lms/lms/doctype/lms_class/lms_class.py | 36 --- lms/lms/doctype/lms_course/lms_course.py | 115 +--------- lms/lms/utils.py | 166 +++++++++++++- lms/public/css/style.css | 3 + lms/public/icons/symbol-defs.svg | 7 + lms/public/js/common_functions.js | 59 ++++- lms/www/billing/billing.html | 15 +- lms/www/billing/billing.js | 22 +- lms/www/billing/billing.py | 69 +++++- lms/www/classes/class.html | 14 +- lms/www/classes/class.js | 16 +- lms/www/classes/class.py | 13 +- lms/www/classes/class_details.html | 214 ++++++++++++++++++ lms/www/classes/class_details.js | 3 + lms/www/classes/class_details.py | 21 ++ lms/www/classes/index.html | 24 +- lms/www/classes/index.py | 5 +- lms/www/courses/course.html | 10 +- lms/www/utils.py | 13 ++ 22 files changed, 663 insertions(+), 244 deletions(-) create mode 100644 lms/www/classes/class_details.html create mode 100644 lms/www/classes/class_details.js create mode 100644 lms/www/classes/class_details.py diff --git a/lms/hooks.py b/lms/hooks.py index f8796e7e..fc78d28e 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -189,9 +189,13 @@ website_route_rules = [ "to_route": "quiz_submission/quiz_submission", }, { - "from_route": "/billing/", + "from_route": "/billing//", "to_route": "billing/billing", }, + { + "from_route": "/classes/details/", + "to_route": "classes/class_details", + }, ] website_redirects = [ @@ -249,6 +253,7 @@ jinja = { "lms.lms.utils.can_create_courses", "lms.lms.utils.get_telemetry_boot_info", "lms.lms.utils.is_onboarding_complete", + "lms.www.utils.is_student", ], "filters": [], } diff --git a/lms/lms/doctype/class_student/class_student.json b/lms/lms/doctype/class_student/class_student.json index 8b3e9df2..55017ec0 100644 --- a/lms/lms/doctype/class_student/class_student.json +++ b/lms/lms/doctype/class_student/class_student.json @@ -10,10 +10,13 @@ "student", "student_name", "username", - "column_break_zvlp", "address", + "column_break_zvlp", "amount", - "currency" + "currency", + "order_id", + "payment_id", + "payment_received" ], "fields": [ { @@ -59,12 +62,28 @@ "fieldtype": "Link", "label": "Currency", "options": "Currency" + }, + { + "fieldname": "order_id", + "fieldtype": "Data", + "label": "Order ID" + }, + { + "fieldname": "payment_id", + "fieldtype": "Data", + "label": "Payment ID" + }, + { + "default": "0", + "fieldname": "payment_received", + "fieldtype": "Check", + "label": "Payment Received" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-08-18 19:19:34.701473", + "modified": "2023-08-22 10:41:40.577437", "modified_by": "Administrator", "module": "LMS", "name": "Class Student", diff --git a/lms/lms/doctype/lms_class/lms_class.json b/lms/lms/doctype/lms_class/lms_class.json index dd789b13..b80cfa21 100644 --- a/lms/lms/doctype/lms_class/lms_class.json +++ b/lms/lms/doctype/lms_class/lms_class.json @@ -19,11 +19,16 @@ "category", "column_break_flwy", "seat_count", - "paid_class", "section_break_6", "description", + "prerequisite", "students", "courses", + "section_break_gsac", + "paid_class", + "column_break_iens", + "amount", + "currency", "section_break_ubxi", "custom_component", "assessment_tab", @@ -89,6 +94,7 @@ }, { "default": "0", + "description": "Students will be enrolled in a paid class once they complete the payment", "fieldname": "paid_class", "fieldtype": "Check", "label": "Paid Class" @@ -158,11 +164,39 @@ "fieldname": "schedule_tab", "fieldtype": "Tab Break", "label": "Schedule" + }, + { + "fieldname": "section_break_gsac", + "fieldtype": "Section Break", + "label": "Pricing" + }, + { + "fieldname": "column_break_iens", + "fieldtype": "Column Break" + }, + { + "depends_on": "paid_class", + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount" + }, + { + "depends_on": "paid_class", + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "options": "Currency" + }, + { + "fieldname": "prerequisite", + "fieldtype": "Small Text", + "label": "Prerequisite", + "reqd": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-08-10 12:54:44.351907", + "modified": "2023-08-22 11:53:22.248596", "modified_by": "Administrator", "module": "LMS", "name": "LMS Class", @@ -192,18 +226,6 @@ "role": "Moderator", "share": 1, "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Class Evaluator", - "share": 1, - "write": 1 } ], "sort_field": "modified", diff --git a/lms/lms/doctype/lms_class/lms_class.py b/lms/lms/doctype/lms_class/lms_class.py index b625d01b..31db3d95 100644 --- a/lms/lms/doctype/lms_class/lms_class.py +++ b/lms/lms/doctype/lms_class/lms_class.py @@ -185,42 +185,6 @@ def authenticate(): return response.json()["access_token"] -@frappe.whitelist() -def create_class( - title, - start_date, - end_date, - description=None, - seat_count=0, - start_time=None, - end_time=None, - medium="Online", - category=None, - name=None, -): - frappe.only_for("Moderator") - if name: - class_details = frappe.get_doc("LMS Class", name) - else: - class_details = frappe.get_doc({"doctype": "LMS Class"}) - - class_details.update( - { - "title": title, - "start_date": start_date, - "end_date": end_date, - "description": description, - "seat_count": seat_count, - "start_time": start_time, - "end_time": end_time, - "medium": medium, - "category": category, - } - ) - class_details.save() - return class_details - - @frappe.whitelist() def fetch_lessons(courses): lessons = [] diff --git a/lms/lms/doctype/lms_course/lms_course.py b/lms/lms/doctype/lms_course/lms_course.py index d4144e0a..505ae7c3 100644 --- a/lms/lms/doctype/lms_course/lms_course.py +++ b/lms/lms/doctype/lms_course/lms_course.py @@ -5,12 +5,11 @@ import json import random import frappe from frappe.model.document import Document -from frappe.utils import cint, validate_phone_number +from frappe.utils import cint from frappe.utils.telemetry import capture from lms.lms.utils import get_chapters, can_create_courses from ...utils import generate_slug, validate_image from frappe import _ -import razorpay class LMSCourse(Document): @@ -362,115 +361,3 @@ def reorder_chapter(chapter_array): "idx": chapter_array.index(chap) + 1, }, ) - - -@frappe.whitelist() -def get_payment_options(course, phone): - validate_phone_number(phone, True) - course_details = frappe.db.get_value( - "LMS Course", course, ["name", "title", "currency", "course_price"], as_dict=True - ) - razorpay_key = frappe.db.get_single_value("LMS Settings", "razorpay_key") - client = get_client() - order = create_order(client, course_details) - - options = { - "key_id": razorpay_key, - "name": frappe.db.get_single_value("Website Settings", "app_name"), - "description": _("Payment for {0} course").format(course_details["title"]), - "order_id": order["id"], - "amount": order["amount"] * 100, - "currency": order["currency"], - "prefill": { - "name": frappe.db.get_value("User", frappe.session.user, "full_name"), - "email": frappe.session.user, - "contact": phone, - }, - } - return options - - -def save_address(address): - address = json.loads(address) - address.update( - { - "address_title": frappe.db.get_value("User", frappe.session.user, "full_name"), - "address_type": "Billing", - "is_primary_address": 1, - "email_id": frappe.session.user, - } - ) - doc = frappe.new_doc("Address") - doc.update(address) - doc.save(ignore_permissions=True) - return doc.name - - -def get_client(): - razorpay_key = frappe.db.get_single_value("LMS Settings", "razorpay_key") - razorpay_secret = frappe.db.get_single_value("LMS Settings", "razorpay_secret") - - if not razorpay_key and not razorpay_secret: - frappe.throw( - _( - "There is a problem with the payment gateway. Please contact the Administrator to proceed." - ) - ) - - return razorpay.Client(auth=(razorpay_key, razorpay_secret)) - - -def create_order(client, course_details): - try: - return client.order.create( - { - "amount": course_details.course_price * 100, - "currency": course_details.currency, - } - ) - except Exception as e: - frappe.throw( - _("Error during payment: {0}. Please contact the Administrator.").format(e) - ) - - -@frappe.whitelist() -def verify_payment(response, course, address, order_id): - response = json.loads(response) - client = get_client() - client.utility.verify_payment_signature( - { - "razorpay_order_id": order_id, - "razorpay_payment_id": response["razorpay_payment_id"], - "razorpay_signature": response["razorpay_signature"], - } - ) - - return create_membership(address, response, course, client) - - -def create_membership(address, response, course, client): - try: - address_name = save_address(address) - membership = frappe.new_doc("LMS Batch Membership") - payment = client.payment.fetch(response["razorpay_payment_id"]) - - membership.update( - { - "member": frappe.session.user, - "course": course, - "address": address_name, - "payment_received": 1, - "order_id": response["razorpay_order_id"], - "payment_id": response["razorpay_payment_id"], - "amount": payment["amount"] / 100, - "currency": payment["currency"], - } - ) - membership.save(ignore_permissions=True) - - return f"/courses/{course}/learn/1.1" - except Exception as e: - frappe.throw( - _("Error during payment: {0}. Please contact the Administrator.").format(e) - ) diff --git a/lms/lms/utils.py b/lms/lms/utils.py index 96da419c..72832ad9 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -1,6 +1,8 @@ import re import string import frappe +import json +import razorpay from frappe import _ from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result from frappe.desk.doctype.notification_log.notification_log import make_notification_logs @@ -13,9 +15,9 @@ from frappe.utils import ( format_date, get_datetime, getdate, + validate_phone_number, ) from frappe.utils.dateutils import get_period - from lms.lms.md import find_macros, markdown_to_html RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+") @@ -825,3 +827,165 @@ def get_upcoming_evals(student, courses): evals.course_title = frappe.db.get_value("LMS Course", evals.course, "title") evals.evaluator_name = frappe.db.get_value("User", evals.evaluator, "full_name") return upcoming_evals + + +@frappe.whitelist() +def get_payment_options(doctype, docname, phone): + if not frappe.db.exists(doctype, docname): + frappe.throw(_("Invalid document provided.")) + + validate_phone_number(phone, True) + if doctype == "LMS Course": + details = frappe.db.get_value( + "LMS Course", + docname, + ["name", "title", "paid_course", "currency", "course_price as amount"], + as_dict=True, + ) + if not details.paid_course: + frappe.throw(_("This course is free.")) + else: + details = frappe.db.get_value( + "LMS Class", + docname, + ["name", "title", "paid_class", "currency", "amount"], + as_dict=True, + ) + if not details.paid_class: + frappe.throw(_("To join this class, please contact the Administrator.")) + + razorpay_key = frappe.db.get_single_value("LMS Settings", "razorpay_key") + client = get_client() + order = create_order(client, details.amount, details.currency) + + options = { + "key_id": razorpay_key, + "name": frappe.db.get_single_value("Website Settings", "app_name"), + "description": _("Payment for {0} course").format(details["title"]), + "order_id": order["id"], + "amount": order["amount"] * 100, + "currency": order["currency"], + "prefill": { + "name": frappe.db.get_value("User", frappe.session.user, "full_name"), + "email": frappe.session.user, + "contact": phone, + }, + } + return options + + +def save_address(address): + address = json.loads(address) + address.update( + { + "address_title": frappe.db.get_value("User", frappe.session.user, "full_name"), + "address_type": "Billing", + "is_primary_address": 1, + "email_id": frappe.session.user, + } + ) + doc = frappe.new_doc("Address") + doc.update(address) + doc.save(ignore_permissions=True) + return doc.name + + +def get_client(): + razorpay_key = frappe.db.get_single_value("LMS Settings", "razorpay_key") + razorpay_secret = frappe.db.get_single_value("LMS Settings", "razorpay_secret") + + if not razorpay_key and not razorpay_secret: + frappe.throw( + _( + "There is a problem with the payment gateway. Please contact the Administrator to proceed." + ) + ) + + return razorpay.Client(auth=(razorpay_key, razorpay_secret)) + + +def create_order(client, amount, currency): + try: + return client.order.create( + { + "amount": amount * 100, + "currency": currency, + } + ) + except Exception as e: + frappe.throw( + _("Error during payment: {0}. Please contact the Administrator.").format(e) + ) + + +@frappe.whitelist() +def verify_payment(response, doctype, docname, address, order_id): + response = json.loads(response) + client = get_client() + client.utility.verify_payment_signature( + { + "razorpay_order_id": order_id, + "razorpay_payment_id": response["razorpay_payment_id"], + "razorpay_signature": response["razorpay_signature"], + } + ) + + if doctype == "LMS Course": + return create_membership(address, response, docname, client) + else: + return add_student_to_class(address, response, docname, client) + + +def create_membership(address, response, course, client): + try: + address_name = save_address(address) + payment = client.payment.fetch(response["razorpay_payment_id"]) + membership = frappe.new_doc("LMS Batch Membership") + + membership.update( + { + "member": frappe.session.user, + "course": course, + "address": address_name, + "payment_received": 1, + "order_id": response["razorpay_order_id"], + "payment_id": response["razorpay_payment_id"], + "amount": payment["amount"] / 100, + "currency": payment["currency"], + } + ) + membership.save(ignore_permissions=True) + + return f"/courses/{course}/learn/1.1" + except Exception as e: + frappe.throw( + _("Error during payment: {0}. Please contact the Administrator.").format(e) + ) + + +def add_student_to_class(address, response, classname, client): + try: + address_name = save_address(address) + payment = client.payment.fetch(response["razorpay_payment_id"]) + student = frappe.new_doc("Class Student") + + student.update( + { + "student": frappe.session.user, + "parent": classname, + "parenttype": "LMS Class", + "parentfield": "students", + "address": address_name, + "amount": payment["amount"] / 100, + "currency": payment["currency"], + "payment_received": 1, + "order_id": response["razorpay_order_id"], + "payment_id": response["razorpay_payment_id"], + } + ) + student.save(ignore_permissions=True) + return f"/classes/{classname}" + except Exception as e: + frappe.throw( + _("Error during payment: {0}. Please contact the Administrator.").format(e) + ) diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 77ca8197..35401e67 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -2302,3 +2302,6 @@ select { padding-right: 1rem !important; } +.class-overlay { + top: 30%; +} \ No newline at end of file diff --git a/lms/public/icons/symbol-defs.svg b/lms/public/icons/symbol-defs.svg index f7823137..102d9efe 100644 --- a/lms/public/icons/symbol-defs.svg +++ b/lms/public/icons/symbol-defs.svg @@ -104,11 +104,18 @@ + + + + + + + diff --git a/lms/public/js/common_functions.js b/lms/public/js/common_functions.js index 6911b0a9..39adf583 100644 --- a/lms/public/js/common_functions.js +++ b/lms/public/js/common_functions.js @@ -320,6 +320,41 @@ const open_class_dialog = () => { default: class_info && class_info.description, reqd: 1, }, + { + fieldtype: "Small Text", + label: __("Prerequisite"), + fieldname: "prerequisite", + default: class_info && class_info.prerequisite, + reqd: 1, + }, + { + fieldtype: "Section Break", + label: __("Pricing"), + fieldname: "pricing", + }, + { + fieldtype: "Check", + label: __("Paid Class"), + fieldname: "paid_class", + default: class_info && class_info.paid_class, + }, + { + fieldtype: "Currency", + label: __("Amount"), + fieldname: "amount", + default: class_info && class_info.amount, + mandatory_depends_on: "paid_class", + depends_on: "paid_class", + }, + { + fieldtype: "Link", + label: __("Currency"), + fieldname: "currency", + options: "Currency", + default: class_info && class_info.currency, + mandatory_depends_on: "paid_class", + depends_on: "paid_class", + }, ], primary_action_label: __("Save"), primary_action: (values) => { @@ -330,19 +365,19 @@ const open_class_dialog = () => { }; const save_class = (values) => { + let method, args; + if (class_info) { + method = "frappe.client.save"; + args = Object.assign(class_info, values); + } else { + method = "frappe.client.insert"; + args = values; + args.doctype = "LMS Class"; + } frappe.call({ - method: "lms.lms.doctype.lms_class.lms_class.create_class", + method: method, args: { - title: values.title, - start_date: values.start_date, - end_date: values.end_date, - description: values.description, - seat_count: values.seat_count, - start_time: values.start_time, - end_time: values.end_time, - medium: values.medium, - category: values.category, - name: class_info && class_info.name, + doc: args, }, callback: (r) => { if (r.message) { @@ -353,7 +388,7 @@ const save_class = (values) => { indicator: "green", }); this.class_dialog.hide(); - window.location.href = `/classes/${r.message.name}`; + window.location.href = `/classes/details/${r.message.name}`; } }, }); diff --git a/lms/www/billing/billing.html b/lms/www/billing/billing.html index 31e84e04..4229d863 100644 --- a/lms/www/billing/billing.html +++ b/lms/www/billing/billing.html @@ -1,6 +1,6 @@ {% extends "lms/templates/lms_base.html" %} {% block title %} - {{ course.title if course.title else _("New Course") }} + {{ title }} {{ _("Billing") }} {% endblock %} @@ -8,7 +8,7 @@
@@ -20,23 +20,24 @@ {{ _("Order Details") }}
- {{ _("Enter the billing information and complete the payment to purchase this course.") }} + {{ _("Enter the billing information and complete the payment to purchase this {0}.").format(module) }}
{% endmacro %} -{% macro CourseDetails() %} +{% macro Details() %}
- {{ _("Course Name: ") }} {{ course.title }} + {% set label = "Course Name" if module == "course" else "Class Name" %} + {{ _(label) }} : {{ title }}
- {{ _("Total Price: ") }} {{ frappe.utils.fmt_money(course.course_price, 2, course.currency) }} + {{ _("Total Price: ") }} {{ frappe.utils.fmt_money(amount, 2, currency) }}
@@ -49,7 +50,7 @@ {{ _("Billing Details") }}
- diff --git a/lms/www/billing/billing.js b/lms/www/billing/billing.js index 6d2cf41f..cc0c6413 100644 --- a/lms/www/billing/billing.js +++ b/lms/www/billing/billing.js @@ -1,6 +1,8 @@ frappe.ready(() => { if ($("#billing-form").length) { - setup_billing(); + frappe.require("controls.bundle.js", () => { + setup_billing(); + }); } $(".btn-pay").click((e) => { @@ -66,19 +68,22 @@ const setup_billing = () => { const generate_payment_link = (e) => { address = this.billing.get_values(); - let course = decodeURIComponent($(e.currentTarget).attr("data-course")); + let doctype = $(e.currentTarget).attr("data-doctype"); + let docname = decodeURIComponent($(e.currentTarget).attr("data-name")); frappe.call({ - method: "lms.lms.doctype.lms_course.lms_course.get_payment_options", + method: "lms.lms.utils.get_payment_options", args: { - course: course, + doctype: doctype, + docname: docname, phone: address.phone, }, callback: (data) => { data.message.handler = (response) => { handle_success( response, - course, + doctype, + docname, address, data.message.order_id ); @@ -89,12 +94,13 @@ const generate_payment_link = (e) => { }); }; -const handle_success = (response, course, address, order_id) => { +const handle_success = (response, doctype, docname, address, order_id) => { frappe.call({ - method: "lms.lms.doctype.lms_course.lms_course.verify_payment", + method: "lms.lms.utils.verify_payment", args: { response: response, - course: course, + doctype: doctype, + docname: docname, address: address, order_id: order_id, }, diff --git a/lms/www/billing/billing.py b/lms/www/billing/billing.py index 562e6969..0b2b7130 100644 --- a/lms/www/billing/billing.py +++ b/lms/www/billing/billing.py @@ -3,21 +3,66 @@ from frappe import _ def get_context(context): - course_name = frappe.form_dict.course - - if not course_name: - raise ValueError(_("Course is required.")) + module = frappe.form_dict.module + docname = frappe.form_dict.modulename if frappe.session.user == "Guest": raise frappe.PermissionError(_("You are not allowed to access this page.")) - membership = frappe.db.exists( - "LMS Batch Membership", {"member": frappe.session.user, "course": course_name} - ) + if module not in ["course", "class"]: + raise ValueError(_("Module is incorrect.")) - if membership: - raise frappe.PermissionError(_("You are already enrolled for this course")) + doctype = "LMS Course" if module == "course" else "LMS Class" + context.module = module + context.docname = docname + context.doctype = doctype - context.course = frappe.db.get_value( - "LMS Course", course_name, ["title", "name", "course_price", "currency"], as_dict=True - ) + if not frappe.db.exists(doctype, docname): + print(doctype, docname) + raise ValueError(_("Module Name is incorrect or does not exist.")) + + if doctype == "LMS Course": + membership = frappe.db.exists( + "LMS Batch Membership", {"member": frappe.session.user, "course": docname} + ) + if membership: + raise frappe.PermissionError(_("You are already enrolled for this course")) + + else: + membership = frappe.db.exists( + "Class Student", {"student": frappe.session.user, "parent": docname} + ) + if membership: + raise frappe.PermissionError(_("You are already enrolled for this class")) + + if doctype == "LMS Course": + course = frappe.db.get_value( + "LMS Course", + docname, + ["title", "name", "paid_course", "course_price", "currency"], + as_dict=True, + ) + + if not course.paid_course: + raise frappe.PermissionError(_("This course is free.")) + + context.title = course.title + context.amount = course.course_price + context.currency = course.currency + + else: + class_info = frappe.db.get_value( + "LMS Class", + docname, + ["title", "name", "paid_class", "amount", "currency"], + as_dict=True, + ) + + if not class_info.paid_class: + raise frappe.PermissionError( + _("To join this class, please contact the Administrator.") + ) + + context.title = class_info.title + context.amount = class_info.amount + context.currency = class_info.currency diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index 8726da4b..a0a88962 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -85,12 +85,6 @@ {% macro ClassSections(class_info, class_courses, class_students, flow) %}
- {% if is_moderator %} - - {% endif %} -
diff --git a/lms/www/classes/index.py b/lms/www/classes/index.py index 4c0c83ff..ff21a7ab 100644 --- a/lms/www/classes/index.py +++ b/lms/www/classes/index.py @@ -1,11 +1,12 @@ import frappe from frappe.utils import getdate -from lms.lms.utils import has_course_moderator_role +from lms.lms.utils import has_course_moderator_role, has_course_evaluator_role def get_context(context): context.no_cache = 1 context.is_moderator = has_course_moderator_role() + context.is_evaluator = has_course_evaluator_role() classes = frappe.get_all( "LMS Class", fields=[ @@ -15,6 +16,8 @@ def get_context(context): "start_date", "end_date", "paid_class", + "amount", + "currency", "seat_count", ], ) diff --git a/lms/www/courses/course.html b/lms/www/courses/course.html index cd294edd..b8f5b7fe 100644 --- a/lms/www/courses/course.html +++ b/lms/www/courses/course.html @@ -10,8 +10,8 @@ {{ CourseHomeHeader(course) }}
+ {{ CourseHeaderOverlay(course) }}
- {{ CourseHeaderOverlay(course) }} {{ Description(course) }} {{ widgets.CourseOutline(course=course, membership=membership, is_user_interested=is_user_interested) }} {% if course.status == "Approved" and not frappe.utils.cint(course.upcoming) %} @@ -41,7 +41,7 @@ {% macro BreadCrumb(course) %} {% endmacro %} @@ -57,8 +57,8 @@ {% endfor %}
-
- {% if course.title %} {{ course.title }} {% endif %} +
+ {{ course.title }}
@@ -228,7 +228,7 @@ {% elif course.paid_course and not is_instructor %} - + {{ _("Buy This Course") }} diff --git a/lms/www/utils.py b/lms/www/utils.py index a99046bc..33567c75 100644 --- a/lms/www/utils.py +++ b/lms/www/utils.py @@ -128,3 +128,16 @@ def get_quiz_details(assessment, member): existing_submission[0].name if len(existing_submission) else "new-submission" ) assessment.url = f"/quiz-submission/{assessment.assessment_name}/{submission_name}" + + +def is_student(class_name, member=None): + if not member: + member = frappe.session.user + + return frappe.db.exists( + "Class Student", + { + "student": member, + "parent": class_name, + }, + )