Compare commits

..

9 Commits

Author SHA1 Message Date
pateljannat
d7e1745c09 fix: added back update progress code 2021-08-18 18:17:40 +05:30
pateljannat
ef238c1b25 fix: export label 2021-08-18 18:08:33 +05:30
pateljannat
cb60d97bb7 feat: certification 2021-08-18 18:04:47 +05:30
Jannat Patel
f0ee8d7b88 Merge pull request #173 from fossunited/sketch-redesign
fix: sketch cards
2021-08-16 13:47:05 +05:30
Jannat Patel
7e5203f058 Merge pull request #179 from fossunited/discussions
feat: Discussions
2021-08-16 13:45:00 +05:30
pateljannat
7017382451 fix: removed sketch card 2021-08-11 11:40:57 +05:30
pateljannat
3e2c6b3343 fix: sketch image call 2021-08-10 17:08:32 +05:30
pateljannat
5ea744de5c fix: removed unused file 2021-08-10 16:46:48 +05:30
pateljannat
aedb3d3d45 fix: sketch cards 2021-08-10 16:39:17 +05:30
21 changed files with 8231 additions and 25 deletions

View File

@@ -133,7 +133,7 @@ fixtures = ["Custom Field"]
primary_rules = [
{"from_route": "/sketches/<sketch>", "to_route": "sketches/sketch"},
{"from_route": "/courses/<course>", "to_route": "courses/course"},
{"from_route": "/courses/<course>/<topic>", "to_route": "courses/topic"},
{"from_route": "/courses/<course>/<certificate>", "to_route": "courses/certificate"},
{"from_route": "/hackathons/<hackathon>", "to_route": "hackathons/hackathon"},
{"from_route": "/hackathons/<hackathon>/<project>", "to_route": "hackathons/project"},
{"from_route": "/add-a-new-batch", "to_route": "add-a-new-batch"},

View File

@@ -91,7 +91,8 @@ def save_progress(lesson, course, status):
"lesson": lesson,
"status": status,
}).save(ignore_permissions=True)
return "OK"
course_details = frappe.get_doc("LMS Course", course)
return course_details.get_course_progress()
def update_progress(lesson):
user = frappe.session.user

View File

@@ -0,0 +1,14 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('LMS Certification', {
onload: function (frm) {
frm.set_query("student", function (doc) {
return {
filters: {
"ignore_user_type": 1,
}
};
});
}
});

View File

@@ -0,0 +1,70 @@
{
"actions": [],
"creation": "2021-08-16 15:47:19.494055",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"student",
"issue_date",
"column_break_3",
"course",
"expiry_date"
],
"fields": [
{
"fieldname": "student",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Student",
"options": "User"
},
{
"fieldname": "issue_date",
"fieldtype": "Date",
"label": "Issue Date"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "course",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Course",
"options": "LMS Course"
},
{
"fieldname": "expiry_date",
"fieldtype": "Date",
"label": "Expiry Date"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-08-16 15:47:19.494055",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Certification",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,41 @@
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
from frappe.utils import nowdate, add_years
from frappe import _
from frappe.utils.pdf import get_pdf
class LMSCertification(Document):
def validate(self):
certificates = frappe.get_all("LMS Certification", {
"student": self.student,
"course": self.course,
"expiry_date": [">", nowdate()]
})
if len(certificates):
full_name = frappe.db.get_value("User", self.student, "full_name")
course_name = frappe.db.get_value("LMS Course", self.course, "title")
frappe.throw(_("There is already a valid certificate for user {0} for the course {1}").format(full_name, course_name))
@frappe.whitelist()
def create_certificate(course):
course_details = frappe.get_doc("LMS Course", course)
certificate = course_details.is_certified()
if certificate:
return certificate
else:
expires_after_yrs = course_details.expiry
certificate = frappe.get_doc({
"doctype": "LMS Certification",
"student": frappe.session.user,
"course": course,
"issue_date": nowdate(),
"expiry_date": add_years(nowdate(), int(expires_after_yrs))
})
certificate.save(ignore_permissions=True)
return certificate.name

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
# import frappe
import unittest
class TestLMSCertification(unittest.TestCase):
pass

View File

@@ -25,7 +25,10 @@
"section_break_5",
"short_introduction",
"description",
"chapters"
"chapters",
"certification_section",
"enable_certification",
"expiry"
],
"fields": [
{
@@ -94,6 +97,25 @@
"fieldtype": "Table",
"label": "Chapters",
"options": "Chapters"
},
{
"fieldname": "certification_section",
"fieldtype": "Section Break",
"label": "Certification"
},
{
"default": "0",
"fieldname": "enable_certification",
"fieldtype": "Check",
"label": "Enable Certification"
},
{
"default": "0",
"depends_on": "enable_certification",
"fieldname": "expiry",
"fieldtype": "Select",
"label": "Certification Expires After Years",
"options": "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10"
}
],
"index_web_pages_for_search": 1,
@@ -115,7 +137,7 @@
"link_fieldname": "course"
}
],
"modified": "2021-07-28 19:01:50.677445",
"modified": "2021-08-18 18:02:12.623807",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Course",

