@@ -13,7 +13,7 @@ import os, sys, importlib, inspect, json | |||
from .exceptions import * | |||
from .utils.jinja import get_jenv, get_template, render_template | |||
__version__ = '7.2.29' | |||
__version__ = '7.2.30' | |||
__title__ = "Frappe Framework" | |||
local = Local() | |||
@@ -17,6 +17,7 @@ def get_notifications(): | |||
cache = frappe.cache() | |||
notification_count = {} | |||
for name in groups: | |||
count = cache.hget("notification_count:" + name, frappe.session.user) | |||
if count is not None: | |||
@@ -110,12 +111,19 @@ def get_notifications_for_doctypes(config, notification_count): | |||
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): | |||
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) | |||
return | |||
if doctype in config.for_module_doctypes: | |||
delete_notification_count_for(config.for_module_doctypes[doctype]) | |||
def get_notification_info_for_boot(): | |||
out = get_notifications() | |||
config = get_notification_config() | |||
@@ -158,7 +163,7 @@ def get_notification_config(): | |||
config = frappe._dict() | |||
for notification_config in frappe.get_hooks().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[key].update(nc.get(key, {})) | |||
return config | |||
@@ -99,6 +99,7 @@ def get_root_connection(root_login='root', root_password=None): | |||
return frappe.local.flags.root_connection | |||
def install_app(name, verbose=False, set_as_patched=True): | |||
frappe.flags.in_install = name | |||
frappe.clear_cache() | |||
app_hooks = frappe.get_hooks(app_name=name) | |||
installed_apps = frappe.get_installed_apps() | |||
@@ -381,6 +381,9 @@ class BaseDocument(object): | |||
self.set("modified", now()) | |||
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, | |||
self.modified, self.modified_by, update_modified=update_modified) | |||
@@ -697,6 +697,7 @@ class Document(BaseDocument): | |||
def _evaluate_alert(alert): | |||
if not alert.name in self.flags.email_alerts_executed: | |||
evaluate_alert(self, alert.name, alert.event) | |||
self.flags.email_alerts_executed.append(alert.name) | |||
event_map = { | |||
"on_update": "Save", | |||
@@ -708,6 +709,7 @@ class Document(BaseDocument): | |||
if not self.flags.in_insert: | |||
# value change is not applicable in insert | |||
event_map['validate'] = 'Value Change' | |||
event_map['before_change'] = 'Value Change' | |||
for alert in self.flags.email_alerts: | |||
event = event_map.get(method, None) | |||
@@ -64,6 +64,7 @@ | |||
"public/js/lib/jquery/jquery.hotkeys.js", | |||
"public/js/lib/summernote/summernote.js", | |||
"public/js/lib/notify.js", | |||
"public/js/lib/tag-it.min.js", | |||
"public/js/lib/bootstrap.min.js", | |||
"public/js/lib/moment/moment-with-locales.min.js", | |||
"public/js/lib/moment/moment-timezone-with-data.min.js", | |||
@@ -62,9 +62,6 @@ frappe.Application = Class.extend({ | |||
this.show_notes(); | |||
} | |||
// ask to allow notifications | |||
frappe.utils.if_notify_permitted(); | |||
// listen to csrf_update | |||
frappe.realtime.on("csrf_generated", function(data) { | |||
// 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."); | |||
}, | |||
if_notify_permitted: function(callback) { | |||
if (Notify.needsPermission) { | |||
Notify.requestPermission(callback); | |||
} else { | |||
callback && callback(); | |||
} | |||
}, | |||
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) { | |||
@@ -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 | |||
import redis | |||
from urllib import unquote | |||
from frappe.desk.notifications import clear_notifications | |||
@frappe.whitelist() | |||
def clear(user=None): | |||
@@ -45,6 +46,8 @@ def clear_cache(user=None): | |||
clear_global_cache() | |||
frappe.defaults.clear_cache() | |||
clear_notifications(user) | |||
def clear_global_cache(): | |||
frappe.model.meta.clear_cache() | |||
frappe.cache().delete_value(["app_hooks", "installed_apps", | |||