diff --git a/community/community/doctype/community_course_member/community_course_member.py b/community/community/doctype/community_course_member/community_course_member.py index 06861201..8455178f 100644 --- a/community/community/doctype/community_course_member/community_course_member.py +++ b/community/community/doctype/community_course_member/community_course_member.py @@ -50,18 +50,3 @@ class CommunityCourseMember(WebsiteGenerator): send_priority=0, queue_separately=True, args=args) - - def create_user(self): - user = frappe.get_doc({ - "doctype": "User", - "email": self.email, - "first_name": self.full_name.split(" ")[0], - "full_name": self.full_name, - "username": self.user_name, - "send_welcome_email": 0, - "user_type": 'Website User', - "redirect_url": self.name - }) - user.save(ignore_permissions=True) - update_password_link = user.reset_password() - return user, update_password_link diff --git a/community/hooks.py b/community/hooks.py index 90ad31b8..6b1fb371 100644 --- a/community/hooks.py +++ b/community/hooks.py @@ -163,7 +163,8 @@ whitelist = [ "/hackathons", "/dashboard", "/join-request" - "/add-a-new-batch" + "/add-a-new-batch", + "/new-sign-up" ] whitelist_rules = [{"from_route": p, "to_route": p[1:]} for p in whitelist] diff --git a/community/lms/doctype/invite_request/__init__.py b/community/lms/doctype/invite_request/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/community/lms/doctype/invite_request/invite_request.js b/community/lms/doctype/invite_request/invite_request.js new file mode 100644 index 00000000..1d4e0738 --- /dev/null +++ b/community/lms/doctype/invite_request/invite_request.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, FOSS United and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Invite Request', { + // refresh: function(frm) { + + // } +}); diff --git a/community/lms/doctype/invite_request/invite_request.json b/community/lms/doctype/invite_request/invite_request.json new file mode 100644 index 00000000..ad4cfa41 --- /dev/null +++ b/community/lms/doctype/invite_request/invite_request.json @@ -0,0 +1,88 @@ +{ + "actions": [], + "creation": "2021-04-29 16:29:56.857914", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "invite_email", + "signup_email", + "column_break_4", + "status", + "full_name", + "username", + "invite_code" + ], + "fields": [ + { + "allow_in_quick_entry": 1, + "fieldname": "invite_email", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Invite Email", + "options": "Email", + "unique": 1 + }, + { + "fieldname": "full_name", + "fieldtype": "Data", + "label": "Full Name" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "signup_email", + "fieldtype": "Data", + "label": "Signup Email", + "options": "Email" + }, + { + "fieldname": "username", + "fieldtype": "Data", + "label": "Username" + }, + { + "fieldname": "invite_code", + "fieldtype": "Data", + "label": "Invite Code" + }, + { + "default": "Pending", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "Pending\nApproved\nRejected\nRegistered" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-05-03 09:22:20.954921", + "modified_by": "Administrator", + "module": "LMS", + "name": "Invite Request", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "search_fields": "invite_email, signup_email", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "invite_email", + "track_changes": 1 +} diff --git a/community/lms/doctype/invite_request/invite_request.py b/community/lms/doctype/invite_request/invite_request.py new file mode 100644 index 00000000..5b422643 --- /dev/null +++ b/community/lms/doctype/invite_request/invite_request.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, FOSS United and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.model.document import Document +import json +from frappe.utils.password import get_decrypted_password + +class InviteRequest(Document): + def on_update(self): + if self.has_value_changed('status') and self.status == "Approved": + self.send_email() + + def create_user(self, password): + full_name_split = self.full_name.split(" ") + user = frappe.get_doc({ + "doctype": "User", + "email": self.signup_email, + "first_name": full_name_split[0], + "last_name": full_name_split[1] if len(full_name_split) > 1 else "", + "username": self.username, + "send_welcome_email": 0, + "user_type": "Website User", + "new_password": password + }) + user.save(ignore_permissions=True) + return user + + def send_email(self): + subject = _("Your request has been approved.") + args = { + "full_name": self.full_name, + "signup_form_link": "/new-sign-up?invite_code={0}".format(self.name), + "site_url": frappe.utils.get_url() + } + frappe.sendmail( + recipients=self.invite_email, + sender=frappe.db.get_single_value("LMS Settings", "email_sender"), + subject=subject, + header=[subject, "green"], + template = "lms_invite_request_approved", + args=args) + +@frappe.whitelist(allow_guest=True) +def create_invite_request(invite_email): + try: + frappe.get_doc({ + "doctype": "Invite Request", + "invite_email": invite_email + }).save(ignore_permissions=True) + return "OK" + except frappe.UniqueValidationError: + frappe.throw(_("Email {0} has already been used to request an invite").format(invite_email)) + +@frappe.whitelist(allow_guest=True) +def update_invite(data): + data = frappe._dict(json.loads(data)) if type(data) == str else frappe._dict(data) + + try: + doc = frappe.get_doc("Invite Request", data.invite_code) + except frappe.DoesNotExistError: + frappe.throw(_("Invalid Invite Code.")) + + doc.signup_email = data.signup_email + doc.username = data.username + doc.full_name = data.full_name + doc.invite_code = data.invite_code + doc.save(ignore_permissions=True) + + user = doc.create_user(data.password) + if user: + doc.status = "Registered" + doc.save(ignore_permissions=True) + + return "OK" diff --git a/community/lms/doctype/invite_request/test_invite_request.py b/community/lms/doctype/invite_request/test_invite_request.py new file mode 100644 index 00000000..e7ff5787 --- /dev/null +++ b/community/lms/doctype/invite_request/test_invite_request.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, FOSS United and Contributors +# See license.txt +from __future__ import unicode_literals +from community.lms.doctype.invite_request.invite_request import create_invite_request, update_invite +import frappe +import unittest + +class TestInviteRequest(unittest.TestCase): + + @classmethod + def setUpClass(self): + create_invite_request("test_invite@example.com") + + def test_create_invite_request(self): + if frappe.db.exists("Invite Request", {"invite_email": "test_invite@example.com"}): + invite = frappe.db.get_value("Invite Request", + filters={"invite_email": "test_invite@example.com"}, + fieldname=["invite_email", "status", "signup_email"], + as_dict=True) + self.assertEqual(invite.status, "Pending") + self.assertEqual(invite.signup_email, None) + + def test_create_invite_request_update(self): + if frappe.db.exists("Invite Request", {"invite_email": "test_invite@example.com"}): + + data = { + "signup_email": "test_invite@example.com", + "username": "test_invite", + "full_name": "Test Invite", + "password": "Test@invite", + "invite_code": frappe.db.get_value("Invite Request", {"invite_email": "test_invite@example.com"}, "name") + } + + update_invite(data) + invite = frappe.db.get_value("Invite Request", + filters={"invite_email": "test_invite@example.com"}, + fieldname=["invite_email", "status", "signup_email", "full_name", "username", "invite_code", "name"], + as_dict=True) + self.assertEqual(invite.signup_email, "test_invite@example.com") + self.assertEqual(invite.full_name, "Test Invite") + self.assertEqual(invite.username, "test_invite") + self.assertEqual(invite.invite_code, invite.name) + self.assertEqual(invite.status, "Registered") + + user = frappe.db.get_value("User", "test_invite@example.com", + fieldname=["first_name", "username", "send_welcome_email", "user_type"], + as_dict=True) + self.assertTrue(user) + self.assertEqual(user.first_name, invite.full_name.split(" ")[0]) + self.assertEqual(user.username, invite.username) + self.assertEqual(user.send_welcome_email, 0) + self.assertEqual(user.user_type, "Website User") + + member = frappe.db.get_value("Community Member", {"email": "test_invite@example.com"}) + self.assertTrue(member) + + @classmethod + def tearDownClass(self): + + if frappe.db.exists("Community Member", {"email": "test_invite@example.com"}): + frappe.delete_doc("Community Member", {"email": "test_invite@example.com"}) + + if frappe.db.exists("User", "test_invite@example.com"): + frappe.delete_doc("User", "test_invite@example.com") + + invite_request = frappe.db.exists("Invite Request", {"invite_email": "test_invite@example.com"}) + if invite_request: + frappe.delete_doc("Invite Request", invite_request) diff --git a/community/lms/doctype/lms_batch/lms_batch.json b/community/lms/doctype/lms_batch/lms_batch.json index 0e7f5de1..8f0b94f0 100644 --- a/community/lms/doctype/lms_batch/lms_batch.json +++ b/community/lms/doctype/lms_batch/lms_batch.json @@ -27,6 +27,8 @@ { "fieldname": "course", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Course", "options": "LMS Course" }, @@ -91,11 +93,13 @@ { "fieldname": "start_date", "fieldtype": "Date", + "in_list_view": 1, "label": "Start Date" }, { "fieldname": "start_time", "fieldtype": "Time", + "in_list_view": 1, "label": "Start Time" }, { @@ -106,6 +110,7 @@ { "fieldname": "end_time", "fieldtype": "Time", + "in_list_view": 1, "label": "End Time" } ], @@ -117,7 +122,7 @@ "link_fieldname": "batch" } ], - "modified": "2021-04-21 12:45:21.144972", + "modified": "2021-04-30 09:52:18.941276", "modified_by": "Administrator", "module": "LMS", "name": "LMS Batch", diff --git a/community/lms/doctype/lms_batch_membership/lms_batch_membership.py b/community/lms/doctype/lms_batch_membership/lms_batch_membership.py index 87ae7962..ba218209 100644 --- a/community/lms/doctype/lms_batch_membership/lms_batch_membership.py +++ b/community/lms/doctype/lms_batch_membership/lms_batch_membership.py @@ -14,7 +14,7 @@ class LMSBatchMembership(Document): self.validate_membership_in_different_batch_same_course() def validate_membership_in_same_batch(self): - previous_membership = frappe.db.get_value("LMS Batch Membership", {"member": self.member, "batch": self.batch}, ["member_type","member"], as_dict=1) + previous_membership = frappe.db.get_value("LMS Batch Membership", {"member": self.member, "batch": self.batch, "name": ["!=", self.name]}, ["member_type","member"], as_dict=1) if previous_membership: member_name = frappe.db.get_value("Community Member", self.member, "full_name") frappe.throw(_("{0} is already a {1} of {2}").format(member_name, previous_membership.member_type, self.batch)) @@ -29,7 +29,7 @@ class LMSBatchMembership(Document): frappe.throw(_("{0} is already a {1} of {2} course through {3} batch").format(member_name, membership.member_type, course, membership.batch)) @frappe.whitelist() -def create_membership(batch, member=None, member_type="Student", role="Member"): +def create_membership(batch, course, member=None, member_type="Student", role="Member"): if not member: member = frappe.db.get_value("Community Member", {"email": frappe.session.user}, "name") frappe.get_doc({ @@ -39,4 +39,5 @@ def create_membership(batch, member=None, member_type="Student", role="Member"): "member_type": member_type, "member": member }).save(ignore_permissions=True) - return "OK" \ No newline at end of file + course_slug = frappe.db.get_value("LMS Course", {"title": course}, ["slug"]) + return course_slug \ No newline at end of file diff --git a/community/lms/doctype/lms_mentor_request/lms_mentor_request.py b/community/lms/doctype/lms_mentor_request/lms_mentor_request.py index 640bd94b..b98a2f45 100644 --- a/community/lms/doctype/lms_mentor_request/lms_mentor_request.py +++ b/community/lms/doctype/lms_mentor_request/lms_mentor_request.py @@ -79,7 +79,7 @@ def send_creation_email(course, member): subject = _('Request for Mentorship') send_email([frappe.session.user, get_course_author(course)], None, subject, message) -def send_email(recipients, cc, subject, message): +def send_email(recipients, cc=None, subject=None, message=None, template=None, args=None): frappe.sendmail( recipients = recipients, cc = cc, @@ -87,5 +87,7 @@ def send_email(recipients, cc, subject, message): subject = subject, send_priority = 0, queue_separately = True, - message = message + message = message, + template=template, + args=args ) \ No newline at end of file diff --git a/community/lms/doctype/lms_settings/lms_settings.json b/community/lms/doctype/lms_settings/lms_settings.json index b08996d2..f5b7a03f 100644 --- a/community/lms/doctype/lms_settings/lms_settings.json +++ b/community/lms/doctype/lms_settings/lms_settings.json @@ -6,10 +6,11 @@ "engine": "InnoDB", "field_order": [ "livecode_url", + "column_break_2", + "email_sender", "mentor_request_section", "mentor_request_creation", - "mentor_request_status_update", - "email_sender" + "mentor_request_status_update" ], "fields": [ { @@ -40,12 +41,16 @@ "fieldname": "mentor_request_section", "fieldtype": "Section Break", "label": "Mentor Request" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-04-21 13:28:35.783395", + "modified": "2021-04-29 17:14:43.589700", "modified_by": "Administrator", "module": "LMS", "name": "LMS Settings", diff --git a/community/lms/doctype/lms_sketch/lms_sketch.py b/community/lms/doctype/lms_sketch/lms_sketch.py index e26ff490..5c665305 100644 --- a/community/lms/doctype/lms_sketch/lms_sketch.py +++ b/community/lms/doctype/lms_sketch/lms_sketch.py @@ -52,11 +52,15 @@ class LMSSketch(Document): return value @staticmethod - def get_recent_sketches(limit=100): + def get_recent_sketches(limit=100, owner=None): """Returns the recent sketches. """ + filters = {} + if owner: + filters = {"owner": owner} sketches = frappe.get_all( "LMS Sketch", + filters=filters, fields='*', order_by='modified desc', page_length=limit diff --git a/community/lms/web_form/add_a_new_batch/add_a_new_batch.js b/community/lms/web_form/add_a_new_batch/add_a_new_batch.js index 6cabe776..8e45c529 100644 --- a/community/lms/web_form/add_a_new_batch/add_a_new_batch.js +++ b/community/lms/web_form/add_a_new_batch/add_a_new_batch.js @@ -1,35 +1,43 @@ frappe.ready(function () { - frappe.web_form.after_save = () => { - let data = frappe.web_form.get_values(); - frappe.call({ - "method": "community.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership", - "args": { - "batch": data.title, - "member_type": "Mentor" - }, - "callback": (data) => { - if (data.message == "OK") { - window.location.href = "/courses" - } - } - }) - } + frappe.web_form.after_save = () => { + let data = frappe.web_form.get_values(); + frappe.call({ + "method": "community.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership", + "args": { + "batch": data.title, + "member_type": "Mentor", + "course": data.course + }, + "callback": (data) => { + if (data.message) { + window.location.href = `courses/${data.message}` + } + } + }) + } - frappe.web_form.validate = () => { - let data = frappe.web_form.get_values(); - if (!frappe.datetime.validate(data.start_time) || !frappe.datetime.validate(data.end_time)) { - frappe.msgprint(__('Invalid Start or End Time.')); - return false; - } - if (data.start_time > data.end_time) { - frappe.msgprint(__('Start Time should be less than End Time.')); - return false; - } - console.log(data.start_date, date.nowdate()) - if (data.start_date < date.nowdate()) { - frappe.msgprint(__('Start date cannot be a past date.')) - return false; - } - return true; - }; -}) \ No newline at end of file + frappe.web_form.validate = () => { + let sysdefaults = frappe.boot.sysdefaults; + let time_format = sysdefaults && sysdefaults.time_format ? sysdefaults.time_format : 'HH:mm:ss'; + let data = frappe.web_form.get_values(); + + data.start_time = moment(data.start_time,time_format).format(time_format) + data.end_time = moment(data.end_time,time_format).format(time_format) + + if (!frappe.datetime.validate(data.start_time) || !frappe.datetime.validate(data.end_time)) { + frappe.msgprint(__('Invalid Start or End Time.')); + return false; + } + + if (data.start_time > data.end_time) { + frappe.msgprint(__('Start Time should be less than End Time.')); + return false; + } + + if (data.start_date < date.nowdate()) { + frappe.msgprint(__('Start date cannot be a past date.')) + return false; + } + return true; + }; +}) diff --git a/community/lms/web_form/add_a_new_batch/add_a_new_batch.json b/community/lms/web_form/add_a_new_batch/add_a_new_batch.json index 61903c7c..e1785069 100644 --- a/community/lms/web_form/add_a_new_batch/add_a_new_batch.json +++ b/community/lms/web_form/add_a_new_batch/add_a_new_batch.json @@ -11,6 +11,7 @@ "apply_document_permissions": 0, "button_label": "Save", "creation": "2021-04-20 11:37:49.135114", + "custom_css": ".datepicker.active {\n background-color: white;\n}", "doc_type": "LMS Batch", "docstatus": 0, "doctype": "Web Form", @@ -18,7 +19,7 @@ "is_standard": 1, "login_required": 1, "max_attachment_size": 0, - "modified": "2021-04-26 11:08:00.026388", + "modified": "2021-04-30 11:22:18.188712", "modified_by": "Administrator", "module": "LMS", "name": "add-a-new-batch", @@ -37,13 +38,13 @@ { "allow_read_on_all_link_options": 0, "fieldname": "course", - "fieldtype": "Link", + "fieldtype": "Data", "hidden": 0, "label": "Course", "max_length": 0, "max_value": 0, - "options": "LMS Course", - "read_only": 0, + "options": "", + "read_only": 1, "reqd": 0, "show_in_filter": 0 }, @@ -90,7 +91,7 @@ "fieldname": "start_time", "fieldtype": "Data", "hidden": 0, - "label": "Start Time (HH:MM:SS)", + "label": "Start Time", "max_length": 0, "max_value": 0, "read_only": 0, @@ -102,7 +103,7 @@ "fieldname": "end_time", "fieldtype": "Data", "hidden": 0, - "label": "End Time (HH:MM:SS)", + "label": "End Time", "max_length": 0, "max_value": 0, "read_only": 0, @@ -110,4 +111,4 @@ "show_in_filter": 0 } ] -} \ No newline at end of file +} diff --git a/community/lms/web_form/add_a_new_batch/add_a_new_batch.py b/community/lms/web_form/add_a_new_batch/add_a_new_batch.py index 2334f8b2..96e8cb34 100644 --- a/community/lms/web_form/add_a_new_batch/add_a_new_batch.py +++ b/community/lms/web_form/add_a_new_batch/add_a_new_batch.py @@ -3,5 +3,5 @@ from __future__ import unicode_literals import frappe def get_context(context): - # do your magic here - pass + # do your magic here + pass diff --git a/community/lms/widgets/RequestInvite.html b/community/lms/widgets/RequestInvite.html new file mode 100644 index 00000000..3a32ee02 --- /dev/null +++ b/community/lms/widgets/RequestInvite.html @@ -0,0 +1,25 @@ +
+ diff --git a/community/public/css/style.less b/community/public/css/style.less index a138e984..ea328dc5 100644 --- a/community/public/css/style.less +++ b/community/public/css/style.less @@ -66,3 +66,8 @@ section.lightgray { #hero .jumbotron { background: inherit; } + +.field-width { + width: 40%; + display: inline-block; +} diff --git a/community/templates/emails/lms_invite_request_approved.html b/community/templates/emails/lms_invite_request_approved.html new file mode 100644 index 00000000..0b454f91 --- /dev/null +++ b/community/templates/emails/lms_invite_request_approved.html @@ -0,0 +1,18 @@ +{{_("Dear Community Member,")}}
+{{_("Your Invite Request to be a part of {0} has + been approved.").format(site_link)}}
+Click on the link below to complete your Sign up and set a new password
++ {{ _("Complete Sign Up") }} +
+
+ {{_("You can also copy-paste following link in your browser")}}
+ {{ site_url }}{{ signup_form_link }}
+
Thanks and Regards,
+Your Community.
+Hands-on online courses designed by experts, delivered by passionate mentors.
-- Request Invite -
+ {{ widgets.RequestInvite() }}