View File

@@ -347,6 +347,16 @@ class LMSCourse(Document):
"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

@@ -1,16 +0,0 @@
<div class="sketch-teaser">
<div class="sketch-image">
<a href="/sketches/{{sketch.sketch_id}}">
{{ sketch.to_svg() }}
</a>
</div>
<div class="sketch-footer">
<div class="sketch-title">
<a href="sketches/{{sketch.sketch_id}}">{{sketch.title}}</a>
</div>
<div class="sketch-author">
{% set owner = sketch.get_owner() %}
by <a href="/{{owner.username}}">{{owner.full_name}}</a>
</div>
</div>
</div>

View File

@@ -1295,3 +1295,40 @@ textarea.form-control {
color: #192734;
margin-left: 0.5rem;
}
.certificate-page .common-card-style {
flex-direction: column;
font-family: Inter;
color: black;
font-size: 2rem;
text-align: center;
padding: 5rem;
background-image: url(/assets/community/images/certificate-background.png);
}
.certificate-heading {
font-size: 4rem;
margin-bottom: 3rem;
font-weight: bold;
}
.certificate-para {
margin-bottom: 3rem;
}
@media (max-width: 768px) {
.certificate-page .common-card-style {
padding: 2rem;
font-size: 1.5rem;
}
.certificate-heading {
font-size: 3rem;
}
}
@media (max-width: 360px) {
.certificate-heading {
font-size: 2rem;
}
}

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.0746 3.60967C12.7814 3.31071 12.4333 3.07355 12.0501 2.91174C11.6669 2.74994 11.2562 2.66666 10.8415 2.66666C10.4267 2.66666 10.016 2.74994 9.63283 2.91174C9.24966 3.07355 8.90152 3.31071 8.60831 3.60967L7.99979 4.22983L7.39126 3.60967C6.79899 3.00607 5.9957 2.66697 5.1581 2.66697C4.32051 2.66697 3.51721 3.00607 2.92494 3.60967C2.33267 4.21327 1.99994 5.03192 1.99994 5.88554C1.99994 6.73916 2.33267 7.55782 2.92494 8.16142L3.53347 8.78158L7.99979 13.3333L12.4661 8.78158L13.0746 8.16142C13.368 7.86259 13.6007 7.5078 13.7595 7.11729C13.9182 6.72679 13.9999 6.30824 13.9999 5.88554C13.9999 5.46284 13.9182 5.04429 13.7595 4.65379C13.6007 4.26329 13.368 3.90849 13.0746 3.60967V3.60967Z" stroke="#4C5A67" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 871 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,32 @@
<div class="common-card-style">
<div class="certificate-heading">
Certificate of Completion
</div>
<div class="certificate-para">
This is to certify that <span class="font-weight-bold">{{ student.full_name }}</span> has successfully completed
<span class="font-weight-bold">{{ course.title }}</span> online course on
<span class="font-weight-bold">{{ frappe.utils.format_date(certificate.issue_date, "medium") }}</span>
</div>
<div style="display: flex; justify-content: space-between;" class="certificate-footer">
<div>
<div class="font-weight-bold">
Instructor:
</div>
<div>
{{ instructor.full_name }}
</div>
</div>
<div>
<div class="font-weight-bold">
Expiry Date:
</div>
<div>
{{ frappe.utils.format_date(certificate.expiry_date, "medium") }}
</div>
</div>
</div>
<div>
<img src="{{ logo }}" style="height: 50px;">
</div>
</div>
<script src="/assets/community/js/html2canvas.js"></script>

View File

@@ -63,7 +63,7 @@
<div>
{% if prev_url %}
<a class="button is-secondary dark-links" href="{{ prev_url }}">
<a class="button is-secondary dark-links prev" href="{{ prev_url }}">
<img class="mr-2" src="/assets/community/icons/left-arrow.svg">
Prev
</a>
@@ -87,10 +87,14 @@
<div>
{% if next_url %}
<a class="button is-primary" href="{{ next_url }}">
<a class="button is-primary next" href="{{ next_url }}">
Next
<img class="ml-2" src="/assets/community/icons/side-arrow-white.svg">
</a>
{% elif course.enable_certification %}
<div class="button is-primary {% if course.get_course_progress() != 100 %} hide {% endif %}" id="certification">
Get Certificate
</div>
{% endif %}
</div>

