Merge pull request #1694 from pateljannat/home-page

feat: home page
This commit is contained in:
Jannat Patel
2025-08-26 12:30:01 +05:30
committed by GitHub
18 changed files with 1077 additions and 142 deletions

View File

@@ -2,6 +2,7 @@ import hashlib
import json
import re
import string
from datetime import datetime, timedelta
import frappe
import razorpay
@@ -39,12 +40,12 @@ def slugify(title, used_slugs=None):
If a list of used slugs is specified, it will make sure the generated slug
is not one of them.
>>> slugify("Hello World!")
'hello-world'
>>> slugify("Hello World!", ["hello-world"])
'hello-world-2'
>>> slugify("Hello World!", ["hello-world", "hello-world-2"])
'hello-world-3'
>>> slugify("Hello World!")
'hello-world'
>>> slugify("Hello World!", ["hello-world"])
'hello-world-2'
>>> slugify("Hello World!", ["hello-world", "hello-world-2"])
'hello-world-3'
"""
if not used_slugs:
used_slugs = []
@@ -844,14 +845,22 @@ def get_evaluator(course, batch=None):
@frappe.whitelist()
def get_upcoming_evals(student, courses, batch=None):
def get_upcoming_evals(courses=None, batch=None):
if frappe.session.user == "Guest":
return []
if not courses:
courses = []
filters = {
"member": student,
"course": ["in", courses],
"member": frappe.session.user,
"date": [">=", frappe.utils.nowdate()],
"status": "Upcoming",
}
if len(courses) > 0:
filters["course"] = ["in", courses]
if batch:
filters["batch_name"] = batch
@@ -1127,7 +1136,7 @@ def get_course_details(course):
# course_details.is_instructor = is_instructor(course_details.name)
if course_details.paid_course or course_details.paid_certificate:
"""course_details.course_price, course_details.currency = check_multicurrency(
course_details.course_price, course_details.currency, None, course_details.amount_usd
course_details.course_price, course_details.currency, None, course_details.amount_usd
)"""
course_details.price = fmt_money(course_details.course_price, 0, course_details.currency)
@@ -2133,3 +2142,340 @@ def get_related_courses(course):
def persona_captured():
frappe.db.set_single_value("LMS Settings", "persona_captured", 1)
@frappe.whitelist()
def get_my_courses():
my_courses = []
if frappe.session.user == "Guest":
return my_courses
courses = get_my_latest_courses()
if not len(courses):
courses = get_featured_home_courses()
if not len(courses):
courses = get_popular_courses()
for course in courses:
my_courses.append(get_course_details(course))
return my_courses
def get_my_latest_courses():
return frappe.get_all(
"LMS Enrollment",
{
"member": frappe.session.user,
},
order_by="creation desc",
limit=3,
pluck="course",
)
def get_featured_home_courses():
return frappe.get_all(
"LMS Course",
{"published": 1, "featured": 1},
order_by="published_on desc",
limit=3,
pluck="name",
)
def get_popular_courses():
return frappe.get_all(
"LMS Course",
{
"published": 1,
},
order_by="enrollments desc",
limit=3,
pluck="name",
)
@frappe.whitelist()
def get_my_batches():
my_batches = []
if frappe.session.user == "Guest":
return my_batches
batches = get_my_latest_batches()
if not len(batches):
batches = get_upcoming_batches()
for batch in batches:
batch_details = get_batch_details(batch)
if batch_details:
my_batches.append(batch_details)
return my_batches
def get_my_latest_batches():
return frappe.get_all(
"LMS Batch Enrollment",
{
"member": frappe.session.user,
},
order_by="creation desc",
limit=4,
pluck="batch",
)
def get_upcoming_batches():
return frappe.get_all(
"LMS Batch",
{
"published": 1,
"start_date": [">=", getdate()],
},
order_by="start_date asc",
limit=4,
pluck="name",
)
@frappe.whitelist()
def get_my_live_classes():
my_live_classes = []
if frappe.session.user == "Guest":
return my_live_classes
batches = frappe.get_all(
"LMS Batch Enrollment",
{
"member": frappe.session.user,
},
order_by="creation desc",
pluck="batch",
)
live_class_details = frappe.get_all(
"LMS Live Class",
filters={
"date": [">=", getdate()],
"batch_name": ["in", batches],
},
fields=[
"name",
"title",
"description",
"time",
"date",
"duration",
"attendees",
"start_url",
"join_url",
"owner",
],
limit=2,
order_by="date",
)
if len(live_class_details):
for live_class in live_class_details:
live_class.course_title = frappe.db.get_value("LMS Course", live_class.course, "title")
my_live_classes.append(live_class)
return my_live_classes
@frappe.whitelist()
def get_created_courses():
created_courses = []
if frappe.session.user == "Guest":
return created_courses
CourseInstructor = frappe.qb.DocType("Course Instructor")
Course = frappe.qb.DocType("LMS Course")
query = (
frappe.qb.from_(CourseInstructor)
.join(Course)
.on(CourseInstructor.parent == Course.name)
.select(Course.name)
.where(CourseInstructor.instructor == frappe.session.user)
.orderby(Course.published_on, order=frappe.qb.desc)
.limit(3)
)
results = query.run(as_dict=True)
courses = [row["name"] for row in results]
for course in courses:
course_details = get_course_details(course)
created_courses.append(course_details)
return created_courses
@frappe.whitelist()
def get_created_batches():
created_batches = []
if frappe.session.user == "Guest":
return created_batches
CourseInstructor = frappe.qb.DocType("Course Instructor")
Batch = frappe.qb.DocType("LMS Batch")
query = (
frappe.qb.from_(CourseInstructor)
.join(Batch)
.on(CourseInstructor.parent == Batch.name)
.select(Batch.name)
.where(CourseInstructor.instructor == frappe.session.user)
.where(Batch.start_date >= getdate())
.orderby(Batch.start_date, order=frappe.qb.asc)
.limit(4)
)
results = query.run(as_dict=True)
batches = [row["name"] for row in results]
for batch in batches:
batch_details = get_batch_details(batch)
created_batches.append(batch_details)
return created_batches
@frappe.whitelist()
def get_admin_live_classes():
if frappe.session.user == "Guest":
return []
CourseInstructor = frappe.qb.DocType("Course Instructor")
LMSLiveClass = frappe.qb.DocType("LMS Live Class")
query = (
frappe.qb.from_(CourseInstructor)
.join(LMSLiveClass)
.on(CourseInstructor.parent == LMSLiveClass.batch_name)
.select(
LMSLiveClass.name,
LMSLiveClass.title,
LMSLiveClass.description,
LMSLiveClass.time,
LMSLiveClass.date,
LMSLiveClass.duration,
LMSLiveClass.attendees,
LMSLiveClass.start_url,
LMSLiveClass.join_url,
LMSLiveClass.owner,
)
.where(CourseInstructor.instructor == frappe.session.user)
.where(LMSLiveClass.date >= getdate())
.orderby(LMSLiveClass.date, order=frappe.qb.asc)
.limit(4)
)
results = query.run(as_dict=True)
return results
@frappe.whitelist()
def get_admin_evals():
if frappe.session.user == "Guest":
return []
evals = frappe.get_all(
"LMS Certificate Request",
{
"evaluator": frappe.session.user,
"date": [">=", getdate()],
},
[
"name",
"date",
"start_time",
"course",
"evaluator",
"google_meet_link",
"member",
"member_name",
],
limit=4,
order_by="date asc",
)
for evaluation in evals:
evaluation.course_title = frappe.db.get_value("LMS Course", evaluation.course, "title")
return evals
def fetch_activity_dates(user):
doctypes = [
"LMS Course Progress",
"LMS Quiz Submission",
"LMS Assignment Submission",
"LMS Programming Exercise Submission",
]
all_dates = []
for dt in doctypes:
all_dates.extend(frappe.get_all(dt, {"member": user}, pluck="creation"))
return sorted({d.date() if hasattr(d, "date") else d for d in all_dates})
def calculate_streaks(all_dates):
streak = 0
longest_streak = 0
prev_day = None
for d in all_dates:
if d.weekday() in (5, 6):
continue
if prev_day:
expected = prev_day + timedelta(days=1)
while expected.weekday() in (5, 6):
expected += timedelta(days=1)
streak = streak + 1 if d == expected else 1
else:
streak = 1
longest_streak = max(longest_streak, streak)
prev_day = d
return streak, longest_streak
def calculate_current_streak(all_dates, streak):
if not all_dates:
return 0
last_date = all_dates[-1]
today = getdate()
ref_day = today
while ref_day.weekday() in (5, 6):
ref_day -= timedelta(days=1)
if last_date == ref_day or last_date == ref_day - timedelta(days=1):
return streak
return 0
@frappe.whitelist()
def get_streak_info():
if frappe.session.user == "Guest":
return {}
all_dates = fetch_activity_dates(frappe.session.user)
streak, longest_streak = calculate_streaks(all_dates)
current_streak = calculate_current_streak(all_dates, streak)
return {
"current_streak": current_streak,
"longest_streak": longest_streak,
}

View File

@@ -1,105 +0,0 @@
function getLiveCodeOptions() {
var START = `
import sketch
code = open("main.py").read()
env = dict(sketch.__dict__)
exec(code, env)
`;
var SKETCH = `
import json
def sendmsg(msgtype, function, args):
"""Sends a message to the frontend.
The frontend will receive the specified message whenever
this function is called. The frontend can decide to some
action on each of these messages.
"""
msg = dict(msgtype=msgtype, function=function, args=args)
print("--MSG--", json.dumps(msg))
def _draw(func, **kwargs):
sendmsg(msgtype="draw", function=func, args=kwargs)
def circle(x, y, d):
"""Draws a circle of diameter d with center (x, y).
"""
_draw("circle", x=x, y=y, d=d)
def line(x1, y1, x2, y2):
"""Draws a line from point (x1, y1) to point (x2, y2).
"""
_draw("line", x1=x1, y1=y1, x2=x2, y2=y2)
def rect(x, y, w, h):
"""Draws a rectangle on the canvas.
Parameters
----------
x: x coordinate of the top-left corner of the rectangle
y: y coordinate of the top-left corner of the rectangle
w: width of the rectangle
h: height of the rectangle
"""
_draw("rect", x=x, y=y, w=w, h=h)
def clear():
_draw("clear")
# clear the canvas on start
clear()
`;
const CANVAS_FUNCTIONS = {
circle: function (ctx, args) {
ctx.beginPath();
ctx.arc(args.x, args.y, args.d / 2, 0, 2 * Math.PI);
ctx.stroke();
},
line: function (ctx, args) {
ctx.beginPath();
ctx.moveTo(args.x1, args.y1);
ctx.lineTo(args.x2, args.y2);
ctx.stroke();
},
rect: function (ctx, args) {
ctx.beginPath();
ctx.rect(args.x, args.y, args.w, args.h);
ctx.stroke();
},
clear: function (ctx, args) {
var width = 300;
var height = 300;
ctx.clearRect(0, 0, width, height);
},
};
function drawOnCanvas(canvasElement, funcName, args) {
var ctx = canvasElement.getContext("2d");
var func = CANVAS_FUNCTIONS[funcName];
var scalex = canvasElement.width / 300;
var scaley = canvasElement.height / 300;
ctx.save();
ctx.scale(scalex, scaley);
func(ctx, args);
ctx.restore();
}
return {
runtime: "python",
files: [
{ filename: "start.py", contents: START },
{ filename: "sketch.py", contents: SKETCH },
],
command: ["python", "start.py"],
codemirror: true,
onMessage: {
draw: function (editor, msg) {
const canvasElement = editor.parent.querySelector("canvas");
drawOnCanvas(canvasElement, msg.function, msg.args);
},
},
};
}

View File

@@ -1,5 +0,0 @@
frappe.provide("lms.setup");
// redirect to desk page 'lms' after setup wizard is complete
// 'lms' desk page redirects to '/courses'
//frappe.setup.welcome_page = "/app/lms-home";