fix: get_doc references

This commit is contained in:
Jannat Patel
2022-02-10 09:53:08 +05:30
parent e17637d27b
commit fd5b7976e7
21 changed files with 334 additions and 286 deletions

View File

@@ -1,14 +1,14 @@
{% set color = get_palette(member.full_name) %}
<a class="button-links" href="{{ get_profile_url(member.username) }}">
<span class="avatar {{ avatar_class }}" title="{{ member.full_name }}">
{% if member.user_image %}
<img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}" title="{{ member.full_name }}">
</img>
{% else %}
<span class="avatar-frame standard-image" title="{{ member.full_name }}"
style="background-color: var({{color[0]}}); color: var({{color[1]}});">
{{ frappe.utils.get_abbr(member.full_name) }}
</span>
{% endif %}
<span class="avatar {{ avatar_class }}" title="{{ member.full_name }}">
<a class="button-links" href="{{ get_profile_url(member.username) }}">
{% if member.user_image %}
<img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}" title="{{ member.full_name }}">
</img>
{% else %}
<span class="avatar-frame standard-image" title="{{ member.full_name }}"
style="background-color: var({{color[0]}}); color: var({{color[1]}});">
{{ frappe.utils.get_abbr(member.full_name) }}
</span>
{% endif %}
</a>
</span>

View File

@@ -162,10 +162,26 @@ update_website_context = [
jinja = {
"methods": [
"school.page_renderers.get_profile_url",
"school.overrides.user.get_authored_courses",
"school.overrides.user.get_palette",
"school.www.utils.get_membership",
"school.www.utils.get_lessons",
"school.www.utils.get_tags"
"school.www.utils.get_tags",
"school.www.utils.get_instructors",
"school.www.utils.get_students",
"school.www.utils.get_average_rating",
"school.www.utils.is_certified",
"school.www.utils.get_lesson_index",
"school.www.utils.get_lesson_url",
"school.www.utils.get_chapters",
"school.www.utils.get_slugified_chapter_title",
"school.www.utils.get_progress",
"school.www.utils.render_html",
"school.www.utils.is_mentor",
"school.www.utils.is_cohort_staff",
"school.www.utils.get_mentors",
"school.www.utils.get_reviews",
"school.www.utils.is_eligible_to_review",
],
"filters": []
}

View File

@@ -5,7 +5,8 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from ...md import markdown_to_html, find_macros
from ...md import find_macros
from school.www.utils import get_course_progress
class CourseLesson(Document):
def validate(self):
@@ -57,9 +58,6 @@ class CourseLesson(Document):
folder = frappe.get_doc(args)
folder.save(ignore_permissions=True)
def render_html(self):
return markdown_to_html(self.body)
def get_exercises(self):
if not self.body:
return []
@@ -107,7 +105,6 @@ def save_progress(lesson, course, status):
"status": status,
}).save(ignore_permissions=True)
course_details = frappe.get_doc("LMS Course", course)
progress = course_details.get_course_progress()
progress = get_course_progress(course)
frappe.db.set_value("LMS Batch Membership", membership, "progress", progress)
return progress

View File