View File

@@ -28,6 +28,10 @@ frappe.ready(() => {
try_quiz_again(e);
});
$("#certification").click((e) => {
create_certificate(e);
})
})
var save_current_lesson = () => {
@@ -71,8 +75,9 @@ var mark_progress = (e) => {
status: status
},
callback: (data) => {
if (data.message == "OK") {
change_progress_indicators(status, e);
if (data.message == 100 && !$(".next").length && $("#certification").hasClass("hide")) {
$("#certification").removeClass("hide");
}
}
})
@@ -166,3 +171,17 @@ var add_to_local_storage = (quiz_name, current_index, answer, is_correct) => {
quiz_stored ? quiz_stored.push(quiz_obj) : quiz_stored = [quiz_obj]
localStorage.setItem(quiz_name, JSON.stringify(quiz_stored))
}
var create_certificate = (e) => {
e.preventDefault();
course = $(".title").attr("data-course");
frappe.call({
method: "community.lms.doctype.lms_certification.lms_certification.create_certificate",
args: {
"course": course
},
callback: (data) => {
window.location.href = `/courses/${course}/${data.message}`;
}
})
}

View File

@@ -0,0 +1,23 @@
{% extends "templates/base.html" %}
{% from "www/macros/common_macro.html" import MentorsSection %}
{% block title %} {{ student.full_name }} - {{ course.title }} {% endblock %}
{% block content %}
<div class="common-page-style">
<div class="container certificate-page">
<div class="breadcrumb">
<a class="dark-links" href="/courses">All Courses</a>
<img class="ml-1 mr-1" src="/assets/community/icons/chevron-right.svg">
<a class="dark-links" href="/courses/{{ course.name }}">{{ course.title }}</a>
</div>
<div class="comment-footer mb-5">
<div class="button is-secondary pull-right" id="export-as-pdf" data-certificate="{{ certificate.name }}"
data-certificate-name="{{ student.full_name }} - {{ course.title }}">Export</div>
</div>
{% include "community/templates/certificate.html" %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,29 @@
frappe.ready(() => {
if ($(document).width() <= 550) {
$(".certificate-footer").css("flex-direction", "column");
$(".certificate-footer").children().addClass("mb-5");
}
$("#export-as-pdf").click((e) => {
export_as_pdf(e);
})
})
var export_as_pdf = (e) => {
var button = $(e.currentTarget);
button.text(__("Exporting..."));
html2canvas(document.querySelector('.common-card-style'), {
scrollY: -window.scrollY,
scrollX: 0
}).then(function(canvas) {
let dataURL = canvas.toDataURL('image/png');
let a = document.createElement('a');
a.href = dataURL;
a.download = button.attr("data-certificate-name");
a.click();
}).finally(() => {
button.text(__("Export"))
});
}

View File

@@ -0,0 +1,31 @@
import frappe
def get_context(context):
context.no_cache = 1
try:
course_name = frappe.form_dict["course"]
certificate_name = frappe.form_dict["certificate"]
except KeyError:
redirect_to_course_list()
context.certificate = frappe.db.get_value("LMS Certification", certificate_name,
["name", "student", "issue_date", "expiry_date", "course"], as_dict=True)
if context.certificate.course != course_name:
redirect_to_course_list()
context.course = frappe.db.get_value("LMS Course", course_name,
["owner", "title", "name"], as_dict=True)
context.instructor = frappe.db.get_value("User", context.course.owner,
["full_name", "username"], as_dict=True)
context.student = frappe.db.get_value("User", context.certificate.student,
["full_name"], as_dict=True)
context.logo = frappe.db.get_single_value("Website Settings", "banner_image")
def redirect_to_course_list():
frappe.local.flags.redirect_location = "/courses"
raise frappe.Redirect

View File

@@ -70,6 +70,10 @@
<img class="ml-2" src="/assets/community/images/play.png" />
</div>
{% endif %}
{% set certificate = course.is_certified() %}
{% if certificate %}
<a class="button wide-button is-secondary dark-links" href="/courses/{{ course.name }}/{{ certificate }}">Get Certificate</a>
{% endif %}
</div>
</div>
</div>
@@ -119,7 +123,7 @@
{% if progress != 100 %}
Great work so far!
{% else %}
Excellent Work on completing this course 👏
Excellent work on completing this course 👏
{% endif %}
</p>
<p class="progress-text">