From 9484ea1af3141964bea7534a38b944271e082e68 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 9 Mar 2017 18:09:42 +0530 Subject: [PATCH 1/8] [fix] clear notifications for user --- frappe/desk/notifications.py | 22 ++++++++++++---------- frappe/sessions.py | 3 +++ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index d01de3fdeb..dc2bdf678c 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -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: @@ -108,12 +109,16 @@ 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): + 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) @@ -126,9 +131,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() @@ -156,7 +158,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 diff --git a/frappe/sessions.py b/frappe/sessions.py index 898ced3e0c..5fa2ba27dc 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -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", From 95e6306cc0361abd6deeb7e0897c3e4ffc458730 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 9 Mar 2017 18:18:13 +0530 Subject: [PATCH 2/8] [minor] fix for in_install --- frappe/desk/notifications.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index dc2bdf678c..8f94c1c70e 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -110,6 +110,9 @@ def get_notifications_for_doctypes(config, notification_count): return open_count_doctype 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() From 89185c4efeb5dc5cbfd14f8a387f6e05ba1d46ae Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 9 Mar 2017 18:27:41 +0530 Subject: [PATCH 3/8] [minor] fix for in_install --- frappe/installer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/installer.py b/frappe/installer.py index 8da3af524d..e032595528 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -97,6 +97,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() From 5cd886b8cfe94471f095d3bc981429e8d8898cac Mon Sep 17 00:00:00 2001 From: mbauskar Date: Thu, 9 Mar 2017 17:53:04 +0530 Subject: [PATCH 4/8] [hook] added before_change hook to trigger email_alerts before db_set --- frappe/model/base_document.py | 3 +++ frappe/model/document.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index ae01396150..98bd849637 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -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) diff --git a/frappe/model/document.py b/frappe/model/document.py index 7dfb880926..e633897813 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -681,6 +681,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", @@ -692,6 +693,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) From 82254cbe68f347ca73ec82b18e55afd1d0093d15 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 10 Mar 2017 11:47:18 +0530 Subject: [PATCH 5/8] [minor] remove push notifications --- frappe/public/build.json | 1 - frappe/public/js/frappe/misc/utils.js | 83 ++++++++--- frappe/public/js/lib/notify.js | 194 -------------------------- 3 files changed, 63 insertions(+), 215 deletions(-) delete mode 100644 frappe/public/js/lib/notify.js diff --git a/frappe/public/build.json b/frappe/public/build.json index 9f31095371..78afbbdf9a 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -59,7 +59,6 @@ "public/js/lib/jquery/jquery-ui.min.js", "public/js/lib/Sortable.min.js", "public/js/lib/tag-it.min.js", - "public/js/lib/notify.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", diff --git a/frappe/public/js/frappe/misc/utils.js b/frappe/public/js/frappe/misc/utils.js index a785a6eb99..88eaed2a14 100644 --- a/frappe/public/js/frappe/misc/utils.js +++ b/frappe/public/js/frappe/misc/utils.js @@ -185,20 +185,21 @@ frappe.utils = { me.intro_area = null; } }, - set_footnote: function(me, wrapper, txt) { - if(!me.footnote_area) { - me.footnote_area = $('
') + set_footnote: function(footnote_area, wrapper, txt) { + if(!footnote_area) { + footnote_area = $('
') .appendTo(wrapper); } if(txt) { - if(txt.search(/

/)==-1) txt = '

' + txt + '

'; - me.footnote_area.html(txt); + if(!txt.includes('

')) + txt = '

' + txt + '

'; + footnote_area.html(txt); } else { - me.footnote_area.remove(); - me.footnote_area = null; + footnote_area.remove(); + footnote_area = null; } - return me.footnote_area; + return footnote_area; }, get_args_dict_from_url: function(txt) { var args = {}; @@ -526,18 +527,7 @@ frappe.utils = { }, 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) { @@ -593,3 +583,56 @@ frappe.utils = { return email_list; } }; + +// String.prototype.includes polyfill +// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/includes +if (!String.prototype.includes) { + String.prototype.includes = function(search, start) { + 'use strict'; + if (typeof start !== 'number') { + start = 0; + } + if (start + search.length > this.length) { + return false; + } else { + return this.indexOf(search, start) !== -1; + } + }; +} +// Array.prototype.includes polyfill +// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/includes +if (!Array.prototype.includes) { + Object.defineProperty(Array.prototype, 'includes', { + value: function(searchElement, fromIndex) { + if (this == null) { + throw new TypeError('"this" is null or not defined'); + } + var o = Object(this); + var len = o.length >>> 0; + if (len === 0) { + return false; + } + var n = fromIndex | 0; + var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); + while (k < len) { + if (o[k] === searchElement) { + return true; + } + k++; + } + return false; + } + }); +} +// Array de duplicate +if (!Array.prototype.uniqBy) { + Object.defineProperty(Array.prototype, 'uniqBy', { + value: function (key) { + var seen = {}; + return this.filter(function (item) { + var k = key(item); + return seen.hasOwnProperty(k) ? false : (seen[k] = true); + }) + } + }) +} \ No newline at end of file diff --git a/frappe/public/js/lib/notify.js b/frappe/public/js/lib/notify.js deleted file mode 100644 index a45c6d3822..0000000000 --- a/frappe/public/js/lib/notify.js +++ /dev/null @@ -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; - -})); From 3716564b8f0391bde2b3ef0d26b55e5921fafd33 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 10 Mar 2017 12:22:08 +0530 Subject: [PATCH 6/8] [minor] remove notifications --- frappe/public/js/frappe/desk.js | 3 --- frappe/public/js/frappe/misc/utils.js | 8 -------- 2 files changed, 11 deletions(-) diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 26ca555e49..bf56407ae5 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.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 diff --git a/frappe/public/js/frappe/misc/utils.js b/frappe/public/js/frappe/misc/utils.js index 88eaed2a14..6144f8e9e8 100644 --- a/frappe/public/js/frappe/misc/utils.js +++ b/frappe/public/js/frappe/misc/utils.js @@ -518,14 +518,6 @@ 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) { console.log('push notifications are evil and deprecated'); }, From cd3257d2d3d1ffadce0cf781427edeb180ab2a85 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 10 Mar 2017 15:35:05 +0530 Subject: [PATCH 7/8] [minor] rever set_footnote function --- frappe/public/js/frappe/misc/utils.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/frappe/public/js/frappe/misc/utils.js b/frappe/public/js/frappe/misc/utils.js index 6144f8e9e8..2de69a0b32 100644 --- a/frappe/public/js/frappe/misc/utils.js +++ b/frappe/public/js/frappe/misc/utils.js @@ -185,21 +185,20 @@ frappe.utils = { me.intro_area = null; } }, - set_footnote: function(footnote_area, wrapper, txt) { - if(!footnote_area) { - footnote_area = $('
') + set_footnote: function(me, wrapper, txt) { + if(!me.footnote_area) { + me.footnote_area = $('
') .appendTo(wrapper); } if(txt) { - if(!txt.includes('

')) - txt = '

' + txt + '

'; - footnote_area.html(txt); + if(txt.search(/

/)==-1) txt = '

' + txt + '

'; + me.footnote_area.html(txt); } else { - footnote_area.remove(); - footnote_area = null; + me.footnote_area.remove(); + me.footnote_area = null; } - return footnote_area; + return me.footnote_area; }, get_args_dict_from_url: function(txt) { var args = {}; From 566eca110acf154a1888c8f29ec6027a94fb1236 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 15 Mar 2017 11:56:00 +0600 Subject: [PATCH 8/8] bumped to version 7.2.30 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 62dd4011df..d76cbf7f31 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -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()