@@ -8,7 +8,7 @@ import json
from ...utils import slugify
from school.query import find, find_all
from frappe.utils import flt, cint
from ...utils import slugify
from school.www.utils import get_chapters
class LMSCourse(Document):
@@ -98,31 +98,7 @@ class LMSCourse(Document):
})
doc.insert()
def get_mentors(self):
"""Returns the list of all mentors for this course.
"""
course_mentors = []
mentors = frappe.get_all("LMS Course Mentor Mapping", {"course": self.name}, ["mentor"])
for mentor in mentors:
member = frappe.get_doc("User", mentor.mentor)
member.batch_count = frappe.db.count("LMS Batch Membership",
{
"member": member.name,
"member_type": "Mentor"
})
course_mentors.append(member)
return course_mentors
def is_mentor(self, email):
"""Checks if given user is a mentor for this course.
"""
if not email:
return False
return frappe.db.count("LMS Course Mentor Mapping",
{
"course": self.name,
"mentor": email
})
def get_student_batch(self, email):
"""Returns the batch the given student is part of.
@@ -142,12 +118,6 @@ class LMSCourse(Document):
fieldname="batch")
return batch_name and frappe.get_doc("LMS Batch", batch_name)
def get_slugified_chapter_title(self, chapter):
return slugify(chapter)
def get_batch(self, batch_name):
return find("LMS Batch", name=batch_name, course=self.name)
def get_batches(self, mentor=None):
batches = find_all("LMS Batch", course=self.name)
if mentor:
@@ -166,36 +136,8 @@ class LMSCourse(Document):
name = frappe.get_value("Cohort", {"course": self.name, "slug": cohort_slug})
return name and frappe.get_doc("Cohort", name)
def is_cohort_staff(self, user_email):
"""Returns True if the user is either a mentor or a staff for one or more active cohorts of this course.
"""
q1 = {
"doctype": "Cohort Staff",
"course": self.name,
"email": user_email
}
q2 = {
"doctype": "Cohort Mentor",
"course": self.name,
"email": user_email
}
return frappe.db.exists(q1) or frappe.db.exists(q2)
def get_lesson_index(self, lesson_name):
"""Returns the {chapter_index}.{lesson_index} for the lesson.
"""
lesson = frappe.db.get_value("Lesson Reference", {"lesson": lesson_name}, ["idx", "parent"], as_dict=True)
if not lesson:
return None
chapter = frappe.db.get_value("Chapter Reference", {"chapter": lesson.parent}, ["idx"], as_dict=True)
if not chapter:
return None
return f"{chapter.idx}.{lesson.idx}"
def reindex_exercises(self):
for i, c in enumerate(self.get_chapters(), start=1):
for i, c in enumerate(get_chapters(self.name), start=1):
self._reindex_exercises_in_chapter(c, i)
def _reindex_exercises_in_chapter(self, c, index):
@@ -207,117 +149,12 @@ class LMSCourse(Document):
exercise.save()
i += 1
def get_learn_url(self, lesson_number):
if not lesson_number:
return
return f"/courses/{self.name}/learn/{lesson_number}"
def get_all_memberships(self, member):
all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": self.name}, ["batch"])
for membership in all_memberships:
membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title")
return all_memberships
def get_students(self, batch=None):
"""Returns (email, full_name, username) of all the students of this batch as a list of dict.
"""
filters = {
"course": self.name,
"member_type": "Student"
}
if batch:
filters["batch"] = batch
memberships = frappe.get_all(
"LMS Batch Membership",
filters,
["member"])
member_names = [m['member'] for m in memberships]
return find_all("User", name=["IN", member_names])
def get_reviews(self):
reviews = frappe.get_all("LMS Course Review",
{
"course": self.name
},
["review", "rating", "owner"],
order_by= "creation desc")
out_of_ratings = frappe.db.get_all("DocField",
{
"parent": "LMS Course Review",
"fieldtype": "Rating"
},
["options"])
out_of_ratings = (len(out_of_ratings) and out_of_ratings[0].options) or 5
for review in reviews:
review.rating = review.rating * out_of_ratings
review.owner_details = frappe.get_doc("User", review.owner)
return reviews
def is_eligible_to_review(self, membership):
""" Checks if user is eligible to review the course """
if not membership:
return False
if frappe.db.count("LMS Course Review",
{
"course": self.name,
"owner": frappe.session.user
}):
return False
return True
def get_average_rating(self):
ratings = [review.rating for review in self.get_reviews()]
if not len(ratings):
return None
return sum(ratings)/len(ratings)
def get_progress(self, lesson):
return frappe.db.get_value("LMS Course Progress",
{
"course": self.name,
"owner": frappe.session.user,
"lesson": lesson
},
["status"])
def get_course_progress(self, member=None):
""" Returns the course progress of the session user """
lesson_count = len(self.get_lessons())
if not lesson_count:
return 0
completed_lessons = frappe.db.count("LMS Course Progress",
{
"course": self.name,
"owner": member or frappe.session.user,
"status": "Complete"
})
precision = cint(frappe.db.get_default("float_precision")) or 3
return flt(((completed_lessons/lesson_count) * 100), precision)
def get_neighbours(self, current, lessons):
current = flt(current)
numbers = sorted(lesson.number for lesson in lessons)
index = numbers.index(current)
return {
"prev": numbers[index-1] if index-1 >= 0 else None,
"next": numbers[index+1] if index+1 < len(numbers) else None
}
def is_certified(self):
certificate = frappe.get_all("LMS Certification",
{
"student": frappe.session.user,
"course": self.name
})
if len(certificate):
return certificate[0].name
return
@frappe.whitelist()
def reindex_exercises(doc):
course_data = json.loads(doc)

View File

@@ -105,7 +105,7 @@ def sanitize_html(html, macro):
any broken tags. This makes sures that all those things are fixed
before passing to the etree parser.
"""
soup = BeautifulSoup(html, features="html5lib")
soup = BeautifulSoup(html, features="lxml")
nodes = soup.body.children
classname = ""
if macro == "YouTubeVideo":

View File

