feat: #28 info page, #27 discussion page initial structure, add batch webform change

This commit is contained in:
pateljannat
2021-04-27 10:33:49 +05:30
parent 44dc42d7dd
commit 424967b03e
18 changed files with 372 additions and 185 deletions

View File

@@ -5,6 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from community.www.courses.utils import get_member_with_email
class LMSBatch(Document):
def validate(self):
@@ -15,3 +16,25 @@ class LMSBatch(Document):
short_code = frappe.db.get_value("LMS Course", self.course, "short_code")
course_batches = frappe.get_all("LMS Batch",{"course":self.course})
self.code = short_code + str(len(course_batches) + 1)
@frappe.whitelist()
def get_messages(batch):
messages = frappe.get_all("LMS Message", {"batch": batch}, ["*"], order_by="creation")
for message in messages:
message.message = frappe.utils.md_to_html(message.message)
member_email = frappe.db.get_value("Community Member", message.author, ["email"])
if member_email == frappe.session.user:
message.author_name = "You"
message.is_author = True
return messages
@frappe.whitelist()
def save_message(message, batch):
doc = frappe.get_doc({
"doctype": "LMS Message",
"batch": batch,
"author": get_member_with_email(),
"message": message
})
doc.save(ignore_permissions=True)
return doc

View File

