@@ -13,7 +13,7 @@ import os, sys, importlib, inspect, json | |||||
from .exceptions import * | from .exceptions import * | ||||
from .utils.jinja import get_jenv, get_template, render_template | from .utils.jinja import get_jenv, get_template, render_template | ||||
__version__ = '7.2.29' | |||||
__version__ = '7.2.30' | |||||
__title__ = "Frappe Framework" | __title__ = "Frappe Framework" | ||||
local = Local() | local = Local() | ||||
@@ -17,6 +17,7 @@ def get_notifications(): | |||||
cache = frappe.cache() | cache = frappe.cache() | ||||
notification_count = {} | notification_count = {} | ||||
for name in groups: | for name in groups: | ||||
count = cache.hget("notification_count:" + name, frappe.session.user) | count = cache.hget("notification_count:" + name, frappe.session.user) | ||||
if count is not None: | if count is not None: | ||||
@@ -110,12 +111,19 @@ def get_notifications_for_doctypes(config, notification_count): | |||||
return open_count_doctype | return open_count_doctype | ||||
def clear_notifications(user="*"): | |||||
if user=="*": | |||||
frappe.cache().delete_keys("notification_count:") | |||||
else: | |||||
# delete count for user | |||||
frappe.cache().hdel_keys("notification_count:", user) | |||||
def clear_notifications(user=None): | |||||
if frappe.flags.in_install: | |||||
return | |||||
config = get_notification_config() | |||||
groups = config.get("for_doctype").keys() + config.get("for_module").keys() | |||||
cache = frappe.cache() | |||||
for name in groups: | |||||
if user: | |||||
cache.hdel("notification_count:" + name, user) | |||||
else: | |||||
cache.delete_key("notification_count:" + name) | |||||
def delete_notification_count_for(doctype): | def delete_notification_count_for(doctype): | ||||
frappe.cache().delete_key("notification_count:" + doctype) | frappe.cache().delete_key("notification_count:" + doctype) | ||||
@@ -128,9 +136,6 @@ def clear_doctype_notifications(doc, method=None, *args, **kwargs): | |||||
delete_notification_count_for(doctype) | delete_notification_count_for(doctype) | ||||
return | return | ||||
if doctype in config.for_module_doctypes: | |||||
delete_notification_count_for(config.for_module_doctypes[doctype]) | |||||
def get_notification_info_for_boot(): | def get_notification_info_for_boot(): | ||||
out = get_notifications() | out = get_notifications() | ||||
config = get_notification_config() | config = get_notification_config() | ||||
@@ -158,7 +163,7 @@ def get_notification_config(): | |||||
config = frappe._dict() | config = frappe._dict() | ||||
for notification_config in frappe.get_hooks().notification_config: | for notification_config in frappe.get_hooks().notification_config: | ||||
nc = frappe.get_attr(notification_config)() | nc = frappe.get_attr(notification_config)() | ||||
for key in ("for_doctype", "for_module", "for_module_doctypes", "for_other"): | |||||
for key in ("for_doctype", "for_module", "for_other"): | |||||
config.setdefault(key, {}) | config.setdefault(key, {}) | ||||
config[key].update(nc.get(key, {})) | config[key].update(nc.get(key, {})) | ||||
return config | return config | ||||
@@ -99,6 +99,7 @@ def get_root_connection(root_login='root', root_password=None): | |||||
return frappe.local.flags.root_connection | return frappe.local.flags.root_connection | ||||
def install_app(name, verbose=False, set_as_patched=True): | def install_app(name, verbose=False, set_as_patched=True): | ||||
frappe.flags.in_install = name | |||||
frappe.clear_cache() | frappe.clear_cache() | ||||
app_hooks = frappe.get_hooks(app_name=name) | app_hooks = frappe.get_hooks(app_name=name) | ||||
installed_apps = frappe.get_installed_apps() | installed_apps = frappe.get_installed_apps() | ||||
@@ -381,6 +381,9 @@ class BaseDocument(object): | |||||
self.set("modified", now()) | self.set("modified", now()) | ||||
self.set("modified_by", frappe.session.user) | self.set("modified_by", frappe.session.user) | ||||
# to trigger email alert on value change | |||||
self.run_method('before_change') | |||||
frappe.db.set_value(self.doctype, self.name, fieldname, value, | frappe.db.set_value(self.doctype, self.name, fieldname, value, | ||||
self.modified, self.modified_by, update_modified=update_modified) | self.modified, self.modified_by, update_modified=update_modified) | ||||
@@ -697,6 +697,7 @@ class Document(BaseDocument): | |||||
def _evaluate_alert(alert): | def _evaluate_alert(alert): | ||||
if not alert.name in self.flags.email_alerts_executed: | if not alert.name in self.flags.email_alerts_executed: | ||||
evaluate_alert(self, alert.name, alert.event) | evaluate_alert(self, alert.name, alert.event) | ||||
self.flags.email_alerts_executed.append(alert.name) | |||||
event_map = { | event_map = { | ||||
"on_update": "Save", | "on_update": "Save", | ||||
@@ -708,6 +709,7 @@ class Document(BaseDocument): | |||||
if not self.flags.in_insert: | if not self.flags.in_insert: | ||||
# value change is not applicable in insert | # value change is not applicable in insert | ||||
event_map['validate'] = 'Value Change' | event_map['validate'] = 'Value Change' | ||||
event_map['before_change'] = 'Value Change' | |||||
for alert in self.flags.email_alerts: | for alert in self.flags.email_alerts: | ||||
event = event_map.get(method, None) | event = event_map.get(method, None) | ||||
@@ -64,6 +64,7 @@ | |||||
"public/js/lib/jquery/jquery.hotkeys.js", | "public/js/lib/jquery/jquery.hotkeys.js", | ||||
"public/js/lib/summernote/summernote.js", | "public/js/lib/summernote/summernote.js", | ||||
"public/js/lib/notify.js", | "public/js/lib/notify.js", | ||||
"public/js/lib/tag-it.min.js", | |||||
"public/js/lib/bootstrap.min.js", | "public/js/lib/bootstrap.min.js", | ||||
"public/js/lib/moment/moment-with-locales.min.js", | "public/js/lib/moment/moment-with-locales.min.js", | ||||
"public/js/lib/moment/moment-timezone-with-data.min.js", | "public/js/lib/moment/moment-timezone-with-data.min.js", | ||||
@@ -62,9 +62,6 @@ frappe.Application = Class.extend({ | |||||
this.show_notes(); | this.show_notes(); | ||||
} | } | ||||
// ask to allow notifications | |||||
frappe.utils.if_notify_permitted(); | |||||
// listen to csrf_update | // listen to csrf_update | ||||
frappe.realtime.on("csrf_generated", function(data) { | frappe.realtime.on("csrf_generated", function(data) { | ||||
// handles the case when a user logs in again from another tab | // handles the case when a user logs in again from another tab | ||||
@@ -518,27 +518,8 @@ frappe.utils = { | |||||
frappe.msgprint("Note: Changing the Page Name will break previous URL to this page."); | frappe.msgprint("Note: Changing the Page Name will break previous URL to this page."); | ||||
}, | }, | ||||
if_notify_permitted: function(callback) { | |||||
if (Notify.needsPermission) { | |||||
Notify.requestPermission(callback); | |||||
} else { | |||||
callback && callback(); | |||||
} | |||||
}, | |||||
notify: function(subject, body, route, onclick) { | notify: function(subject, body, route, onclick) { | ||||
if(!route) route = "messages"; | |||||
if(!onclick) onclick = function() { | |||||
frappe.set_route(route); | |||||
} | |||||
frappe.utils.if_notify_permitted(function() { | |||||
var notify = new Notify(subject, { | |||||
body: body.replace(/<[^>]*>/g, ""), | |||||
notifyClick: onclick | |||||
}); | |||||
notify.show(); | |||||
}); | |||||
console.log('push notifications are evil and deprecated'); | |||||
}, | }, | ||||
set_title: function(title) { | set_title: function(title) { | ||||
@@ -1,194 +0,0 @@ | |||||
/* | |||||
* Author: Alex Gibson | |||||
* https://github.com/alexgibson/notify.js | |||||
* License: MIT license | |||||
*/ | |||||
(function(global, factory) { | |||||
if (typeof define === 'function' && define.amd) { | |||||
// AMD environment | |||||
define(function() { | |||||
return factory(global, global.document); | |||||
}); | |||||
} else if (typeof module !== 'undefined' && module.exports) { | |||||
// CommonJS environment | |||||
module.exports = factory(global, global.document); | |||||
} else { | |||||
// Browser environment | |||||
global.Notify = factory(global, global.document); | |||||
} | |||||
} (typeof window !== 'undefined' ? window : this, function (w, d) { | |||||
'use strict'; | |||||
function isFunction (item) { | |||||
return typeof item === 'function'; | |||||
} | |||||
function Notify(title, options) { | |||||
if (typeof title !== 'string') { | |||||
throw new Error('Notify(): first arg (title) must be a string.'); | |||||
} | |||||
this.title = title; | |||||
this.options = { | |||||
icon: '', | |||||
body: '', | |||||
tag: '', | |||||
notifyShow: null, | |||||
notifyClose: null, | |||||
notifyClick: null, | |||||
notifyError: null, | |||||
timeout: null | |||||
}; | |||||
this.permission = null; | |||||
if (!Notify.isSupported) { | |||||
return; | |||||
} | |||||
//User defined options for notification content | |||||
if (typeof options === 'object') { | |||||
for (var i in options) { | |||||
if (options.hasOwnProperty(i)) { | |||||
this.options[i] = options[i]; | |||||
} | |||||
} | |||||
//callback when notification is displayed | |||||
if (isFunction(this.options.notifyShow)) { | |||||
this.onShowCallback = this.options.notifyShow; | |||||
} | |||||
//callback when notification is closed | |||||
if (isFunction(this.options.notifyClose)) { | |||||
this.onCloseCallback = this.options.notifyClose; | |||||
} | |||||
//callback when notification is clicked | |||||
if (isFunction(this.options.notifyClick)) { | |||||
this.onClickCallback = this.options.notifyClick; | |||||
} | |||||
//callback when notification throws error | |||||
if (isFunction(this.options.notifyError)) { | |||||
this.onErrorCallback = this.options.notifyError; | |||||
} | |||||
} | |||||
} | |||||
// true if the browser supports HTML5 Notification | |||||
Notify.isSupported = 'Notification' in w; | |||||
// true if the permission is not granted | |||||
Notify.needsPermission = !(Notify.isSupported && Notification.permission === 'granted'); | |||||
// returns current permission level ('granted', 'default', 'denied' or null) | |||||
Notify.permissionLevel = (Notify.isSupported ? Notification.permission : null); | |||||
// asks the user for permission to display notifications. Then calls the callback functions is supplied. | |||||
Notify.requestPermission = function (onPermissionGrantedCallback, onPermissionDeniedCallback) { | |||||
if (!Notify.isSupported) { | |||||
return; | |||||
} | |||||
w.Notification.requestPermission(function (perm) { | |||||
switch (perm) { | |||||
case 'granted': | |||||
Notify.needsPermission = false; | |||||
if (isFunction(onPermissionGrantedCallback)) { | |||||
onPermissionGrantedCallback(); | |||||
} | |||||
break; | |||||
case 'denied': | |||||
if (isFunction(onPermissionDeniedCallback)) { | |||||
onPermissionDeniedCallback(); | |||||
} | |||||
break; | |||||
} | |||||
}); | |||||
}; | |||||
Notify.prototype.show = function () { | |||||
if (!Notify.isSupported) { | |||||
return; | |||||
} | |||||
this.myNotify = new Notification(this.title, { | |||||
'body': this.options.body, | |||||
'tag' : this.options.tag, | |||||
'icon' : this.options.icon | |||||
}); | |||||
if (this.options.timeout && !isNaN(this.options.timeout)) { | |||||
setTimeout(this.close.bind(this), this.options.timeout * 1000); | |||||
} | |||||
this.myNotify.addEventListener('show', this, false); | |||||
this.myNotify.addEventListener('error', this, false); | |||||
this.myNotify.addEventListener('close', this, false); | |||||
this.myNotify.addEventListener('click', this, false); | |||||
}; | |||||
Notify.prototype.onShowNotification = function (e) { | |||||
if (this.onShowCallback) { | |||||
this.onShowCallback(e); | |||||
} | |||||
}; | |||||
Notify.prototype.onCloseNotification = function (e) { | |||||
if (this.onCloseCallback) { | |||||
this.onCloseCallback(e); | |||||
} | |||||
this.destroy(); | |||||
}; | |||||
Notify.prototype.onClickNotification = function (e) { | |||||
if (this.onClickCallback) { | |||||
this.onClickCallback(e); | |||||
} | |||||
}; | |||||
Notify.prototype.onErrorNotification = function (e) { | |||||
if (this.onErrorCallback) { | |||||
this.onErrorCallback(e); | |||||
} | |||||
this.destroy(); | |||||
}; | |||||
Notify.prototype.destroy = function () { | |||||
this.myNotify.removeEventListener('show', this, false); | |||||
this.myNotify.removeEventListener('error', this, false); | |||||
this.myNotify.removeEventListener('close', this, false); | |||||
this.myNotify.removeEventListener('click', this, false); | |||||
}; | |||||
Notify.prototype.close = function () { | |||||
this.myNotify.close(); | |||||
}; | |||||
Notify.prototype.handleEvent = function (e) { | |||||
switch (e.type) { | |||||
case 'show': | |||||
this.onShowNotification(e); | |||||
break; | |||||
case 'close': | |||||
this.onCloseNotification(e); | |||||
break; | |||||
case 'click': | |||||
this.onClickNotification(e); | |||||
break; | |||||
case 'error': | |||||
this.onErrorNotification(e); | |||||
break; | |||||
} | |||||
}; | |||||
return Notify; | |||||
})); |
@@ -18,6 +18,7 @@ import frappe.translate | |||||
from frappe.utils.change_log import get_change_log | from frappe.utils.change_log import get_change_log | ||||
import redis | import redis | ||||
from urllib import unquote | from urllib import unquote | ||||
from frappe.desk.notifications import clear_notifications | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def clear(user=None): | def clear(user=None): | ||||
@@ -45,6 +46,8 @@ def clear_cache(user=None): | |||||
clear_global_cache() | clear_global_cache() | ||||
frappe.defaults.clear_cache() | frappe.defaults.clear_cache() | ||||
clear_notifications(user) | |||||
def clear_global_cache(): | def clear_global_cache(): | ||||
frappe.model.meta.clear_cache() | frappe.model.meta.clear_cache() | ||||
frappe.cache().delete_value(["app_hooks", "installed_apps", | frappe.cache().delete_value(["app_hooks", "installed_apps", | ||||