Merge pull request #379 from pateljannat/notifications
This commit is contained in:
@@ -85,8 +85,8 @@ web_include_js = ["website.bundle.js", "controls.bundle.js"]
|
|||||||
# Override standard doctype classes
|
# Override standard doctype classes
|
||||||
|
|
||||||
override_doctype_class = {
|
override_doctype_class = {
|
||||||
"User": "lms.overrides.user.CustomUser",
|
"User": "lms.overrides.user.CustomUser",
|
||||||
"Web Template": "lms.overrides.web_template.CustomWebTemplate"
|
"Web Template": "lms.overrides.web_template.CustomWebTemplate"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Document Events
|
# Document Events
|
||||||
@@ -94,7 +94,9 @@ override_doctype_class = {
|
|||||||
# Hook on document methods and events
|
# Hook on document methods and events
|
||||||
|
|
||||||
doc_events = {
|
doc_events = {
|
||||||
|
"Discussion Reply": {
|
||||||
|
"after_insert": "lms.lms.utils.create_notification_log"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Scheduled Tasks
|
# Scheduled Tasks
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from frappe.utils import flt, cint, cstr, getdate, add_months, fmt_money
|
|||||||
from lms.lms.md import markdown_to_html, find_macros
|
from lms.lms.md import markdown_to_html, find_macros
|
||||||
import string
|
import string
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
|
||||||
|
|
||||||
RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+")
|
RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+")
|
||||||
|
|
||||||
@@ -469,3 +470,35 @@ def validate_image(path):
|
|||||||
file.save(ignore_permissions=True)
|
file.save(ignore_permissions=True)
|
||||||
return file.file_url
|
return file.file_url
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def create_notification_log(doc, method):
|
||||||
|
topic = frappe.db.get_value("Discussion Topic", doc.topic,
|
||||||
|
["reference_doctype", "reference_docname", "owner", "title"], as_dict=1)
|
||||||
|
|
||||||
|
if topic.reference_doctype != "Course Lesson":
|
||||||
|
return
|
||||||
|
|
||||||
|
course = frappe.db.get_value("Course Lesson", topic.reference_docname, "course")
|
||||||
|
instructors = frappe.db.get_all("Course Instructor", { "parent": course }, pluck="instructor")
|
||||||
|
|
||||||
|
notification = frappe._dict({
|
||||||
|
"subject": _("New reply on the topic {0}").format(topic.title),
|
||||||
|
"email_content": doc.reply,
|
||||||
|
"document_type": topic.reference_doctype,
|
||||||
|
"document_name": topic.reference_docname,
|
||||||
|
"for_user": topic.owner,
|
||||||
|
"from_user": doc.owner,
|
||||||
|
"type": "Alert"
|
||||||
|
})
|
||||||
|
|
||||||
|
users = []
|
||||||
|
if doc.owner != topic.owner:
|
||||||
|
users.append(topic.owner)
|
||||||
|
|
||||||
|
if doc.owner not in instructors:
|
||||||
|
users += instructors
|
||||||
|
make_notification_logs(notification, users)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
{% set color = get_palette(member.full_name) %}
|
{% set color = get_palette(member.full_name) %}
|
||||||
<span class="avatar {{ avatar_class }}" title="{{ member.full_name }}">
|
<span class="avatar {{ avatar_class }}" title="{{ member.full_name }}">
|
||||||
<a class="button-links" href="{{ get_profile_url(member.username) }}">
|
<a class="button-links" href="{{ get_profile_url(member.username) }}">
|
||||||
{% if member.user_image %}
|
{% if member.user_image %}
|
||||||
<img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}" title="{{ member.full_name }}">
|
<img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}"
|
||||||
</img>
|
title="{{ member.full_name }}">
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="avatar-frame standard-image" title="{{ member.full_name }}"
|
<span class="avatar-frame standard-image" title="{{ member.full_name }}"
|
||||||
style="background-color: var({{color[0]}}); color: var({{color[1]}});">
|
style="background-color: var({{color[0]}}); color: var({{color[1]}});">
|
||||||
{{ frappe.utils.get_abbr(member.full_name) }}
|
{{ frappe.utils.get_abbr(member.full_name) }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
{% set progress = frappe.utils.cint(membership.progress) %}
|
{% set progress = frappe.utils.cint(membership.progress) %}
|
||||||
<div class="common-card-style course-card" data-course="{{ course.name }}">
|
<div class="common-card-style course-card" data-course="{{ course.name }}">
|
||||||
|
|
||||||
<div class="course-image {% if not course.image %}default-image{% endif %}" {% if course.image %}
|
<div class="course-image {% if not course.image %}default-image{% endif %}"
|
||||||
style="background-image: url( {{ course.image | urlencode }} );" {% endif %}>
|
{% if course.image %} style="background-image: url( {{ course.image | urlencode }} );" {% endif %}>
|
||||||
<div class="course-tags">
|
<div class="course-tags">
|
||||||
{% for tag in get_tags(course.name) %}
|
{% for tag in get_tags(course.name) %}
|
||||||
<div class="course-card-pills">{{ tag }}</div>
|
<div class="course-card-pills">{{ tag }}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% if not course.image %}
|
||||||
|
<div class="default-image-text">{{ course.title[0] }}</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if not course.image %}
|
|
||||||
<div class="default-image-text">{{ course.title[0] }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="course-card-content">
|
<div class="course-card-content">
|
||||||
<div class="course-card-meta">
|
<div class="course-card-meta">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
{{ _("Write a review") }}
|
{{ _("Write a review") }}
|
||||||
</span>
|
</span>
|
||||||
{% elif not is_instructor(course.name) and frappe.session.user == "Guest" %}
|
{% elif not is_instructor(course.name) and frappe.session.user == "Guest" %}
|
||||||
<a class="btn btn-secondary btn-s pull-rightm" href="/login?redirect-to=/courses/{{ course.name }}"> {{ _("Login") }} </a>
|
<a class="btn btn-secondary btn-sm pull-right" href="/login?redirect-to=/courses/{{ course.name }}"> {{ _("Login") }} </a>
|
||||||
{% elif not is_instructor(course.name) and not membership and course.status == "Approved" %}
|
{% elif not is_instructor(course.name) and not membership and course.status == "Approved" %}
|
||||||
<div class="btn btn-secondary btn-sm join-batch pull-right" data-course="{{ course.name | urlencode }}"> {{ _("Start Learning") }} </div>
|
<div class="btn btn-secondary btn-sm join-batch pull-right" data-course="{{ course.name | urlencode }}"> {{ _("Start Learning") }} </div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1399,15 +1399,17 @@ pre {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.lms-nav .nav-link {
|
.lms-nav .nav-link {
|
||||||
color: var(--text-muted);
|
padding: var(--padding-sm) 0;
|
||||||
padding: var(--padding-md) 0;
|
|
||||||
margin: 0 var(--margin-md);
|
margin: 0 var(--margin-md);
|
||||||
|
font-size: var(--text-base);
|
||||||
|
color: var(--text-muted);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.lms-nav .nav-link.active {
|
.lms-nav .nav-link.active {
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
border-bottom: 1px solid var(--primary);
|
border-bottom: 1px solid var(--primary-color);
|
||||||
color: var(--text-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 500px) {
|
@media (min-width: 500px) {
|
||||||
@@ -1416,10 +1418,6 @@ pre {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lms-nav .nav-link:hover {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-button {
|
.dashboard-button {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -50px;
|
top: -50px;
|
||||||
@@ -1708,3 +1706,23 @@ li {
|
|||||||
width: 2.75rem;
|
width: 2.75rem;
|
||||||
height: 2.75rem;
|
height: 2.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal .comment-field {
|
||||||
|
height: 150px !important;
|
||||||
|
resize: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-card:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
}
|
||||||
|
|||||||
30
lms/templates/notifications.html
Normal file
30
lms/templates/notifications.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{% if notifications | length %}
|
||||||
|
<div class="common-card-style column-card">
|
||||||
|
{% for notification in notifications %}
|
||||||
|
<div class="notification-card">
|
||||||
|
<div>
|
||||||
|
{% set member = frappe.db.get_value("User", notification.from_user,
|
||||||
|
["username", "full_name", "user_image"], as_dict=1) %}
|
||||||
|
{{ widgets.Avatar(member=member, avatar_class="avatar-medium") }}
|
||||||
|
</div>
|
||||||
|
<div class="ml-2">
|
||||||
|
<div class="medium">
|
||||||
|
{{ notification.subject }} {{ frappe.utils.pretty_date(notification.creation) }}
|
||||||
|
</div>
|
||||||
|
<!-- <div class="timestamp"> {{ frappe.utils.pretty_date(notification.creation) }} </div> -->
|
||||||
|
</div>
|
||||||
|
<a class="stretched-link" href="{{ notification.url }}"></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<div class="empty-state">
|
||||||
|
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
|
||||||
|
<div class="empty-state-text">
|
||||||
|
<div class="empty-state-heading">{{ _("No Notifications") }}</div>
|
||||||
|
<div class="course-meta">{{ _("You don't have any notifications.") }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
|
|
||||||
<!-- Edit Button -->
|
<!-- Edit Button -->
|
||||||
{% if (is_instructor or has_course_moderator_role()) and not lesson.edit_mode %}
|
{% if (is_instructor or has_course_moderator_role()) and not lesson.edit_mode %}
|
||||||
<button class="button is-default button-links ml-auto btn-edit"> {{ _("Edit") }} </button>
|
<button class="button is-default button-links ml-2 btn-edit"> {{ _("Edit") }} </button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ def get_context(context):
|
|||||||
context.chapter = frappe.db.get_value("Chapter Reference", {
|
context.chapter = frappe.db.get_value("Chapter Reference", {
|
||||||
"idx": chapter_index,
|
"idx": chapter_index,
|
||||||
"parent": context.course.name
|
"parent": context.course.name
|
||||||
}, "chapter")
|
}, "chapter")
|
||||||
|
|
||||||
if not chapter_index or not lesson_index:
|
if not chapter_index or not lesson_index:
|
||||||
if context.batch:
|
if context.batch:
|
||||||
|
|||||||
@@ -347,7 +347,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if is_instructor(course.name) or has_course_moderator_role() %}
|
{% if is_instructor(course.name) or has_course_moderator_role() %}
|
||||||
<a class="btn btn-secondary wide-button" href="/courses/{{ course.name }}?edit=1"> {{ _("Edit Course") }} </a>
|
<a class="btn btn-secondary wide-button mt-3" href="/courses/{{ course.name }}?edit=1"> {{ _("Edit Course") }} </a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
|||||||
@@ -17,14 +17,14 @@
|
|||||||
<ul class="nav lms-nav" id="courses-tab">
|
<ul class="nav lms-nav" id="courses-tab">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link active" data-toggle="tab" href="#courses-enrolled">
|
<a class="nav-link active" data-toggle="tab" href="#courses-enrolled">
|
||||||
{{ _("Courses Enrolled") }}
|
{{ _("Enrolled") }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% if show_creators_section %}
|
{% if show_creators_section %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" data-toggle="tab" href="#courses-created">
|
<a class="nav-link" data-toggle="tab" href="#courses-created">
|
||||||
{{ _("Courses Created") }}
|
{{ _("Created") }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -32,10 +32,14 @@
|
|||||||
{% if show_review_section %}
|
{% if show_review_section %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" data-toggle="tab" href="#courses-under-review">
|
<a class="nav-link" data-toggle="tab" href="#courses-under-review">
|
||||||
{{ _("Courses Under Review") }}
|
{{ _("Under Review") }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" data-toggle="tab" href="#notifications">{{ _("Notifications") }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="border-bottom mb-4"></div>
|
<div class="border-bottom mb-4"></div>
|
||||||
@@ -57,6 +61,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="tab-pane" id="notifications" role="tabpanel" aria-labelledby="notifications">
|
||||||
|
{% include "lms/templates/notifications.html" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from lms.lms.utils import has_course_instructor_role, has_course_moderator_role
|
from datetime import datetime
|
||||||
|
from lms.lms.utils import has_course_instructor_role, has_course_moderator_role, get_lesson_index
|
||||||
|
|
||||||
|
|
||||||
def get_context(context):
|
def get_context(context):
|
||||||
@@ -7,3 +8,17 @@ def get_context(context):
|
|||||||
portal_course_creation = frappe.db.get_single_value("LMS Settings", "portal_course_creation")
|
portal_course_creation = frappe.db.get_single_value("LMS Settings", "portal_course_creation")
|
||||||
context.show_creators_section = portal_course_creation == "Anyone" or has_course_instructor_role()
|
context.show_creators_section = portal_course_creation == "Anyone" or has_course_instructor_role()
|
||||||
context.show_review_section = has_course_moderator_role()
|
context.show_review_section = has_course_moderator_role()
|
||||||
|
context.notifications = get_notifications()
|
||||||
|
|
||||||
|
|
||||||
|
def get_notifications():
|
||||||
|
notifications = frappe.get_all("Notification Log", {
|
||||||
|
"document_type": "Course Lesson",
|
||||||
|
"for_user": frappe.session.user
|
||||||
|
}, ["subject", "creation", "from_user", "document_name"])
|
||||||
|
|
||||||
|
for notification in notifications:
|
||||||
|
course = frappe.db.get_value("Course Lesson", notification.document_name, "course")
|
||||||
|
notification.url = "/courses/{0}/learn/{1}".format(course, get_lesson_index(notification.document_name))
|
||||||
|
|
||||||
|
return notifications
|
||||||
|
|||||||
Reference in New Issue
Block a user