fix: removing get_doc dependencies for lms course

This commit is contained in:
Jannat Patel
2022-02-08 16:13:38 +05:30
parent 50c624e305
commit 57c69a7d6c
12 changed files with 193 additions and 213 deletions

View File

@@ -1,4 +1,4 @@
{% set color = member.get_palette() %} {% set color = get_palette(member.full_name) %}
<a class="button-links" href="{{ get_profile_url(member.username) }}"> <a class="button-links" href="{{ get_profile_url(member.username) }}">
<span class="avatar {{ avatar_class }}" title="{{ member.full_name }}"> <span class="avatar {{ avatar_class }}" title="{{ member.full_name }}">
{% if member.user_image %} {% if member.user_image %}

View File

@@ -161,7 +161,11 @@ update_website_context = [
jinja = { jinja = {
"methods": [ "methods": [
"school.page_renderers.get_profile_url" "school.page_renderers.get_profile_url",
"school.overrides.user.get_palette",
"school.www.utils.get_membership",
"school.www.utils.get_lessons",
"school.www.utils.get_tags"
], ],
"filters": [] "filters": []
} }

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe and contributors
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
@@ -143,46 +142,7 @@ class LMSCourse(Document):
fieldname="batch") fieldname="batch")
return batch_name and frappe.get_doc("LMS Batch", batch_name) return batch_name and frappe.get_doc("LMS Batch", batch_name)
def get_instructor(self):
if self.instructor:
return frappe.get_doc("User", self.instructor)
return frappe.get_doc("User", self.owner)
def get_chapters(self):
"""Returns all chapters of this course.
"""
chapters = []
for row in self.chapters:
chapter_details = frappe.db.get_value("Course Chapter", row.chapter,
["name", "title", "description"],
as_dict=True)
chapter_details.idx = row.idx
chapters.append(chapter_details)
return chapters
def get_lessons(self, 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 self.get_lesson_details(chapter)
for chapter in self.get_chapters():
lesson = self.get_lesson_details(chapter)
lessons += lesson
return lessons
def get_lesson_details(self, chapter):
lessons = []
lesson_list = frappe.get_all("Lesson Reference", {"parent": chapter.name},
["lesson", "idx"], order_by="idx")
for row in lesson_list:
lesson_details = frappe.get_doc("Course Lesson", row.lesson)
lesson_details.number = flt("{}.{}".format(chapter.idx, row.idx))
lessons.append(lesson_details)
return lessons
def get_slugified_chapter_title(self, chapter): def get_slugified_chapter_title(self, chapter):
return slugify(chapter) return slugify(chapter)
@@ -201,15 +161,6 @@ class LMSCourse(Document):
batch_names = {m.batch for m in memberships} batch_names = {m.batch for m in memberships}
return [b for b in batches if b.name in batch_names] return [b for b in batches if b.name in batch_names]
def get_upcoming_batches(self):
now = frappe.utils.nowdate()
batches = find_all("LMS Batch",
course=self.name,
start_date=[">", now],
status="Active",
visibility="Public")
return batches
def get_cohorts(self): def get_cohorts(self):
return find_all("Cohort", course=self.name, order_by="creation") return find_all("Cohort", course=self.name, order_by="creation")
@@ -263,22 +214,7 @@ class LMSCourse(Document):
return return
return f"/courses/{self.name}/learn/{lesson_number}" return f"/courses/{self.name}/learn/{lesson_number}"
def get_membership(self, member, batch=None):
filters = {
"member": member,
"course": self.name
}
if batch:
filters["batch"] = batch
membership = frappe.db.get_value("LMS Batch Membership",
filters,
["name", "batch", "current_lesson", "member_type", "progress"],
as_dict=True)
if membership and membership.batch:
membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title")
return membership
def get_all_memberships(self, member): def get_all_memberships(self, member):
all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": self.name}, ["batch"]) all_memberships = frappe.get_all("LMS Batch Membership", {"member": member, "course": self.name}, ["batch"])
@@ -302,8 +238,7 @@ class LMSCourse(Document):
member_names = [m['member'] for m in memberships] member_names = [m['member'] for m in memberships]
return find_all("User", name=["IN", member_names]) return find_all("User", name=["IN", member_names])
def get_tags(self):
return self.tags.split(",") if self.tags else []
def get_reviews(self): def get_reviews(self):
reviews = frappe.get_all("LMS Course Review", reviews = frappe.get_all("LMS Course Review",

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2021, FOSS United and contributors # Copyright (c) 2021, Frappe and contributors
# For license information, please see license.txt # For license information, please see license.txt
import frappe import frappe

View File

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

View File

@@ -1,47 +1,41 @@
{% set membership = course.get_membership(frappe.session.user) %} {% set membership = get_membership(course.name, frappe.session.user) %}
{% set progress = frappe.utils.cint(membership.progress) %} {% set progress = frappe.utils.cint(membership.progress) %}
<div class="common-card-style course-card" data-course="{{ course.name }}"> <div class="common-card-style course-card" data-course="{{ course.name }}">
<div class="course-image {% if not course.image %}default-image{% endif %}" {% if course.image %} <div class="course-image {% if not course.image %}default-image{% endif %}" {% if course.image %}
style="background-image: url( {{ course.image }} );" {% endif %}> style="background-image: url( {{ course.image }} );" {% endif %}>
<div class="course-tags"> <div class="course-tags">
{% for tag in course.get_tags() %} {% for tag in get_tags(course.name) %}
<div class="course-card-pills">{{ tag }}</div> <div class="course-card-pills">{{ tag }}</div>
{% endfor %} {% endfor %}
{% if membership and not read_only %}
{% if progress < 100 %} <div class="course-card-pills dark-pills ml-auto">{{ frappe.utils.rounded(progress) }}%
{{ _("Completed") }}
</div> </div>
{% else %} {% if not course.image %}
<div class="course-card-pills dark-pills ml-auto"> <img src="/assets/school/icons/check.svg"> {{ _("Completed") <div class="default-image-text">{{ course.title[0] }}</div>
}}</div>
{% endif %} {% endif %}
{% endif %}
</div>
{% if not course.image %}
<div class="default-image-text">{{ course.title[0] }}</div>
{% endif %}
</div> </div>
<div class="course-card-content"> <div class="course-card-content">
<div class="course-card-meta muted-text"> <div class="course-card-meta">
{% if course.get_chapters() | length %} {% if get_lessons(course.name) | length %}
<span> <span>
{{ course.get_chapters() | length }} {{ _("Chapters") }} {{ get_lessons(course.name) | length }} {{ _("Lessons") }}
</span>
{% endif %}
{% if course.get_chapters() | length and course.get_upcoming_batches() | length %}
<span class="font-weight-bold ml-3 mr-3"> . </span>
{% endif %}
{% if course.get_upcoming_batches() | length %}
<span class="">
{{ course.get_upcoming_batches() | length }} {{ _("Open Batches") }}
</span> </span>
{% endif %} {% endif %}
</div> </div>
<div class="card-heading course-card-title">{{ course.title }}</div> <div class="card-heading course-card-title">{{ course.title }}</div>
<div {% if not read_only %} class="mb-4" {% endif %}>
<span class="zindex"> {% if membership and not read_only %}
<div class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="{{ progress }}"
aria-valuemin="0" aria-valuemax="100" style="width:{{ progress }}%">
<span class="sr-only"> {{ progress }} Complete</span>
</div>
</div>
<div class="progress-percentage">{{ progress }}% Completed</div>
{% endif %}
<div class="course-card-footer">
<span>
{{ widgets.Avatar(member=course.get_instructor(), avatar_class="avatar-small") }} {{ widgets.Avatar(member=course.get_instructor(), avatar_class="avatar-small") }}
<a class="button-links" href="{{ get_profile_url(course.get_instructor().username) }}"> <a class="button-links" href="{{ get_profile_url(course.get_instructor().username) }}">
<span class="course-instructor"> <span class="course-instructor">
@@ -76,38 +70,21 @@
{% set certificate = course.is_certified() %} {% set certificate = course.is_certified() %}
{% if certificate %} {% if certificate %}
<div class="view-course-link is-default">
{{ _("Get Certificate") }}
</div>
<a class="stretched-link" href="/courses/{{ course.name }}/{{ certificate }}"></a> <a class="stretched-link" href="/courses/{{ course.name }}/{{ certificate }}"></a>
{% elif course.enable_certification and progress == 100 %} {% elif course.enable_certification and progress == 100 %}
<div class="view-course-link is-default" id="certification" data-course="{{ course.name }}"> <a class="stretched-link" id="certification" data-course="{{ course.name }}"></a>
{{ _("Get Certificate") }}
</div>
{% elif progress == 100 %} {% elif progress == 100 %}
<div class="view-course-link is-default">
{{ _("Course Completed") }}
</div>
<a class="stretched-link" href="/courses/{{ course.name }}"></a> <a class="stretched-link" href="/courses/{{ course.name }}"></a>
{% elif course.upcoming %} {% elif course.upcoming %}
<div class="view-course-link is-secondary border">
{{ _("Upcoming Course") }}
</div>
<a class="stretched-link" href="/courses/{{ course.name }}"></a> <a class="stretched-link" href="/courses/{{ course.name }}"></a>
{% elif membership %} {% elif membership %}
<div class="view-course-link is-primary">
{{ _("Continue Course") }}
</div>
<a class="stretched-link" href="{{ course.get_learn_url(lesson_index) }}{{ query_parameter }}"></a> <a class="stretched-link" href="{{ course.get_learn_url(lesson_index) }}{{ query_parameter }}"></a>
{% else %} {% else %}
<div class="view-course-link is-default">
{{ _("View Course") }}
</div>
<a class="stretched-link" href="/courses/{{ course.name }}"></a> <a class="stretched-link" href="/courses/{{ course.name }}"></a>
{% endif %} {% endif %}
@@ -117,21 +94,12 @@
<script> <script>
frappe.ready(() => { frappe.ready(() => {
trim_course_titles();
$("#certification").unbind().click((e) => { $("#certification").unbind().click((e) => {
create_certificate(e); create_certificate(e);
}); });
})
var trim_course_titles = () => { })
$(".course-card-title").each((i, element) => {
var title = $(element).text();
var length = $(window).width() <= 375 ? 60 : 65;
var suffix = title.length > length ? "..." : "";
$(element).text(title.substring(0, length) + suffix);
});
}
var create_certificate = (e) => { var create_certificate = (e) => {
e.preventDefault(); e.preventDefault();

View File

@@ -93,27 +93,6 @@ class CustomUser(User):
'is_published': True 'is_published': True
}) })
def get_palette(self):
"""
Returns a color unique to each member for Avatar """
palette = [
['--orange-avatar-bg', '--orange-avatar-color'],
['--pink-avatar-bg', '--pink-avatar-color'],
['--blue-avatar-bg', '--blue-avatar-color'],
['--green-avatar-bg', '--green-avatar-color'],
['--dark-green-avatar-bg', '--dark-green-avatar-color'],
['--red-avatar-bg', '--red-avatar-color'],
['--yellow-avatar-bg', '--yellow-avatar-color'],
['--purple-avatar-bg', '--purple-avatar-color'],
['--gray-avatar-bg', '--gray-avatar-color0']
]
encoded_name = str(self.full_name).encode("utf-8")
hash_name = hashlib.md5(encoded_name).hexdigest()
idx = cint((int(hash_name[4:6], 16) + 1) / 5.33)
return palette[idx % 8]
def get_batch_count(self) -> int: def get_batch_count(self) -> int:
"""Returns the number of batches authored by this user. """Returns the number of batches authored by this user.
""" """
@@ -174,6 +153,27 @@ class CustomUser(User):
"completed": completed "completed": completed
} }
def get_palette(full_name):
"""
Returns a color unique to each member for Avatar """
palette = [
['--orange-avatar-bg', '--orange-avatar-color'],
['--pink-avatar-bg', '--pink-avatar-color'],
['--blue-avatar-bg', '--blue-avatar-color'],
['--green-avatar-bg', '--green-avatar-color'],
['--dark-green-avatar-bg', '--dark-green-avatar-color'],
['--red-avatar-bg', '--red-avatar-color'],
['--yellow-avatar-bg', '--yellow-avatar-color'],
['--purple-avatar-bg', '--purple-avatar-color'],
['--gray-avatar-bg', '--gray-avatar-color0']
]
encoded_name = str(full_name).encode("utf-8")
hash_name = hashlib.md5(encoded_name).hexdigest()
idx = cint((int(hash_name[4:6], 16) + 1) / 5.33)
return palette[idx % 8]
@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def sign_up(email, full_name, verify_terms): def sign_up(email, full_name, verify_terms):
if is_signup_disabled(): if is_signup_disabled():

