From b56e6fe4db38e0f1bb445f38d2b807f7a351b3c6 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 21 Dec 2021 14:57:09 +0530 Subject: [PATCH 01/17] fix: Safe decode base64 data in case of incorrect padding This could possibly happen in case of corrupted/partial files. But for the most part, if there's a missing trailing = or three at the end too. Traceback: Traceback (most recent call last): File "/home/frappe/frappe-bench/apps/frappe/frappe/app.py", line 68, in application response = frappe.api.handle() File "/home/frappe/frappe-bench/apps/frappe/frappe/api.py", line 55, in handle return frappe.handler.handle() File "/home/frappe/frappe-bench/apps/frappe/frappe/handler.py", line 31, in handle data = execute_cmd(cmd) File "/home/frappe/frappe-bench/apps/frappe/frappe/handler.py", line 67, in execute_cmd return frappe.call(method, **frappe.form_dict) File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1208, in call return fn(*args, **newargs) File "/home/frappe/frappe-bench/apps/frappe/frappe/desk/form/save.py", line 21, in savedocs doc.save() File "/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py", line 285, in save return self._save(*args, **kwargs) File "/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py", line 307, in _save self.insert() File "/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py", line 239, in insert self._validate() File "/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py", line 499, in _validate self._extract_images_from_text_editor() File "/home/frappe/frappe-bench/apps/frappe/frappe/model/base_document.py", line 983, in _extract_images_from_text_editor extract_images_from_doc(self, df.fieldname) File "/home/frappe/frappe-bench/apps/frappe/frappe/core/doctype/file/file.py", line 779, in extract_images_from_doc content = extract_images_from_html(doc, content) File "/home/frappe/frappe-bench/apps/frappe/frappe/core/doctype/file/file.py", line 822, in extract_images_from_html content = re.sub(r']*src\s*=\s*["\'](?=data:)(.*?)["\']', _save_file, content) File "/home/frappe/frappe-bench/env/lib/python3.6/re.py", line 191, in sub return _compile(pattern, flags).sub(repl, string, count) File "/home/frappe/frappe-bench/apps/frappe/frappe/core/doctype/file/file.py", line 814, in _save_file _file.save(ignore_permissions=True) File "/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py", line 285, in save return self._save(*args, **kwargs) File "/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py", line 307, in _save self.insert() File "/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py", line 231, in insert self.run_method("before_insert") File "/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py", line 860, in run_method out = Document.hook(fn)(self, *args, **kwargs) File "/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py", line 1158, in composer return composed(self, method, *args, **kwargs) File "/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py", line 1141, in runner add_to_return_value(self, fn(self, *args, **kwargs)) File "/home/frappe/frappe-bench/apps/frappe/frappe/model/document.py", line 854, in fn = lambda self, *args, **kwargs: getattr(self, method)(*args, **kwargs) File "/home/frappe/frappe-bench/apps/frappe/frappe/core/doctype/file/file.py", line 56, in before_insert self.save_file(content=self.content, decode=self.decode) File "/home/frappe/frappe-bench/apps/frappe/frappe/core/doctype/file/file.py", line 436, in save_file self.content = base64.b64decode(self.content) File "/home/frappe/frappe-bench/env/lib/python3.6/base64.py", line 87, in b64decode return binascii.a2b_base64(s) binascii.Error: Incorrect padding --- frappe/core/doctype/file/file.py | 5 +++-- frappe/utils/file_manager.py | 20 +++++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 91090bdd77..adf10b9a03 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -29,6 +29,7 @@ from frappe import _, conf, safe_decode from frappe.model.document import Document from frappe.utils import call_hook_method, cint, cstr, encode, get_files_path, get_hook_method, random_string, strip from frappe.utils.image import strip_exif_data, optimize_image +from frappe.utils.file_manager import safe_b64decode class MaxFileSizeReachedError(frappe.ValidationError): pass @@ -436,7 +437,7 @@ class File(Document): if b"," in self.content: self.content = self.content.split(b",")[1] - self.content = base64.b64decode(self.content) + self.content = safe_b64decode(self.content) if not self.is_private: self.is_private = 0 @@ -852,7 +853,7 @@ def extract_images_from_html(doc, content, is_private=False): content = content.encode("utf-8") if b"," in content: content = content.split(b",")[1] - content = base64.b64decode(content) + content = safe_b64decode(content) content = optimize_image(content, mtype) diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py index 56394442f3..1e654d7881 100644 --- a/frappe/utils/file_manager.py +++ b/frappe/utils/file_manager.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE import frappe @@ -17,6 +17,20 @@ class MaxFileSizeReachedError(frappe.ValidationError): pass +def safe_b64decode(binary: bytes) -> bytes: + """Adds padding if doesn't already exist before decoding. + + This attempts to avoid the `binascii.Error: Incorrect padding` error raised + when the number of trailing = is simply not enough :crie:. Although, it may + be an indication of corrupted data. + + Refs: + * https://en.wikipedia.org/wiki/Base64 + * https://stackoverflow.com/questions/2941995/python-ignore-incorrect-padding-error-when-base64-decoding + """ + return base64.b64decode(binary + b"===") + + def get_file_url(file_data_name): data = frappe.db.get_value("File", file_data_name, ["file_name", "file_url"], as_dict=True) return data.file_url or data.file_name @@ -112,7 +126,7 @@ def get_uploaded_content(): if 'filedata' in frappe.form_dict: if "," in frappe.form_dict.filedata: frappe.form_dict.filedata = frappe.form_dict.filedata.rsplit(",", 1)[1] - frappe.uploaded_content = base64.b64decode(frappe.form_dict.filedata) + frappe.uploaded_content = safe_b64decode(frappe.form_dict.filedata) frappe.uploaded_filename = frappe.form_dict.filename return frappe.uploaded_filename, frappe.uploaded_content else: @@ -126,7 +140,7 @@ def save_file(fname, content, dt, dn, folder=None, decode=False, is_private=0, d if b"," in content: content = content.split(b",")[1] - content = base64.b64decode(content) + content = safe_b64decode(content) file_size = check_max_file_size(content) content_hash = get_content_hash(content) From ac9094ac8a08580dc8ae5931507f556cfa25f479 Mon Sep 17 00:00:00 2001 From: hrwx Date: Wed, 22 Dec 2021 14:18:33 +0000 Subject: [PATCH 02/17] chore: update msg_print messages --- frappe/www/update-password.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/www/update-password.html b/frappe/www/update-password.html index 7ac88d04b0..a7254b3fd6 100644 --- a/frappe/www/update-password.html +++ b/frappe/www/update-password.html @@ -69,14 +69,14 @@ frappe.ready(function() { const confirm_password = $('#confirm_password').val() if (!args.old_password && !args.key) { frappe.msgprint({ - title: "{{ _('Message') }}", + title: "{{ _('Missing Value Required') }}", message: "{{ _('Old Password Required.') }}", clear: true }); } if (!args.new_password) { frappe.msgprint({ - title: "{{ _('Message') }}", + title: "{{ _('Missing Value Required') }}", message: "{{ _('New Password Required.') }}", clear: true }); @@ -110,8 +110,8 @@ frappe.ready(function() { .html("{{ _('Status Updated') }}"); if(r.message) { frappe.msgprint({ - title: "{{ _('Message') }}", - message: "{{ _('Password Updated') }}", + title: "{{ _('Password changed') }}", + message: "{{ _('Password has been changed successfully.') }}", // password is updated successfully // clear any server message clear: true From f123ed09b5cf817bb18cd35c0a4d620576c37d36 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Wed, 22 Dec 2021 20:08:15 +0000 Subject: [PATCH 03/17] chore: update title and messages for frappe.msgprint Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- frappe/www/update-password.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frappe/www/update-password.html b/frappe/www/update-password.html index a7254b3fd6..15cdd6ffc2 100644 --- a/frappe/www/update-password.html +++ b/frappe/www/update-password.html @@ -69,15 +69,15 @@ frappe.ready(function() { const confirm_password = $('#confirm_password').val() if (!args.old_password && !args.key) { frappe.msgprint({ - title: "{{ _('Missing Value Required') }}", - message: "{{ _('Old Password Required.') }}", + title: "{{ _('Missing Value') }}", + message: "{{ _('Please enter your old password.') }}", clear: true }); } if (!args.new_password) { frappe.msgprint({ - title: "{{ _('Missing Value Required') }}", - message: "{{ _('New Password Required.') }}", + title: "{{ _('Missing Value') }}", + message: "{{ _('Please enter your new password.') }}", clear: true }); } @@ -110,8 +110,8 @@ frappe.ready(function() { .html("{{ _('Status Updated') }}"); if(r.message) { frappe.msgprint({ - title: "{{ _('Password changed') }}", - message: "{{ _('Password has been changed successfully.') }}", + title: "{{ _('Password set') }}", + message: "{{ _('Your new password has been set successfully.') }}", // password is updated successfully // clear any server message clear: true From 074d0b498bd4ebeadd1908c7643433dfd4c56f68 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 23 Dec 2021 12:09:51 +0530 Subject: [PATCH 04/17] refactor: show dashboard section on particular tabs (set options = Dashboard) --- frappe/public/js/frappe/form/form.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 8af1631b48..821fcba95f 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -211,12 +211,24 @@ frappe.ui.form.Form = class FrappeForm { this.fields = this.layout.fields_list; let dashboard_parent = $('
'); + let dashboard_added = false; if (this.layout.tabs.length) { - this.layout.tabs[0].wrapper.prepend(dashboard_parent); + this.layout.tabs.every(tab => { + if (tab.df.options === 'Dashboard') { + tab.wrapper.prepend(dashboard_parent); + dashboard_added = true; + return false; + } + return true; + }); + if (!dashboard_added) { + this.layout.tabs[0].wrapper.prepend(dashboard_parent); + } } else { dashboard_parent.insertAfter(this.layout.wrapper.find('.form-message')); } + this.dashboard = new frappe.ui.form.Dashboard(dashboard_parent, this); this.tour = new frappe.ui.form.FormTour({ From 27f8a1ea56fd56a3af43ccaa6a26828aef5d0472 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 23 Dec 2021 18:34:36 +0530 Subject: [PATCH 05/17] refactor: Fractional ratings patch * Change column types of rating fields * Workaround added for truncated/NULL values Co-authored-by: Ankush Menat Co-authored-by: Suraj Shetty --- .../patches/v14_0/save_ratings_in_fraction.py | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/frappe/patches/v14_0/save_ratings_in_fraction.py b/frappe/patches/v14_0/save_ratings_in_fraction.py index bdc5dfee3d..15a8c9ddf1 100644 --- a/frappe/patches/v14_0/save_ratings_in_fraction.py +++ b/frappe/patches/v14_0/save_ratings_in_fraction.py @@ -1,12 +1,44 @@ import frappe +from frappe.query_builder import DocType + def execute(): - rating_fields = frappe.get_all("DocField", fields=["parent", "fieldname"], filters={"fieldtype": "Rating"}) + rating_fields = frappe.get_all( + "DocField", fields=["parent", "fieldname"], filters={"fieldtype": "Rating"} + ) - custom_rating_fields = frappe.get_all("Custom Field", fields=["dt", "fieldname"], filters={"fieldtype": "Rating"}) + custom_rating_fields = frappe.get_all( + "Custom Field", fields=["dt", "fieldname"], filters={"fieldtype": "Rating"} + ) - for field in rating_fields + custom_rating_fields: - doctype_name = field.get("parent") or field.get("dt") - doctype = frappe.qb.DocType(doctype_name) - field = field.fieldname - (frappe.qb.update(doctype_name).set(doctype[field], doctype[field]/5)).run() + for _field in rating_fields + custom_rating_fields: + doctype_name = _field.get("parent") or _field.get("dt") + doctype = DocType(doctype_name) + field = _field.fieldname + + # update NULL values to 0 to avoid data truncated error (temp) + # commit for upcoming DLL + frappe.qb.update(doctype).set( + doctype[field], 0 + ).where( + doctype[field].isnull() + ).run() + frappe.db.commit() + + # alter column types for rating fieldtype + frappe.db.change_column_type(doctype_name, column=field, type="decimal(3,2)") + + # update data: int => decimal + frappe.qb.update(doctype).set( + doctype[field], doctype[field] / 5 + ).run() + + # revert 0 to NULL conversion + frappe.qb.update(doctype).set( + doctype[field], None + ).where( + doctype[field] == 0 + ).run() + + # commit to flush updated rows + frappe.db.commit() From fd11b0e8e76b18ab77d884c9dd46ffa9c4774e22 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 23 Dec 2021 18:40:53 +0530 Subject: [PATCH 06/17] fix: Re-run patch to ensure column conversion Co-authored-by: Ankush Menat Co-authored-by: Suraj Shetty --- frappe/patches.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/patches.txt b/frappe/patches.txt index 1760a04f35..27ba1a145d 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -188,5 +188,5 @@ frappe.patches.v14_0.copy_mail_data #08.03.21 frappe.patches.v14_0.update_workspace2 # 20.09.2021 frappe.patches.v14_0.update_github_endpoints #08-11-2021 frappe.patches.v14_0.remove_db_aggregation -frappe.patches.v14_0.save_ratings_in_fraction +frappe.patches.v14_0.save_ratings_in_fraction #23-12-2021 frappe.patches.v14_0.update_color_names_in_kanban_board_column From 141f4922e7f132e9c6845a77fb47c71bb38d8d13 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 23 Dec 2021 19:09:22 +0530 Subject: [PATCH 07/17] fix: Skip patch if column already converted Co-authored-by: Ankush Menat Co-authored-by: Suraj Shetty --- frappe/patches/v14_0/save_ratings_in_fraction.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frappe/patches/v14_0/save_ratings_in_fraction.py b/frappe/patches/v14_0/save_ratings_in_fraction.py index 15a8c9ddf1..c37d3f134b 100644 --- a/frappe/patches/v14_0/save_ratings_in_fraction.py +++ b/frappe/patches/v14_0/save_ratings_in_fraction.py @@ -3,6 +3,7 @@ from frappe.query_builder import DocType def execute(): + RATING_FIELD_TYPE = "decimal(3,2)" rating_fields = frappe.get_all( "DocField", fields=["parent", "fieldname"], filters={"fieldtype": "Rating"} ) @@ -16,6 +17,13 @@ def execute(): doctype = DocType(doctype_name) field = _field.fieldname + # TODO: Add postgres support (for the check) + if ( + frappe.conf.db_type == "mariadb" + and frappe.db.get_column_type(doctype_name, field) == RATING_FIELD_TYPE + ): + continue + # update NULL values to 0 to avoid data truncated error (temp) # commit for upcoming DLL frappe.qb.update(doctype).set( @@ -26,7 +34,7 @@ def execute(): frappe.db.commit() # alter column types for rating fieldtype - frappe.db.change_column_type(doctype_name, column=field, type="decimal(3,2)") + frappe.db.change_column_type(doctype_name, column=field, type=RATING_FIELD_TYPE) # update data: int => decimal frappe.qb.update(doctype).set( From 442e58415b9d96b3d05f9cbe748c74d026016a2c Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 23 Dec 2021 21:29:14 +0530 Subject: [PATCH 08/17] fix: mutating route history while get_last_doc co-authored-by: @skjbulcher --- frappe/public/js/frappe/utils/address_and_contact.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/utils/address_and_contact.js b/frappe/public/js/frappe/utils/address_and_contact.js index 3b05e5f0bb..61339d4e24 100644 --- a/frappe/public/js/frappe/utils/address_and_contact.js +++ b/frappe/public/js/frappe/utils/address_and_contact.js @@ -29,7 +29,7 @@ $.extend(frappe.contacts, { } }, get_last_doc: function(frm) { - const reverse_routes = frappe.route_history.reverse(); + const reverse_routes = frappe.route_history.slice().reverse(); const last_route = reverse_routes.find(route => { return route[0] === 'Form' && route[1] !== frm.doctype }) From 059e1cc36493c490a790e45582465d15a8b3d76c Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 23 Dec 2021 21:49:45 +0530 Subject: [PATCH 09/17] fix: Report boilerplate (js, py) year to current year (#15429) Co-authored-by: mymi14s --- frappe/core/doctype/report/boilerplate/controller.js | 2 +- frappe/core/doctype/report/boilerplate/controller.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/report/boilerplate/controller.js b/frappe/core/doctype/report/boilerplate/controller.js index 5148f34462..9cf71a8c09 100644 --- a/frappe/core/doctype/report/boilerplate/controller.js +++ b/frappe/core/doctype/report/boilerplate/controller.js @@ -1,4 +1,4 @@ -// Copyright (c) 2016, {app_publisher} and contributors +// Copyright (c) {year}, {app_publisher} and contributors // For license information, please see license.txt /* eslint-disable */ diff --git a/frappe/core/doctype/report/boilerplate/controller.py b/frappe/core/doctype/report/boilerplate/controller.py index ccf732a405..72da0c7ce5 100644 --- a/frappe/core/doctype/report/boilerplate/controller.py +++ b/frappe/core/doctype/report/boilerplate/controller.py @@ -1,5 +1,5 @@ -# Copyright (c) 2013, {app_publisher} and contributors -# License: MIT. See LICENSE +# Copyright (c) {year}, {app_publisher} and contributors +# For license information, please see license.txt # import frappe From 7e34c27690efa57e567c33d723c96658f4629d10 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 24 Dec 2021 00:20:57 +0530 Subject: [PATCH 10/17] fix: Incorrect timespan daterange calculation (backport #15369) (#15431) Co-authored-by: Kartik Sharma --- frappe/desk/page/leaderboard/leaderboard.js | 8 ++++---- frappe/utils/data.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frappe/desk/page/leaderboard/leaderboard.js b/frappe/desk/page/leaderboard/leaderboard.js index 076d672db5..aa1678af37 100644 --- a/frappe/desk/page/leaderboard/leaderboard.js +++ b/frappe/desk/page/leaderboard/leaderboard.js @@ -382,10 +382,10 @@ class Leaderboard { let timespan = this.options.selected_timespan.toLowerCase(); let current_date = frappe.datetime.now_date(); let date_range_map = { - "this week": [frappe.datetime.week_start(), current_date], - "this month": [frappe.datetime.month_start(), current_date], - "this quarter": [frappe.datetime.quarter_start(), current_date], - "this year": [frappe.datetime.year_start(), current_date], + "this week": [frappe.datetime.week_start(), frappe.datetime.week_end()], + "this month": [frappe.datetime.month_start(), frappe.datetime.month_end()], + "this quarter": [frappe.datetime.quarter_start(), frappe.datetime.quarter_end()], + "this year": [frappe.datetime.year_start(), frappe.datetime.year_end()], "last week": [frappe.datetime.add_days(current_date, -7), current_date], "last month": [frappe.datetime.add_months(current_date, -1), current_date], "last quarter": [frappe.datetime.add_months(current_date, -3), current_date], diff --git a/frappe/utils/data.py b/frappe/utils/data.py index de0242df07..206f0eac64 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -507,10 +507,10 @@ def get_timespan_date_range(timespan): "yesterday": lambda: (add_to_date(today, days=-1),) * 2, "today": lambda: (today, today), "tomorrow": lambda: (add_to_date(today, days=1),) * 2, - "this week": lambda: (get_first_day_of_week(today), today), - "this month": lambda: (get_first_day(today), today), - "this quarter": lambda: (get_quarter_start(today), today), - "this year": lambda: (get_year_start(today), today), + "this week": lambda: (get_first_day_of_week(today), get_last_day_of_week(today)), + "this month": lambda: (get_first_day(today), get_last_day(today)), + "this quarter": lambda: (get_quarter_start(today), get_quarter_ending(today)), + "this year": lambda: (get_year_start(today), get_year_ending(today)), "next week": lambda: (get_first_day_of_week(add_to_date(today, days=7)), get_last_day_of_week(add_to_date(today, days=7))), "next month": lambda: (get_first_day(add_to_date(today, months=1)), get_last_day(add_to_date(today, months=1))), "next quarter": lambda: (get_quarter_start(add_to_date(today, months=3)), get_quarter_ending(add_to_date(today, months=3))), From 33b7d7d74f0c97dc43e297ae260d7fd30cfc7959 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 24 Dec 2021 13:28:40 +0530 Subject: [PATCH 11/17] fix: Handle custom child tables via check_parent_permission Check `tabCustom Field` and `tabDocfield` for matching parent existence. --- frappe/model/db_query.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 16c0d18d9f..6d261dd4fd 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -787,12 +787,15 @@ class DatabaseQuery(object): def check_parent_permission(parent, child_doctype): if parent: # User may pass fake parent and get the information from the child table - if child_doctype and not frappe.db.exists('DocField', - {'parent': parent, 'options': child_doctype}): + if child_doctype and not ( + frappe.db.exists('DocField', {'parent': parent, 'options': child_doctype}) + or frappe.db.exists('Custom Field', {'dt': parent, 'options': child_doctype}) + ): raise frappe.PermissionError if frappe.permissions.has_permission(parent): return + # Either parent not passed or the user doesn't have permission on parent doctype of child table! raise frappe.PermissionError From 3b7b555148d8ffa9a4f10ffcb52855a28b3879ae Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 24 Dec 2021 13:57:07 +0530 Subject: [PATCH 12/17] fix: Pass parent_doctype required for DatabaseQuery perm checks has_child_table_permission would throw 'Parent DocType Required: Please specify a valid parent DocType for {TEST DOCTYPE}' after the previous commit 33b7d7d74f0c97dc43e297ae260d7fd30cfc7959. Passing parent_doctype solves this --- frappe/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/client.py b/frappe/client.py index 6641e471af..e835e7fee7 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -32,6 +32,7 @@ def get_list(doctype, fields=None, filters=None, order_by=None, args = frappe._dict( doctype=doctype, + parent_doctype=parent, fields=fields, filters=filters, or_filters=or_filters, From 84ebdabe49fc9447acead8300808b4af3387974a Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 24 Dec 2021 13:59:57 +0530 Subject: [PATCH 13/17] refactor(minor): frappe.has_permission The throw block was very clearly broken. Referencing frappe.throw inside __init__.py rip. Added drop in replacement msgprint call --- frappe/__init__.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 96aa2b1b5f..08c0f794b3 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -740,17 +740,26 @@ def has_permission(doctype=None, ptype="read", doc=None, user=None, verbose=Fals :param doc: [optional] Checks User permissions for given doc. :param user: [optional] Check for given user. Default: current user. :param parent_doctype: Required when checking permission for a child DocType (unless doc is specified).""" + import frappe.permissions + if not doctype and doc: doctype = doc.doctype - import frappe.permissions out = frappe.permissions.has_permission(doctype, ptype, doc=doc, verbose=verbose, user=user, raise_exception=throw, parent_doctype=parent_doctype) + if throw and not out: - if doc: - frappe.throw(_("No permission for {0}").format(doc.doctype + " " + doc.name)) - else: - frappe.throw(_("No permission for {0}").format(doctype)) + # mimics frappe.throw + document_label = f"{doc.doctype} {doc.name}" if doc else doctype + msgprint( + _("No permission for {0}").format(document_label), + raise_exception=ValidationError, + title=None, + indicator='red', + is_minimizable=None, + wide=None, + as_list=False + ) return out From 0f71dd411bbbb3a3eaa30150f4d0a7acc4971e55 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 24 Dec 2021 14:01:26 +0530 Subject: [PATCH 14/17] style: DatabaseQuery.execute's permission cond block --- frappe/model/db_query.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 6d261dd4fd..cb2c2af898 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -36,10 +36,12 @@ class DatabaseQuery(object): ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False, update=None, add_total_row=None, user_settings=None, reference_doctype=None, run=True, strict=True, pluck=None, ignore_ddl=False, parent_doctype=None) -> List: - if not ignore_permissions and \ - not frappe.has_permission(self.doctype, "select", user=user, parent_doctype=parent_doctype) and \ - not frappe.has_permission(self.doctype, "read", user=user, parent_doctype=parent_doctype): + if ( + not ignore_permissions + and not frappe.has_permission(self.doctype, "select", user=user, parent_doctype=parent_doctype) + and not frappe.has_permission(self.doctype, "read", user=user, parent_doctype=parent_doctype) + ): frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(self.doctype)) raise frappe.PermissionError(self.doctype) From a2a6e112eef3a48020688f7a46d97010deb89516 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 24 Dec 2021 14:42:32 +0530 Subject: [PATCH 15/17] refactor(UI): Single card design for form --- frappe/public/js/frappe/form/form.js | 4 ++-- frappe/public/scss/common/form.scss | 4 ++-- frappe/public/scss/common/grid.scss | 14 ++++++++---- frappe/public/scss/desk/form.scss | 34 ++++++++++++++++++---------- 4 files changed, 35 insertions(+), 21 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 821fcba95f..9a75e510da 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -190,7 +190,7 @@ frappe.ui.form.Form = class FrappeForm { setup_std_layout() { this.form_wrapper = $('
').appendTo(this.layout_main); - this.body = $('
').appendTo(this.form_wrapper); + this.body = $('
').appendTo(this.form_wrapper); // only tray this.meta.section_style='Simple'; // always simple! @@ -226,7 +226,7 @@ frappe.ui.form.Form = class FrappeForm { this.layout.tabs[0].wrapper.prepend(dashboard_parent); } } else { - dashboard_parent.insertAfter(this.layout.wrapper.find('.form-message')); + this.layout.wrapper.find('.form-page').prepend(dashboard_parent); } this.dashboard = new frappe.ui.form.Dashboard(dashboard_parent, this); diff --git a/frappe/public/scss/common/form.scss b/frappe/public/scss/common/form.scss index 2fa6ab619e..43ba477624 100644 --- a/frappe/public/scss/common/form.scss +++ b/frappe/public/scss/common/form.scss @@ -1,4 +1,5 @@ .form-control { + height: inherit; border: none; font-size: var(--text-md); position: relative; @@ -13,10 +14,9 @@ font-weight: normal; font-size: var(--text-sm); } - min-height: var(--input-height); border-radius: $border-radius; font-weight: 400; - padding: 8px 12px; + padding: 6px 12px; cursor: default; color: var(--disabled-text-color); background-color: var(--disabled-control-bg); diff --git a/frappe/public/scss/common/grid.scss b/frappe/public/scss/common/grid.scss index 3014211222..1903413fbb 100644 --- a/frappe/public/scss/common/grid.scss +++ b/frappe/public/scss/common/grid.scss @@ -79,10 +79,9 @@ .grid-static-col, .row-index { - height: 39px; - padding: var(--padding-sm) var(--padding-md); + height: 34px; + padding: 8px; max-height: 200px; - // border-right: 1px solid var(--border-color); } .grid-row-check { @@ -108,6 +107,7 @@ .grid-row > .row { .col:last-child { margin-right: calc(-1 * var(--margin-sm)); + border-right: none; } .col { @@ -149,7 +149,7 @@ } textarea { - height: 40px !important; + height: 37px !important; } .form-control { @@ -157,7 +157,7 @@ border: 0px; padding-top: 8px; padding-bottom: 9px; - height: 40px; + height: 34px; } .link-btn { @@ -196,6 +196,10 @@ } } + .grid-static-col[data-fieldtype="Check"] .static-area { + padding-top: 2px; + } + .grid-static-col[data-fieldtype="Rating"] .field-area { margin-top: 1rem; margin-left: 1rem; diff --git a/frappe/public/scss/desk/form.scss b/frappe/public/scss/desk/form.scss index d48987b500..7e7d6170c9 100644 --- a/frappe/public/scss/desk/form.scss +++ b/frappe/public/scss/desk/form.scss @@ -1,6 +1,12 @@ @import "../common/form.scss"; @import '~cropperjs/dist/cropper.min'; +.std-form-layout > .form-layout > .form-page { + border-radius: var(--border-radius-md); + box-shadow: var(--card-shadow); + background-color: var(--card-bg); +} + .form-section, .form-dashboard-section { margin: 0px; @@ -12,6 +18,7 @@ .section-head { @extend .head-title; + font-size: var(--text-base); width: 100%; padding: var(--padding-sm) var(--padding-md); margin: 0; @@ -47,8 +54,12 @@ .form-section.card-section, .form-dashboard-section { - margin-bottom: var(--margin-lg); - @extend .frappe-card; + border-bottom: 1px solid var(--gray-200); + padding: var(--padding-xs); +} + +.row.form-section.card-section.visible-section:last-child { + border-bottom: none; } .form-dashboard-section { @@ -57,9 +68,8 @@ } .section-body { display: block; - padding-left: var(--padding-md); - padding-right: var(--padding-md); - padding-bottom: var(--padding-md); + padding: var(--padding-md); + padding-top: 0; } } @@ -85,7 +95,8 @@ .comment-box { @include card(); - padding: 25px var(--padding-xl); + margin-top: var(--margin-lg); + padding: var(--padding-lg); .comment-input-header { @extend .head-title; margin-bottom: var(--margin-sm); @@ -304,19 +315,18 @@ } .form-tabs-list { - margin-bottom: var(--margin-lg); + padding-left: var(--padding-xs); + border-bottom: 1px solid var(--gray-200); .form-tabs { .nav-item { .nav-link { - padding-bottom: var(--padding-md); color: var(--gray-700); - padding-left: 0; - padding-right: 0; - margin-right: var(--margin-xl); + padding: var(--padding-md) 0; + margin: 0 var(--margin-md); &.active { - font-weight: 500; + font-weight: 600; border-bottom: 1px solid var(--primary); color: var(--text-color); } From 600156b1a17b12b86bc6fa94706f49738048d857 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 24 Dec 2021 15:29:19 +0530 Subject: [PATCH 16/17] refactor!: allow modified column to be nullable * updated change_column_type to allow making columns nullable. * breaking change: in postgres the method was previously nullable by default, changed it to be consistent with mariadb. --- frappe/database/mariadb/database.py | 5 +++-- frappe/database/postgres/database.py | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index afd912bc6b..6b827a4e89 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -135,9 +135,10 @@ class MariaDBDatabase(Database): table_name = get_table_name(doctype) return self.sql(f"DESC `{table_name}`") - def change_column_type(self, doctype: str, column: str, type: str) -> Union[List, Tuple]: + def change_column_type(self, doctype: str, column: str, type: str, nullable: bool = False) -> Union[List, Tuple]: table_name = get_table_name(doctype) - return self.sql(f"ALTER TABLE `{table_name}` MODIFY `{column}` {type} NOT NULL") + null_constraint = "NOT NULL" if not nullable else "" + return self.sql(f"ALTER TABLE `{table_name}` MODIFY `{column}` {type} {null_constraint}") # exception types @staticmethod diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index 3cea1440cf..008635b1b3 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -183,9 +183,12 @@ class PostgresDatabase(Database): table_name = get_table_name(doctype) return self.sql(f"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME = '{table_name}'") - def change_column_type(self, doctype: str, column: str, type: str) -> Union[List, Tuple]: + def change_column_type(self, doctype: str, column: str, type: str, nullable: bool = False) -> Union[List, Tuple]: table_name = get_table_name(doctype) - return self.sql(f'ALTER TABLE "{table_name}" ALTER COLUMN "{column}" TYPE {type}') + null_constraint = "SET NOT NULL" if not nullable else "DROP NOT NULL" + return self.sql(f"""ALTER TABLE "{table_name}" + ALTER COLUMN "{column}" TYPE {type}, + ALTER COLUMN "{column}" {null_constraint}""") def create_auth_table(self): self.sql_ddl("""create table if not exists "__Auth" ( From 0b7f2804d79dda620982063579600f86f4803714 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 24 Dec 2021 15:30:25 +0530 Subject: [PATCH 17/17] refactor: make ratings field nullable while altering --- .../patches/v14_0/save_ratings_in_fraction.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/frappe/patches/v14_0/save_ratings_in_fraction.py b/frappe/patches/v14_0/save_ratings_in_fraction.py index c37d3f134b..c933179b44 100644 --- a/frappe/patches/v14_0/save_ratings_in_fraction.py +++ b/frappe/patches/v14_0/save_ratings_in_fraction.py @@ -24,29 +24,16 @@ def execute(): ): continue - # update NULL values to 0 to avoid data truncated error (temp) - # commit for upcoming DLL - frappe.qb.update(doctype).set( - doctype[field], 0 - ).where( - doctype[field].isnull() - ).run() + # commit any changes so far for upcoming DDL frappe.db.commit() # alter column types for rating fieldtype - frappe.db.change_column_type(doctype_name, column=field, type=RATING_FIELD_TYPE) + frappe.db.change_column_type(doctype_name, column=field, type=RATING_FIELD_TYPE, nullable=True) # update data: int => decimal frappe.qb.update(doctype).set( doctype[field], doctype[field] / 5 ).run() - # revert 0 to NULL conversion - frappe.qb.update(doctype).set( - doctype[field], None - ).where( - doctype[field] == 0 - ).run() - # commit to flush updated rows frappe.db.commit()