@@ -39,7 +39,6 @@
{
"fieldname": "role",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Role",
"options": "\nMember\nAdmin"
@@ -59,7 +58,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-04-22 15:30:00.069946",
"modified": "2021-04-26 12:52:59.826509",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Batch Membership",

View File

@@ -5,10 +5,13 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"batch",
"author",
"message",
"pin"
"batch",
"column_break_3",
"author_name",
"pin",
"section_break_6",
"message"
],
"fields": [
{
@@ -36,11 +39,26 @@
"fieldname": "pin",
"fieldtype": "Check",
"label": "Pin"
},
{
"fetch_from": "author.full_name",
"fieldname": "author_name",
"fieldtype": "Data",
"label": "Author Name",
"read_only": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-03-23 18:06:25.086377",
"modified": "2021-04-26 16:11:20.142164",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Message",

View File

@@ -9,9 +9,7 @@ from frappe import _
from frappe.utils import add_days, nowdate
class LMSMessage(Document):
def after_insert(self):
message = self.as_dict()
message['broadcast'] = True
frappe.publish_realtime('new_lms_message', message, after_commit=True)
frappe.publish_realtime("new_lms_message", {"message":"JJannat"}, user="Administrator")
self.send_email()
def send_email(self):

View File

@@ -18,7 +18,7 @@
"is_standard": 1,
"login_required": 1,
"max_attachment_size": 0,
"modified": "2021-04-21 16:39:09.457653",
"modified": "2021-04-26 11:08:00.026388",
"modified_by": "Administrator",
"module": "LMS",
"name": "add-a-new-batch",
@@ -47,41 +47,6 @@
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "start_date",
"fieldtype": "Date",
"hidden": 0,
"label": "Start Date",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "start_time",
"fieldtype": "Data",
"hidden": 0,
"label": "Start Time (HH:MM:SS)",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "",
"fieldtype": "Column Break",
"hidden": 0,
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "title",
@@ -94,6 +59,18 @@
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "start_date",
"fieldtype": "Date",
"hidden": 0,
"label": "Start Date",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"description": "",
@@ -108,6 +85,18 @@
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "start_time",
"fieldtype": "Data",
"hidden": 0,
"label": "Start Time (HH:MM:SS)",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 1,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "end_time",

View File

@@ -8,8 +8,8 @@
--c4: #2a9d8f;
--c5: #f4a261;
--c6: #e76f51;
--c7: #ccd5ae;
--c8: #EEEEEE;
--bg: var(--c1);
--header-bg: var(--c2);
@@ -23,6 +23,8 @@
--text-color-light: #ccc;
--cta-color: var(--c4);
--send-message: var(--c7);
--received-message: var(--c8);
}
body {
@@ -232,4 +234,60 @@ nav.navbar {
width: 59px;
height: 57px;
border-radius: 50%;
}
.msger-inputarea {
position: fixed;
bottom: 0;
width: 100%;
display: flex;
padding: 10px;
border-top: 2px solid #ddd;
background: #eee;
z-index: 1;
}
.msger-inputarea * {
padding: 10px;
border: none;
border-radius: 3px;
font-size: 1em;
}
.msger-input {
flex: 1;
background: #ddd;
}
.msger-send-btn {
margin-left: 10px;
background: var(--cta-color);
color: #fff;
font-weight: bold;
cursor: pointer;
transition: background 0.23s;
}
.discussion {
border: 1px solid var(--text-color);
padding: 10px;
margin: 10px;
border-radius: 10px;
background: var(--received-message);
width: 50%;
display: flex;
flex-direction: column;
}
.is-author {
float: right;
background: var(--send-message);
}
.batch-header {
position: fixed;
top: 0;
background: var(--bg);
width: 100%;
}
.message-section {
margin-left: 5%;
}

View File

@@ -1,5 +1,7 @@
{% extends "templates/base.html" %}
{% from "www/macros/sidebar.html" import Sidebar %}
{% from "www/macros/common_macro.html" import InstructorsSection, MentorsSection %}
{% block title %}About{% endblock %}
{% block head_include %}
<meta name="description" content="Courses" />
@@ -7,7 +9,38 @@
{% endblock %}
{% block content %}
{{ Sidebar(course, batch_code) }}
{{ Sidebar(course_slug, batch_code) }}
<div class="container">
{{ CourseBasicDetail(course)}}
{{ InstructorsSection(instructor) }}
{% if batch.description %}
{{ BatchDetails(batch.description) }}
{% endif %}
{{ MentorsSection(mentors, True) }}
</div>
{% endblock %}
{% macro CourseBasicDetail(course) %}
<h2>{{course.name}}</h2>
<div class="course-description">
{{course.short_introduction}}
</div>
{% if course.video_link %}
<div class="preview-video">
<iframe width="560" height="315" src="{{course.video_link}}" title="YouTube video player" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>
</div>
{% endif %}
<h2>About the Course</h2>
<div>{{frappe.utils.md_to_html(course.description)}}</div>
{% endmacro %}
{% macro BatchDetails(description) %}
<div class="mt-5">
<h3>About the Batch</h3>
<div>
{{ frappe.utils.md_to_html(description) }}
</div>
</div>
{% endmacro %}

View File

@@ -1,8 +1,23 @@
import frappe
from community.www.courses.utils import redirect_if_not_a_member
from community.www.courses.utils import redirect_if_not_a_member, get_course, get_instructor, get_batch
def get_context(context):
context.no_cache = 1
context.course = frappe.form_dict["course"]
context.course_slug = frappe.form_dict["course"]
context.course = get_course(context.course_slug)
context.batch_code = frappe.form_dict["batch"]
redirect_if_not_a_member(context.course, context.batch_code)
redirect_if_not_a_member(context.course_slug, context.batch_code)
context.instructor = get_instructor(context.course.owner)
context.batch = get_batch(context.batch_code)
context.mentors = get_mentors(context.batch.name)
print(context.mentors)
def get_mentors(batch):
mentors = []
memberships = frappe.get_all("LMS Batch Membership", {"batch": batch, "member_type": "Mentor"}, ["member"])
for membership in memberships:
member = frappe.db.get_value("Community Member", membership.member, ["name","full_name"], as_dict=True)
member.batch_count = len(frappe.get_all("LMS Batch Membership", {"member": member.name, "member_type": "Mentor"}))
mentors.append(member)
return mentors

View File

@@ -1,4 +1,5 @@
{% extends "templates/base.html" %}
{% from "www/macros/common_macro.html" import InstructorsSection, MentorsSection %}
{% block title %}{{ course.title }}{% endblock %}
{% block head_include %}
<meta name="description" content="Courses" />
@@ -27,7 +28,7 @@
</div>
<div class="sidebar">
{{ MentorsSection(mentors) }}
{{ MentorsSection(mentors, is_mentor) }}
</div>
</div>
</div>
@@ -59,7 +60,7 @@
{% macro BatchSection(course, is_mentor, upcoming_batches, mentor_batches) %}
{% if is_mentor %}
{{ BatchSectionForMentors(course, mentor_batches) }}
{% else %}
{% else %}
{{ BatchSectionForStudents(course, upcoming_batches) }}
{% endif %}
{% endmacro %}
@@ -152,27 +153,3 @@
</div>
{% endfor %}
{% endmacro %}
{% macro InstructorsSection(instructor) %}
<h3>Instructor</h3>
<div class="instructor">
<div class="instructor-title">{{instructor.full_name}}</div>
<div class="instructor-subtitle">Created {{instructor.course_count}} courses</div>
</div>
{% endmacro %}
{% macro MentorsSection(instructor) %}
<h3>Mentors</h3>
{% for m in mentors %}
<div class="instructor">
<div class="instructor-title">{{m.full_name}}</div>
<div class="instructor-subtitle">Mentored {{m.batch_count}} batches</div>
</div>
{% endfor %}
<div class="notice">
Interested to become a mentor?
<div><a href="#">Apply Now!</a></div>
</div>
{% endmacro %}

View File

@@ -1,4 +1,15 @@
frappe.ready(() => {
frappe.require("/assets/frappe/js/lib/socket.io.min.js");
frappe.require("/assets/frappe/js/frappe/socketio_client.js");
if (window.dev_server) {
frappe.boot.socketio_port = "9000" //use socketio port shown when bench starts
}
frappe.socketio.init();
console.log(frappe.socketio)
//frappe.socketio.emittedDemo("mydata");
frappe.realtime.on("new_lms_message", (data) => {
console.log(data)
})
if (frappe.session.user != "Guest") {
frappe.call({
'method': 'community.lms.doctype.lms_mentor_request.lms_mentor_request.has_requested',
@@ -29,28 +40,6 @@ frappe.ready(() => {
})
})
$(".send-message").click((e) => {
var message = $(".message-text").val().trim();
if (message) {
frappe.call({
"method": "community.www.courses.course.save_message",
"args": {
"batch": decodeURIComponent($(e.target).attr("data-batch")),
"author": decodeURIComponent($(e.target).attr("data-author")),
"message": message
},
"callback": (data) => {
$(".message-text").val("");
var element = add_message(data.message, true)
$(".discussions").prepend(element);
}
})
}
else {
$(".message-text").val("");
}
})
$(".apply-now").click((e) => {
if (frappe.session.user == "Guest") {
window.location.href = "/login";
@@ -104,15 +93,6 @@ frappe.ready(() => {
}
})
})
var add_message = (message, session_user = false) => {
var author_name = session_user ? "You" : message.author_name
return `<div class="list-group-item">
<h6> ${author_name} </h6>
${message.message}
<div class="small text-muted text-right"> ${message.creation} </div>
</div>`;
}
})
/*
var show_enrollment_badge = () => {

View File

@@ -1,4 +1,5 @@
import frappe
from community.www.courses.utils import get_instructor
from frappe.utils import nowdate, getdate
def get_context(context):
@@ -61,16 +62,6 @@ def get_membership(batches):
memberships.append(membership)
return memberships
def get_instructor(owner):
instructor = frappe._dict()
try:
instructor = frappe.get_doc("Community Member", {"email": owner})
except frappe.DoesNotExistError:
instructor.full_name = owner
instructor.abbr = ("").join([ s[0] for s in owner.split() ])
instructor.course_count = len(frappe.get_all("LMS Course", {"owner": owner}))
return instructor
def get_mentors(course):
course_mentors = []
mentors = frappe.get_all("LMS Course Mentor Mapping", {"course": course}, ["mentor"])
@@ -108,25 +99,3 @@ def is_mentor(course):
if len(mapping):
return True
@frappe.whitelist()
def get_messages(batch):
messages = frappe.get_all("LMS Message", {"batch": batch}, ["*"], order_by="creation desc")
for message in messages:
message.message = frappe.utils.md_to_html(message.message)
message.creation = frappe.utils.format_datetime(message.creation, "medium")
message.author_name, member_email = frappe.db.get_value("Community Member", message.author, ["full_name","email"])
if member_email == frappe.session.user:
message.author_name = "You"
return messages
@frappe.whitelist()
def save_message(message, author, batch):
doc = frappe.get_doc({
"doctype": "LMS Message",
"author": author,
"batch": batch,
"message": message
})
doc.save(ignore_permissions=True)
return doc

View File

@@ -1,5 +1,7 @@
{% extends "templates/base.html" %}
{% from "www/macros/sidebar.html" import Sidebar %}
{% from "www/macros/common_macro.html" import BatchHearder %}
{% block title %}Discuss{% endblock %}
{% block head_include %}
<meta name="description" content="Courses" />
@@ -7,7 +9,40 @@
{% endblock %}
{% block content %}
{{ Sidebar(course, batch_code) }}
<div class="container">
{{ Sidebar(course_slug, batch_code) }}
<div class="">
<div class="batch-header">
{{ BatchHearder(course.name, member_count) }}
</div>
<div class="message-section">
{{ Messages(messages) }}
</div>
{{ TextArea() }}
</div>
{% endblock %}
{% macro Messages(messages) %}
{% for message in messages %}
<div class="discussion {% if message.is_author %} is-author {% endif %}">
<div class="d-flex justify-content-between">
<div class="font-weight-bold">
{{ message.author_name }}
</div>
<div class="text-muted">
{{ frappe.utils.pretty_date(message.creation) }}
</div>
</div>
<div class="mt-5">
{{ message.message }}
</div>
</div>
{% endfor %}
{% endmacro %}
{% macro TextArea() %}
<form class="msger-inputarea">
<input type="text" class="msger-input" placeholder="Write your message...">
<button type="submit" class="msger-send-btn" data-batch="{{batch.name | urlencode }}">Send</button>
</form>
{% endmacro %}

View File

@@ -0,0 +1,41 @@
frappe.ready(() => {
const assets = [
"/assets/frappe/js/lib/socket.io.min.js",
"/assets/frappe/js/frappe/socketio_client.js"
]
frappe.require(assets, () => {
if (window.dev_server) {
frappe.boot.socketio_port = "9000" //use socketio port shown when bench starts
}
frappe.socketio.init(9000);
console.log(frappe.socketio)
})
frappe.realtime.on("new_lms_message", (data) => {
console.log(data)
})
setTimeout(() => {
window.scrollTo(0, document.body.scrollHeight);
}, 0);
$(".msger-send-btn").click((e) => {
e.preventDefault();
var message = $(".msger-input").val().trim();
if (message) {
frappe.call({
"method": "community.lms.doctype.lms_batch.lms_batch.save_message",
"args": {
"batch": decodeURIComponent($(e.target).attr("data-batch")),
"message": message
},
"callback": (data) => {
$(".msger-input").val("");
frappe.realtime.publish("new_lms_message", {"message":"JJK"})
}
})
}
else {
$(".msger-input").val("");
}
})
})

View File

@@ -1,8 +1,15 @@
import frappe
from community.www.courses.utils import redirect_if_not_a_member
from community.www.courses.utils import redirect_if_not_a_member, get_course, get_batch_members, get_batch
from community.lms.doctype.lms_batch.lms_batch import get_messages
def get_context(context):
context.no_cache = 1
context.course = frappe.form_dict["course"]
context.course_slug = frappe.form_dict["course"]
context.course = get_course(context.course_slug)
context.batch_code = frappe.form_dict["batch"]
redirect_if_not_a_member(context.course, context.batch_code)
redirect_if_not_a_member(context.course_slug, context.batch_code)
context.batch = get_batch(context.batch_code)
context.members = get_batch_members(context.batch.name)
context.member_count = len(context.members)
context.messages = get_messages(context.batch.name)

View File

@@ -1,6 +1,8 @@
{% extends "templates/base.html" %}
{% from "www/macros/sidebar.html" import Sidebar %}
{% from "www/macros/profile.html" import Profile %}
{% from "www/macros/common_macro.html" import BatchHearder %}
{% block title %}Members{% endblock %}
{% block head_include %}
<meta name="description" content="Courses" />
@@ -8,28 +10,29 @@
{% endblock %}
{% block content %}
{{ Sidebar(course, batch_code) }}
{{ Sidebar(course_slug, batch_code) }}
<div class="container">
<div class="border p-3">
<h3>{{course.name}}</h3>
<div class="text-muted">{{member_count}} members</div>
</div>
<div class="mt-5">
{% for member in members %}
<div class="d-flex align-items-center">
<div>
{{ Profile(member.photo, member.full_name, member.abbr, "small") }}
</div>
<div class="mr-5">
{{member.full_name}}
</div>
{% if member.is_mentor %}
<div class="badge badge-success">Mentor</div>
{% endif %}
</div>
<hr>
{% endfor %}
</div>
{{ BatchHearder(course.name, member_count)}}
{{ MembersList(members)}}
</div>
{% endblock %}
{% endblock %}
{% macro MembersList(members) %}
<div class="mt-5">
{% for member in members %}
<div class="d-flex align-items-center">
<div>
{{ Profile(member.photo, member.full_name, member.abbr, "small") }}
</div>
<div class="mr-5">
{{member.full_name}}
</div>
{% if member.is_mentor %}
<div class="badge badge-success">Mentor</div>
{% endif %}
</div>
<hr>
{% endfor %}
</div>
{% endmacro %}

View File

@@ -1,5 +1,5 @@
import frappe
from community.www.courses.utils import redirect_if_not_a_member, get_batch, get_member_with_name, get_course
from community.www.courses.utils import redirect_if_not_a_member, get_batch, get_member_with_name, get_course, get_batch_members
def get_context(context):
context.no_cache = 1
@@ -8,16 +8,5 @@ def get_context(context):
context.batch_code = frappe.form_dict["batch"]
redirect_if_not_a_member(context.course_slug, context.batch_code)
context.batch = get_batch(context.batch_code)
context.members = get_members(context.batch)
context.member_count = len(context.members)
def get_members(batch):
members = []
memberships = frappe.get_all("LMS Batch Membership", {"batch": batch}, ["member", "member_type"])
for membership in memberships:
member = get_member_with_name(membership.member)
if membership.member_type == "Mentor":
member.is_mentor = True
members.append(member)
return members
context.members = get_batch_members(context.batch.name)
context.member_count = len(context.members)

View File

@@ -14,12 +14,12 @@ def get_member_with_name(name):
def get_batch(code):
try:
return frappe.db.get_value("LMS Batch", {"code": code}, "name")
return frappe.db.get_value("LMS Batch", {"code": code}, ["name", "description"], as_dict=True)
except frappe.DoesNotExistError:
return
def is_member_of_batch(batch_code):
membership = frappe.get_all("LMS Batch Membership", {"batch": get_batch(batch_code), "member": get_member_with_email()})
membership = frappe.get_all("LMS Batch Membership", {"batch": get_batch(batch_code).name, "member": get_member_with_email()})
if len(membership):
return True
return False
@@ -31,6 +31,27 @@ def redirect_if_not_a_member(course,batch_code):
def get_course(slug):
try:
return frappe.db.get_value("LMS Course", {"slug": slug}, "name", as_dict=True)
return frappe.get_doc("LMS Course", {"slug": slug})
except frappe.DoesNotExistError:
return
return
def get_instructor(owner):
instructor = frappe._dict()
try:
instructor = frappe.get_doc("Community Member", {"email": owner})
except frappe.DoesNotExistError:
instructor.full_name = owner
instructor.abbr = ("").join([ s[0] for s in owner.split() ])
instructor.course_count = len(frappe.get_all("LMS Course", {"owner": owner}))
return instructor
def get_batch_members(batch):
members = []
memberships = frappe.get_all("LMS Batch Membership", {"batch": batch}, ["member", "member_type"])
for membership in memberships:
member = get_member_with_name(membership.member)
if membership.member_type == "Mentor":
member.is_mentor = True
members.append(member)
return members

View File

@@ -0,0 +1,32 @@
{% macro InstructorsSection(instructor) %}
<h3>Instructor</h3>
<div class="instructor">
<div class="instructor-title">{{instructor.full_name}}</div>
<div class="instructor-subtitle">Created {{instructor.course_count}} courses</div>
</div>
{% endmacro %}
{% macro MentorsSection(mentors, is_mentor) %}
<h3>Mentors</h3>
{% for m in mentors %}
<div class="instructor">
<div class="instructor-title">{{m.full_name}}</div>
<div class="instructor-subtitle">Mentored {{m.batch_count}} batches</div>
</div>
{% endfor %}
{% if not is_mentor %}
<div class="notice">
Interested to become a mentor?
<div><a href="#">Apply Now!</a></div>
</div>
{% endif %}
{% endmacro %}
{% macro BatchHearder(course_name, member_count) %}
<div class="border p-3">
<h3>{{course_name}}</h3>
<div class="text-muted">{{member_count}} members</div>
</div>
{% endmacro %}