View File

@@ -10,6 +10,7 @@
--text-3xl: 22px; --text-3xl: 22px;
--text-4xl: 44px; --text-4xl: 44px;
--navbar-shadow: 0px 1px 8px rgba(0, 0, 0, 0.08) --navbar-shadow: 0px 1px 8px rgba(0, 0, 0, 0.08)
--gray-750: #505A62;
} }
input[type=checkbox] { input[type=checkbox] {
@@ -56,15 +57,16 @@ input[type=checkbox] {
.course-card-pills { .course-card-pills {
background: #ffffff; background: #ffffff;
margin-left: 0; margin-left: 0;
margin-right: .5rem; margin-right: 1rem;
border-radius: 4px; border-radius: 6px;
padding: 4px 6px; padding: 3.5px 8px;
font-size: 10px; font-size: 11px;
text-align: center; text-align: center;
letter-spacing: 0.011em; letter-spacing: 0.011em;
text-transform: uppercase; text-transform: uppercase;
font-weight: bold; font-weight: 600;
box-shadow: var(--shadow-base); box-shadow: var(--popover-box-shadow);
color: var(--gray-900);
} }
.dark-pills { .dark-pills {
@@ -94,6 +96,8 @@ input[type=checkbox] {
.course-card { .course-card {
flex-direction: column; flex-direction: column;
height: 100%;
min-height: 350px;
} }
.muted-text { .muted-text {
@@ -102,17 +106,24 @@ input[type=checkbox] {
} }
.course-card-meta { .course-card-meta {
margin: 16px 0px 8px; margin: 0.75rem 0 0.5rem;
font-size: 14px;
}
.course-card-meta-2 {
color: var(--gray-750);
} }
.course-card-content { .course-card-content {
width: 100%; padding: 0 1.25rem 1.25rem;
padding: 0px 1rem 1.25rem; display: flex;
flex-direction: column;
flex: 1 1 auto;
} }
@media (max-width: 350px) { @media (max-width: 350px) {
.course-card-content { .course-card-content {
padding: 0px 10px 20px; padding: 0px 10px 10px;
} }
} }
@@ -124,8 +135,8 @@ input[type=checkbox] {
.course-card-title { .course-card-title {
font-size: 1.125rem; font-size: 1.125rem;
margin-bottom: 1.5rem; font-weight: 600;
height: 56px; margin-bottom: 1.25rem;
} }
@media (max-width: 360px) { @media (max-width: 360px) {
@@ -145,16 +156,19 @@ input[type=checkbox] {
} }
.course-instructor { .course-instructor {
margin-left: 8px; margin-left: 0.625rem;
font-size: 12px; font-size: 0.875rem;
line-height: 135%;
color: var(--text-color-dark);
} }
.course-student-count { .course-student-count {
display: flex; display: flex;
font-size: 12px; font-size: 12px;
float: right; float: right;
font-weight: 500;
}
.course-card-footer {
margin-top: auto;
} }
.view-course-link { .view-course-link {
@@ -1181,7 +1195,7 @@ input[type=checkbox] {
.progress { .progress {
width: 100%; width: 100%;
height: 8px; height: 4px;
} }
.progress-bar { .progress-bar {
@@ -1189,12 +1203,9 @@ input[type=checkbox] {
} }
.progress-percentage { .progress-percentage {
width: 100%; margin: 0.5rem 0 1.3rem;
font-size: 12px; font-size: 0.8rem;
line-height: 165%; font-weight: 500;
letter-spacing: 0.02em;
color: #000000;
text-align: center;
} }
pre { pre {

View File

@@ -1,35 +0,0 @@
import frappe
from school.lms.models import Course
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)
if not course:
context.template = "www/404.html"
return
context.course = course
context.lessons = course.get_lessons()
membership = course.get_membership(frappe.session.user, batch_name)
context.membership = membership
if membership:
batch = course.get_batch(membership.batch)
if batch:
context.batch = batch
context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else ""
context.livecode_url = get_livecode_url()
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
raise frappe.Redirect

View File

@@ -19,13 +19,13 @@
{% include "school/templates/search_course/search_course.html" %} {% include "school/templates/search_course/search_course.html" %}
<div class="course-list"> <div class="course-list">
{% set title = _("Live Courses") %}
{% set courses = live_courses %} {% set courses = live_courses %}
{% set title = _("All Live Courses ({0})").format(courses | length) %}
{% set classes = "live-courses" %} {% set classes = "live-courses" %}
{% include "school/templates/course_list.html" %} {% include "school/templates/course_list.html" %}
{% set title = _("Upcoming Courses") %}
{% set courses = upcoming_courses %} {% set courses = upcoming_courses %}
{% set title = _("All Upcoming Courses ({0})").format(courses | length) %}
{% set classes = "upcoming-courses mt-10" %} {% set classes = "upcoming-courses mt-10" %}
{% include "school/templates/course_list.html" %} {% include "school/templates/course_list.html" %}
</div> </div>

View File

@@ -6,21 +6,21 @@ def get_context(context):
context.live_courses, context.upcoming_courses = get_courses() context.live_courses, context.upcoming_courses = get_courses()
context.restriction = check_profile_restriction() context.restriction = check_profile_restriction()
context.metatags = { context.metatags = {
"title": "All Courses", "title": "All Live Courses",
"image": frappe.db.get_single_value("Website Settings", "banner_image"), "image": frappe.db.get_single_value("Website Settings", "banner_image"),
"description": "This page lists all the courses published on our website", "description": "This page lists all the courses published on our website",
"keywords": "All Courses, Courses, Learn" "keywords": "All Courses, Courses, Learn"
} }
def get_courses(): def get_courses():
course_names = frappe.get_all("LMS Course", courses = frappe.get_all("LMS Course",
filters={"is_published": True}, filters={"is_published": True},
fields=["name", "upcoming"]) fields=["name", "upcoming", "title", "image"])
live_courses, upcoming_courses = [], [] live_courses, upcoming_courses = [], []
for course in course_names: for course in courses:
if course.upcoming: if course.upcoming:
upcoming_courses.append(frappe.get_doc("LMS Course", course.name)) upcoming_courses.append(course)
else: else:
live_courses.append(frappe.get_doc("LMS Course", course.name)) live_courses.append(course)
return live_courses, upcoming_courses return live_courses, upcoming_courses

97
school/www/utils.py Normal file
View File

@@ -0,0 +1,97 @@
import frappe
from school.lms.models import Course
from frappe.utils import flt
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)
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.membership = membership
if membership:
batch = course.get_batch(membership.batch)
if batch:
context.batch = batch
context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else ""
context.livecode_url = get_livecode_url()
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
raise frappe.Redirect
def get_membership(course, member, batch=None):
filters = {
"member": member,
"course": course
}
if batch:
filters["batch"] = batch
membership = frappe.db.get_value("LMS Batch Membership",
filters,
["name", "batch", "current_lesson", "member_type", "progress"],
as_dict=True)
if membership and membership.batch:
membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title")
return membership
def get_chapters(course):
"""Returns all chapters of this course.
"""
chapters = frappe.get_all("Course Chapter",
{ "course": course },
["name", "title", "description", "idx"])
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)
for chapter in get_chapters(course):
lesson = get_lesson_details(chapter)
lessons += lesson
return lessons
def get_lesson_details(chapter):
lessons = []
lesson_list = frappe.get_all("Lesson Reference",
{"parent": chapter.name},
["lesson", "idx"],
order_by="idx")
for row in lesson_list:
lesson_details = frappe.db.get_value("Course Lesson", row.lesson, "name", as_dict=True)
lesson_details.number = flt("{}.{}".format(chapter.idx, row.idx))
lessons.append(lesson_details)
return lessons
def get_tags(course):
tags = frappe.db.get_value("LMS Course", course, "tags")
return tags.split(",") if tags else []
def get_instructor(course):
if self.instructor:
return frappe.get_doc("User", self.instructor)
return frappe.get_doc("User", self.owner)