fix: conflicts
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "field:title",
|
||||
"creation": "2021-03-18 19:37:34.614796",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
@@ -51,10 +50,12 @@
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"default": "Public",
|
||||
"fieldname": "visibility",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Visibility",
|
||||
"options": "\nPublic\nUnlisted\nPrivate"
|
||||
"options": "Public\nUnlisted\nPrivate"
|
||||
},
|
||||
{
|
||||
"fieldname": "membership",
|
||||
@@ -63,16 +64,19 @@
|
||||
"options": "\nOpen\nRestricted\nInvite Only\nClosed"
|
||||
},
|
||||
{
|
||||
"default": "Active",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Status",
|
||||
"options": "\nActive\nInactive"
|
||||
"options": "Active\nInactive"
|
||||
},
|
||||
{
|
||||
"default": "Ready",
|
||||
"fieldname": "stage",
|
||||
"fieldtype": "Select",
|
||||
"label": "Stage",
|
||||
"options": "\nReady\nIn Progress\nCompleted\nCancelled"
|
||||
"options": "Ready\nIn Progress\nCompleted\nCancelled"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
@@ -122,7 +126,7 @@
|
||||
"link_fieldname": "batch"
|
||||
}
|
||||
],
|
||||
"modified": "2021-04-30 09:52:18.941276",
|
||||
"modified": "2021-05-06 05:46:38.469120",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Batch",
|
||||
|
||||
@@ -8,6 +8,7 @@ from frappe.model.document import Document
|
||||
from community.www.courses.utils import get_member_with_email
|
||||
from frappe import _
|
||||
from community.lms.doctype.lms_batch_membership.lms_batch_membership import create_membership
|
||||
from community.query import find, find_all
|
||||
|
||||
class LMSBatch(Document):
|
||||
def validate(self):
|
||||
@@ -29,15 +30,20 @@ class LMSBatch(Document):
|
||||
self.code = short_code + str(len(course_batches) + 1)
|
||||
|
||||
def get_mentors(self):
|
||||
mentors = []
|
||||
memberships = frappe.get_all(
|
||||
"LMS Batch Membership",
|
||||
{"batch": self.name, "member_type": "Mentor"},
|
||||
["member"])
|
||||
for membership in memberships:
|
||||
member = frappe.db.get_value("Community Member", membership.member, ["full_name", "photo", "abbr"], as_dict=1)
|
||||
mentors.append(member)
|
||||
return mentors
|
||||
member_names = [m['member'] for m in memberships]
|
||||
return find_all("Community Member", name=["IN", member_names])
|
||||
|
||||
def is_member(self, email):
|
||||
"""Checks if a person is part of a batch.
|
||||
"""
|
||||
member = find("Community Member", email=email)
|
||||
return member and frappe.db.exists(
|
||||
"LMS Batch Membership",
|
||||
{"batch": self.name, "member": member.name})
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_messages(batch):
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
"actions": [],
|
||||
"allow_guest_to_view": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
"creation": "2021-03-01 16:49:33.622422",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"slug",
|
||||
"is_published",
|
||||
"column_break_3",
|
||||
"short_code",
|
||||
@@ -43,14 +41,6 @@
|
||||
"fieldtype": "Data",
|
||||
"label": "Short Code"
|
||||
},
|
||||
{
|
||||
"description": "The slug of the course. Autogenerated from the title if not specified.",
|
||||
"fieldname": "slug",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Slug",
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
@@ -87,9 +77,14 @@
|
||||
"group": "Mentors",
|
||||
"link_doctype": "LMS Course Mentor Mapping",
|
||||
"link_fieldname": "course"
|
||||
},
|
||||
{
|
||||
"group": "Mentors",
|
||||
"link_doctype": "LMS Mentor Request",
|
||||
"link_fieldname": "course"
|
||||
}
|
||||
],
|
||||
"modified": "2021-05-03 05:52:30.396824",
|
||||
"modified": "2021-05-06 13:37:03.318829",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Course",
|
||||
@@ -108,7 +103,7 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"search_fields": "slug",
|
||||
"search_fields": "title",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "title",
|
||||
|
||||
@@ -6,13 +6,18 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from ...utils import slugify
|
||||
from community.query import find, find_all
|
||||
|
||||
class LMSCourse(Document):
|
||||
@staticmethod
|
||||
def find(slug):
|
||||
"""Returns the course with specified slug.
|
||||
def find(name):
|
||||
"""Returns the course with specified name.
|
||||
"""
|
||||
return find("LMS Course", is_published=True, slug=slug)
|
||||
return find("LMS Course", is_published=True, name=name)
|
||||
|
||||
def autoname(self):
|
||||
if not self.name:
|
||||
self.name = self.generate_slug(title=self.title)
|
||||
|
||||
@staticmethod
|
||||
def find_all():
|
||||
@@ -20,19 +25,15 @@ class LMSCourse(Document):
|
||||
"""
|
||||
return find_all("LMS Course", is_published=True)
|
||||
|
||||
def before_save(self):
|
||||
if not self.slug:
|
||||
self.slug = self.generate_slug(title=self.title)
|
||||
|
||||
def generate_slug(self, title):
|
||||
result = frappe.get_all(
|
||||
'LMS Course',
|
||||
fields=['slug'])
|
||||
slugs = set([row['slug'] for row in result])
|
||||
fields=['name'])
|
||||
slugs = set([row['name'] for row in result])
|
||||
return slugify(title, used_slugs=slugs)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Course#{self.name} {self.slug}>"
|
||||
return f"<Course#{self.name}>"
|
||||
|
||||
def get_topic(self, slug):
|
||||
"""Returns the topic with given slug in this course as a Document.
|
||||
@@ -123,6 +124,9 @@ class LMSCourse(Document):
|
||||
# TODO: chapters should have a way to specify the order
|
||||
return find_all("Chapter", course=self.name, order_by="creation")
|
||||
|
||||
def get_batch(self, batch_name):
|
||||
return find("LMS Batch", name=batch_name, course=self.name)
|
||||
|
||||
def get_batches(self, mentor=None):
|
||||
batches = find_all("LMS Batch", course=self.name)
|
||||
if mentor:
|
||||
@@ -139,24 +143,8 @@ class LMSCourse(Document):
|
||||
now = frappe.utils.nowdate()
|
||||
batches = find_all("LMS Batch",
|
||||
course=self.name,
|
||||
start_date=[">", now])
|
||||
start_date=[">", now],
|
||||
status="Active",
|
||||
visibility="Public")
|
||||
return batches
|
||||
|
||||
def find_all(doctype, order_by=None, **filters):
|
||||
"""Queries the database for documents of a doctype matching given filters.
|
||||
"""
|
||||
rows = frappe.db.get_all(doctype,
|
||||
filters=filters,
|
||||
fields='*',
|
||||
order_by=order_by)
|
||||
return [frappe.get_doc(dict(row, doctype=doctype)) for row in rows]
|
||||
|
||||
def find(doctype, **filters):
|
||||
"""Queries the database for a document of given doctype matching given filters.
|
||||
"""
|
||||
rows = frappe.db.get_all(doctype,
|
||||
filters=filters,
|
||||
fields='*')
|
||||
if rows:
|
||||
row = rows[0]
|
||||
return frappe.get_doc(dict(row, doctype=doctype))
|
||||
|
||||
@@ -24,7 +24,7 @@ class TestLMSCourse(unittest.TestCase):
|
||||
def test_new_course(self):
|
||||
course = self.new_course("Test Course")
|
||||
assert course.title == "Test Course"
|
||||
assert course.slug == "test-course"
|
||||
assert course.name == "test-course"
|
||||
assert course.get_mentors() == []
|
||||
|
||||
def test_find_all(self):
|
||||
@@ -41,7 +41,7 @@ class TestLMSCourse(unittest.TestCase):
|
||||
|
||||
# now we should find one course
|
||||
courses = LMSCourse.find_all()
|
||||
assert [c.slug for c in courses] == [course.slug]
|
||||
assert [c.name for c in courses] == [course.name]
|
||||
|
||||
# disabled this test as it is failing
|
||||
def _test_add_mentors(self):
|
||||
|
||||
@@ -8,86 +8,119 @@ from frappe.model.document import Document
|
||||
from frappe import _
|
||||
|
||||
class LMSMentorRequest(Document):
|
||||
def on_update(self):
|
||||
if self.has_value_changed('status'):
|
||||
template = frappe.db.get_single_value('LMS Settings', 'mentor_request_status_update')
|
||||
if not template:
|
||||
return
|
||||
def on_update(self):
|
||||
if self.has_value_changed('status'):
|
||||
|
||||
email_template = frappe.get_doc('Email Template', template)
|
||||
message = frappe.render_template(email_template.response, {'member_name': self.member_name, 'status': self.status})
|
||||
subject = _('The status of your application has changed.')
|
||||
member_email = frappe.db.get_value("Community Member", self.member, "email")
|
||||
|
||||
if self.status == 'Approved' or self.status == 'Rejected':
|
||||
reviewed_by = frappe.db.get_value('Community Member', self.reviewed_by, 'email')
|
||||
send_email(member_email, [get_course_author(self.course), reviewed_by], subject, message)
|
||||
|
||||
elif self.status == 'Withdrawn':
|
||||
send_email([member_email, get_course_author(self.course)], None, subject, message)
|
||||
if self.status == "Approved":
|
||||
self.create_course_mentor_mapping()
|
||||
|
||||
if self.status != "Pending":
|
||||
self.send_status_change_email()
|
||||
|
||||
def create_course_mentor_mapping(self):
|
||||
mapping = frappe.get_doc({
|
||||
"doctype": "LMS Course Mentor Mapping",
|
||||
"mentor": self.member,
|
||||
"course": self.course
|
||||
})
|
||||
mapping.save()
|
||||
|
||||
def send_creation_email(self, member):
|
||||
email_template = self.get_email_template('mentor_request_creation')
|
||||
if not email_template:
|
||||
return
|
||||
|
||||
course_details = frappe.db.get_value("LMS Course", self.course, ["owner", "slug", "title"], as_dict=True)
|
||||
message = frappe.render_template(email_template.response,
|
||||
{
|
||||
'member_name': member.full_name,
|
||||
'course_url': '/courses/' + course_details.slug,
|
||||
'course': course_details.title
|
||||
})
|
||||
|
||||
email_args = {
|
||||
"recipients": [frappe.session.user, course_details.owner],
|
||||
"subject": email_template.subject,
|
||||
"header": email_template.subject,
|
||||
"message": message
|
||||
}
|
||||
frappe.enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args)
|
||||
|
||||
def send_status_change_email(self):
|
||||
email_template = self.get_email_template('mentor_request_status_update')
|
||||
if not email_template:
|
||||
return
|
||||
|
||||
course_details = frappe.db.get_value("LMS Course", self.course, ["owner", "title"], as_dict=True)
|
||||
message = frappe.render_template(email_template.response,
|
||||
{
|
||||
'member_name': self.member_name,
|
||||
'status': self.status,
|
||||
'course': course_details.title
|
||||
})
|
||||
|
||||
member_email = frappe.db.get_value("Community Member", self.member, "email")
|
||||
if self.status == 'Approved' or self.status == 'Rejected':
|
||||
reviewed_by = frappe.db.get_value('Community Member', self.reviewed_by, 'email')
|
||||
email_args = {
|
||||
"recipients": member_email,
|
||||
"cc": [course_details.owner, reviewed_by],
|
||||
"subject": email_template.subject,
|
||||
"header": email_template.subject,
|
||||
"message": message
|
||||
}
|
||||
frappe.enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args)
|
||||
|
||||
elif self.status == 'Withdrawn':
|
||||
email_args = {
|
||||
"recipients": [member_email, course_details.owner],
|
||||
"subject": email_template.subject,
|
||||
"header": email_template.subject,
|
||||
"message": message
|
||||
}
|
||||
frappe.enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args)
|
||||
|
||||
def get_email_template(self, template_name):
|
||||
template = frappe.db.get_single_value('LMS Settings', template_name)
|
||||
if template:
|
||||
return frappe.get_doc('Email Template', template)
|
||||
|
||||
@frappe.whitelist()
|
||||
def has_requested(course):
|
||||
return len(frappe.get_all('LMS Mentor Request',
|
||||
filters = {
|
||||
'member': get_member().name,
|
||||
'course': course,
|
||||
'status': ['in', ('Pending', 'Approved')]
|
||||
}
|
||||
)
|
||||
return frappe.db.count('LMS Mentor Request',
|
||||
filters = {
|
||||
'member': get_member().name,
|
||||
'course': course,
|
||||
'status': ['in', ('Pending', 'Approved')]
|
||||
}
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_request(course):
|
||||
if not has_requested(course):
|
||||
member = get_member()
|
||||
frappe.get_doc({
|
||||
'doctype': 'LMS Mentor Request',
|
||||
'member': member.name,
|
||||
'course': course,
|
||||
'status': 'Pending'
|
||||
}).save(ignore_permissions=True)
|
||||
send_creation_email(course, member)
|
||||
return 'OK'
|
||||
else:
|
||||
return 'Already Applied'
|
||||
if not has_requested(course):
|
||||
member = get_member()
|
||||
request = frappe.get_doc({
|
||||
'doctype': 'LMS Mentor Request',
|
||||
'member': member.name,
|
||||
'course': course,
|
||||
'status': 'Pending'
|
||||
})
|
||||
request.save(ignore_permissions=True)
|
||||
request.send_creation_email(member)
|
||||
return 'OK'
|
||||
|
||||
else:
|
||||
return 'Already Applied'
|
||||
|
||||
@frappe.whitelist()
|
||||
def cancel_request(course):
|
||||
request = frappe.get_doc('LMS Mentor Request', {'member': get_member().name, 'course': course, 'status': ['in', ('Pending', 'Approved')]})
|
||||
request.status = 'Withdrawn'
|
||||
request.save(ignore_permissions=True)
|
||||
return 'OK'
|
||||
request = frappe.get_doc('LMS Mentor Request', {'member': get_member().name, 'course': course, 'status': ['in', ('Pending', 'Approved')]})
|
||||
request.status = 'Withdrawn'
|
||||
request.save(ignore_permissions=True)
|
||||
return 'OK'
|
||||
|
||||
def get_member():
|
||||
try:
|
||||
return frappe.get_doc('Community Member', {'email': frappe.session.user})
|
||||
except frappe.DoesNotExistError:
|
||||
return
|
||||
|
||||
def get_course_author(course):
|
||||
return frappe.db.get_value('LMS Course', course, 'owner')
|
||||
|
||||
def send_creation_email(course, member):
|
||||
template = frappe.db.get_single_value('LMS Settings', 'mentor_request_creation')
|
||||
if not template:
|
||||
return
|
||||
|
||||
email_template = frappe.get_doc('Email Template', template)
|
||||
member_name = member.full_name
|
||||
message = frappe.render_template(email_template.response, {'member_name': member_name})
|
||||
subject = _('Request for Mentorship')
|
||||
send_email([frappe.session.user, get_course_author(course)], None, subject, message)
|
||||
|
||||
def send_email(recipients, cc=None, subject=None, message=None, template=None, args=None):
|
||||
frappe.sendmail(
|
||||
recipients = recipients,
|
||||
cc = cc,
|
||||
sender = frappe.db.get_single_value('LMS Settings', 'email_sender'),
|
||||
subject = subject,
|
||||
send_priority = 0,
|
||||
queue_separately = True,
|
||||
message = message,
|
||||
template=template,
|
||||
args=args
|
||||
)
|
||||
try:
|
||||
return frappe.get_doc('Community Member', {'email': frappe.session.user})
|
||||
except frappe.DoesNotExistError:
|
||||
return
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="chapter-teaser">
|
||||
<div class="teaser-body">
|
||||
<h3 class="chapter-title">{{ chapter.title }}</h3>
|
||||
<h3 class="chapter-title"><span class="chapter-number">{{index}}</span> {{ chapter.title }}</h3>
|
||||
<div class="chapter-description">
|
||||
{{ chapter.description or "" }}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<div class="course-teaser">
|
||||
<div class="course-body">
|
||||
<h3 class="course-title"><a href="/courses/{{ course.slug }}">{{ course.title }}</a></h3>
|
||||
<h3 class="course-title"><a href="/courses/{{ course.name }}">{{ course.title }}</a></h3>
|
||||
<div class="course-intro">
|
||||
{{ course.short_introduction or "" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="course-footer">
|
||||
<div class="course-author">
|
||||
<img class="course-author-avatar" src="{{ course.get_instructor().avatar }}" />{{ course.get_instructor().full_name }}
|
||||
{% with author = course.get_instructor() %}
|
||||
{{ widgets.Avatar(member=author, avatar_class="avatar-medium") }} <a href="/{{author.username}}">{{ author.full_name }}</a>
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<form id="invite-request-form">
|
||||
<input class="form-control field-width mr-5" id="invite_email" type="email" placeholder="Email Address">
|
||||
<a type="submit" id="submit-invite-request" class="btn btn-primary btn-lg" href="#" role="button">Request Invite</a>
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<input class="form-control w-100 mr-5 mb-5 mt-2" id="invite_email" type="email" placeholder="Email Address">
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<a type="submit" id="submit-invite-request" class="btn btn-primary btn-lg" href="#" role="button">Request Invite</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
@@ -16,19 +22,19 @@
|
||||
|
||||
if (data.message == "OK") {
|
||||
var message = `<div>
|
||||
<p class="lead">Thanks for your interest in Mon School. We have recorded your interest and we will get back to you shortly.</p>
|
||||
<p class="lead alert alert-secondary">Thanks for your interest in Mon School. We have recorded your interest and we will get back to you shortly.</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
else if (data.message == "invite") {
|
||||
var message = `<div>
|
||||
<p class="lead">Email ${invite_email} has already been used to request an invitation.</p>
|
||||
<p class="lead alert alert-secondary">Email ${invite_email} has already been used to request an invitation.</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
else if (data.message == "user") {
|
||||
var message = `<div>
|
||||
<p class="lead">Looks like there is already an account with email ${invite_email}. Would you like to <a href="/login">login</a>.</p>
|
||||
<p class="lead alert alert-secondary">Looks like there is already an account with email ${invite_email}. Would you like to <a href="/login">login</a>?</p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
<a href="sketches/{{sketch.sketch_id}}">{{sketch.title}}</a>
|
||||
</div>
|
||||
<div class="sketch-author">
|
||||
by {{sketch.get_owner().full_name}}
|
||||
{% set owner = sketch.get_owner() %}
|
||||
by <a href="/{{owner.username}}">{{owner.full_name}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user