diff --git a/.gitignore b/.gitignore index f6a99e30..222921e1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ tags community/docs/current community/public/dist +__pycache__/ +*.py[cod] +*$py.class diff --git a/community/hooks.py b/community/hooks.py index eca23eac..dbf8f2a2 100644 --- a/community/hooks.py +++ b/community/hooks.py @@ -165,7 +165,8 @@ whitelist = [ "/add-a-new-batch", "/new-sign-up", "/message", - "/about" + "/about", + "/lms-course-review" ] whitelist_rules = [{"from_route": p, "to_route": p[1:]} for p in whitelist] @@ -187,6 +188,10 @@ update_website_context = 'community.widgets.update_website_context' ## subclass of community.community.plugins.PageExtension # community_lesson_page_extension = None +community_lesson_page_extensions = [ + "community.plugins.LiveCodeExtension" +] + ## Markdown Macros for Lessons community_markdown_macro_renderers = { "Exercise": "community.plugins.exercise_renderer", diff --git a/community/lms/doctype/exercise_submission/exercise_submission.json b/community/lms/doctype/exercise_submission/exercise_submission.json index bd662b11..125af148 100644 --- a/community/lms/doctype/exercise_submission/exercise_submission.json +++ b/community/lms/doctype/exercise_submission/exercise_submission.json @@ -6,12 +6,17 @@ "engine": "InnoDB", "field_order": [ "exercise", - "solution", + "status", + "batch", + "column_break_4", "exercise_title", "course", - "batch", "lesson", - "image" + "section_break_8", + "solution", + "image", + "test_results", + "comments" ], "fields": [ { @@ -21,12 +26,6 @@ "label": "Exercise", "options": "Exercise" }, - { - "fieldname": "solution", - "fieldtype": "Code", - "in_list_view": 1, - "label": "Solution" - }, { "fetch_from": "exercise.title", "fieldname": "exercise_title", @@ -61,11 +60,41 @@ "fieldtype": "Code", "label": "Image", "read_only": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Correct\nIncorrect" + }, + { + "fieldname": "test_results", + "fieldtype": "Small Text", + "label": "Test Results" + }, + { + "fieldname": "comments", + "fieldtype": "Small Text", + "label": "Comments" + }, + { + "fieldname": "solution", + "fieldtype": "Code", + "in_list_view": 1, + "label": "Solution" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_8", + "fieldtype": "Section Break" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-05-21 11:28:45.833018", + "modified": "2021-06-24 16:22:50.570845", "modified_by": "Administrator", "module": "LMS", "name": "Exercise Submission", diff --git a/community/lms/doctype/lms_course/lms_course.py b/community/lms/doctype/lms_course/lms_course.py index 7889eb23..c3d797fd 100644 --- a/community/lms/doctype/lms_course/lms_course.py +++ b/community/lms/doctype/lms_course/lms_course.py @@ -199,7 +199,12 @@ class LMSCourse(Document): } if batch: filters["batch"] = batch - membership = frappe.db.get_value("LMS Batch Membership", filters, ["name","batch", "current_lesson"], as_dict=True) + + membership = frappe.db.get_value("LMS Batch Membership", + filters, + ["name", "batch", "current_lesson", "member_type"], + as_dict=True) + if membership and membership.batch: membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title") return membership @@ -244,6 +249,32 @@ class LMSCourse(Document): def get_tags(self): return self.tags.split(",") if self.tags else [] + def get_reviews(self): + reviews = frappe.get_all("LMS Course Review", + { + "course": self.name + }, + ["review", "rating", "owner"], + order_by= "creation desc") + + for review in reviews: + 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_outline(self): return CourseOutline(self) diff --git a/community/lms/doctype/lms_course_review/__init__.py b/community/lms/doctype/lms_course_review/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/lms/doctype/lms_course_review/lms_course_review.js b/community/lms/doctype/lms_course_review/lms_course_review.js new file mode 100644 index 00000000..8382eb52 --- /dev/null +++ b/community/lms/doctype/lms_course_review/lms_course_review.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, FOSS United and contributors +// For license information, please see license.txt + +frappe.ui.form.on('LMS Course Review', { + // refresh: function(frm) { + + // } +}); diff --git a/community/lms/doctype/lms_course_review/lms_course_review.json b/community/lms/doctype/lms_course_review/lms_course_review.json new file mode 100644 index 00000000..c2684bbe --- /dev/null +++ b/community/lms/doctype/lms_course_review/lms_course_review.json @@ -0,0 +1,54 @@ +{ + "actions": [], + "creation": "2021-06-28 13:36:36.146718", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "review", + "rating", + "course" + ], + "fields": [ + { + "fieldname": "review", + "fieldtype": "Small Text", + "label": "Review" + }, + { + "fieldname": "rating", + "fieldtype": "Rating", + "label": "Rating" + }, + { + "fieldname": "course", + "fieldtype": "Link", + "label": "Course", + "options": "LMS Course" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-06-28 15:00:35.146196", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Course Review", + "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 +} \ No newline at end of file diff --git a/community/lms/doctype/lms_course_review/lms_course_review.py b/community/lms/doctype/lms_course_review/lms_course_review.py new file mode 100644 index 00000000..e2361ad0 --- /dev/null +++ b/community/lms/doctype/lms_course_review/lms_course_review.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, FOSS United and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class LMSCourseReview(Document): + pass diff --git a/community/lms/doctype/lms_course_review/test_lms_course_review.py b/community/lms/doctype/lms_course_review/test_lms_course_review.py new file mode 100644 index 00000000..da8a1450 --- /dev/null +++ b/community/lms/doctype/lms_course_review/test_lms_course_review.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, FOSS United and Contributors +# See license.txt + +# import frappe +import unittest + +class TestLMSCourseReview(unittest.TestCase): + pass diff --git a/community/lms/widgets/Reviews.html b/community/lms/widgets/Reviews.html new file mode 100644 index 00000000..9e0cf18b --- /dev/null +++ b/community/lms/widgets/Reviews.html @@ -0,0 +1,64 @@ +
+
+
Review
+ {% if course.is_eligible_to_review(membership) %} + + Provide your Feedback + + {% endif %} +
+
+ {% for review in course.get_reviews() %} +
+
{{ review.review }}
+
+ +
+ {% endfor %} +
+
+ + + + + + diff --git a/community/plugins.py b/community/plugins.py index 76cfd4d0..eb9b74fc 100644 --- a/community/plugins.py +++ b/community/plugins.py @@ -67,6 +67,25 @@ class ProfileTab: """ raise NotImplementedError() +class LiveCodeExtension(PageExtension): + def render_header(self): + livecode_url = frappe.get_value("LMS Settings", None, "livecode_url") + context = { + "livecode_url": livecode_url + } + return frappe.render_template( + "templates/livecode/extension_header.html", + context) + + def render_footer(self): + livecode_url = frappe.get_value("LMS Settings", None, "livecode_url") + context = { + "livecode_url": livecode_url + } + return frappe.render_template( + "templates/livecode/extension_footer.html", + context) + def quiz_renderer(quiz_name): quiz = frappe.get_doc("LMS Quiz", quiz_name) context = dict(quiz=quiz) diff --git a/community/public/css/style.css b/community/public/css/style.css index 4c8b8311..89efd067 100644 --- a/community/public/css/style.css +++ b/community/public/css/style.css @@ -296,10 +296,13 @@ input[type=checkbox] { align-items: flex-start; background: #FFFFFF; border-radius: 8px; + box-shadow: 0px 5px 10px rgb(0 0 0 / 10%); +} + +.course-card { width: 352px; height: 380px; margin: 0px 16px 32px; - box-shadow: 0px 5px 10px rgb(0 0 0 / 10%); } @media (max-width: 768px) { @@ -317,13 +320,14 @@ input[type=checkbox] { .course-card-meta { font-size: 12px; line-height: 135%; - margin: 12px 16px 8px; + margin: 16px 0px 8px; color: var(--muted-text); height: 15px; } .course-card-content { width: 100%; + padding: 0px 24px 20px; } .course-card-title { @@ -333,17 +337,17 @@ input[type=checkbox] { letter-spacing: -0.014em; color: var(--text-color); align-self: stretch; - margin: 0px 16px 16px; + margin-bottom: 16px; height: 45px; } .card-divider { border: 1px solid #E2E6E9; - margin: 0px 16px 16px; + margin-bottom: 16px; } .course-card-meta-2 { - margin: 0px 16px 16px; + margin-bottom: 16px; } .course-instructor { @@ -362,7 +366,6 @@ input[type=checkbox] { .view-course-link { height: 32px; - margin: 0px 16px 16px; background: var(--button-background); border-radius: 4px; font-size: 12px; @@ -432,3 +435,32 @@ input[type=checkbox] { .small-margin { margin-left: 10px; } + +.reviews-heading { + display: flex; + justify-content: space-between; +} + +.reviews-title { + font-weight: 600; + font-size: 22px; + line-height: 145%; +} + +.review-card { + width: 300px; + height: 200px; + margin: 0px 16px 32px; + padding: 16px; +} + +.review-card-footer { + width: 100%; + display: flex; + justify-content: space-between; +} + +.review-content { + font-size: 15px; + line-height: 135%; +} diff --git a/community/templates/livecode/extension_footer.html b/community/templates/livecode/extension_footer.html new file mode 100644 index 00000000..7079acb2 --- /dev/null +++ b/community/templates/livecode/extension_footer.html @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + diff --git a/community/templates/livecode/extension_header.html b/community/templates/livecode/extension_header.html new file mode 100644 index 00000000..1499d750 --- /dev/null +++ b/community/templates/livecode/extension_header.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/community/www/courses/course.html b/community/www/courses/course.html index 7947fd5f..b1e13352 100644 --- a/community/www/courses/course.html +++ b/community/www/courses/course.html @@ -15,7 +15,7 @@

{{course.title}}

- {% if not course.disable_self_learning and not course.is_mentor(frappe.session.user) %} + {% if not course.disable_self_learning and not membership %}
@@ -32,6 +32,7 @@ {{ widgets.InstructorSection(instructor=course.get_instructor()) }} {{ BatchSection(course) }} {{ widgets.CourseOutline(course=course, show_link=membership) }} + {{ widgets.Reviews(course=course, membership=membership) }}
diff --git a/community/www/courses/course.py b/community/www/courses/course.py index 7f5c5fab..c847cdba 100644 --- a/community/www/courses/course.py +++ b/community/www/courses/course.py @@ -20,5 +20,5 @@ def get_context(context): context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else "" context.membership = membership if not course.is_mentor(frappe.session.user) and membership: - frappe.local.flags.redirect_location = f"/courses/{course.name}/learn" - raise frappe.Redirect + """ frappe.local.flags.redirect_location = f"/courses/{course.name}/learn" + raise frappe.Redirect """ diff --git a/community/www/courses/index.html b/community/www/courses/index.html index 253f9ade..f10c0b1f 100644 --- a/community/www/courses/index.html +++ b/community/www/courses/index.html @@ -28,7 +28,7 @@ {% macro course_card(course) %} -
+
{% for tag in course.get_tags() %} diff --git a/community/www/macros/livecode.html b/community/www/macros/livecode.html index a4ec142f..9714b313 100644 --- a/community/www/macros/livecode.html +++ b/community/www/macros/livecode.html @@ -1,4 +1,3 @@ - {% macro LiveCodeEditorLarge(name, code) %}