diff --git a/frappe/__init__.py b/frappe/__init__.py
index 5cb0df79ca..23ab95b46c 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -23,7 +23,7 @@ if sys.version[0] == '2':
reload(sys)
sys.setdefaultencoding("utf-8")
-__version__ = '11.1.31'
+__version__ = '11.1.32'
__title__ = "Frappe Framework"
local = Local()
diff --git a/frappe/database.py b/frappe/database.py
index 9323aa9518..3a2e0ce160 100644
--- a/frappe/database.py
+++ b/frappe/database.py
@@ -987,12 +987,26 @@ class Database:
if values:
query = self._cursor.mogrify(query, values)
if query.strip().lower().split()[0] in ('insert', 'delete', 'update', 'alter'):
- # ([`\"']?) Captures ', " or ` at the begining of the table name (if provided)
+ # single_word_regex is designed to match following patterns
+ # `tabXxx`, tabXxx and "tabXxx"
+
+ # multi_word_regex is designed to match following patterns
+ # `tabXxx Xxx` and "tabXxx Xxx"
+
+ # ([`"]?) Captures " or ` at the begining of the table name (if provided)
+ # \1 matches the first captured group (quote character) at the end of the table name
+ # multi word table name must have surrounding quotes.
+
# (tab([A-Z]\w+)( [A-Z]\w+)*) Captures table names that start with "tab"
# and are continued with multiple words that start with a captital letter
# e.g. 'tabXxx' or 'tabXxx Xxx' or 'tabXxx Xxx Xxx' and so on
- # \1 matches the first captured group (quote character) at the end of the table name
- tables = [groups[1] for groups in re.findall(r'([`"\']?)(tab([A-Z]\w+)( [A-Z]\w+)*)\1', query)]
+
+ single_word_regex = r'([`"]?)(tab([A-Z]\w+))\1'
+ multi_word_regex = r'([`"])(tab([A-Z]\w+)( [A-Z]\w+)+)\1'
+ tables = []
+ for regex in (single_word_regex, multi_word_regex):
+ tables += [groups[1] for groups in re.findall(regex, query)]
+
if frappe.flags.touched_tables is None:
frappe.flags.touched_tables = set()
frappe.flags.touched_tables.update(tables)
diff --git a/frappe/email/__init__.py b/frappe/email/__init__.py
index 53c6140d04..a48dbec195 100644
--- a/frappe/email/__init__.py
+++ b/frappe/email/__init__.py
@@ -23,7 +23,7 @@ def get_contact_list(txt, page_length=20):
out = frappe.db.sql("""select email_id as value,
concat(first_name, ifnull(concat(' ',last_name), '' )) as description
from tabContact
- where name like %(txt)s
+ where name like %(txt)s or email_id like %(txt)s
%(condition)s
limit %(page_length)s
""", {'txt': "%%%s%%" % frappe.db.escape(txt),
diff --git a/frappe/email/doctype/email_unsubscribe/email_unsubscribe.json b/frappe/email/doctype/email_unsubscribe/email_unsubscribe.json
index 3624a55641..b24359f82f 100644
--- a/frappe/email/doctype/email_unsubscribe/email_unsubscribe.json
+++ b/frappe/email/doctype/email_unsubscribe/email_unsubscribe.json
@@ -3,7 +3,7 @@
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
- "creation": "2015-03-18 09:41:20.216319",
+ "creation": "2015-03-18 09:41:20.216320",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
@@ -35,7 +35,7 @@
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
- "search_index": 0,
+ "search_index": 1,
"set_only_once": 0,
"unique": 0
},
@@ -172,4 +172,4 @@
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
-}
\ No newline at end of file
+}
diff --git a/frappe/email/queue.py b/frappe/email/queue.py
index e62e7ca674..d0893d66d9 100755
--- a/frappe/email/queue.py
+++ b/frappe/email/queue.py
@@ -78,19 +78,35 @@ def send(recipients=None, sender=None, subject=None, message=None, text_content=
except HTMLParser.HTMLParseError:
text_content = "See html attachment"
- if reference_doctype and reference_name:
- unsubscribed = [d.email for d in frappe.db.get_all("Email Unsubscribe", "email",
- {"reference_doctype": reference_doctype, "reference_name": reference_name})]
+ recipients = list(set(recipients))
+ cc = list(set(cc))
- unsubscribed += [d.email for d in frappe.db.get_all("Email Unsubscribe", "email",
- {"global_unsubscribe": 1})]
- else:
- unsubscribed = []
+ all_ids = recipients + cc
- recipients = [r for r in list(set(recipients)) if r and r not in unsubscribed]
+ unsubscribed = frappe.db.sql_list('''
+ SELECT
+ distinct email
+ from
+ `tabEmail Unsubscribe`
+ where
+ email in %(all_ids)s
+ and (
+ (
+ reference_doctype = %(reference_doctype)s
+ and reference_name = %(reference_name)s
+ )
+ or global_unsubscribe = 1
+ )
+ ''', {
+ 'all_ids': all_ids,
+ 'reference_doctype': reference_doctype,
+ 'reference_name': reference_name,
+ })
+
+ recipients = [r for r in recipients if r and r not in unsubscribed]
if cc:
- cc = [r for r in list(set(cc)) if r and r not in unsubscribed]
+ cc = [r for r in cc if r and r not in unsubscribed]
if not recipients and not cc:
# Recipients may have been unsubscribed, exit quietly
diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py
index e009dd354c..a7ff19a2cd 100644
--- a/frappe/model/db_query.py
+++ b/frappe/model/db_query.py
@@ -381,7 +381,7 @@ class DatabaseQuery(object):
elif f.operator.lower() in ('in', 'not in'):
values = f.value or ''
- if not isinstance(values, (list, tuple)):
+ if isinstance(values, frappe.string_types):
values = values.split(",")
fallback = "''"
diff --git a/frappe/patches/v11_0/apply_customization_to_custom_doctype.py b/frappe/patches/v11_0/apply_customization_to_custom_doctype.py
index d7c10a903d..0855616711 100644
--- a/frappe/patches/v11_0/apply_customization_to_custom_doctype.py
+++ b/frappe/patches/v11_0/apply_customization_to_custom_doctype.py
@@ -39,13 +39,17 @@ def execute():
df.set(ps.property, value)
for cf in custom_fields:
- df = frappe.new_doc('DocField', meta, 'fields')
cf.pop('parenttype')
cf.pop('parentfield')
cf.pop('parent')
cf.pop('name')
- df.update(cf)
- meta.fields.append(df)
+ field = meta.get_field(cf.fieldname)
+ if field:
+ field.update(cf)
+ else:
+ df = frappe.new_doc('DocField', meta, 'fields')
+ df.update(cf)
+ meta.fields.append(df)
frappe.db.sql('DELETE FROM `tabCustom Field` WHERE name=%s', cf.name)
meta.save()
diff --git a/frappe/public/js/frappe/form/footer/timeline.js b/frappe/public/js/frappe/form/footer/timeline.js
index f6c365495f..511859582d 100644
--- a/frappe/public/js/frappe/form/footer/timeline.js
+++ b/frappe/public/js/frappe/form/footer/timeline.js
@@ -376,7 +376,6 @@ frappe.ui.form.Timeline = class Timeline {
@abc with the below line of code.
*/
- c.content_html = c.content_html.replace(/(<[a][^>]*>)/g, "");
// bold the @mentions
c.content_html = c.content_html.replace(/(@[^\s@]*)@[^\s@|<]*/g, "$1");
}
diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js
index 3438c59a1f..4e569033ba 100644
--- a/frappe/public/js/frappe/form/multi_select_dialog.js
+++ b/frappe/public/js/frappe/form/multi_select_dialog.js
@@ -59,6 +59,7 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
{ fieldtype: "Button", fieldname: "more_btn", label: __("More"),
click: function(){
me.start += 20;
+ frappe.flags.auto_scroll = true;
me.get_results();
}
}
@@ -117,10 +118,12 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
});
this.$parent.find('.input-with-feedback').on('change', (e) => {
+ frappe.flags.auto_scroll = false;
this.get_results();
});
this.$parent.find('[data-fieldname="date_range"]').on('blur', (e) => {
+ frappe.flags.auto_scroll = false;
this.get_results();
});
@@ -128,6 +131,7 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
var $this = $(this);
clearTimeout($this.data('timeout'));
$this.data('timeout', setTimeout(function() {
+ frappe.flags.auto_scroll = false;
me.get_results();
}, 300));
});
@@ -176,11 +180,17 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
var me = this;
var more_btn = me.dialog.fields_dict.more_btn.$wrapper;
+
+ // Make empty result set if filter is set
+ if (!frappe.flags.auto_scroll) {
+ this.$results.empty();
+ }
+
if(results.length === 0) {
this.$results.empty();
more_btn.hide();
return;
- } else {
+ } else if(more) {
more_btn.show();
}
@@ -188,7 +198,9 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
me.$results.append(me.make_list_row(result));
});
- this.$results.animate({scrollTop: me.$results.prop('scrollHeight')}, 500);
+ if (frappe.flags.auto_scroll) {
+ this.$results.animate({scrollTop: me.$results.prop('scrollHeight')}, 500);
+ }
},
get_results: function() {
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 841f62d6a3..b5b94339e1 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -642,7 +642,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
.includes(user) ? '' : 'bold';
let subject_html = `
-
+
cstr($(check).data().name));
+ .map(check => cstr(unescape($(check).data().name)));
if (only_docnames) return docnames;
diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py
index e3c1a62a78..551b0f2f27 100644
--- a/frappe/tests/test_db.py
+++ b/frappe/tests/test_db.py
@@ -45,6 +45,11 @@ class TestDB(unittest.TestCase):
todo.save()
self.assertIn('tabToDo', frappe.flags.touched_tables)
+ frappe.flags.touched_tables = set()
+ frappe.db.sql("UPDATE tabToDo SET description = 'Updated Description'")
+ self.assertNotIn('tabToDo SET', frappe.flags.touched_tables)
+ self.assertIn('tabToDo', frappe.flags.touched_tables)
+
frappe.flags.touched_tables = set()
todo.delete()
self.assertIn('tabToDo', frappe.flags.touched_tables)
diff --git a/package.json b/package.json
index 3bf733cf8a..8052734731 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
"cookie": "^0.3.1",
"express": "^4.16.2",
"fast-deep-equal": "^2.0.1",
- "frappe-datatable": "^1.13.1",
+ "frappe-datatable": "^1.13.2",
"frappe-gantt": "^0.1.0",
"fuse.js": "^3.2.0",
"highlight.js": "^9.12.0",
diff --git a/requirements.txt b/requirements.txt
index cc94f7cc5c..e832dbdf13 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -15,7 +15,7 @@ rauth>=0.6.2
requests
redis==2.10.6
selenium
-babel
+babel==2.6.0
ipython
html2text==2016.9.19
email_reply_parser
diff --git a/yarn.lock b/yarn.lock
index 611503e063..75aa246a07 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1224,10 +1224,10 @@ forwarded@~0.1.2:
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
-frappe-datatable@^1.13.1:
- version "1.13.1"
- resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.13.1.tgz#dbfa27fe735832cea54a0b35e3d3bfb839778c8d"
- integrity sha512-FOC8dpsOSI+KnF5sBrYja9b2Y+3qUvYy/H6108QchKSvXYvuWVr/uuLk2N5xlz38PWU3d7n5lK0dkx+zXTKJ0w==
+frappe-datatable@^1.13.2:
+ version "1.13.2"
+ resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.13.2.tgz#8b36c7cfc0ea660fc72eea8b1ae3c5dcc2a7d67d"
+ integrity sha512-4PyPDX22K4e4S3WGlLQx3oyxIW+ENsbGiN9L6aUpmjU+fOCC7J/FfSwGKdua2f+4yD+2ObpkyJYazBl3inAeCA==
dependencies:
hyperlist "^1.0.0-beta"
lodash "^4.17.5"