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_doctype_class = {
|
||||
"User": "lms.overrides.user.CustomUser",
|
||||
"Web Template": "lms.overrides.web_template.CustomWebTemplate"
|
||||
"User": "lms.overrides.user.CustomUser",
|
||||
"Web Template": "lms.overrides.web_template.CustomWebTemplate"
|
||||
}
|
||||
|
||||
# Document Events
|
||||
@@ -94,7 +94,9 @@ override_doctype_class = {
|
||||
# Hook on document methods and events
|
||||
|
||||
doc_events = {
|
||||
|
||||
"Discussion Reply": {
|
||||
"after_insert": "lms.lms.utils.create_notification_log"
|
||||
}
|
||||
}
|
||||
|
||||
# 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
|
||||
import string
|
||||
from frappe import _
|
||||
from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
|
||||
|
||||
RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+")
|
||||
|
||||
@@ -469,3 +470,35 @@ def validate_image(path):
|
||||
file.save(ignore_permissions=True)
|
||||
return file.file_url
|
||||
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) %}
|
||||
<span class="avatar {{ avatar_class }}" title="{{ member.full_name }}">
|
||||
<a class="button-links" href="{{ get_profile_url(member.username) }}">
|
||||
{% if member.user_image %}
|
||||
<img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}" title="{{ member.full_name }}">
|
||||
</img>
|
||||
{% else %}
|
||||
<span class="avatar-frame standard-image" title="{{ member.full_name }}"
|
||||
style="background-color: var({{color[0]}}); color: var({{color[1]}});">
|
||||
{{ frappe.utils.get_abbr(member.full_name) }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
<a class="button-links" href="{{ get_profile_url(member.username) }}">
|
||||
{% if member.user_image %}
|
||||
<img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}"
|
||||
title="{{ member.full_name }}">
|
||||
{% else %}
|
||||
<span class="avatar-frame standard-image" title="{{ member.full_name }}"
|
||||
style="background-color: var({{color[0]}}); color: var({{color[1]}});">
|
||||
{{ frappe.utils.get_abbr(member.full_name) }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</span>
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
{% set progress = frappe.utils.cint(membership.progress) %}
|
||||
<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 %}
|
||||
style="background-image: url( {{ course.image | urlencode }} );" {% endif %}>
|
||||
<div class="course-tags">
|
||||
{% for tag in get_tags(course.name) %}
|
||||
<div class="course-card-pills">{{ tag }}</div>
|
||||
{% endfor %}
|
||||
<div class="course-image {% if not course.image %}default-image{% endif %}"
|
||||
{% if course.image %} style="background-image: url( {{ course.image | urlencode }} );" {% endif %}>
|
||||
<div class="course-tags">
|
||||
{% for tag in get_tags(course.name) %}
|
||||
<div class="course-card-pills">{{ tag }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if not course.image %}
|
||||
<div class="default-image-text">{{ course.title[0] }}</div>
|
||||
{% endif %}
|
||||
</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-meta">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{{ _("Write a review") }}
|
||||
</span>
|
||||
{% 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" %}
|
||||
<div class="btn btn-secondary btn-sm join-batch pull-right" data-course="{{ course.name | urlencode }}"> {{ _("Start Learning") }} </div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1399,15 +1399,17 @@ pre {
|
||||
}
|
||||
|
||||
.lms-nav .nav-link {
|
||||
color: var(--text-muted);
|
||||
padding: var(--padding-md) 0;
|
||||
padding: var(--padding-sm) 0;
|
||||
margin: 0 var(--margin-md);
|
||||
font-size: var(--text-base);
|
||||
color: var(--text-muted);
|
||||
|
||||
}
|
||||
|
||||
.lms-nav .nav-link.active {
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid var(--primary);
|
||||
color: var(--text-color);
|
||||
font-weight: 500;
|
||||
border-bottom: 1px solid var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
@@ -1416,10 +1418,6 @@ pre {
|
||||
}
|
||||
}
|
||||
|
||||
.lms-nav .nav-link:hover {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.dashboard-button {
|
||||
position: relative;
|
||||
top: -50px;
|
||||
@@ -1708,3 +1706,23 @@ li {
|
||||
width: 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 -->
|
||||
{% 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 %}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ def get_context(context):
|
||||
context.chapter = frappe.db.get_value("Chapter Reference", {
|
||||
"idx": chapter_index,
|
||||
"parent": context.course.name
|
||||
}, "chapter")
|
||||
}, "chapter")
|
||||
|
||||
if not chapter_index or not lesson_index:
|
||||
if context.batch:
|
||||
|
||||
@@ -347,7 +347,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% 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 %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
<ul class="nav lms-nav" id="courses-tab">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" href="#courses-enrolled">
|
||||
{{ _("Courses Enrolled") }}
|
||||
{{ _("Enrolled") }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if show_creators_section %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#courses-created">
|
||||
{{ _("Courses Created") }}
|
||||
{{ _("Created") }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@@ -32,10 +32,14 @@
|
||||
{% if show_review_section %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#courses-under-review">
|
||||
{{ _("Courses Under Review") }}
|
||||
{{ _("Under Review") }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#notifications">{{ _("Notifications") }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="border-bottom mb-4"></div>
|
||||
@@ -57,6 +61,10 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="tab-pane" id="notifications" role="tabpanel" aria-labelledby="notifications">
|
||||
{% include "lms/templates/notifications.html" %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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):
|
||||
@@ -7,3 +8,17 @@ def get_context(context):
|
||||
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_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