@@ -2,7 +2,8 @@
<h2 class="section-title">{{ title }}</h2>
<div class="cards-parent mt-10">
{% for course_row in courses %}
{% set course = frappe.get_doc("LMS Course", course_row.course) %}
{% set course = frappe.db.get_value("LMS Course", course_row.course,
["name", "upcoming", "title", "image", "enable_certification"], as_dict=True) %}
{{ widgets.CourseCard(course=course, read_only=False) }}
{% endfor %}
</div>

View File

@@ -1,12 +1,12 @@
<div>
<div class="chapter-title small-title" data-target="#{{ course.get_slugified_chapter_title(chapter.title) }}"
<div class="chapter-title small-title" data-target="#{{ get_slugified_chapter_title(chapter.title) }}"
data-toggle="collapse" aria-expanded="false">
<img class="chapter-icon" src="/assets/school/icons/chevron-right.svg">
{{ index }}. {{ chapter.title }}
</div>
<div class="chapter-content collapse navbar-collapse" id="{{ course.get_slugified_chapter_title(chapter.title) }}">
<div class="chapter-content collapse navbar-collapse" id="{{ get_slugified_chapter_title(chapter.title) }}">
{% if chapter.description %}
<div class="chapter-description muted-text">
@@ -17,17 +17,17 @@
{% set is_instructor = frappe.session.user == course.instructor %}
<div class="lessons">
{% for lesson in course.get_lessons(chapter) %}
{% for lesson in get_lessons(course.name, chapter) %}
<div class="lesson-info {% if membership.current_lesson == lesson.name %} active-lesson {% endif %}">
{% if membership or lesson.include_in_preview %}
<a class="lesson-links" href="{{ course.get_learn_url(lesson.number) }}{{course.query_parameter}}"
<a class="lesson-links" href="{{ get_lesson_url(course.name, lesson.number) }}{{course.query_parameter}}"
data-course="{{ course.name }}">
{{ lesson.title }}
{% if membership %}
<img class="ml-1 lesson-progress-tick {{ course.get_progress(lesson.name) != 'Complete' and 'hide' }}"
<img class="ml-1 lesson-progress-tick {{ get_progress(course.name, lesson.name) != 'Complete' and 'hide' }}"
src="/assets/school/icons/check.svg">
{% endif %}
@@ -36,7 +36,7 @@
{% elif is_instructor and not lesson.include_in_preview %}
<a class="lesson-links"
title="This lesson is not available for preview. As you are the Instructor of the course only you can see it."
href="{{ course.get_learn_url(lesson.number) }}{{course.query_parameter}}"
href="{{ course.get_lesson_url(lesson.number) }}{{course.query_parameter}}"
data-course="{{ course.name }}">
{{ lesson.title }}
<img class="ml-2" src="/assets/school/icons/lock.svg">
@@ -58,7 +58,7 @@
</div>
</div>
{% if index != course.get_chapters() | length %}
{% if index != get_chapters(course.name) | length %}
<div class="card-divider"></div>
{% endif %}

View File

