From 262c1ea371a9a99291ec78f408d90267e6591fbb Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 12 Apr 2023 23:15:52 +0530 Subject: [PATCH 1/5] feat: certificate download as pdf --- .../doctype/lms_certificate/lms_certificate.py | 16 ++++++++++++++-- lms/lms/print_format/__init__.py | 0 lms/lms/print_format/certificate/__init__.py | 0 lms/templates/certificate.html | 3 +-- lms/www/courses/certificate.html | 6 +----- lms/www/courses/certificate.js | 4 ++-- lms/www/courses/certificate.py | 6 ++++-- requirements.txt | 1 + 8 files changed, 23 insertions(+), 13 deletions(-) create mode 100644 lms/lms/print_format/__init__.py create mode 100644 lms/lms/print_format/certificate/__init__.py diff --git a/lms/lms/doctype/lms_certificate/lms_certificate.py b/lms/lms/doctype/lms_certificate/lms_certificate.py index 3c5f4de8..8d01b63d 100644 --- a/lms/lms/doctype/lms_certificate/lms_certificate.py +++ b/lms/lms/doctype/lms_certificate/lms_certificate.py @@ -6,6 +6,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import add_years, nowdate from frappe.utils.pdf import get_pdf +from weasyprint import HTML, CSS from lms.lms.utils import is_certified @@ -50,7 +51,18 @@ def create_certificate(course): @frappe.whitelist() -def get_certificate_pdf(html): +def get_certificate_pdf(html_str): + + html = HTML(string=html_str) + css = CSS( + string=""" + @page { + size: A4 landscape; + }""" + ) + main = html.render(stylesheets=[css]) + pdf = main.write_pdf() + frappe.local.response.filename = "certificate.pdf" - frappe.local.response.filecontent = get_pdf(html, {"orientation": "LandScape"}) + frappe.local.response.filecontent = pdf frappe.local.response.type = "pdf" diff --git a/lms/lms/print_format/__init__.py b/lms/lms/print_format/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/print_format/certificate/__init__.py b/lms/lms/print_format/certificate/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/templates/certificate.html b/lms/templates/certificate.html index 245970cb..c07aaf40 100644 --- a/lms/templates/certificate.html +++ b/lms/templates/certificate.html @@ -2,8 +2,7 @@
-
+
diff --git a/lms/www/courses/certificate.html b/lms/www/courses/certificate.html index 85b164d7..68e486e1 100644 --- a/lms/www/courses/certificate.html +++ b/lms/www/courses/certificate.html @@ -15,11 +15,7 @@ {{ course.title }}
- {% if custom_template %} - {{ custom_template }} - {% else %} - {% include "lms/templates/certificate.html" %} - {% endif %} + {% include "lms/templates/certificate.html" %}
diff --git a/lms/www/courses/certificate.js b/lms/www/courses/certificate.js index b4118f31..263e262f 100644 --- a/lms/www/courses/certificate.js +++ b/lms/www/courses/certificate.js @@ -1,6 +1,6 @@ frappe.ready(() => { $("#export-as-pdf").click((e) => { - export_as_png(e); + export_as_pdf(e); }); }); @@ -8,7 +8,7 @@ const export_as_pdf = (e) => { var formData = new FormData(); //Push the HTML content into an element - formData.append("html", $("#certificate-card").html()); + formData.append("html_str", $("#certificate-card").html()); var blob = new Blob([], { type: "text/xml" }); formData.append("blob", blob); diff --git a/lms/www/courses/certificate.py b/lms/www/courses/certificate.py index 404b1b4b..8321862b 100644 --- a/lms/www/courses/certificate.py +++ b/lms/www/courses/certificate.py @@ -1,6 +1,6 @@ import frappe from frappe.utils.jinja import render_template - +from frappe.utils import get_url from lms.lms.utils import get_instructors @@ -31,7 +31,9 @@ def get_context(context): "User", context.certificate.member, ["full_name"], as_dict=True ) - context.logo = frappe.db.get_single_value("Website Settings", "banner_image") + context.logo = get_url( + frappe.db.get_single_value("Website Settings", "banner_image"), full_address=True + ) template_name = frappe.db.get_single_value( "LMS Settings", "custom_certificate_template" ) diff --git a/requirements.txt b/requirements.txt index 293d21a8..abb581a8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ websocket_client markdown beautifulsoup4 lxml +cairocffi From 53d2e288d44713a8bf2beeedc91a3118000621d9 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Fri, 14 Apr 2023 17:38:24 +0530 Subject: [PATCH 2/5] feat: certificate pdf and custom certificate --- .../lms_certificate/lms_certificate.json | 2 +- .../lms_certificate/lms_certificate.py | 21 ---- .../print_format/certificate/certificate.json | 32 ++++++ lms/patches.txt | 3 +- lms/patches/v0_0/share_certificates.py | 25 +++++ lms/public/css/style.css | 97 +++++++++---------- lms/templates/certificate.html | 68 +++++++------ lms/www/courses/certificate.html | 5 +- lms/www/courses/certificate.js | 56 ----------- 9 files changed, 143 insertions(+), 166 deletions(-) create mode 100644 lms/lms/print_format/certificate/certificate.json create mode 100644 lms/patches/v0_0/share_certificates.py delete mode 100644 lms/www/courses/certificate.js diff --git a/lms/lms/doctype/lms_certificate/lms_certificate.json b/lms/lms/doctype/lms_certificate/lms_certificate.json index 91ec1085..d1d2a8a2 100644 --- a/lms/lms/doctype/lms_certificate/lms_certificate.json +++ b/lms/lms/doctype/lms_certificate/lms_certificate.json @@ -56,7 +56,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2022-04-06 11:49:36.077370", + "modified": "2023-04-14 12:33:37.839625", "modified_by": "Administrator", "module": "LMS", "name": "LMS Certificate", diff --git a/lms/lms/doctype/lms_certificate/lms_certificate.py b/lms/lms/doctype/lms_certificate/lms_certificate.py index 8d01b63d..5e4f8688 100644 --- a/lms/lms/doctype/lms_certificate/lms_certificate.py +++ b/lms/lms/doctype/lms_certificate/lms_certificate.py @@ -5,9 +5,6 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import add_years, nowdate -from frappe.utils.pdf import get_pdf -from weasyprint import HTML, CSS - from lms.lms.utils import is_certified @@ -48,21 +45,3 @@ def create_certificate(course): ) certificate.save(ignore_permissions=True) return certificate - - -@frappe.whitelist() -def get_certificate_pdf(html_str): - - html = HTML(string=html_str) - css = CSS( - string=""" - @page { - size: A4 landscape; - }""" - ) - main = html.render(stylesheets=[css]) - pdf = main.write_pdf() - - frappe.local.response.filename = "certificate.pdf" - frappe.local.response.filecontent = pdf - frappe.local.response.type = "pdf" diff --git a/lms/lms/print_format/certificate/certificate.json b/lms/lms/print_format/certificate/certificate.json new file mode 100644 index 00000000..f5e01b26 --- /dev/null +++ b/lms/lms/print_format/certificate/certificate.json @@ -0,0 +1,32 @@ +{ + "absolute_value": 0, + "align_labels_right": 0, + "creation": "2023-02-22 21:36:54.560420", + "css": ".certificate-card {\n background: #FFFFFF;\n font-family: \"Inter\" sans-serif;\n font-size: 16px;\n}\n\n.outer-border {\n border-radius: 0.5rem;\n border: 1px solid #EEF0F2;\n padding: 1rem;\n width: 80%;\n margin: auto;\n}\n\n.inner-border {\n border: 10px solid #0089FF;\n border-radius: 8px;\n text-align: center;\n padding: 6rem 4rem;\n background-color: #FFFFFF;\n}\n\n.certificate-logo {\n height: 1.5rem;\n margin-bottom: 4rem;\n}\n\n.certificate-name {\n font-size: 2rem;\n font-weight: 500;\n color: #192734;\n margin-bottom: 0.5rem;\n}\n\n.certificate-footer {\n margin: 4rem auto 0;\n width: 70%;\n text-align: center;\n}\n\n.certificate-footer-item {\n color: #192734;\n}\n\n.cursive-font {\n font-family: cursive;\n font-weight: 600;\n}\n\n.certificate-divider {\n margin: 0.5rem 0;\n}\n\n.certificate-expiry {\n margin-left: 2rem;\n}", + "custom_format": 1, + "disabled": 0, + "doc_type": "LMS Certificate", + "docstatus": 0, + "doctype": "Print Format", + "font_size": 14, + "format_data": "{\"header\":\"
\\n\\t

LMS Certificate

\\n\\t

{{ doc.name }}

\\n
\",\"sections\":[{\"label\":\"\",\"columns\":[{\"label\":\"\",\"fields\":[{\"label\":\"Course\",\"fieldname\":\"course\",\"fieldtype\":\"Link\",\"options\":\"LMS Course\"},{\"label\":\"Member\",\"fieldname\":\"member\",\"fieldtype\":\"Link\",\"options\":\"User\"},{\"label\":\"Member Name\",\"fieldname\":\"member_name\",\"fieldtype\":\"Data\"},{\"label\":\"Evaluator\",\"fieldname\":\"evaluator\",\"fieldtype\":\"Data\",\"options\":\"\"}]},{\"label\":\"\",\"fields\":[{\"label\":\"Issue Date\",\"fieldname\":\"issue_date\",\"fieldtype\":\"Date\"},{\"label\":\"Expiry Date\",\"fieldname\":\"expiry_date\",\"fieldtype\":\"Date\"},{\"label\":\"Version\",\"fieldname\":\"version\",\"fieldtype\":\"Select\",\"options\":\"V13\\nV14\"},{\"label\":\"Module Names for Certificate\",\"fieldname\":\"module_names_for_certificate\",\"fieldtype\":\"Data\"}]}],\"has_fields\":true}]}", + "html": "{% set certificate = frappe.db.get_value(\"LMS Certificate\", doc.name, [\"name\", \"member\", \"issue_date\", \"expiry_date\", \"course\"], as_dict=True) %}\n{% set member = frappe.db.get_value(\"User\", doc.member, [\"full_name\"], as_dict=True) %}\n{% set course = frappe.db.get_value(\"LMS Course\", doc.course, [\"title\", \"name\", \"image\"], as_dict=True) %}\n{% set logo = frappe.db.get_single_value(\"Website Settings\", \"banner_image\") %}\n{% set instructors = frappe.get_all(\"Course Instructor\", {\"parent\": doc.course}, pluck=\"instructor\", order_by=\"idx\") %}\n\n\n\n\n\n\n
\n
\n
\n \n \n
\n {{ _(\"This certifies that\") }}\n
\n \n
\n {{ member.full_name }}\n
\n
\n {{ _(\"has successfully completed the course on\") }}\n {{ course.title }} \n on {{ frappe.utils.format_date(certificate.issue_date, \"medium\") }}.\n
\n \n \n \n {% if instructors %}\n \n {% endif %}\n \n {% if certificate.expiry_date %}\n \n {% endif %}\n \n
\n \n
\n
{{ _(\"Course Instructor\") }}
\n
\n \n
\n
{{ _(\"Expiry Date\") }}
\n
\n
\n
\n
", + "idx": 0, + "line_breaks": 0, + "margin_bottom": 15.0, + "margin_left": 15.0, + "margin_right": 15.0, + "margin_top": 15.0, + "modified": "2023-04-14 11:01:24.545418", + "modified_by": "Administrator", + "module": "LMS", + "name": "Certificate", + "owner": "Administrator", + "page_number": "Hide", + "print_format_builder": 0, + "print_format_builder_beta": 1, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 0, + "standard": "Yes" +} \ No newline at end of file diff --git a/lms/patches.txt b/lms/patches.txt index f4d1f1dd..48a35875 100644 --- a/lms/patches.txt +++ b/lms/patches.txt @@ -51,4 +51,5 @@ lms.patches.v0_0.rename_exercise_doctype lms.patches.v0_0.add_question_type #09-04-2023 lms.patches.v0_0.add_evaluator_to_assignment #09-04-2023 lms.patches.v0_0.convert_lesson_markdown_to_html #05-04-2023 -lms.patches.v0_0.convert_course_description_to_html \ No newline at end of file +lms.patches.v0_0.convert_course_description_to_html +lms.patches.v0_0.share_certificates \ No newline at end of file diff --git a/lms/patches/v0_0/share_certificates.py b/lms/patches/v0_0/share_certificates.py new file mode 100644 index 00000000..ed9b3154 --- /dev/null +++ b/lms/patches/v0_0/share_certificates.py @@ -0,0 +1,25 @@ +import frappe + + +def execute(): + certificates = frappe.get_all("LMS Certificate", fields=["member", "name"]) + + for certificate in certificates: + if not frappe.db.exists( + "DocShare", + { + "share_doctype": "LMS Certificate", + "share_name": certificate.name, + "user": certificate.member, + }, + ): + share = frappe.get_doc( + { + "doctype": "DocShare", + "user": certificate.member, + "share_doctype": "LMS Certificate", + "share_name": certificate.name, + "read": 1, + } + ) + share.save(ignore_permissions=True) diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 61271c10..b0893c43 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -836,71 +836,62 @@ pre { object-fit: cover; } -.certificate-content { - background-color: #FFFFFF; - border-width: 10px; - border-style: solid; -} - -@media (max-width: 500px) { - .certificate-content { - border-width: 50px; - } -} - -.certificate-footer { - display: flex; - justify-content: center; - margin: 4rem auto 0; - width: fit-content; -} - -.certificate-ribbon { - background-color: var(--primary-color); - padding: 0.5rem; - border-radius: var(--border-radius-md); -} - -.certificate-heading { - font-size: 2rem; - font-weight: 500; - color: var(--text-color); -} - -.certificate-para { - margin-bottom: 4rem; -} - .certificate-card { background: #FFFFFF; - border-radius: var(--border-radius-md); - position: relative; - box-shadow: var(--shadow-sm); + font-size: 14px +} + +.certificate-content { + border-radius: 0.5rem; + border: 1px solid #EEF0F2; padding: 1rem; + font-size: var(--text-lg); +} + +.certificate-border { + border: 10px solid #0089FF; + /* border-image: url(/assets/lms/images/border.png); + border-width: 100; + border-style: solid ; */ + border-radius: 8px; + padding: 6rem 4rem; + background-color: #FFFFFF; text-align: center; } -.certificate-footer-item { - color: var(--text-color); - font-weight: bold; - font-family: cursive; - font-size: 1.25rem; -} .certificate-logo { - height: 1.5rem; + height: 1.5rem; + margin-bottom: 4rem; } -@media (max-width: 768px) { - .certificate-card { - margin: 0; - } +.certificate-name { + font-size: 2rem; + font-weight: 500; + color: #192734; + margin-bottom: 0.25rem; } -@media (max-width: 550px) { - .certificate-content { - padding: 1rem; - } +.certificate-footer { + margin: 4rem auto 0; + width: fit-content; +} + +.certificate-footer-item { + color: #192734; +} + +.cursive-font { + font-family: cursive; + font-weight: 600; +} + +.certificate-divider { + margin: 0.5rem 0; +} + +.certificate-expiry { + margin-left: 2rem; } .column-card { diff --git a/lms/templates/certificate.html b/lms/templates/certificate.html index c07aaf40..b72f2ebf 100644 --- a/lms/templates/certificate.html +++ b/lms/templates/certificate.html @@ -1,43 +1,47 @@ -
+
-
+
- -
- {{ _("This certifies that") }} -
+
-
- {{ member.full_name }} -
-
- {{ _("has successfully completed the course on") }} - {{ course.title }} - on {{ frappe.utils.format_date(certificate.issue_date, "medium") }}. -
- -
- {% if instructors %} +
-
- {{ instructors }} -
-
-
{{ _("Course Instructor") }}
+ {{ _("This certifies that") }}
- {% endif %} - {% if certificate.expiry_date %} -
-
- {{ frappe.utils.format_date(certificate.expiry_date, "medium") }} -
-
-
{{ _("Expiry date") }}
+
+ {{ member.full_name }}
- {% endif %} +
+ {{ _("has successfully completed the course on") }} + {{ course.title }} + on {{ frappe.utils.format_date(certificate.issue_date, "medium") }}. +
+ + + + {% if instructors %} + + {% endif %} + {% if certificate.expiry_date %} + + + {% endif %} + +
diff --git a/lms/www/courses/certificate.html b/lms/www/courses/certificate.html index 68e486e1..818527aa 100644 --- a/lms/www/courses/certificate.html +++ b/lms/www/courses/certificate.html @@ -6,8 +6,9 @@
{% if certificate.member == frappe.session.user %} -
{{ _("Export") }}
+ + {{ _("Export") }} + {% endif %}