diff --git a/community/lms/doctype/lms_course/lms_course.py b/community/lms/doctype/lms_course/lms_course.py
index fe44f04b..47b2f92d 100644
--- a/community/lms/doctype/lms_course/lms_course.py
+++ b/community/lms/doctype/lms_course/lms_course.py
@@ -13,6 +13,38 @@ from ...utils import slugify
class LMSCourse(Document):
+ def on_update(self):
+ if not self.upcoming and self.has_value_changed("upcoming"):
+ self.send_email_to_interested_users()
+
+ def send_email_to_interested_users(self):
+ interested_users = frappe.get_all("LMS Course Interest",
+ {
+ "course": self.name
+ },
+ ["name", "user"])
+ subject = self.title + " is available!"
+ args = {
+ "title": self.title,
+ "course_link": "/courses/{0}".format(self.name),
+ "app_name": frappe.db.get_single_value("System Settings", "app_name"),
+ "site_url": frappe.utils.get_url()
+ }
+
+ for user in interested_users:
+ args["first_name"] = frappe.db.get_value("User", user.user, "first_name")
+ email_args = frappe._dict(
+ recipients = user.user,
+ sender = frappe.db.get_single_value("LMS Settings", "email_sender"),
+ subject = subject,
+ header = [subject, "green"],
+ template = "lms_course_interest",
+ args = args,
+ now = True
+ )
+ frappe.enqueue(method=frappe.sendmail, queue='short', timeout=300, is_async=True, **email_args)
+ frappe.db.set_value("LMS Course Interest", user.name, "email_sent", True)
+
@staticmethod
def find(name):
"""Returns the course with specified name.
diff --git a/community/lms/doctype/lms_course_interest/__init__.py b/community/lms/doctype/lms_course_interest/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/community/lms/doctype/lms_course_interest/lms_course_interest.js b/community/lms/doctype/lms_course_interest/lms_course_interest.js
new file mode 100644
index 00000000..ff469e10
--- /dev/null
+++ b/community/lms/doctype/lms_course_interest/lms_course_interest.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 Interest', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/community/lms/doctype/lms_course_interest/lms_course_interest.json b/community/lms/doctype/lms_course_interest/lms_course_interest.json
new file mode 100644
index 00000000..f69e0c06
--- /dev/null
+++ b/community/lms/doctype/lms_course_interest/lms_course_interest.json
@@ -0,0 +1,61 @@
+{
+ "actions": [],
+ "creation": "2021-08-06 17:37:20.184849",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "course",
+ "user",
+ "email_sent"
+ ],
+ "fields": [
+ {
+ "fieldname": "course",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Course",
+ "options": "LMS Course"
+ },
+ {
+ "fieldname": "user",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "User",
+ "options": "User"
+ },
+ {
+ "default": "0",
+ "fieldname": "email_sent",
+ "fieldtype": "Check",
+ "label": "Email Sent",
+ "options": "email_sent"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-08-06 18:06:21.370741",
+ "modified_by": "Administrator",
+ "module": "LMS",
+ "name": "LMS Course Interest",
+ "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_interest/lms_course_interest.py b/community/lms/doctype/lms_course_interest/lms_course_interest.py
new file mode 100644
index 00000000..7aed3232
--- /dev/null
+++ b/community/lms/doctype/lms_course_interest/lms_course_interest.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2021, FOSS United and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe.model.document import Document
+
+class LMSCourseInterest(Document):
+ pass
+
+@frappe.whitelist()
+def capture_interest(course):
+ frappe.get_doc({
+ "doctype": "LMS Course Interest",
+ "course": course,
+ "user": frappe.session.user
+ }).save(ignore_permissions=True)
+ return "OK"
diff --git a/community/lms/doctype/lms_course_interest/test_lms_course_interest.py b/community/lms/doctype/lms_course_interest/test_lms_course_interest.py
new file mode 100644
index 00000000..b5689d52
--- /dev/null
+++ b/community/lms/doctype/lms_course_interest/test_lms_course_interest.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, FOSS United and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+class TestLMSCourseInterest(unittest.TestCase):
+ pass
diff --git a/community/lms/widgets/CourseCard.html b/community/lms/widgets/CourseCard.html
index 3599604a..15501f5d 100644
--- a/community/lms/widgets/CourseCard.html
+++ b/community/lms/widgets/CourseCard.html
@@ -58,7 +58,7 @@
{% set query_parameter = "?batch=" + membership.batch if membership and membership.batch else "" %}
{% if course.upcoming %}
-
+
Upcoming Course
diff --git a/community/overrides/test_user.py b/community/overrides/test_user.py
index cb6ce268..d89a9e01 100644
--- a/community/overrides/test_user.py
+++ b/community/overrides/test_user.py
@@ -34,14 +34,22 @@ class TestCustomUser(unittest.TestCase):
}).insert()
self.assertEqual(new_user.username[:8], "username")
- def test_with_hyphen_at_end(self):
+ def test_with_underscore_at_end(self):
new_user = frappe.get_doc({
"doctype": "User",
- "email": "test_with_hyphen_at_end@example.com",
- "first_name": "Username---"
+ "email": "test_with_underscore_at_end@example.com",
+ "first_name": "Username___"
}).insert()
- length = len(new_user.username)
- self.assertNotEqual(new_user.username[length-1], "-")
+ self.assertNotEqual(new_user.username[-1], "_")
+
+
+ def test_with_short_first_name(self):
+ new_user = frappe.get_doc({
+ "doctype": "User",
+ "email": "test_with_short_first_name@example.com",
+ "first_name": "USN"
+ }).insert()
+ self.assertGreaterEqual(len(new_user.username), 4)
@classmethod
def tearDownClass(cls) -> None:
@@ -49,6 +57,7 @@ class TestCustomUser(unittest.TestCase):
"test_with_basic_username@example.com",
"test-without-username@example.com",
"test_with_illegal_characters@example.com",
- "test_with_hyphen_at_end@example.com"
+ "test_with_underscore_at_end@example.com",
+ "test_with_short_first_name@example.com"
]
frappe.db.delete("User", {"name": ["in", users]})
diff --git a/community/overrides/user.py b/community/overrides/user.py
index 0a85e44c..51ba0bad 100644
--- a/community/overrides/user.py
+++ b/community/overrides/user.py
@@ -13,38 +13,45 @@ class CustomUser(User):
self.validate_username_characters()
def validate_username_characters(self):
+ if len(self.username):
+ underscore_condition = self.username[0] == "_" or self.username[-1] == "_"
+ else:
+ underscore_condition = ''
+
if self.is_new():
+ if not self.username:
+ self.username = self.get_username_from_first_name()
if self.username.find(" "):
self.username.replace(" ", "")
- if not re.match("^[A-Za-z0-9_]*$", self.username):
+ if not re.match("^[A-Za-z0-9_]*$", self.username) or underscore_condition:
self.username = self.remove_illegal_characters()
- if not self.username:
- self.username = self.get_username_from_first_name()
+ if len(self.username) < 4:
+ self.username = self.email.replace("@", "").replace(".", "")
- if self.username_exists():
+ while self.username_exists():
self.username = self.remove_illegal_characters() + str(random.randint(0, 99))
else:
- if not re.match("^[A-Za-z0-9_-]*$", self.username):
- frappe.throw(_("Username can only contain alphabets, numbers, hyphen and underscore."))
+ if not self.username:
+ frappe.throw(_("Username already exists."))
- if self.username[0] == "-" or self.username[len(self.username) - 1] == "-":
- frappe.throw(_("First and Last character of username cannot be Hyphen(-)."))
+ if not re.match("^[A-Za-z0-9_]*$", self.username):
+ frappe.throw(_("Username can only contain alphabets, numbers and unedrscore."))
+
+ if underscore_condition:
+ frappe.throw(_("First and Last character of username cannot be Underscore(_)."))
+
+ if len(self.username) < 4:
+ frappe.throw(_("Username cannot be less than 4 characters"))
def get_username_from_first_name(self):
return frappe.scrub(self.first_name) + str(random.randint(0, 99))
def remove_illegal_characters(self):
- username = ''.join([c for c in self.username if c.isalnum() or c in ['-', '_']])
- while username[0] == "-" or username[len(username) - 1] == "-":
- if username[0] == "-":
- username = username[1:]
- if username[len(username) - 1]:
- username = username[:1]
- return username
+ return re.sub("[^\w]+", "", self.username).strip("_")
def get_authored_courses(self) -> int:
"""Returns the number of courses authored by this user.
diff --git a/community/public/css/style.css b/community/public/css/style.css
index 53840022..e2b29df5 100644
--- a/community/public/css/style.css
+++ b/community/public/css/style.css
@@ -635,6 +635,11 @@ input[type=checkbox] {
font-size: 12px;
line-height: 135%;
letter-spacing: -0.011em;
+ border: none;
+}
+
+.button:disabled {
+ cursor: not-allowed;
}
.wide-button {
@@ -660,6 +665,11 @@ input[type=checkbox] {
color: inherit;
}
+.is-default {
+ background: #98A1A9;
+ color: #ffffff;
+}
+
@media (max-width: 600px) {
.video-preview {
margin-top: 16px;
@@ -676,12 +686,6 @@ input[type=checkbox] {
color: #FFFFFF;
}
-.is-default {
- background-color: white;
- border: 1px solid #C8CFD5;
- box-sizing: border-box;
-}
-
.course-home-outline {
margin-top: 3rem;
}
diff --git a/community/templates/emails/lms_course_interest.html b/community/templates/emails/lms_course_interest.html
new file mode 100644
index 00000000..1658b3e6
--- /dev/null
+++ b/community/templates/emails/lms_course_interest.html
@@ -0,0 +1,19 @@
+
+ {% set site_link = "
" + app_name + "" %}
+
{{ _("Hi {0},").format(first_name) }}
+
+
{{ _("The course {0} is now available on {1}.").format(frappe.bold(title), app_name) }}
+
+
Click on the link below to start learning.
+
+ {{ _("Start Learning") }}
+
+
+
+ {{ _("You can also copy-paste following link in your browser") }}
+ {{ site_url }}{{ course_link }}
+
+
+
Thanks and Regards,
+
{{ app_name }}
+
diff --git a/community/www/courses/course.html b/community/www/courses/course.html
index 0982e1ed..53b1d54d 100644
--- a/community/www/courses/course.html
+++ b/community/www/courses/course.html
@@ -58,6 +58,12 @@
Continue Learning

{% endif %}
+ {% if course.upcoming %}
+
+ {% endif %}
{% if course.video_link %}
Watch Video Preview
diff --git a/community/www/courses/course.js b/community/www/courses/course.js
index e73b3b70..2e7688dc 100644
--- a/community/www/courses/course.js
+++ b/community/www/courses/course.js
@@ -41,6 +41,10 @@ frappe.ready(() => {
submit_review(e);
})
+ $("#notify-me").click((e) => {
+ notify_user(e);
+ })
+
})
var check_mentor_request = () => {
@@ -209,3 +213,23 @@ var submit_review = (e) => {
}
})
}
+
+var notify_user = (e) => {
+ e.preventDefault();
+ var course = decodeURIComponent($(e.currentTarget).attr("data-course"));
+ if (frappe.session.user == "Guest") {
+ window.location.href = `/login?redirect-to=/courses/${course}`;
+ return;
+ }
+
+ frappe.call({
+ method: "community.lms.doctype.lms_course_interest.lms_course_interest.capture_interest",
+ args: {
+ "course": course
+ },
+ callback: (data) => {
+ frappe.msgprint(__("Your interest has been noted. We'll notify you via email when this course becomes available."));
+ $("#notify-me").attr("disabled", true).attr("title", "Your interest has already been noted");
+ }
+ })
+}
diff --git a/community/www/courses/course.py b/community/www/courses/course.py
index f0fb0990..71e1708e 100644
--- a/community/www/courses/course.py
+++ b/community/www/courses/course.py
@@ -18,9 +18,18 @@ def get_context(context):
membership = course.get_membership(frappe.session.user)
context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else ""
context.membership = membership
+ if context.course.upcoming:
+ context.is_user_interested = get_user_interest(context.course.name)
context.metatags = {
"title": course.title,
"image": course.image,
"description": course.short_introduction,
"keywords": course.title
}
+
+def get_user_interest(course):
+ return frappe.db.count("LMS Course Interest",
+ {
+ "course": course,
+ "user": frappe.session.user
+ })