@@ -34,23 +34,38 @@
<div class="progress-percentage">{{ progress }}% Completed</div>
{% endif %}
<div class="course-card-footer">
<span>
{{ widgets.Avatar(member=course.get_instructor(), avatar_class="avatar-small") }}
<a class="button-links" href="{{ get_profile_url(course.get_instructor().username) }}">
<div class="zindex course-card-footer">
<span class="">
{% set instructors = get_instructors(course.name) %}
{% for instructor in instructors %}
{% if instructors | length > 1 and loop.index == 1 %}
<div class="avatar-group left overlap">
{% endif %}
{{ widgets.Avatar(member=instructor, avatar_class="avatar-small") }}
{% if instructors | length > 1 and loop.index == instructors | length %}
</div>
{% endif %}
{% endfor %}
<a class="button-links" href="{{ get_profile_url(instructors[0].username) }}">
<span class="course-instructor">
{{ instructor.full_name }}
{% if instructors | length == 1 %}
{{ instructors[0].full_name }}
{% else %}
{{ instructors[0].full_name.split(" ")[0] }} and {{ instructors | length - 1 }} others
{% endif %}
</span>
</a>
</span>
{% endfor %}
</span>
{% set student_count = get_students(course.name) | length %}
<span class="course-student-count">
{% if course.get_students() | length %}
{% if student_count %}
<span class="vertically-center mr-4">
<img class="icon-background" src="/assets/school/icons/user.svg" />
{{ course.get_students() | length }}
</span> {% endif %}
{% set avg_rating = course.get_average_rating() %}
{{ student_count }}
</span>
{% endif %}
{% set avg_rating = get_average_rating(course.name) %}
{% if avg_rating %}
<span class="vertically-center">
<img class="icon-background" src="/assets/school/icons/rating.svg" />
@@ -64,11 +79,11 @@
<a class="stretched-link" href="/courses/{{ course.name }}"></a>
{% else %}
{% set lesson_index = course.get_lesson_index(membership.current_lesson) if membership and
{% set lesson_index = get_lesson_index(membership.current_lesson) if membership and
membership.current_lesson else '1.1' %}
{% set query_parameter = "?batch=" + membership.batch if membership and
membership.batch else "" %}
{% set certificate = course.is_certified() %}
{% set certificate = is_certified(course.name) %}
{% if certificate %}
<a class="stretched-link" href="/courses/{{ course.name }}/{{ certificate }}"></a>
@@ -83,7 +98,7 @@
<a class="stretched-link" href="/courses/{{ course.name }}"></a>
{% elif membership %}
<a class="stretched-link" href="{{ course.get_learn_url(lesson_index) }}{{ query_parameter }}"></a>
<a class="stretched-link" href="{{ get_lesson_url(course.name, lesson_index) }}{{ query_parameter }}"></a>
{% else %}
<a class="stretched-link" href="/courses/{{ course.name }}"></a>

View File

@@ -1,10 +1,10 @@
{% if course.get_chapters() | length %}
{% if get_chapters(course.name) | length %}
<div class="">
<div class="course-home-headings">
Course Outline
</div>
<div class="common-card-style course-outline">
{% for chapter in course.get_chapters() %}
{% for chapter in get_chapters(course.name) %}
{{ widgets.ChapterTeaser(index=loop.index, chapter=chapter, course=course, batch=batch, membership=membership) }}
{% endfor %}
</div>

View File

@@ -3,7 +3,7 @@
<div class="small-title member-card-title {% if show_course_count %} font-weight-bold {% endif %}">
{{ member.full_name }}
</div>
{% set course_count = member.get_authored_courses() | length %}
{% set course_count = get_authored_courses(member.name) | length %}
{% if show_course_count and course_count > 0 %}
{% set suffix = "Courses" if course_count > 1 else "Course" %}
<div class="small-title">

View File

@@ -1,11 +1,11 @@
{% if not course.upcoming %}
<div class="reviews-parent">
{% set reviews = course.get_reviews() %}
{% set reviews = get_reviews(course.name) %}
<div class="mb-5">
<span class="course-home-headings">Reviews</span>
{% if course.is_eligible_to_review(membership) and reviews | length %}
<span class="course-home-headings"> {{ _("Reviews") }} </span>
{% if is_eligible_to_review(course.name, membership) and reviews | length %}
<span class="review-link button is-secondary pull-right">
Write a review
{{ _("Write a review") }}
</span>
{% endif %}
</div>
@@ -41,9 +41,9 @@
<img class="icon icon-xl" src="/assets/frappe/icons/timeless/message.svg">
<div class="course-home-headings mt-4 mb-0" style="color: inherit;"> {{ _("Review the course") }} </div>
<div class="small mb-6"> {{ _("Help us improve our course material.") }} </div>
{% if course.is_eligible_to_review(membership) %}
{% if is_eligible_to_review(course.name, membership) %}
<span class="review-link button is-secondary ml-auto mr-auto mt-3">
Write a review
{{ _("Write a review") }}
</span>
{% elif frappe.session.user == "Guest" %}
<a class="button is-secondary dark-links ml-auto mr-auto mt-3" href="/login?redirect-to=/courses/{{ course.name }}"> {{ _("Login") }} </a>
@@ -59,7 +59,7 @@
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<div class="font-weight-bold">Write a Review</div>
<div class="font-weight-bold">{{ _("Write a review") }}</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
@@ -68,7 +68,7 @@
<form class="review-form" id="review-form">
<div class="form-group">
<div class="clearfix">
<label class="control-label reqd" style="padding-right: 0px;">Rating</label>
<label class="control-label reqd" style="padding-right: 0px;">{{ _("Rating") }}</label>
</div>
<div class="control-input-wrapper">
<div class="control-input">
@@ -85,7 +85,7 @@
<div class="form-group">
<div class="clearfix">
<label class="control-label reqd" style="padding-right: 0px;">Review</label>
<label class="control-label reqd" style="padding-right: 0px;">{{ _("Review") }}</label>
</div>
<div class="control-input-wrapper">
<div class="control-input">
@@ -100,7 +100,7 @@
</div>
<div class="modal-footer">
<div class="button submit-review is-primary" data-course="{{ course.name | urlencode}}" id="submit-review">
Submit</div>
{{ _("Submit") }}</div>
</div>
</div>
</div>

View File

@@ -84,14 +84,7 @@ class CustomUser(User):
self.profile_complete = all_fields_have_value
def get_authored_courses(self) -> int:
"""Returns the number of courses authored by this user.
"""
return frappe.get_all(
'LMS Course', {
'instructor': self.name,
'is_published': True
})
def get_batch_count(self) -> int:
"""Returns the number of batches authored by this user.
@@ -131,7 +124,8 @@ class CustomUser(User):
for map in mapping:
if frappe.db.get_value("LMS Course", map.course, "is_published"):
course = frappe.get_doc("LMS Course", map.course)
course = frappe.db.get_value("LMS Course", map.course,
["name", "upcoming", "title", "image", "enable_certification"], as_dict=True)
mentored_courses.append(course)
return mentored_courses
@@ -141,7 +135,8 @@ class CustomUser(User):
completed = []
memberships = self.get_course_membership("Student")
for membership in memberships:
course = frappe.get_doc("LMS Course", membership.course)
course = frappe.db.get_value("LMS Course", membership.course,
["name", "upcoming", "title", "image", "enable_certification"], as_dict=True)
progress = cint(membership.progress)
if progress < 100:
in_progress.append(course)
@@ -152,6 +147,14 @@ class CustomUser(User):
"in_progress": in_progress,
"completed": completed
}
def get_authored_courses(member):
"""Returns the number of courses authored by this user.
"""
return frappe.get_all(
'LMS Course', {
'instructor': member,
'is_published': True
})
def get_palette(full_name):
"""

View File

@@ -1,5 +1,6 @@
import frappe
from frappe.utils import rounded
from school.www.utils import get_course_progress
def execute():
frappe.reload_doc("lms", "doctype", "lms_batch_membership")
@@ -14,9 +15,8 @@ def execute():
if current_course != membership.course:
current_course = membership.course
course_details = frappe.get_doc("LMS Course", current_course)
progress = rounded(course_details.get_course_progress(membership.member))
progress = rounded(get_course_progress(current_course, membership.member))
frappe.db.set_value("LMS Batch Membership", membership.name, "progress", progress)
frappe.db.delete("Prepared Report", {"ref_report_doctype": "Course Progress Summary"})
frappe.db.set_value("Report", "Course Progress Summary", "prepared_report", 0)
frappe.db.set_value("Report", "Course Progress Summary", "prepared_report", 0)

View File

@@ -79,7 +79,6 @@ input[type=checkbox] {
}
.common-page-style {
background: #F4F5F6;
padding-bottom: 5rem;
min-height: 60vh;
padding-top: 3rem;
@@ -171,6 +170,10 @@ input[type=checkbox] {
margin-top: auto;
}
.course-card-footer .avatar-group {
display: inherit;
}
.view-course-link {
height: 32px;
border-radius: 4px;

View File

@@ -40,7 +40,7 @@
{% if membership or is_instructor %} eligible-for-submission {% endif %}" data-lesson="{{ lesson.name }}"
data-course="{{ course.name }}">
{{ lesson.title }}
<span class="lesson-progress {{hide if course.get_progress(lesson.name) != 'Complete' else ''}}">COMPLETED</span>
<span class="lesson-progress {{hide if get_progress(course.name, lesson.name) != 'Complete' else ''}}">COMPLETED</span>
</div>
@@ -52,7 +52,7 @@
<a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>
</small>
{% endif %}
{{ lesson.render_html() }}</div>
{{ render_html(lesson.body) }}</div>
{% else %}
<div class="common-card-style lesson-content-card">
<div class="w-25 text-center" style="margin: 0 auto;">
@@ -78,8 +78,8 @@
</div>
{% if not course.is_mentor(frappe.session.user) and membership %}
{% set progress = course.get_progress(lesson.name) %}
{% if not is_mentor(course.name, frappe.session.user) and membership %}
{% set progress = get_progress(course.name, lesson.name) %}
<div class="custom-checkbox {% if progress == 'Complete' %} hide {% endif %}">
<label class="quiz-label">
<input class="option mark-progress" type="checkbox" checked>

View File

@@ -1,28 +1,27 @@
from re import I
import frappe
from . import utils
from frappe.utils import cstr
from school.www.utils import get_common_context, redirect_to_lesson, get_lesson_url
from frappe.utils import cstr, flt
from school.www import batch
def get_context(context):
utils.get_common_context(context)
get_common_context(context)
chapter_index = frappe.form_dict.get("chapter")
lesson_index = frappe.form_dict.get("lesson")
lesson_number = f"{chapter_index}.{lesson_index}"
if not chapter_index or not lesson_index:
if context.batch:
index_ = get_lesson_index(context.course, context.batch, frappe.session.user) or "1.1"
else:
index_ = "1.1"
utils.redirect_to_lesson(context.course, index_)
redirect_to_lesson(context.course, index_)
context.lesson = get_current_lesson_details(lesson_number, context)
neighbours = context.course.get_neighbours(lesson_number, context.lessons)
context.next_url = get_learn_url(neighbours["next"], context.course)
context.prev_url = get_learn_url(neighbours["prev"], context.course)
neighbours = get_neighbours(lesson_number, context.lessons)
context.next_url = get_url(neighbours["next"], context.course)
context.prev_url = get_url(neighbours["prev"], context.course)
meta_info = context.lesson.title + " - " + context.course.title
context.metatags = {
@@ -38,15 +37,16 @@ def get_context(context):
"lesson": context.lesson.name,
"is_member": context.membership is not None
}
print(context)
def get_current_lesson_details(lesson_number, context):
details_list = list(filter(lambda x: cstr(x.number) == lesson_number, context.lessons))
if not len(details_list):
utils.redirect_to_lesson(context.course)
redirect_to_lesson(context.course)
return details_list[0]
def get_learn_url(lesson_number, course):
return course.get_learn_url(lesson_number) and course.get_learn_url(lesson_number) + course.query_parameter
def get_url(lesson_number, course):
return get_lesson_url(course.name, lesson_number) and get_lesson_url(course.name, lesson_number) + course.query_parameter
def get_lesson_index(course, batch, user):
lesson = batch.get_current_lesson(user)
@@ -59,3 +59,12 @@ def get_page_extensions(context):
for e in extensions:
e.set_context(context)
return extensions
def get_neighbours(current, lessons):
current = flt(current)
numbers = sorted(lesson.number for lesson in lessons)
index = numbers.index(current)
return {
"prev": numbers[index-1] if index-1 >= 0 else None,
"next": numbers[index+1] if index+1 < len(numbers) else None
}

View File

@@ -49,11 +49,11 @@
</div>
{% endif %}
{% if membership %}
{% set lesson_index = course.get_lesson_index(membership.current_lesson) if membership and
{% set lesson_index = get_lesson_index(membership.current_lesson) if membership and
membership.current_lesson
else '1.1' %}
<a class="button wide-button is-primary" id="continue-learning"
href="{{ course.get_learn_url(lesson_index) }}{{ course.query_parameter }}">
href="{{ get_lesson_url(course.name, lesson_index) }}{{ course.query_parameter }}">
Continue Learning <img class="ml-2" src="/assets/school/icons/white-arrow.svg" />
</a>
{% endif %}
@@ -70,7 +70,7 @@
</div>
{% endif %}
{% if course.is_cohort_staff(frappe.session.user) %}
{% if is_cohort_staff(course.name, frappe.session.user) %}
<a class="button wide-button is-secondary"
href="/courses/{{course.name}}/manage"
style="color: inherit;">
@@ -129,14 +129,14 @@
<div class="course-home-headings">
Creator
</div>
{% for instructor in course.get_instructors() %}
{% for instructor in get_instructors(course.name) %}
{{ widgets.MemberCard(member=instructor, show_course_count=True, avatar_class="avatar-large") }}
{% endfor %}
</div>
{% endmacro %}
{% macro Progress(course) %}
{% set certificate = course.is_certified() %}
{% set certificate = is_certified(course.name) %}
{% set progress = frappe.utils.cint(membership.progress) %}
{% if progress %}
<div class="course-progress-section">
@@ -186,17 +186,17 @@
{% endmacro %}
{% macro Overview(course) %}
{% set avg_rating = course.get_average_rating() %}
{% if course.get_students() | length or avg_rating %}
{% set avg_rating = get_average_rating(course.name) %}
{% if get_students(course.name) | length or avg_rating %}
<div class="course-overview-section">
<div class="course-home-headings">
Overview
</div>
<div class="common-card-style overview-card small-title">
{% if course.get_students() | length %}
{% if get_students(course.name) | length %}
<div class="overview-item">
<img class="icon-background mr-1" src="/assets/school/icons/user.svg" />
{{ course.get_students() | length }} Enrolled
{{ get_students(course.name) | length }} Enrolled
</div>
{% endif %}
{% if avg_rating %}
@@ -213,13 +213,13 @@
<!-- Mentors -->
{% macro Mentors(course) %}
{% if course.get_mentors() | length %}
{% if get_mentors(course.name) | length %}
<div class="course-home-mentors">
<div class="course-home-headings">
Mentors
</div>
<div class="member-parent">
{% for mentor in course.get_mentors() %}
{% for mentor in get_mentors(course.name) %}
{{ widgets.MemberCard(member=mentor, show_course_count=False) }}
{% endfor %}
</div>

View File

@@ -1,5 +1,6 @@
import frappe
from school.lms.doctype.lms_settings.lms_settings import check_profile_restriction
from school.www.utils import get_membership
def get_context(context):
context.no_cache = 1
@@ -16,7 +17,7 @@ def get_context(context):
raise frappe.Redirect
context.course = course
membership = course.get_membership(frappe.session.user)
membership = get_membership(course.name, frappe.session.user)
context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else ""
context.membership = membership
if context.course.upcoming:

View File

@@ -15,7 +15,7 @@ def get_context(context):
def get_courses():
courses = frappe.get_all("LMS Course",
filters={"is_published": True},
fields=["name", "upcoming", "title", "image"])
fields=["name", "upcoming", "title", "image", "enable_certification"])
live_courses, upcoming_courses = [], []
for course in courses:

View File

@@ -29,7 +29,7 @@
<div class="profile-info">
<div class="profile-name-section">
<div class="profile-name"> {{ member.full_name }} </div>
{% if member.get_authored_courses() | length %}
{% if get_authored_courses(member.name) | length %}
<div class="creator-badge"> Creator </div>
{% endif %}
{% if member.looking_for_job %}
@@ -104,14 +104,16 @@
{% macro CoursesCreated(member, read_only) %}
{% if member.get_authored_courses() | length %}
{% set authored_courses = get_authored_courses(member.name) %}
{% if authored_courses | length %}
<div class="profile-courses">
<div class="course-home-headings">
Courses Created
</div>
<div class="cards-parent">
{% for course in member.get_authored_courses() %}
{% set course_details = frappe.get_doc("LMS Course", course) %}
{% for course in authored_courses %}
{% set course_details = frappe.db.get_value("LMS Course", course,
["name", "upcoming", "title", "image", "enable_certification"], as_dict=True) %}
{{ widgets.CourseCard(course=course_details, read_only=read_only) }}
{% endfor %}
</div>

View File

@@ -1,26 +1,27 @@
import frappe
from school.lms.models import Course
from frappe.utils import flt
from frappe.utils import flt, cint
from school.lms.utils import slugify
from school.lms.md import markdown_to_html
def get_common_context(context):
context.no_cache = 1
course_name = frappe.form_dict["course"]
try:
batch_name = frappe.form_dict["batch"]
except KeyError:
batch_name = None
course = frappe.get_doc("LMS Course", course_name)
course = frappe.db.get_value("LMS Course",
frappe.form_dict["course"], ["name", "title", "video_link"], as_dict=True)
if not course:
context.template = "www/404.html"
return
context.course = course
context.lessons = course.get_lessons()
membership = get_membership(course_name, frappe.session.user, batch_name)
context.lessons = get_lessons(course.name)
membership = get_membership(course.name, frappe.session.user, batch_name)
context.membership = membership
if membership:
batch = course.get_batch(membership.batch)
batch = get_batch(course.name, membership.batch)
if batch:
context.batch = batch
@@ -32,7 +33,7 @@ def get_livecode_url():
return frappe.db.get_single_value("LMS Settings", "livecode_url")
def redirect_to_lesson(course, index_="1.1"):
frappe.local.flags.redirect_location = course.get_learn_url(index_) + course.query_parameter
frappe.local.flags.redirect_location = get_lesson_url(course.name, index_) + course.query_parameter
raise frappe.Redirect
def get_membership(course, member, batch=None):
@@ -55,16 +56,18 @@ def get_membership(course, member, batch=None):
def get_chapters(course):
"""Returns all chapters of this course.
"""
chapters = frappe.get_all("Course Chapter",
{ "course": course },
["name", "title", "description", "idx"])
chapters = frappe.get_all("Chapter Reference", {"parent": course},
["idx", "chapter"], order_by="idx")
for chapter in chapters:
chapter_details = frappe.db.get_value("Course Chapter", { "name": chapter.chapter },
["name", "title", "description"], as_dict=True)
chapter.update(chapter_details)
return chapters
def get_lessons(course, chapter=None):
""" If chapter is passed, returns lessons of only that chapter.
Else returns lessons of all chapters of the course """
lessons = []
if chapter:
return get_lesson_details(chapter)
@@ -82,7 +85,8 @@ def get_lesson_details(chapter):
order_by="idx")
for row in lesson_list:
lesson_details = frappe.db.get_value("Course Lesson", row.lesson, "name", as_dict=True)
lesson_details = frappe.db.get_value("Course Lesson", row.lesson,
["name", "title", "include_in_preview", "body"], as_dict=True)
lesson_details.number = flt("{}.{}".format(chapter.idx, row.idx))
lessons.append(lesson_details)
return lessons
@@ -91,11 +95,171 @@ def get_tags(course):
tags = frappe.db.get_value("LMS Course", course, "tags")
return tags.split(",") if tags else []
def get_instructors(self):
instructors = []
if self.instructors:
for instructor in self.instructors:
instructors.append(frappe.get_doc("User", instructor.instructor))
else:
instructors.append(frappe.get_doc("User", self.owner))
return instructors
def get_instructors(course):
instructor_details = []
instructors = frappe.get_all("Course Instructor", {"parent": course},
["instructor"], order_by="idx")
if not instructors:
instructors = frappe.db.get_value("LMS Course", course, "owner").split(" ")
for instructor in instructors:
instructor_details.append(frappe.db.get_value("User",
instructor.instructor,
["name", "username", "full_name", "user_image"],
as_dict=True))
return instructor_details
def get_students(course, batch=None):
"""Returns (email, full_name, username) of all the students of this batch as a list of dict.
"""
filters = {
"course": course,
"member_type": "Student"
}
if batch:
filters["batch"] = batch
return frappe.get_all(
"LMS Batch Membership",
filters,
["member"])
def get_average_rating(course):
ratings = [review.rating for review in get_reviews(course)]
if not len(ratings):
return None
return sum(ratings)/len(ratings)
def get_reviews(course):
reviews = frappe.get_all("LMS Course Review",
{
"course": course
},
["review", "rating", "owner"],
order_by= "creation desc")
out_of_ratings = frappe.db.get_all("DocField",
{
"parent": "LMS Course Review",
"fieldtype": "Rating"
},
["options"])
out_of_ratings = (len(out_of_ratings) and out_of_ratings[0].options) or 5
for review in reviews:
review.rating = review.rating * out_of_ratings
review.owner_details = frappe.db.get_value("User",
review.owner, ["name", "username", "full_name", "user_image"], as_dict=True)
return reviews
def is_certified(course):
certificate = frappe.get_all("LMS Certification",
{
"student": frappe.session.user,
"course": course
})
if len(certificate):
return certificate[0].name
return
def get_lesson_index(lesson_name):
"""Returns the {chapter_index}.{lesson_index} for the lesson.
"""
lesson = frappe.db.get_value("Lesson Reference",
{"lesson": lesson_name}, ["idx", "parent"], as_dict=True)
if not lesson:
return None
chapter = frappe.db.get_value("Chapter Reference",
{"chapter": lesson.parent}, ["idx"], as_dict=True)
if not chapter:
return None
return f"{chapter.idx}.{lesson.idx}"
def get_lesson_url(course, lesson_number):
if not lesson_number:
return
return f"/courses/{course}/learn/{lesson_number}"
def get_batch(course, batch_name):
return frappe.get_all("LMS Batch", {"name": batch_name, "course": course})
def get_slugified_chapter_title(chapter):
return slugify(chapter)
def get_progress(course, lesson):
return frappe.db.get_value("LMS Course Progress", {
"course": course,
"owner": frappe.session.user,
"lesson": lesson
},
["status"])
def render_html(body):
return markdown_to_html(body)
def is_mentor(course, email):
"""Checks if given user is a mentor for this course.
"""
if not email:
return False
return frappe.db.count("LMS Course Mentor Mapping",
{
"course": course,
"mentor": email
})
def is_cohort_staff(course, user_email):
"""Returns True if the user is either a mentor or a staff for one or more active cohorts of this course.
"""
staff = {
"doctype": "Cohort Staff",
"course": course,
"email": user_email
}
mentor = {
"doctype": "Cohort Mentor",
"course": course,
"email": user_email
}
return frappe.db.exists(staff) or frappe.db.exists(mentor)
def get_mentors(course):
"""Returns the list of all mentors for this course.
"""
course_mentors = []
mentors = frappe.get_all("LMS Course Mentor Mapping", {"course": course}, ["mentor"])
for mentor in mentors:
member = frappe.db.get_value("User", mentor.mentor,
["name", "username", "full_name", "user_image"])
member.batch_count = frappe.db.count("LMS Batch Membership",
{
"member": member.name,
"member_type": "Mentor"
})
course_mentors.append(member)
return course_mentors
def is_eligible_to_review(course, membership):
""" Checks if user is eligible to review the course """
if not membership:
return False
if frappe.db.count("LMS Course Review",
{
"course": course,
"owner": frappe.session.user
}):
return False
return True
def get_course_progress(course, member=None):
""" Returns the course progress of the session user """
lesson_count = len(get_lessons(course))
if not lesson_count:
return 0
completed_lessons = frappe.db.count("LMS Course Progress",
{
"course": course,
"owner": member or frappe.session.user,
"status": "Complete"
})
precision = cint(frappe.db.get_default("float_precision")) or 3
return flt(((completed_lessons/lesson_count) * 100), precision)