diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js
index 6fc487f03f..a924522052 100644
--- a/frappe/public/js/frappe/list/base_list.js
+++ b/frappe/public/js/frappe/list/base_list.js
@@ -335,11 +335,11 @@ frappe.views.BaseList = class BaseList {
freeze_message: this.freeze_message || (__('Loading') + '...')
}).then(r => {
// render
- this.freeze(false);
this.prepare_data(r);
this.toggle_result_area();
this.before_render();
this.render();
+ this.freeze(false);
});
}
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 704d134c0c..2e017e80da 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -61,7 +61,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
// build menu items
this.menu_items = this.menu_items.concat(this.get_menu_items());
- this.freeze_on_refresh = true;
this.actions_menu_items = this.get_actions_menu_items();
this.patch_refresh_and_load_lib();
@@ -150,6 +149,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
this.settings.onload && this.settings.onload(this);
}
+ setup_freeze_area() {
+ this.$freeze =
+ $(`
${__('Loading')}...
`)
+ .hide();
+ this.$result.append(this.$freeze);
+ }
+
setup_footnote_area() {
const match_rules_list = frappe.perm.get_match_rules(this.doctype);
@@ -227,6 +233,12 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
`;
}
+ freeze(show) {
+ this.$result.find('.list-header-meta').html(__('Refreshing') + '...');
+ this.$result.find('.checkbox-actions').toggle(show);
+ this.$result.find('.list-header-subject').toggle(!show);
+ }
+
get_args() {
const args = super.get_args();
@@ -263,11 +275,15 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
render() {
if (this.data.length > 0) {
- const html = `
- ${this.get_header_html()}
- ${this.data.map(doc => this.get_list_row_html(doc)).join('')}
- `;
- this.$result.html(html);
+ this.$result.find('.list-row-container').remove();
+ if (this.$result.find('.list-row-head').length === 0) {
+ // append header once
+ this.$result.prepend(this.get_header_html());
+ }
+ // append rows
+ this.$result.append(
+ this.data.map(doc => this.get_list_row_html(doc)).join('')
+ );
}
this.render_count();
this.render_tags();
diff --git a/frappe/public/js/frappe/model/user_settings.js b/frappe/public/js/frappe/model/user_settings.js
index 932b6a359c..30bd98008d 100644
--- a/frappe/public/js/frappe/model/user_settings.js
+++ b/frappe/public/js/frappe/model/user_settings.js
@@ -6,16 +6,23 @@ $.extend(frappe.model.user_settings, {
.then(r => JSON.parse(r.message || '{}'));
},
save: function(doctype, key, value) {
- var user_settings = frappe.model.user_settings[doctype] || {};
+ const old_user_settings = frappe.model.user_settings[doctype] || {};
+ const new_user_settings = $.extend(true, {}, old_user_settings); // deep copy
if ($.isPlainObject(value)) {
- user_settings[key] = user_settings[key] || {};
- $.extend(user_settings[key], value);
+ new_user_settings[key] = new_user_settings[key] || {};
+ $.extend(new_user_settings[key], value);
} else {
- user_settings[key] = value;
+ new_user_settings[key] = value;
}
- return this.update(doctype, user_settings);
+ const a = JSON.stringify(old_user_settings);
+ const b = JSON.stringify(new_user_settings);
+ if (a !== b) {
+ // update if changed
+ return this.update(doctype, new_user_settings);
+ }
+ return Promise.resolve();
},
remove: function(doctype, key) {
var user_settings = frappe.model.user_settings[doctype] || {};
@@ -33,7 +40,7 @@ $.extend(frappe.model.user_settings, {
callback: function(r) {
frappe.model.user_settings[doctype] = r.message;
}
- })
+ });
}
});
diff --git a/frappe/templates/emails/password_reset.html b/frappe/templates/emails/password_reset.html
index 5ddacb2652..d9e38e38f2 100644
--- a/frappe/templates/emails/password_reset.html
+++ b/frappe/templates/emails/password_reset.html
@@ -1,7 +1,7 @@
{{_("Dear")}} {{ first_name }}{% if last_name %} {{ last_name}}{% endif %},
{{_("Please click on the following link to set your new password")}}:
-Reset your password
+{{_("Reset your password")}}
{{_("Thank you")}},
{{ user_fullname }}
-
\ No newline at end of file
+
diff --git a/frappe/tests/ui/test_oauth20.py b/frappe/tests/ui/test_oauth20.py
index 1cc864416e..d21aba2efd 100644
--- a/frappe/tests/ui/test_oauth20.py
+++ b/frappe/tests/ui/test_oauth20.py
@@ -23,6 +23,9 @@ class TestOAuth20(unittest.TestCase):
frappe_login_key.base_url = "http://localhost:8000"
frappe_login_key.save()
+ def test_invalid_login(self):
+ self.assertFalse(check_valid_openid_response())
+
def test_login_using_authorization_code(self):
# Go to Authorize url
@@ -75,6 +78,15 @@ class TestOAuth20(unittest.TestCase):
self.assertTrue(bearer_token.get("refresh_token"))
self.assertTrue(bearer_token.get("scope"))
self.assertTrue(bearer_token.get("token_type") == "Bearer")
+ self.assertTrue(check_valid_openid_response(bearer_token.get("access_token")))
+
+ # Revoke Token
+ revoke_token_response = requests.post(frappe.get_site_config().host_name + "/api/method/frappe.integrations.oauth2.revoke_token",
+ data="token=" + bearer_token.get("access_token"))
+ self.assertTrue(revoke_token_response.status_code == 200)
+
+ # Check revoked token
+ self.assertFalse(check_valid_openid_response(bearer_token.get("access_token")))
def test_login_using_implicit_token(self):
@@ -118,6 +130,21 @@ class TestOAuth20(unittest.TestCase):
self.assertTrue(response_url.get("expires_in"))
self.assertTrue(response_url.get("scope"))
self.assertTrue(response_url.get("token_type"))
+ self.assertTrue(check_valid_openid_response(response_url.get("access_token")))
def tearDown(self):
self.driver.close()
+
+def check_valid_openid_response(access_token=None):
+ # Returns True for valid response
+
+ # Use token in header
+ headers = {}
+ if access_token:
+ headers["Authorization"] = 'Bearer' + access_token
+
+ # check openid for email test@example.com
+ openid_response = requests.get(frappe.get_site_config().host_name +
+ "/api/method/frappe.integrations.oauth2.openid_profile", headers=headers)
+
+ return True if openid_response.status_code == 200 else False