From 1f2c9b6ea4d371a8017675426bdd66f8816164fd Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 14 Mar 2018 07:06:14 +0530 Subject: [PATCH 01/34] Letterhead cleanup --- frappe/core/doctype/communication/email.py | 2 +- frappe/public/js/frappe/views/communication.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index 00b9918933..8acececa03 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -88,7 +88,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = frappe.db.commit() if cint(send_email): - frappe.flags.print_letterhead = print_letterhead + frappe.flags.print_letterhead = cint(print_letterhead) comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy) return { diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 3ca9715556..5799aec97d 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -555,10 +555,10 @@ frappe.views.CommunicationComposer = Class.extend({ is_print_letterhead_checked: function() { if (this.frm && $(this.frm.wrapper).find('.form-print-wrapper').is(':visible')){ - return $(this.frm.wrapper).find('.print-letterhead').prop('checked'); + return $(this.frm.wrapper).find('.print-letterhead').prop('checked') ? 1 : 0; } else { return (frappe.model.get_doc(":Print Settings", "Print Settings") || - { with_letterhead: 1 }).with_letterhead ? true : false; + { with_letterhead: 1 }).with_letterhead ? 1 : 0; } }, From fa46d65754a888dbcabbe96335f92d1960b5e2e2 Mon Sep 17 00:00:00 2001 From: Sunny Date: Wed, 14 Mar 2018 08:52:20 +0530 Subject: [PATCH 02/34] set fieldname for Section Break and Column Break, only if fieldname is not set, else it breaks existing custom fields. Also Use label for creating fieldname if user has entered label --- .../custom/doctype/custom_field/custom_field.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index 1f2445eb13..dbab4eadcb 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -14,14 +14,17 @@ class CustomField(Document): self.name = self.dt + "-" + self.fieldname def set_fieldname(self): - if self.fieldtype in ["Section Break", "Column Break"]: - self.fieldname = self.fieldtype.lower().replace(" ","_") + "_" + str(self.idx) if not self.fieldname: - if not self.label: - frappe.throw(_("Label is mandatory")) + label = self.label + if not label: + if self.fieldtype in ["Section Break", "Column Break"]: + label = self.fieldtype + "_" + str(self.idx) + else: + frappe.throw(_("Label is mandatory")) + # remove special characters from fieldname self.fieldname = "".join(filter(lambda x: x.isdigit() or x.isalpha() or '_', - cstr(self.label).lower().replace(' ','_'))) + cstr(label).replace(' ','_'))) # fieldnames should be lowercase self.fieldname = self.fieldname.lower() @@ -130,4 +133,4 @@ def create_custom_fields(custom_fields): @frappe.whitelist() def add_custom_field(doctype, df): df = json.loads(df) - return create_custom_field(doctype, df) \ No newline at end of file + return create_custom_field(doctype, df) From b5454a353a1cadc0d80baf70590b080ece2888c5 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 16 Mar 2018 10:25:44 +0530 Subject: [PATCH 03/34] [gitignore] --- .gitignore | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bbd843c61b..3b00b7b374 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,177 @@ build/ frappe/docs/current .vscode node_modules - +.kdev4/ +*.kdev4 +*debug.log # Not Recommended, but will remove once webpack ready package-lock.json + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +.static_storage/ +.media/ +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next \ No newline at end of file From dc124d685a69be7186e7726ac31abe14c354f421 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 19 Mar 2018 11:51:21 +0530 Subject: [PATCH 04/34] [fix] [xss] web search (#5205) --- frappe/www/search.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/www/search.py b/frappe/www/search.py index baf2be4e63..c55970c1fc 100644 --- a/frappe/www/search.py +++ b/frappe/www/search.py @@ -4,13 +4,14 @@ from frappe.utils.global_search import web_search from html2text import html2text from frappe import _ from jinja2 import utils +from frappe.utils import sanitize_html def get_context(context): context.no_cache = 1 if frappe.form_dict.q: - frappe.form_dict.q = str(utils.escape(frappe.form_dict.q)) - context.title = _('Search Results for "{0}"').format(frappe.form_dict.q) - context.update(get_search_results(frappe.form_dict.q)) + query = str(utils.escape(sanitize_html(frappe.form_dict.q))) + context.title = _('Search Results for "{0}"').format(query) + context.update(get_search_results(query)) else: context.title = _('Search') From e2a6a954a5bd7ceb184419eab955428fe5e5e06a Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 19 Mar 2018 18:28:53 +0530 Subject: [PATCH 05/34] [fix] validate minimum transaction amount while creating a charge on stripe (#5223) --- .../doctype/stripe_settings/stripe_settings.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/doctype/stripe_settings/stripe_settings.py b/frappe/integrations/doctype/stripe_settings/stripe_settings.py index c96b508743..62d90cbe53 100644 --- a/frappe/integrations/doctype/stripe_settings/stripe_settings.py +++ b/frappe/integrations/doctype/stripe_settings/stripe_settings.py @@ -23,12 +23,18 @@ class StripeSettings(Document): "XAF", "XOF", "XPF", "YER", "ZAR" ] + currency_wise_minimum_charge_amount = { + 'JPY': 50, 'MXN': 10, 'DKK': 2.50, 'HKD': 4.00, 'NOK': 3.00, 'SEK': 3.00, + 'USD': 0.50, 'AUD': 0.50, 'BRL': 0.50, 'CAD': 0.50, 'CHF': 0.50, 'EUR': 0.50, + 'GBP': 0.30, 'NZD': 0.50, 'SGD': 0.50 + } + def validate(self): create_payment_gateway('Stripe') call_hook_method('payment_gateway_enabled', gateway='Stripe') if not self.flags.ignore_mandatory: self.validate_stripe_credentails() - + def validate_stripe_credentails(self): if self.publishable_key and self.secret_key: header = {"Authorization": "Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))} @@ -36,11 +42,17 @@ class StripeSettings(Document): make_get_request(url="https://api.stripe.com/v1/charges", headers=header) except Exception: frappe.throw(_("Seems Publishable Key or Secret Key is wrong !!!")) - + def validate_transaction_currency(self, currency): if currency not in self.supported_currencies: frappe.throw(_("Please select another payment method. Stripe does not support transactions in currency '{0}'").format(currency)) + def validate_minimum_transaction_amount(self, currency, amount): + if currency in self.currency_wise_minimum_charge_amount: + if flt(amount) < self.currency_wise_minimum_charge_amount.get(currency, 0.0): + frappe.throw(_("For currency {0}, the minimum transaction amount should be {1}").format(currency, + self.currency_wise_minimum_charge_amount.get(currency, 0.0))) + def get_payment_url(self, **kwargs): return get_url("./integrations/stripe_checkout?{0}".format(urlencode(kwargs))) From ba5ec5bcc86ee802f0f923f2e41a0deb64768f8c Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 20 Mar 2018 11:40:32 +0530 Subject: [PATCH 06/34] [fix] fix ajax call to update doc not replace (#5204) --- frappe/public/js/frappe/model/sync.js | 36 ++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/model/sync.js b/frappe/public/js/frappe/model/sync.js index 645585b082..ddcd587695 100644 --- a/frappe/public/js/frappe/model/sync.js +++ b/frappe/public/js/frappe/model/sync.js @@ -20,7 +20,12 @@ $.extend(frappe.model, { for(var i=0, l=r.docs.length; i d[fieldname].length) { + local_doc[fieldname].length = d[fieldname].length; + } + } else { + // literal + local_doc[fieldname] = d[fieldname]; + } + } } }); From d00b20a610b9d04ddefb9ab5eb8c406c7fa0e6d3 Mon Sep 17 00:00:00 2001 From: Achilles Rasquinha Date: Wed, 21 Mar 2018 12:37:04 +0530 Subject: [PATCH 07/34] user image fix --- frappe/core/doctype/user/user.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index edf4c285b4..69d6cb1628 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -68,6 +68,7 @@ class User(Document): self.validate_user_email_inbox() ask_pass_update() self.validate_roles() + self.validate_user_image() if self.language == "Loading...": self.language = None @@ -81,6 +82,10 @@ class User(Document): self.set('roles', []) self.append_roles(*[role.role for role in role_profile.roles]) + def validate_user_image(self): + if len(self.user_image) > 2000: + frappe.throw(_("Not a valid User Image.")) + def on_update(self): # clear new password self.validate_user_limit() From 73aeb5ea96252507721018ae22c47d3c2d2555d9 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 21 Mar 2018 15:27:24 +0530 Subject: [PATCH 08/34] fix test --- frappe/core/doctype/user/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 69d6cb1628..ab5609a5da 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -83,7 +83,7 @@ class User(Document): self.append_roles(*[role.role for role in role_profile.roles]) def validate_user_image(self): - if len(self.user_image) > 2000: + if self.user_image and len(self.user_image) > 2000: frappe.throw(_("Not a valid User Image.")) def on_update(self): From 13f0f19f75f56e0c87845a6b499da9d515a1a4b6 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 22 Mar 2018 11:15:01 +0530 Subject: [PATCH 09/34] [Fix] RTL for document print (#5244) --- frappe/www/printview.html | 3 +++ frappe/www/printview.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/www/printview.html b/frappe/www/printview.html index 6136426593..ecefab57ad 100644 --- a/frappe/www/printview.html +++ b/frappe/www/printview.html @@ -9,6 +9,9 @@ href="/assets/frappe/css/bootstrap.css"> + {%- if has_rtl -%} + + {%- endif -%} diff --git a/frappe/www/printview.py b/frappe/www/printview.py index 82af3e1f3c..b4e085242d 100644 --- a/frappe/www/printview.py +++ b/frappe/www/printview.py @@ -41,7 +41,8 @@ def get_context(context): no_letterhead=frappe.form_dict.no_letterhead), "css": get_print_style(frappe.form_dict.style, print_format), "comment": frappe.session.user, - "title": doc.get(meta.title_field) if meta.title_field else doc.name + "title": doc.get(meta.title_field) if meta.title_field else doc.name, + "has_rtl": True if frappe.local.lang in ["ar", "he", "fa"] else False } def get_print_format_doc(print_format_name, meta): From 3dab3478db0c4c4c206e1b98c70d7fa59ac862c7 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 22 Mar 2018 12:14:21 +0530 Subject: [PATCH 10/34] Minor fix in autonaming --- frappe/model/naming.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/naming.py b/frappe/model/naming.py index 88fba659c9..e769ac06ac 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -47,7 +47,7 @@ def set_new_name(doc): if autoname.startswith("naming_series:"): set_name_by_naming_series(doc) elif "#" in autoname: - doc.name = make_autoname(autoname) + doc.name = make_autoname(autoname, doc=doc) elif autoname.lower()=='prompt': # set from __newname in save.py if not doc.name: From 763360c68fb16d19f124aabe6b62f4351f42ec2d Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 22 Mar 2018 12:55:42 +0530 Subject: [PATCH 11/34] minor fix in syncing after ajax --- frappe/public/js/frappe/model/sync.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frappe/public/js/frappe/model/sync.js b/frappe/public/js/frappe/model/sync.js index ddcd587695..284e9b2cb4 100644 --- a/frappe/public/js/frappe/model/sync.js +++ b/frappe/public/js/frappe/model/sync.js @@ -33,10 +33,6 @@ $.extend(frappe.model, { frappe.meta.sync(d); } - if(cur_frm && cur_frm.doctype==d.doctype && cur_frm.docname==d.name) { - cur_frm.doc = d; - } - if(d.localname) { frappe.model.new_names[d.localname] = d.name; $(document).trigger('rename', [d.doctype, d.localname, d.name]); From 5debf79e5a0f4db8d35e71664925e58e355aa994 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 22 Mar 2018 15:09:07 +0530 Subject: [PATCH 12/34] Query Report fixes (#5252) - normalize every data row into object - onload event should fire after filters are set --- .../js/frappe/views/reports/query_report.js | 89 +++++++++++-------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index d9789abac7..b7212a7181 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -86,7 +86,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { frappe.run_serially([ () => this.get_report_doc(), () => this.get_report_settings(), - () => this.report_settings.onload && this.report_settings.onload(this), () => this.setup_page_head(), () => this.refresh_report() ]); @@ -98,6 +97,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { return frappe.run_serially([ () => this.setup_filters(), () => this.set_route_filters(), + () => this.report_settings.onload && this.report_settings.onload(this), () => this.get_user_settings(), () => this.refresh(), () => this.save_user_settings() @@ -230,22 +230,20 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } render_report(data) { - this._data = data.result; - this._columns = this.prepare_columns(data.columns); - this.is_tree_report = this._data.some(d => 'indent' in d); - - const columns = this.get_columns_for_datatable(); + this.columns = this.prepare_columns(data.columns); + this.data = this.prepare_data(data.result); + // const columns = this.get_columns_for_datatable(); if (this.datatable) { - this.datatable.refresh(this._data, columns); + this.datatable.refresh(this.data, this.columns); return; } this.datatable = new DataTable(this.$report[0], { - columns: columns, - data: this._data, + columns: this.columns, + data: this.data, inlineFilters: true, - treeView: this.is_tree_report, + treeView: this.data.some(d => 'indent' in d), layout: 'fixed', events: { onRemoveColumn: () => this.save_user_settings(), @@ -298,28 +296,51 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { [fieldtype, options] = fieldtype.split('/'); } - return { + column = { label, fieldname: label, fieldtype, width, options }; + } else { + column = { + label: column, + fieldname: column, + fieldtype: 'Data' + }; } - - return { - label: column, - fieldname: column, - fieldtype: 'Data' - }; } - return column; + return Object.assign(column, { + id: column.fieldname, + name: column.label, + width: parseInt(column.width) || null, + editable: false, + format: (value, row, column, data) => + frappe.format(value || '', column, + {for_print: false, always_show_decimals: true}, data) + }); + }); + } + + prepare_data(data) { + return data.map(row => { + let row_obj = {}; + if (Array.isArray(row)) { + this.columns.forEach((column, i) => { + row_obj[column.id] = row[i] || null; + }); + + return row_obj; + } + return row; }); } get_columns_for_datatable() { - const columns = this._columns.map(df => { + // return columns according to user_settings + const columns = this.columns.map(df => { return { id: df.fieldname, name: df.label, @@ -331,15 +352,15 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { }; }); - return columns; + // return columns; - // if (this.user_settings.column_order && this.user_settings.column_order.length > 0) { - // return this.user_settings.column_order - // .map(id => columns.find(col => col.id === id)) - // .filter(Boolean); - // } else { - // return columns; - // } + if (this.user_settings.column_order && this.user_settings.column_order.length > 0) { + return this.user_settings.column_order + .map(id => columns.find(col => col.id === id)) + .filter(Boolean); + } else { + return columns; + } } get_filter_values(raise) { @@ -455,7 +476,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { reqd: 1 }, ({ file_format }) => { if (file_format === 'CSV') { - const column_row = this._columns.map(col => col.label); + const column_row = this.columns.map(col => col.label); const data = this.get_data_for_print(); const out = [column_row].concat(data); @@ -483,7 +504,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } get_columns_for_print() { - return this._columns || []; + return this.columns || []; } get_menu_items() { @@ -596,12 +617,4 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { get get_values() { return this.get_filter_values; } - - get data() { - return this._data; - } - - get columns() { - return this._columns; - } -}; \ No newline at end of file +}; From 84e7f8ba678b0d084a389b2760b75933b12c5563 Mon Sep 17 00:00:00 2001 From: Achilles Rasquinha Date: Thu, 22 Mar 2018 22:52:20 +0530 Subject: [PATCH 13/34] fix timedelta conversions for pymysq --- frappe/database.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/database.py b/frappe/database.py index 07ff5cb32f..d55a028976 100644 --- a/frappe/database.py +++ b/frappe/database.py @@ -81,10 +81,14 @@ class Database: conversions.update({ FIELD_TYPE.NEWDECIMAL: float, FIELD_TYPE.DATETIME: get_datetime, - TimeDelta: conversions[binary_type], UnicodeWithAttrs: conversions[text_type] }) + if six.PY2: + conversions.update({ + TimeDelta: conversions[binary_type] + }) + if usessl: self._conn = pymysql.connect(self.host, self.user or '', self.password or '', charset='utf8mb4', use_unicode = True, ssl=self.ssl, conv = conversions, local_infile = self.local_infile) From bc0a2acadff78416c514b59986b7c5abc493dd03 Mon Sep 17 00:00:00 2001 From: Achilles Rasquinha Date: Thu, 22 Mar 2018 23:02:20 +0530 Subject: [PATCH 14/34] fix timedelta conversions for pymysq --- frappe/database.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/database.py b/frappe/database.py index d55a028976..939a82fea7 100644 --- a/frappe/database.py +++ b/frappe/database.py @@ -17,6 +17,7 @@ from frappe import _ from frappe.model.utils.link_count import flush_local_link_count from frappe.utils.background_jobs import execute_job, get_queue from frappe import as_unicode +import six # imports - compatibility imports from six import ( From d0f3b9b6e6c47dcd0d1f641cf22b2ac7c76631b0 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 23 Mar 2018 15:09:28 +0530 Subject: [PATCH 15/34] Dropbox settings UX fix (#5253) * Fixed dropbox UX issue * Fixed typo --- .../dropbox_settings/dropbox_settings.js | 5 ++- .../dropbox_settings/dropbox_settings.json | 40 +++---------------- .../dropbox_settings/dropbox_settings.py | 11 ++--- 3 files changed, 14 insertions(+), 42 deletions(-) diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.js b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.js index 2e3c707a5e..c264d12dec 100644 --- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.js +++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.js @@ -3,6 +3,7 @@ frappe.ui.form.on('Dropbox Settings', { refresh: function(frm) { + frm.toggle_display(["app_access_key", "app_secret_key"], !(frm.doc.__onload && frm.doc.__onload.dropbox_setup_via_site_config)); frm.clear_custom_buttons(); frm.events.take_backup(frm); }, @@ -19,7 +20,7 @@ frappe.ui.form.on('Dropbox Settings', { } }) } - else if (frm.doc.dropbox_setup_via_site_config) { + else if (frm.doc.__onload && frm.doc.__onload.dropbox_setup_via_site_config) { frappe.call({ method: "frappe.integrations.doctype.dropbox_settings.dropbox_settings.get_redirect_url", freeze: true, @@ -36,7 +37,7 @@ frappe.ui.form.on('Dropbox Settings', { }, take_backup: function(frm) { - if ((frm.doc.app_access_key && frm.doc.app_secret_key) || frm.doc.dropbox_setup_via_site_config){ + if ((frm.doc.app_access_key && frm.doc.app_secret_key) || (frm.doc.__onload && frm.doc.__onload.dropbox_setup_via_site_config)){ frm.add_custom_button(__("Take Backup Now"), function(frm){ frappe.call({ method: "frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backup", diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.json b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.json index cbe1fe8b1a..a8ab581bf7 100644 --- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.json +++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.json @@ -54,7 +54,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Send Notifications To", "length": 0, @@ -84,7 +84,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Backup Frequency", "length": 0, @@ -108,7 +108,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "eval:!doc.dropbox_setup_via_site_config", + "depends_on": "", "fieldname": "app_access_key", "fieldtype": "Data", "hidden": 0, @@ -139,7 +139,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "eval:!doc.dropbox_setup_via_site_config", + "depends_on": "", "fieldname": "app_secret_key", "fieldtype": "Password", "hidden": 0, @@ -283,36 +283,6 @@ "search_index": 0, "set_only_once": 0, "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "dropbox_setup_via_site_config", - "fieldtype": "Check", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Dropbox Setup via Site Config", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 } ], "has_web_view": 0, @@ -325,7 +295,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2017-06-20 15:45:33.683827", + "modified": "2018-03-22 16:02:00.597029", "modified_by": "Administrator", "module": "Integrations", "name": "Dropbox Settings", diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py index 35027b90c9..283546320f 100644 --- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py +++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py @@ -20,7 +20,8 @@ ignore_list = [".DS_Store"] class DropboxSettings(Document): def onload(self): if not self.app_access_key and frappe.conf.dropbox_access_key: - self.dropbox_setup_via_site_config = 1 + self.set_onload("dropbox_setup_via_site_config", 1) + @frappe.whitelist() def take_backup(): @@ -171,7 +172,7 @@ def upload_file_to_dropbox(filename, folder, dropbox_client): cursor.offset = f.tell() except dropbox.exceptions.ApiError as e: if isinstance(e.error, dropbox.files.UploadError): - error = "File Path: {path}\n".foramt(path=path) + error = "File Path: {path}\n".format(path=path) error += frappe.get_traceback() frappe.log_error(error) else: @@ -201,7 +202,7 @@ def get_dropbox_settings(redirect_uri=False): if redirect_uri: app_details.update({ - 'rediret_uri': get_request_site_address(True) \ + 'redirect_uri': get_request_site_address(True) \ + '/api/method/frappe.integrations.doctype.dropbox_settings.dropbox_settings.dropbox_auth_finish' \ if settings.app_secret_key else frappe.conf.dropbox_broker_site\ + '/api/method/dropbox_erpnext_broker.www.setup_dropbox.generate_dropbox_access_token', @@ -233,7 +234,7 @@ def get_dropbox_authorize_url(): dropbox_oauth_flow = dropbox.DropboxOAuth2Flow( app_details["app_key"], app_details["app_secret"], - app_details["rediret_uri"], + app_details["redirect_uri"], {}, "dropbox-auth-csrf-token" ) @@ -254,7 +255,7 @@ def dropbox_auth_finish(return_access_token=False): dropbox_oauth_flow = dropbox.DropboxOAuth2Flow( app_details["app_key"], app_details["app_secret"], - app_details["rediret_uri"], + app_details["redirect_uri"], { 'dropbox-auth-csrf-token': callback.state }, From 577ce03fd8e8d8058097a59da3372cee9c31ef7d Mon Sep 17 00:00:00 2001 From: Zarrar Date: Fri, 23 Mar 2018 15:13:58 +0530 Subject: [PATCH 16/34] [Hotfix] File upload fix (#5254) * append hash if same filename found while uploading through socketio * pass file size * upload after filename change --- frappe/public/js/frappe/upload.js | 48 +++++++++++++++++++------------ frappe/utils/file_manager.py | 7 +++++ 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/frappe/public/js/frappe/upload.js b/frappe/public/js/frappe/upload.js index c5ed49f29c..c913825b67 100644 --- a/frappe/public/js/frappe/upload.js +++ b/frappe/public/js/frappe/upload.js @@ -296,26 +296,38 @@ frappe.upload = { if (opts.no_socketio || frappe.flags.no_socketio || file_not_big_enough) { upload_with_filedata(); return; + } else { + args.file_size = fileobj.size; + frappe.call({ + method: 'frappe.utils.file_manager.validate_filename', + args: {"filename": args.filename}, + callback: function(r) { + args.filename = r.message; + upload_through_socketio(); + } + }); } - frappe.socketio.uploader.start({ - file: fileobj, - filename: args.filename, - is_private: args.is_private, - fallback: () => { - // if fails, use old filereader - upload_with_filedata(); - }, - callback: (data) => { - args.file_url = data.file_url; - frappe.upload._upload_file(fileobj, args, opts); - }, - on_progress: (percent_complete) => { - let increment = (flt(percent_complete) / frappe.upload.total_files); - frappe.show_progress(__('Uploading'), - start_complete + increment); - } - }); + var upload_through_socketio = function() { + frappe.socketio.uploader.start({ + file: fileobj, + filename: args.filename, + is_private: args.is_private, + fallback: () => { + // if fails, use old filereader + upload_with_filedata(); + }, + callback: (data) => { + args.file_url = data.file_url; + frappe.upload._upload_file(fileobj, args, opts); + }, + on_progress: (percent_complete) => { + let increment = (flt(percent_complete) / frappe.upload.total_files); + frappe.show_progress(__('Uploading'), + start_complete + increment); + } + }); + } }, upload_to_server: function(file, args, opts) { diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py index a62f04eb10..638637b9ee 100644 --- a/frappe/utils/file_manager.py +++ b/frappe/utils/file_manager.py @@ -84,6 +84,7 @@ def save_url(file_url, filename, dt, dn, folder, is_private, df=None): # return None, None file_url = unquote(file_url) + file_size = frappe.form_dict.file_size f = frappe.get_doc({ "doctype": "File", @@ -93,6 +94,7 @@ def save_url(file_url, filename, dt, dn, folder, is_private, df=None): "attached_to_name": dn, "attached_to_field": df, "folder": folder, + "file_size": file_size, "is_private": is_private }) f.flags.ignore_permissions = True @@ -392,3 +394,8 @@ def get_random_filename(extn=None, content_type=None): extn = mimetypes.guess_extension(content_type) return random_string(7) + (extn or "") + +@frappe.whitelist() +def validate_filename(filename): + fname = get_file_name(filename, hashlib.md5(filename).hexdigest()[-6:]) + return fname From fa20790ac9c15af418a943d87ad839aa5d18bdc1 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 23 Mar 2018 15:45:35 +0600 Subject: [PATCH 17/34] bumped to version 10.1.7 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 196d6da92d..9ce53286ab 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json from .exceptions import * from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template -__version__ = '10.1.6' +__version__ = '10.1.7' __title__ = "Frappe Framework" local = Local() From c6be3cbdd792c7ec32f11bfe6b22fa9f4e38f614 Mon Sep 17 00:00:00 2001 From: Achilles Rasquinha Date: Fri, 23 Mar 2018 15:34:46 +0530 Subject: [PATCH 18/34] fix test case --- frappe/core/doctype/data_export/exporter.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/data_export/exporter.py b/frappe/core/doctype/data_export/exporter.py index 98b657d71c..0ea61f2554 100644 --- a/frappe/core/doctype/data_export/exporter.py +++ b/frappe/core/doctype/data_export/exporter.py @@ -29,7 +29,7 @@ def export_data(doctype=None, parent_doctype=None, all_doctypes=True, with_data= select_columns=select_columns, file_type=file_type, template=template, filters=filters) exporter.build_response() -class DataExporter(): +class DataExporter: def __init__(self, doctype=None, parent_doctype=None, all_doctypes=True, with_data=False, select_columns=None, file_type='CSV', template=False, filters=None): self.doctype = doctype @@ -98,10 +98,9 @@ class DataExporter(): self.add_data() if self.with_data and not self.data: frappe.respond_as_web_page(_('No Data'), _('There is no data to be exported'), indicator_color='orange') - return if self.file_type == 'Excel': - return self.build_response_as_excel() + self.build_response_as_excel() else: # write out response as a type csv frappe.response['result'] = cstr(self.writer.getvalue()) @@ -323,7 +322,7 @@ class DataExporter(): def build_response_as_excel(self): filename = frappe.generate_hash("", 10) with open(filename, 'wb') as f: - f.write(cstr(self.writer.getvalue()).encode("utf-8")) + f.write(cstr(self.writer.getvalue())) f = open(filename) reader = csv.reader(f) From 4581077cb86babf2f404296d65a525199994f241 Mon Sep 17 00:00:00 2001 From: Achilles Rasquinha Date: Fri, 23 Mar 2018 15:37:09 +0530 Subject: [PATCH 19/34] fix test case --- frappe/core/doctype/data_export/exporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/data_export/exporter.py b/frappe/core/doctype/data_export/exporter.py index 0ea61f2554..ca1fc6cd3f 100644 --- a/frappe/core/doctype/data_export/exporter.py +++ b/frappe/core/doctype/data_export/exporter.py @@ -322,7 +322,7 @@ class DataExporter: def build_response_as_excel(self): filename = frappe.generate_hash("", 10) with open(filename, 'wb') as f: - f.write(cstr(self.writer.getvalue())) + f.write(cstr(self.writer.getvalue()).encode('utf-8')) f = open(filename) reader = csv.reader(f) From 3c8dbaf13853175b58d1f285bd8a73860182b83b Mon Sep 17 00:00:00 2001 From: Achilles Rasquinha Date: Fri, 23 Mar 2018 17:33:24 +0530 Subject: [PATCH 20/34] fix py3 test case --- frappe/core/doctype/transaction_log/test_transaction_log.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/transaction_log/test_transaction_log.py b/frappe/core/doctype/transaction_log/test_transaction_log.py index 43327b55f8..164a683c38 100644 --- a/frappe/core/doctype/transaction_log/test_transaction_log.py +++ b/frappe/core/doctype/transaction_log/test_transaction_log.py @@ -34,6 +34,9 @@ class TestTransactionLog(unittest.TestCase): sha = hashlib.sha256() - sha.update(str(third_log.transaction_hash) + str(second_log.chaining_hash)) + sha.update( + frappe.safe_encode(str(third_log.transaction_hash)) + + frappe.safe_encode(str(second_log.chaining_hash)) + ) self.assertEqual(sha.hexdigest(), third_log.chaining_hash) From ed8a8dabd3d34f7b5162526a1a7d803092aefbaf Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sat, 24 Mar 2018 12:06:38 +0530 Subject: [PATCH 21/34] List view fixes (#5260) * Listview fixes - fix search icon in standard filter frappe/erpnext#12555 - show skeleton while loading list rows * Optimize remove_script_and_style --- frappe/public/js/frappe/dom.js | 11 +++++++-- frappe/public/js/frappe/list/base_list.js | 30 ++++++++++++----------- frappe/public/js/frappe/list/list_view.js | 26 +++++++++++++------- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/frappe/public/js/frappe/dom.js b/frappe/public/js/frappe/dom.js index 584c441db8..be9356175f 100644 --- a/frappe/public/js/frappe/dom.js +++ b/frappe/public/js/frappe/dom.js @@ -33,12 +33,19 @@ frappe.dom = { document.getElementsByTagName('head')[0].appendChild(el); }, remove_script_and_style: function(txt) { + const evil_tags = ["script", "style", "noscript", "title", "meta", "base", "head"]; + const regex = new RegExp(evil_tags.map(tag => `<${tag}>.*<\\/${tag}>`).join('|')); + if (!regex.test(txt)) { + // no evil tags found, skip the DOM method entirely! + return txt; + } + var div = document.createElement('div'); div.innerHTML = txt; var found = false; - ["script", "style", "noscript", "title", "meta", "base", "head"].forEach(function(e, i) { + evil_tags.forEach(function(e) { var elements = div.getElementsByTagName(e); - var i = elements.length; + i = elements.length; while (i--) { found = true; elements[i].parentNode.removeChild(elements[i]); diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index a924522052..d842885bac 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -225,7 +225,7 @@ frappe.views.BaseList = class BaseList { } setup_result_area() { - this.$result = $(`
`).hide(); + this.$result = $(`
`); this.$frappe_list.append(this.$result); } @@ -531,19 +531,6 @@ class FilterArea { } make_standard_filters() { - $( - `
- -
` - ) - .css({ - height: '30px', - width: '20px', - marginRight: '-2px', - marginLeft: '10px' - }) - .prependTo(this.standard_filters_wrapper); - let fields = [ { fieldtype: 'Data', @@ -602,6 +589,21 @@ class FilterArea { } fields.map(df => this.list_view.page.add_field(df)); + + // search icon in name filter + $('') + .appendTo(this.list_view.page.fields_dict.name.$wrapper) + .css({ + 'position': 'absolute', + 'z-index': '1', + 'right': '7px', + 'top': '9px', + 'font-size': '90%' + }); + + this.list_view.page.fields_dict.name.$wrapper + .find('.form-control') + .css('padding-right', '2em'); } get_standard_filters() { diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 2e017e80da..ed70c71d5d 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -145,6 +145,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { setup_view() { this.setup_columns(); + this.render_header(); + this.render_skeleton(); this.setup_events(); this.settings.onload && this.settings.onload(this); } @@ -233,10 +235,8 @@ 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); + freeze() { + this.$result.find('.list-count').html(`${__('Refreshing')}...`); } get_args() { @@ -264,6 +264,18 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } } + render_header() { + if (this.$result.find('.list-row-head').length === 0) { + // append header once + this.$result.prepend(this.get_header_html()); + } + } + + render_skeleton() { + const $row = this.get_list_row_html_skeleton('
'); + this.$result.append($row.repeat(3)); + } + before_render() { this.settings.before_render && this.settings.before_render(); frappe.model.user_settings.save(this.doctype, 'last_view', this.view_name); @@ -274,12 +286,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } render() { + this.$result.find('.list-row-container').remove(); if (this.data.length > 0) { - 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('') From 9e2f161f3eb0d09c7caaccff289e916e92d18b7b Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Sat, 24 Mar 2018 12:07:48 +0530 Subject: [PATCH 22/34] OAuth 2 ROPC Grant test (#5237) --- frappe/tests/ui/test_oauth20.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/frappe/tests/ui/test_oauth20.py b/frappe/tests/ui/test_oauth20.py index d21aba2efd..cbd9ff5662 100644 --- a/frappe/tests/ui/test_oauth20.py +++ b/frappe/tests/ui/test_oauth20.py @@ -21,6 +21,7 @@ class TestOAuth20(unittest.TestCase): frappe_login_key = frappe.new_doc("Social Login Key") frappe_login_key.get_social_login_provider("Frappe", initialize=True) frappe_login_key.base_url = "http://localhost:8000" + frappe_login_key.enable_social_login = 0 frappe_login_key.save() def test_invalid_login(self): @@ -88,6 +89,26 @@ class TestOAuth20(unittest.TestCase): # Check revoked token self.assertFalse(check_valid_openid_response(bearer_token.get("access_token"))) + def test_resource_owner_password_credentials_grant(self): + # Set payload + payload = "grant_type=password" + payload += "&username=test@example.com" + payload += "&password=Eastern_43A1W" + payload += "&client_id=" + self.client_id + payload += "&scope=openid%20all" + + headers = {'content-type':'application/x-www-form-urlencoded'} + + # Request for bearer token + token_response = requests.post( frappe.get_site_config().host_name + + "/api/method/frappe.integrations.oauth2.get_token", data=payload, headers=headers) + + # Parse bearer token json + bearer_token = token_response.json() + + # Check token for valid response + self.assertTrue(check_valid_openid_response(bearer_token.get("access_token"))) + def test_login_using_implicit_token(self): oauth_client = frappe.get_doc("OAuth Client", self.client_id) From d158a51df5e7390a744bf1d415a56debb3d640f4 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sat, 24 Mar 2018 12:08:00 +0530 Subject: [PATCH 23/34] Query Report fixes (#5258) * Query Report fixes - User saved columns working - Print and PDF fix - Print filters frappe/erpnext#12783 * minor --- .../js/frappe/views/reports/print_grid.html | 6 +- .../js/frappe/views/reports/query_report.js | 149 +++++++++--------- 2 files changed, 77 insertions(+), 78 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/print_grid.html b/frappe/public/js/frappe/views/reports/print_grid.html index 4ec8184e77..c559a234af 100644 --- a/frappe/public/js/frappe/views/reports/print_grid.html +++ b/frappe/public/js/frappe/views/reports/print_grid.html @@ -3,6 +3,10 @@

{{ __(title) }}


{% endif %} +{% if subtitle %} +{{ subtitle }} +
+{% endif %} @@ -24,7 +28,7 @@ {% for col in columns %} {% if col.name && col._id !== "_check" %} - {% var value = col.fieldname ? row[col.fieldname] : row[col.field]; %} + {% var value = col.fieldname ? row[col.fieldname] : row[col.id]; %}
{{ col.formatter diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index b7212a7181..626de0c5e1 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -2,10 +2,10 @@ // MIT License. See license.txt import DataTable from 'frappe-datatable'; -frappe.provide("frappe.views"); -frappe.provide("frappe.query_reports"); +frappe.provide('frappe.views'); +frappe.provide('frappe.query_reports'); -frappe.standard_pages["query-report"] = function() { +frappe.standard_pages['query-report'] = function() { var wrapper = frappe.container.add_page('query-report'); frappe.ui.make_app_page({ @@ -18,7 +18,7 @@ frappe.standard_pages["query-report"] = function() { parent: wrapper, }); - $(wrapper).bind("show", function() { + $(wrapper).bind('show', function() { frappe.query_report.show(); }); }; @@ -208,8 +208,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.toggle_message(true); const filters = this.get_filter_values(true); return new Promise(resolve => frappe.call({ - method: "frappe.desk.query_report.run", - type: "GET", + method: 'frappe.desk.query_report.run', + type: 'GET', args: { report_name: this.report_name, filters: filters @@ -232,18 +232,19 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { render_report(data) { this.columns = this.prepare_columns(data.columns); this.data = this.prepare_data(data.result); + this.tree_report = this.data.some(d => 'indent' in d); - // const columns = this.get_columns_for_datatable(); + const columns = this.get_visible_columns(); if (this.datatable) { - this.datatable.refresh(this.data, this.columns); + this.datatable.refresh(this.data, columns); return; } this.datatable = new DataTable(this.$report[0], { - columns: this.columns, + columns: columns, data: this.data, inlineFilters: true, - treeView: this.data.some(d => 'indent' in d), + treeView: this.tree_report, layout: 'fixed', events: { onRemoveColumn: () => this.save_user_settings(), @@ -278,7 +279,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { save_user_settings(clear_settings = false) { if (clear_settings) { - return frappe.model.user_settings.remove(this.report_name, 'column_order'); + return frappe.model.user_settings.save(this.report_name, 'column_order', []); } if (!this.datatable) return; const column_order = this.datatable.datamanager.getColumns(true).map(col => col.id); @@ -338,28 +339,15 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { }); } - get_columns_for_datatable() { + get_visible_columns() { // return columns according to user_settings - const columns = this.columns.map(df => { - return { - id: df.fieldname, - name: df.label, - width: df.width || null, - editable: false, - format: (value, row, column, data) => - frappe.format(value || '', df, - {for_print: false, always_show_decimals: true}, data) - }; - }); - - // return columns; if (this.user_settings.column_order && this.user_settings.column_order.length > 0) { return this.user_settings.column_order - .map(id => columns.find(col => col.id === id)) + .map(id => this.columns.find(col => col.id === id)) .filter(Boolean); } else { - return columns; + return this.columns; } } @@ -369,8 +357,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { if (raise && missing_mandatory.length > 0) { // this.chart_area.hide(); - // this.wrapper.find(".waiting-area").empty().toggle(false); - // this.$no_result.html(__("Please set filters")).show(); + // this.wrapper.find('.waiting-area').empty().toggle(false); + // this.$no_result.html(__('Please set filters')).show(); if (raise) { frappe.throw(__('Filter missing: {0}', [missing_mandatory.map(f => f.df.label).join(', ')])); } @@ -402,14 +390,17 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } print_report(print_settings) { - const columns = this.get_columns_for_print(); + const custom_format = this.report_settings.html_format || null; + const filters_html = this.get_filters_html_for_print(); + frappe.render_grid({ - template: this.report_settings.html_format || null, + template: custom_format, title: __(this.report_name), + subtitle: filters_html, print_settings: print_settings, filters: this.get_filter_values(), - data: this.get_data_for_print(), - columns: columns, + data: custom_format ? this.data : this.get_data_for_print(), + columns: custom_format ? this.columns: this.get_visible_columns(), report: this }); } @@ -417,49 +408,48 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { pdf_report(print_settings) { const base_url = frappe.urllib.get_base_url(); const print_css = frappe.boot.print_css; - const landscape = print_settings.orientation == "Landscape"; - const columns = this.columns; + const landscape = print_settings.orientation == 'Landscape'; - let html; - if (this.report_settings.html_format) { - const content = frappe.render(this.report_settings.html_format, { - data: this.get_data_for_print(), - filters: this.get_filter_values(), - report: this, - }); + const custom_format = this.report_settings.html_format || null; + const columns = custom_format ? this.columns : this.get_visible_columns(); + const data = custom_format ? this.data : this.get_data_for_print(); + const applied_filters = this.get_filter_values(); - //Render Report in HTML - html = frappe.render_template("print_template", { - title:__(this.report_name), - content: content, - base_url: base_url, - print_css: print_css, - print_settings: print_settings, - landscape: landscape, - columns: columns - }); - } else { - const content = frappe.render_template("print_grid", { - title: __(this.report_name), - data: this.get_data_for_print(), - columns: columns - }); + const filters_html = this.get_filters_html_for_print(); + const content = frappe.render_template(custom_format || 'print_grid', { + title: __(this.report_name), + subtitle: filters_html, + filters: applied_filters, + data: data, + columns: columns, + report: this + }); - //Render Report in HTML - html = frappe.render_template("print_template", { - content: content, - title: __(this.report_name), - base_url: base_url, - print_css: print_css, - print_settings: print_settings, - landscape: landscape, - columns: columns - }); - } + // Render Report in HTML + const html = frappe.render_template('print_template', { + title: __(this.report_name), + content: content, + base_url: base_url, + print_css: print_css, + print_settings: print_settings, + landscape: landscape, + columns: columns + }); frappe.render_pdf(html, print_settings); } + get_filters_html_for_print() { + const applied_filters = this.get_filter_values(); + return Object.keys(applied_filters) + .map(filter_name => { + const label = frappe.query_report_filters_by_name[filter_name].df.label; + const value = applied_filters[filter_name]; + return `
${__(label)}: ${value}
`; + }) + .join(''); + } + export_report() { if (this.export_dialog) { this.export_dialog.clear(); @@ -477,7 +467,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { }, ({ file_format }) => { if (file_format === 'CSV') { const column_row = this.columns.map(col => col.label); - const data = this.get_data_for_print(); + const data = this.get_data_for_csv(); const out = [column_row].concat(data); frappe.tools.downloadify(out, null, this.report_name); @@ -494,13 +484,18 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { open_url_post(frappe.request.url, args); } - }, __("Export Report: "+ this.report_name), __("Download")); + }, __('Export Report: '+ this.report_name), __('Download')); + } + + get_data_for_csv() { + const indices = this.datatable.datamanager.getFilteredRowIndices(); + const out = indices.map(i => this.datatable.datamanager.getRow(i).map(c => c.content)); + return out.map(row => row.slice(1)); } get_data_for_print() { const indices = this.datatable.datamanager.getFilteredRowIndices(); - const out = indices.map(i => this.datatable.datamanager.getRow(i).map(c => c.content)); - return out.map(row => row.slice(1)); + return indices.map(i => this.data[i]); } get_columns_for_print() { @@ -516,7 +511,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { }, { label: __('Edit'), - action: () => frappe.set_route("Form", "Report", this.report_name), + action: () => frappe.set_route('Form', 'Report', this.report_name), condition: () => frappe.user.is_report_manager(), standard: true }, @@ -557,7 +552,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { { label: __('User Permissions'), action: () => frappe.set_route('List', 'User Permission', { - doctype: "Report", + doctype: 'Report', name: this.report_name }), condition: () => frappe.model.can_set_user_permissions('Report'), @@ -578,7 +573,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { setup_page_head() { super.setup_page_head(); - this.page.set_title_sub(``); + this.page.set_title_sub(``); } setup_report_wrapper() { @@ -589,7 +584,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } message_div(message) { - return `
+ return `
${message}
`; } From ec903956066aca9a000c9e4e63be99e493134e9d Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Sat, 24 Mar 2018 15:31:01 +0530 Subject: [PATCH 24/34] [hotfix] mapper (#5267) --- frappe/public/js/frappe/form/save.js | 5 +++-- frappe/public/js/frappe/model/sync.js | 25 ++++++++++++++----------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/frappe/public/js/frappe/form/save.js b/frappe/public/js/frappe/form/save.js index 3aadb8c314..01c8c4d648 100644 --- a/frappe/public/js/frappe/form/save.js +++ b/frappe/public/js/frappe/form/save.js @@ -19,6 +19,7 @@ frappe.ui.form.save = function (frm, action, callback, btn) { var save = function () { remove_empty_rows(); + $(frm.wrapper).addClass('validated-form'); if (check_mandatory()) { _call({ @@ -127,9 +128,8 @@ frappe.ui.form.save = function (frm, action, callback, btn) { if (df.reqd && !frappe.model.has_value(doc.doctype, doc.name, df.fieldname)) { has_errors = true; error_fields[error_fields.length] = __(df.label); - // scroll to field - if (!me.scroll_set) { + if (!frm.scroll_set) { scroll_to(doc.parentfield || df.fieldname); } @@ -141,6 +141,7 @@ frappe.ui.form.save = function (frm, action, callback, btn) { } }); + if (error_fields.length) { if (doc.parenttype) { var message = __('Mandatory fields required in table {0}, Row {1}', diff --git a/frappe/public/js/frappe/model/sync.js b/frappe/public/js/frappe/model/sync.js index 284e9b2cb4..3ec64bd19b 100644 --- a/frappe/public/js/frappe/model/sync.js +++ b/frappe/public/js/frappe/model/sync.js @@ -99,32 +99,35 @@ $.extend(frappe.model, { } } }, - update_in_locals: function(d) { + update_in_locals: function(doc) { // update values in the existing local doc instead of replacing - let local_doc = locals[d.doctype][d.name]; - for (let fieldname in d) { + let local_doc = locals[doc.doctype][doc.name]; + for (let fieldname in doc) { if (local_doc[fieldname] instanceof Array) { // table - if (!(d[fieldname] instanceof Array)) { - d[fieldname] = []; + if (!(doc[fieldname] instanceof Array)) { + doc[fieldname] = []; } // child table, override each row and append new rows if required - for (let i=0; i < d[fieldname].length; i++ ) { + for (let i=0; i < doc[fieldname].length; i++ ) { + let d = doc[fieldname][i]; if (local_doc[fieldname][i]) { // row exists, just copy the values - Object.assign(local_doc[fieldname][i], d[fieldname][i]); + Object.assign(local_doc[fieldname][i], d); } else { - local_doc[fieldname].push(d[fieldname][i]); + local_doc[fieldname].push(d); + if (!d.parent) d.parent = doc.name; + frappe.model.add_to_locals(d); } } // remove extra rows - if (local_doc[fieldname].length > d[fieldname].length) { - local_doc[fieldname].length = d[fieldname].length; + if (local_doc[fieldname].length > doc[fieldname].length) { + local_doc[fieldname].length = doc[fieldname].length; } } else { // literal - local_doc[fieldname] = d[fieldname]; + local_doc[fieldname] = doc[fieldname]; } } } From 7a871230bba78751e5e2187855e5f46dfe8b01c3 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sat, 24 Mar 2018 16:07:10 +0600 Subject: [PATCH 25/34] bumped to version 10.1.8 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 9ce53286ab..eb2f865710 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json from .exceptions import * from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template -__version__ = '10.1.7' +__version__ = '10.1.8' __title__ = "Frappe Framework" local = Local() From d3521b182ac084b1194ed436034504bccc89c86e Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Sat, 24 Mar 2018 15:54:33 +0530 Subject: [PATCH 26/34] [fix] [sync.js] delete extra rows from locals --- frappe/public/js/frappe/model/sync.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frappe/public/js/frappe/model/sync.js b/frappe/public/js/frappe/model/sync.js index 3ec64bd19b..440b139964 100644 --- a/frappe/public/js/frappe/model/sync.js +++ b/frappe/public/js/frappe/model/sync.js @@ -123,7 +123,17 @@ $.extend(frappe.model, { // remove extra rows if (local_doc[fieldname].length > doc[fieldname].length) { + for (let i = doc[fieldname].length; i < local_doc[fieldname].length; i++) { + + // clear from local + let d = local_doc[fieldname][i]; + if (locals[d.doctype] && locals[d.doctype][d.name]) { + delete locals[d.doctype][d.name]; + } + } local_doc[fieldname].length = doc[fieldname].length; + + } } else { // literal From c943d34405bc094b90233969cff0c1acc543e842 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sat, 24 Mar 2018 16:47:24 +0600 Subject: [PATCH 27/34] bumped to version 10.1.9 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index eb2f865710..67d45baa71 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json from .exceptions import * from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template -__version__ = '10.1.8' +__version__ = '10.1.9' __title__ = "Frappe Framework" local = Local() From 0cff49c4607614d1a6dd519e55949251ab41a1db Mon Sep 17 00:00:00 2001 From: Achilles Rasquinha Date: Sun, 25 Mar 2018 22:35:46 +0530 Subject: [PATCH 28/34] socketio.js cleanup --- socketio.js | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/socketio.js b/socketio.js index 1febeaff1f..458563c9c2 100644 --- a/socketio.js +++ b/socketio.js @@ -49,33 +49,6 @@ io.on('connection', function (socket) { socket.user = cookie.parse(socket.request.headers.cookie).user_id; socket.files = {}; - // frappe namespace (temporary till webpack comes in, we can then reuse). - const frappe = { }; - frappe._ = { }; - - frappe._.compact = function (arr) { return arr.filter(Boolean) } - - // frappe.model - // Realtime Database Updates FTW! - function get_model_room (doctype, name, field) { - const site = get_site_name(socket) - const room = frappe._.compact([site, doctype, name, field]).join(":") - - return room - } - - socket.on('frappe.model:subscribe', function (params) { - const doctype = params.doctype - const name = params.name - const field = params.field - - const room = get_model_room(doctype, name, field) - - console.log('frappe.model: Subscribing ' + socket.user + ' to room ' + room); - socket.join(room); - }) - // end frappe.model - // frappe.chat socket.on("frappe.chat.room:subscribe", function (rooms) { if (!Array.isArray(rooms)) { @@ -90,6 +63,7 @@ io.on('connection', function (socket) { socket.join(room); } }); + socket.on("frappe.chat.message:typing", function (data) { const user = data.user; const room = get_chat_room(socket, data.room); @@ -119,9 +93,7 @@ io.on('connection', function (socket) { socket.join(get_site_room(socket)); } }); - - - + socket.on('disconnect', function () { delete socket.files; }) From bab4968a2895d1fff9178af0f9e1efce33313409 Mon Sep 17 00:00:00 2001 From: Achilles Rasquinha Date: Sun, 25 Mar 2018 22:40:04 +0530 Subject: [PATCH 29/34] check if ldap settings exists within context --- frappe/templates/includes/login/login.js | 26 +++++++++++++----------- frappe/www/login.html | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/frappe/templates/includes/login/login.js b/frappe/templates/includes/login/login.js index b424ec88b0..f478263bf7 100644 --- a/frappe/templates/includes/login/login.js +++ b/frappe/templates/includes/login/login.js @@ -56,19 +56,21 @@ login.bind_events = function() { return false; }); - $(".btn-ldap-login").on("click", function(){ - var args = {}; - args.cmd = "{{ ldap_settings.method }}"; - args.usr = ($("#login_email").val() || "").trim(); - args.pwd = $("#login_password").val(); - args.device = "desktop"; - if(!args.usr || !args.pwd) { - login.set_indicator("{{ _("Both login and password required") }}", 'red'); + {% if ldap_settings %} + $(".btn-ldap-login").on("click", function(){ + var args = {}; + args.cmd = "{{ ldap_settings.method }}"; + args.usr = ($("#login_email").val() || "").trim(); + args.pwd = $("#login_password").val(); + args.device = "desktop"; + if(!args.usr || !args.pwd) { + login.set_indicator("{{ _("Both login and password required") }}", 'red'); + return false; + } + login.call(args); return false; - } - login.call(args); - return false; - }); + }); + {% endif %} } diff --git a/frappe/www/login.html b/frappe/www/login.html index cd85d3086f..a0d82b2b66 100644 --- a/frappe/www/login.html +++ b/frappe/www/login.html @@ -26,7 +26,7 @@ - {% if ldap_settings.enabled %} + {% if ldap_settings and ldap_settings.enabled %} {% endif %} From faf9f6c2337647a7df6c1426df563bdcb8855744 Mon Sep 17 00:00:00 2001 From: Achilles Rasquinha Date: Sun, 25 Mar 2018 23:11:23 +0530 Subject: [PATCH 30/34] fix classType --- frappe/utils/error.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/utils/error.py b/frappe/utils/error.py index 7c4d13ac1f..2c66bd85e4 100644 --- a/frappe/utils/error.py +++ b/frappe/utils/error.py @@ -16,6 +16,7 @@ import cgitb import types import datetime import json +import six def make_error_snapshot(exception): if frappe.conf.disable_error_snapshot: @@ -49,7 +50,7 @@ def get_snapshot(exception, context=10): """ etype, evalue, etb = sys.exc_info() - if isinstance(etype, types.ClassType): + if isinstance(etype, six.class_types): etype = etype.__name__ # creates a snapshot dict with some basic information From e6c3d81df8cccad4a97d1c18774a15a9d465e257 Mon Sep 17 00:00:00 2001 From: Achilles Rasquinha Date: Mon, 26 Mar 2018 00:18:26 +0530 Subject: [PATCH 31/34] placeholder fix --- frappe/www/login.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/www/login.html b/frappe/www/login.html index a0d82b2b66..55fa50c740 100644 --- a/frappe/www/login.html +++ b/frappe/www/login.html @@ -16,7 +16,8 @@
From 6d8658decd3af2d03be3a41e34b50a2e9f47c240 Mon Sep 17 00:00:00 2001 From: Achilles Rasquinha Date: Mon, 26 Mar 2018 10:14:45 +0530 Subject: [PATCH 32/34] fix content hash --- frappe/utils/file_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py index 546381d1e8..4949b7dcfd 100644 --- a/frappe/utils/file_manager.py +++ b/frappe/utils/file_manager.py @@ -421,5 +421,6 @@ def get_random_filename(extn=None, content_type=None): @frappe.whitelist() def validate_filename(filename): - fname = get_file_name(filename, hashlib.md5(filename).hexdigest()[-6:]) + hash_ = get_content_hash(filename) + fname = get_file_name(filename, hash_[-6:]) return fname From ce7497bd189f83322f5e262c53a1cdea24312efe Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 26 Mar 2018 14:41:52 +0530 Subject: [PATCH 33/34] Sync childtables data, handle submin/cancel fail and fixed incorrect form dirty (#5277) * Sync childtables data, handle submin/cancel fail and fixed incorrect form dirty * minor fix --- frappe/public/js/frappe/model/sync.js | 8 ++++- frappe/public/js/legacy/form.js | 43 ++++++++++++++++----------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/frappe/public/js/frappe/model/sync.js b/frappe/public/js/frappe/model/sync.js index 440b139964..3d9bacaa2a 100644 --- a/frappe/public/js/frappe/model/sync.js +++ b/frappe/public/js/frappe/model/sync.js @@ -103,11 +103,17 @@ $.extend(frappe.model, { // update values in the existing local doc instead of replacing let local_doc = locals[doc.doctype][doc.name]; for (let fieldname in doc) { - if (local_doc[fieldname] instanceof Array) { + let df = frappe.meta.get_field(doc.doctype, fieldname); + if (df && df.fieldtype === 'Table') { // table if (!(doc[fieldname] instanceof Array)) { doc[fieldname] = []; } + + if (!(local_doc[fieldname] instanceof Array)) { + local_doc[fieldname] = []; + } + // child table, override each row and append new rows if required for (let i=0; i < doc[fieldname].length; i++ ) { let d = doc[fieldname][i]; diff --git a/frappe/public/js/legacy/form.js b/frappe/public/js/legacy/form.js index 96b2513f97..696ab4f01f 100644 --- a/frappe/public/js/legacy/form.js +++ b/frappe/public/js/legacy/form.js @@ -195,12 +195,14 @@ _f.Frm.prototype.watch_model_updates = function() { frappe.model.on(me.doctype, "*", function(fieldname, value, doc) { // set input if(doc.name===me.docname) { - if ((value==='' || value===null) && !doc[value]) { + if ((value==='' || value===null) && !doc[fieldname]) { // both the incoming and outgoing values are falsy // the texteditor, summernote, changes nulls to empty strings on render, // so ignore those changes } else { - me.dirty(); + if (value != doc[fieldname]) { + me.dirty(); + } } me.fields_dict[fieldname] && me.fields_dict[fieldname].refresh(fieldname); @@ -761,53 +763,60 @@ _f.Frm.prototype._save = function(save_action, callback, btn, on_error, resolve) _f.Frm.prototype.savesubmit = function(btn, callback, on_error) { var me = this; + let handle_fail = () => { + $(btn).prop('disabled', false); + if (on_error) { + on_error(); + } + } + return new Promise(resolve => { this.validate_form_action("Submit"); frappe.confirm(__("Permanently Submit {0}?", [this.docname]), function() { frappe.validated = true; me.script_manager.trigger("before_submit").then(function() { if(!frappe.validated) { - if(on_error) { - on_error(); - } + handle_fail(); return; } me.save('Submit', function(r) { if(r.exc) { - if (on_error) { - on_error(); - } + handle_fail(); } else { frappe.utils.play_sound("submit"); callback && callback(); me.script_manager.trigger("on_submit") .then(() => resolve(me)); } - }, btn, on_error, resolve); + }, btn, () => handle_fail(), resolve); }); - }, on_error); + }, () => handle_fail() ); }); }; _f.Frm.prototype.savecancel = function(btn, callback, on_error) { var me = this; + + let handle_fail = () => { + $(btn).prop('disabled', false); + if (on_error) { + on_error(); + } + } + this.validate_form_action('Cancel'); frappe.confirm(__("Permanently Cancel {0}?", [this.docname]), function() { frappe.validated = true; me.script_manager.trigger("before_cancel").then(function() { if(!frappe.validated) { - if(on_error) { - on_error(); - } + handle_fail(); return; } var after_cancel = function(r) { if(r.exc) { - if (on_error) { - on_error(); - } + handle_fail(); } else { frappe.utils.play_sound("cancel"); me.refresh(); @@ -817,7 +826,7 @@ _f.Frm.prototype.savecancel = function(btn, callback, on_error) { }; frappe.ui.form.save(me, "cancel", after_cancel, btn); }); - }, on_error); + }, () => handle_fail()); }; // delete the record From 6bf29f5025db2edce992fee5c25fa2cf926ddeee Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 26 Mar 2018 15:12:50 +0600 Subject: [PATCH 34/34] bumped to version 10.1.10 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 67d45baa71..e13442b94b 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json from .exceptions import * from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template -__version__ = '10.1.9' +__version__ = '10.1.10' __title__ = "Frappe Framework" local = Local()