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"