diff --git a/.eslintrc b/.eslintrc
index a2538feab5..d123023a68 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -147,6 +147,7 @@
"context": true,
"before": true,
"beforeEach": true,
- "qz": true
+ "qz": true,
+ "localforage": true
}
}
diff --git a/frappe/app.py b/frappe/app.py
index 29ef69ef2d..784db3d976 100644
--- a/frappe/app.py
+++ b/frappe/app.py
@@ -152,10 +152,10 @@ def process_response(response):
def set_cors_headers(response):
origin = frappe.request.headers.get('Origin')
- if not origin:
+ allow_cors = frappe.conf.allow_cors
+ if not (origin and allow_cors):
return
- allow_cors = frappe.conf.allow_cors
if allow_cors != "*":
if not isinstance(allow_cors, list):
allow_cors = [allow_cors]
diff --git a/frappe/desk/search.py b/frappe/desk/search.py
index f4e6543844..6faa827dde 100644
--- a/frappe/desk/search.py
+++ b/frappe/desk/search.py
@@ -80,13 +80,15 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0,
is_whitelisted(frappe.get_attr(query))
frappe.response["values"] = frappe.call(query, doctype, txt,
searchfield, start, page_length, filters, as_dict=as_dict)
- except Exception as e:
+ except frappe.exceptions.PermissionError as e:
if frappe.local.conf.developer_mode:
raise e
else:
frappe.respond_as_web_page(title='Invalid Method', html='Method not found',
indicator_color='red', http_status_code=404)
return
+ except Exception as e:
+ raise e
elif not query and doctype in standard_queries:
# from standard queries
search_widget(doctype, txt, standard_queries[doctype][0],
diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py
index 7a90ecaca5..295585665f 100644
--- a/frappe/model/base_document.py
+++ b/frappe/model/base_document.py
@@ -69,13 +69,13 @@ def get_controller(doctype):
if frappe.local.dev_server:
return _get_controller()
-
+
site_controllers = frappe.controllers.setdefault(frappe.local.site, {})
if doctype not in site_controllers:
site_controllers[doctype] = _get_controller()
-
+
return site_controllers[doctype]
-
+
class BaseDocument(object):
ignore_in_getter = ("doctype", "_meta", "meta", "_table_fields", "_valid_columns")
@@ -94,6 +94,14 @@ class BaseDocument(object):
return self._meta
def update(self, d):
+ """ Update multiple fields of a doctype using a dictionary of key-value pairs.
+
+ Example:
+ doc.update({
+ "user": "admin",
+ "balance": 42000
+ })
+ """
if "doctype" in d:
self.set("doctype", d.get("doctype"))
@@ -159,6 +167,15 @@ class BaseDocument(object):
del self.__dict__[key]
def append(self, key, value=None):
+ """ Append an item to a child table.
+
+ Example:
+ doc.append("childtable", {
+ "child_table_field": "value",
+ "child_table_int_field": 0,
+ ...
+ })
+ """
if value==None:
value={}
if isinstance(value, (dict, BaseDocument)):
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 5400c96354..d43690eac2 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -35,6 +35,7 @@ frappe.patches.v11_0.change_email_signature_fieldtype
execute:frappe.reload_doc('core', 'doctype', 'activity_log')
execute:frappe.reload_doc('core', 'doctype', 'deleted_document')
execute:frappe.reload_doc('core', 'doctype', 'domain_settings')
+frappe.patches.v13_0.rename_custom_client_script
frappe.patches.v8_0.rename_page_role_to_has_role #2017-03-16
frappe.patches.v7_2.setup_custom_perms #2017-01-19
frappe.patches.v8_0.set_user_permission_for_page_and_report #2017-03-20
@@ -330,4 +331,3 @@ execute:frappe.get_doc('Role', 'Guest').save() # remove desk access
frappe.patches.v13_0.rename_desk_page_to_workspace # 02.02.2021
frappe.patches.v13_0.delete_package_publish_tool
frappe.patches.v13_0.rename_list_view_setting_to_list_view_settings
-frappe.patches.v13_0.rename_custom_client_script
diff --git a/frappe/patches/v12_0/reset_home_settings.py b/frappe/patches/v12_0/reset_home_settings.py
index db16c31f15..e4b9de6cb2 100644
--- a/frappe/patches/v12_0/reset_home_settings.py
+++ b/frappe/patches/v12_0/reset_home_settings.py
@@ -1,6 +1,7 @@
import frappe
def execute():
+ frappe.reload_doc('core', 'doctype', 'user')
frappe.db.sql('''
UPDATE `tabUser`
SET `home_settings` = ''
diff --git a/frappe/patches/v13_0/enable_custom_script.py b/frappe/patches/v13_0/enable_custom_script.py
index 92284e6dcc..edc242e700 100644
--- a/frappe/patches/v13_0/enable_custom_script.py
+++ b/frappe/patches/v13_0/enable_custom_script.py
@@ -5,9 +5,8 @@ from __future__ import unicode_literals
import frappe
def execute():
- """Enable all the existing custom script"""
- frappe.reload_doc("Custom", "doctype", "Custom Script")
+ """Enable all the existing Client script"""
frappe.db.sql("""
- UPDATE `tabCustom Script` SET enabled=1
+ UPDATE `tabClient Script` SET enabled=1
""")
\ No newline at end of file
diff --git a/frappe/public/build.json b/frappe/public/build.json
index 73b8bc60fe..51a2f55a37 100755
--- a/frappe/public/build.json
+++ b/frappe/public/build.json
@@ -100,6 +100,7 @@
"node_modules/moment/min/moment-with-locales.min.js",
"node_modules/moment-timezone/builds/moment-timezone-with-data.min.js",
"node_modules/socket.io-client/dist/socket.io.slim.js",
+ "node_modules/localforage/dist/localforage.min.js",
"public/js/lib/jSignature.min.js",
"public/js/lib/leaflet/leaflet.js",
"public/js/lib/leaflet/leaflet.draw.js",
diff --git a/frappe/public/icons/timeless/symbol-defs.svg b/frappe/public/icons/timeless/symbol-defs.svg
index f5539d2bab..2b0cc8b696 100644
--- a/frappe/public/icons/timeless/symbol-defs.svg
+++ b/frappe/public/icons/timeless/symbol-defs.svg
@@ -268,7 +268,7 @@
+ fill="var(--icon-stroke)" stroke="none">
diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js
index 260a7d995f..2e7ff4a812 100755
--- a/frappe/public/js/frappe/views/communication.js
+++ b/frappe/public/js/frappe/views/communication.js
@@ -591,26 +591,28 @@ frappe.views.CommunicationComposer = Class.extend({
save_as_draft: function() {
if (this.dialog && this.frm) {
- try {
- let message = this.dialog.get_value('content');
- message = message.split(frappe.separator_element)[0];
- localStorage.setItem(this.frm.doctype + this.frm.docname, message);
- } catch (e) {
- // silently fail
- console.log(e);
- console.warn('[Communication] localStorage is full. Cannot save message as draft');
- }
+ let message = this.dialog.get_value('content');
+ message = message.split(frappe.separator_element)[0];
+ localforage.setItem(this.frm.doctype + this.frm.docname, message).catch(e => {
+ if (e) {
+ // silently fail
+ console.log(e); // eslint-disable-line
+ console.warn('[Communication] localStorage is full. Cannot save message as draft'); // eslint-disable-line
+ }
+ });
+
}
},
delete_saved_draft() {
if (this.dialog) {
- try {
- localStorage.removeItem(this.frm.doctype + this.frm.docname);
- } catch (e) {
- console.log(e);
- console.warn('[Communication] Cannot delete localStorage item'); // eslint-disable-line
- }
+ localforage.getItem(this.frm.doctype + this.frm.docname).catch(e => {
+ if (e) {
+ // silently fail
+ console.log(e); // eslint-disable-line
+ console.warn('[Communication] localStorage is full. Cannot save message as draft'); // eslint-disable-line
+ }
+ });
}
},
@@ -721,7 +723,7 @@ frappe.views.CommunicationComposer = Class.extend({
signature = res.message.signature;
}
- if(!frappe.utils.is_html(signature)) {
+ if (signature && !frappe.utils.is_html(signature)) {
signature = signature.replace(/\n/g, "
");
}
@@ -731,7 +733,7 @@ frappe.views.CommunicationComposer = Class.extend({
// saved draft in localStorage
const { doctype, docname } = this.frm || {};
if (doctype && docname) {
- this.message = localStorage.getItem(doctype + docname) || '';
+ this.message = await localforage.getItem(doctype + docname) || '';
}
}
diff --git a/frappe/utils/data.py b/frappe/utils/data.py
index c38d3ec027..21fa609b80 100644
--- a/frappe/utils/data.py
+++ b/frappe/utils/data.py
@@ -31,7 +31,8 @@ def is_invalid_date_string(date_string):
# datetime functions
def getdate(string_date=None):
"""
- Converts string date (yyyy-mm-dd) to datetime.date object
+ Converts string date (yyyy-mm-dd) to datetime.date object.
+ If no input is provided, current date is returned.
"""
if not string_date:
@@ -518,7 +519,25 @@ def cast_fieldtype(fieldtype, value):
return value
def flt(s, precision=None):
- """Convert to float (ignore commas)"""
+ """Convert to float (ignoring commas in string)
+
+ :param s: Number in string or other numeric format.
+ :param precision: optional argument to specify precision for rounding.
+ :returns: Converted number in python float type.
+
+ Returns 0 if input can not be converted to float.
+
+ Examples:
+
+ >>> flt("43.5", precision=0)
+ 44
+ >>> flt("42.5", precision=0)
+ 42
+ >>> flt("10,500.5666", precision=2)
+ 10500.57
+ >>> flt("a")
+ 0.0
+ """
if isinstance(s, string_types):
s = s.replace(',','')
@@ -532,7 +551,20 @@ def flt(s, precision=None):
return num
def cint(s):
- """Convert to integer"""
+ """Convert to integer
+
+ :param s: Number in string or other numeric format.
+ :returns: Converted number in python integer type.
+
+ Returns 0 if input can not be converted to integer.
+
+ Examples:
+ >>> cint("100")
+ 100
+ >>> cint("a")
+ 0
+
+ """
try: num = int(float(s))
except: num = 0
return num
diff --git a/package.json b/package.json
index 1299bb156f..43cedc158a 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
"highlight.js": "^10.4.1",
"js-sha256": "^0.9.0",
"jsbarcode": "^3.9.0",
+ "localforage": "^1.9.0",
"moment": "^2.20.1",
"moment-timezone": "^0.5.28",
"node-sass": "^4.14.1",
diff --git a/yarn.lock b/yarn.lock
index 200e813076..daca81cfda 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -961,15 +961,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000939:
- version "1.0.30001116"
- resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001116.tgz"
- integrity sha512-f2lcYnmAI5Mst9+g0nkMIznFGsArRmZ0qU+dnq8l91hymdc2J3SFbiPhOJEeDqC1vtE8nc1qNQyklzB8veJefQ==
-
-caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001111:
- version "1.0.30001118"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001118.tgz#116a9a670e5264aec895207f5e918129174c6f62"
- integrity sha512-RNKPLojZo74a0cP7jFMidQI7nvLER40HgNfgKQEJ2PFm225L0ectUungNQoK3Xk3StQcFbpBPNEvoWD59436Hg==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000939, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001111:
+ version "1.0.30001191"
+ resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001191.tgz"
+ integrity sha512-xJJqzyd+7GCJXkcoBiQ1GuxEiOBCLQ0aVW9HMekifZsAVGdj5eJ4mFB9fEhSHipq9IOk/QXFJUiIr9lZT+EsGw==
caseless@~0.12.0:
version "0.12.0"
@@ -3790,6 +3785,13 @@ levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
+lie@3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
+ integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=
+ dependencies:
+ immediate "~3.0.5"
+
lie@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
@@ -3823,6 +3825,13 @@ loadjs@^4.2.0:
resolved "https://registry.yarnpkg.com/loadjs/-/loadjs-4.2.0.tgz#2a0336376397a6a43edf98c9ec3229ddd5abb6f6"
integrity sha512-AgQGZisAlTPbTEzrHPb6q+NYBMD+DP9uvGSIjSUM5uG+0jG15cb8axWpxuOIqrmQjn6scaaH8JwloiP27b2KXA==
+localforage@^1.9.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.9.0.tgz#f3e4d32a8300b362b4634cc4e066d9d00d2f09d1"
+ integrity sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g==
+ dependencies:
+ lie "3.1.1"
+
locate-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"