From 072f0026c26b9821f615c8c470daf72f90a79b78 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 27 Jul 2020 20:38:29 +0530 Subject: [PATCH 001/375] fix: Handle directories in unzip --- frappe/core/doctype/file/file.py | 39 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 1748c60020..6adda307a4 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -278,25 +278,26 @@ class File(Document): base_url = os.path.dirname(self.file_url) files = [] - with zipfile.ZipFile(zip_path) as zf: - zf.extractall(os.path.dirname(zip_path)) - for info in zf.infolist(): - if not info.filename.startswith('__MACOSX'): - file_url = file_url = base_url + '/' + info.filename - file_name = frappe.db.get_value('File', dict(file_url=file_url)) - if file_name: - file_doc = frappe.get_doc('File', file_name) - else: - file_doc = frappe.new_doc("File") - file_doc.file_name = info.filename - file_doc.file_size = info.file_size - file_doc.folder = self.folder - file_doc.is_private = self.is_private - file_doc.file_url = file_url - file_doc.attached_to_doctype = self.attached_to_doctype - file_doc.attached_to_name = self.attached_to_name - file_doc.save() - files.append(file_doc) + with zipfile.ZipFile(zip_path) as z: + for file in z.filelist: + if file.is_dir() or file.filename.startswith('__MACOSX/'): + # skip directories and macos hidden directory + continue + + filename = os.path.basename(file.filename) + if filename.startswith('.'): + # skip hidden files + continue + + file_doc = frappe.new_doc('File') + file_doc.content = z.read(file.filename) + file_doc.file_name = filename + file_doc.folder = self.folder + file_doc.is_private = self.is_private + file_doc.attached_to_doctype = self.attached_to_doctype + file_doc.attached_to_name = self.attached_to_name + file_doc.save() + files.append(file_doc) frappe.delete_doc('File', self.name) return files From 6a2248eef315fd5efe38d8a50ebd385725d4a5ce Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Fri, 7 Aug 2020 20:12:33 +0530 Subject: [PATCH 002/375] fix: move node-sass to dependencies from devDependencies node-sass is required in production for Website Theme yarn --prod will install only dependencies and reduce size fixes #11219 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d6bb3ccda4..07cc91e011 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "jsbarcode": "^3.9.0", "moment": "^2.20.1", "moment-timezone": "^0.5.28", + "node-sass": "^4.13.1", "quagga": "^0.12.1", "quill": "2.0.0-dev.4", "qz-tray": "^2.0.8", @@ -56,7 +57,6 @@ "cypress-file-upload": "^3.1.0", "graphlib": "^2.1.8", "less": "^3.11.1", - "node-sass": "^4.13.1", "rollup": "^1.2.2", "rollup-plugin-buble": "^0.19.2", "rollup-plugin-commonjs": "^8.3.0", From 0eae494dc3200c0c1a0b1e20d6dcdd5790d12f85 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 12 Aug 2020 18:28:40 +0530 Subject: [PATCH 003/375] fix: email formatting in communication and email queue Co-authored-by: Sahil Khan --- frappe/patches.txt | 1 + .../patches/v12_0/fix_email_id_formatting.py | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 frappe/patches/v12_0/fix_email_id_formatting.py diff --git a/frappe/patches.txt b/frappe/patches.txt index 68b4e5d99c..2b729ad87c 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -298,3 +298,4 @@ frappe.patches.v13_0.create_custom_dashboards_cards_and_charts frappe.patches.v13_0.rename_is_custom_field_in_dashboard_chart frappe.patches.v13_0.generate_theme_files_in_public_folder frappe.patches.v13_0.increase_password_length +frappe.patches.v12_0.fix_email_id_formatting diff --git a/frappe/patches/v12_0/fix_email_id_formatting.py b/frappe/patches/v12_0/fix_email_id_formatting.py new file mode 100644 index 0000000000..03f606e0cc --- /dev/null +++ b/frappe/patches/v12_0/fix_email_id_formatting.py @@ -0,0 +1,44 @@ +import frappe + +def execute(): + fix_communications() + fix_show_as_cc_email_queue() + fix_email_queue_recipients() + +def fix_communications(): + for communication in frappe.db.sql('''select name, recipients, cc, bcc from tabCommunication + where creation > '2020-06-01' + and communication_medium='Email' + and communication_type='Communication' + and (cc like '%<%' or bcc like '%<%' or recipients like '%<%') + ''', as_dict=1): + + communication['recipients'] = format_email_id(communication.recipients) + communication['cc'] = format_email_id(communication.cc) + communication['bcc'] = format_email_id(communication.bcc) + + frappe.db.sql('''update `tabCommunication` set recipients=%s,cc=%s,bcc=%s + where name =%s ''', (communication['recipients'], communication['cc'], + communication['bcc'], communication['name'])) + +def fix_show_as_cc_email_queue(): + for queue in frappe.get_all("Email Queue", {'creation': ['>', '2020-06-01'], + 'status': 'Not Sent', 'show_as_cc': ['like', '%<%']}, + ['name', 'show_as_cc']): + + frappe.db.set_value('Email Queue', queue['name'], + 'show_as_cc', format_email_id(queue['show_as_cc'])) + +def fix_email_queue_recipients(): + for recipient in frappe.db.sql('''select recipient, name from + `tabEmail Queue Recipient` where recipient like '%<%' + and status='Not Sent' and creation > '2020-06-01' ''', as_dict=1): + + frappe.db.set_value('Email Queue Recipient', recipient['name'], + 'recipient', format_email_id(recipient['recipient'])) + +def format_email_id(email): + if email and ('<' in email and '>' in email): + return email.replace('>', '>').replace('<', '<') + + return email From af98a1ea38998f00d889b8e58ec2d1bc5119069b Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 17 Aug 2020 13:26:57 +0200 Subject: [PATCH 004/375] fix(login): namespace css and include in website --- frappe/public/scss/login.scss | 203 ++++++++++++++++++++++ frappe/public/scss/website.scss | 1 + frappe/templates/includes/login/login.css | 167 ------------------ frappe/www/login.html | 6 - 4 files changed, 204 insertions(+), 173 deletions(-) create mode 100644 frappe/public/scss/login.scss delete mode 100644 frappe/templates/includes/login/login.css diff --git a/frappe/public/scss/login.scss b/frappe/public/scss/login.scss new file mode 100644 index 0000000000..19d8b50343 --- /dev/null +++ b/frappe/public/scss/login.scss @@ -0,0 +1,203 @@ +/* login-css */ + +#page-login { + .hero-and-content { + /*background-color: #f5f7fa;*/ + background-color: #fafbfc; + } + + .page-sidebar, + #wrap-footer, + .page-header { + display: none; + } + + .page-content { + right: 0%; + width: 100%; + } + + .icon-twitter, + .icon-twitter-sign { + color: #00a0d1; + } + + .icon-linkedin, + .icon-linkedin-sign { + color: #4875b4; + } + + #wrap { + background-color: #7575ff; + } + + .for-login { + display: none; + } + + .for-forgot { + display: none; + } + + .for-signup { + display: none; + } + + .form-signin { + .form-signin-heading, + .checkbox { + margin-bottom: 10px; + } + .checkbox { + font-weight: normal; + } + .form-control { + position: relative; + height: auto; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 6px; + font-size: 14px; + margin-bottom: 10px; + } + .form-control:focus { + z-index: 2; + } + } + + .btn-social { + margin: 10px; + } + + .social-logins .fa { + margin-right: 5px; + color: #8d99a6; + } + + .form-footer { + margin-top: -45px; + text-align: center; + font-size: 12px; + color: #8d99a6; + font-weight: bold; + + a { + font-size: 12px; + color: #8d99a6; + font-weight: bold; + } + + h6 { + font-size: 12px; + color: #8d99a6; + font-weight: bold; + } + + .btn-default { + color: #36414c; + } + } + + h5 { + position: relative; + text-align: center; + margin-top: 20px; + margin-bottom: 20px; + } + + p { + margin-bottom: 20px; + } + + .login-content .btn { + font-size: 14px; + margin-top: 45px; + } + + .page-card { + max-width: 360px; + padding: 15px; + margin: 70px auto; + border: 1px solid #d1d8dd; + border-radius: 4px; + background-color: #fff; + box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.1); + + .page-card-head { + padding: 10px 15px; + margin: -15px; + margin-bottom: 15px; + border-bottom: 1px solid #d1d8dd; + } + + .page-card-head .indicator { + color: #36414c; + font-size: 14px; + } + + .page-card-head .indicator::before { + margin: 0 6px 0.5px 0px; + } + + .btn { + margin-top: 30px; + } + } + + .bordered { + border: 1px solid #d1d8dd; + padding: 10px; + border-radius: 4px; + } + + .toggle-password { + right: 9px; + top: 9px; + position: absolute; + z-index: 2; + cursor: pointer; + font-size: 12px; + } + + .invalid-login { + -webkit-animation: wiggle 0.5s linear; + animation: wiggle 0.5s linear; + } + + @-webkit-keyframes wiggle { + 8%, 41% { + -webkit-transform: translateX(-10px); + } + 25%, 58% { + -webkit-transform: translateX(10px); + } + 75% { + -webkit-transform: translateX(-5px); + } + 92% { + -webkit-transform: translateX(5px); + } + 0%, 100% { + -webkit-transform: translateX(0); + } + } + + @keyframes wiggle { + 8%, 41% { + transform: translate(-10px); + } + 25%, 58% { + transform: translate(10px); + } + 75% { + transform: translate(-5px); + } + 92% { + transform: translate(5px); + } + 0%, 100% { + transform: translate(0); + } + } +} diff --git a/frappe/public/scss/website.scss b/frappe/public/scss/website.scss index 3c11d23252..f0e9540a3e 100644 --- a/frappe/public/scss/website.scss +++ b/frappe/public/scss/website.scss @@ -11,6 +11,7 @@ @import 'sidebar'; @import 'portal'; @import 'doc'; +@import 'login'; .ql-editor.read-mode { padding: 0; diff --git a/frappe/templates/includes/login/login.css b/frappe/templates/includes/login/login.css deleted file mode 100644 index c336ae742e..0000000000 --- a/frappe/templates/includes/login/login.css +++ /dev/null @@ -1,167 +0,0 @@ -/* login-css */ - -.hero-and-content { - /*background-color: #f5f7fa;*/ - background-color: #fafbfc; -} - -.page-sidebar, #wrap-footer, .page-header { - display: none; -} - -.page-content { - right: 0%; - width: 100%; -} - -.icon-twitter, .icon-twitter-sign{ - color: #00a0d1; -} - -.icon-linkedin, .icon-linkedin-sign{ - color: #4875B4; -} - -#wrap { - background-color: #7575ff; -} - -.for-login { - display: none; -} - -.for-forgot { - display: none; -} - -.for-signup { - display: none; -} - -.form-signin .form-signin-heading, -.form-signin .checkbox { - margin-bottom: 10px; -} -.form-signin .checkbox { - font-weight: normal; -} -.form-signin .form-control { - position: relative; - height: auto; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - padding: 6px; - font-size: 14px; - margin-bottom: 10px; -} -.form-signin .form-control:focus { - z-index: 2; -} - -.btn-social { - margin: 10px; -} - -.social-logins .fa { - margin-right: 5px; - color: #8D99A6; -} - -.form-footer { - margin-top: -45px; - text-align: center; -} - -.form-footer, .form-footer a, .form-footer h6 { - font-size: 12px; - color: #8D99A6; - font-weight: bold; -} - -.form-footer .btn-default { - color: #36414C; -} - -h5 { - position: relative; - text-align: center; - margin-top:20px; - margin-bottom:20px; -} - -p { - margin-bottom:20px; -} - -.login-content .btn { - font-size: 14px; - margin-top: 45px; -} - -.page-card { - max-width: 360px; - padding: 15px; - margin: 70px auto; - border: 1px solid #d1d8dd; - border-radius: 4px; - background-color: #fff; - box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.1); -} -.page-card .page-card-head { - padding: 10px 15px; - margin: -15px; - margin-bottom: 15px; - border-bottom: 1px solid #d1d8dd; -} -.page-card .page-card-head .indicator { - color: #36414C; - font-size: 14px; -} -.page-card .page-card-head .indicator::before { - margin: 0 6px 0.5px 0px; -} -.page-card .btn { - margin-top: 30px; -} - -.bordered { - border: 1px solid #d1d8dd; - padding: 10px; - border-radius: 4px; -} - -.toggle-password { - right: 9px; - top: 9px; - position: absolute; - z-index: 2; - cursor: pointer; - font-size: 12px; -} - -.invalid-login { - -webkit-animation: wiggle 0.5s linear; -} - -@-webkit-keyframes wiggle { - 8%, - 41% { - -webkit-transform: translateX(-10px); - } - 25%, - 58% { - -webkit-transform: translateX(10px); - } - 75% { - -webkit-transform: translateX(-5px); - } - 92% { - -webkit-transform: translateX(5px); - } - 0%, - 100% { - -webkit-transform: translateX(0); - } -} - diff --git a/frappe/www/login.html b/frappe/www/login.html index 9d00892b18..96b5bc7026 100644 --- a/frappe/www/login.html +++ b/frappe/www/login.html @@ -1,11 +1,5 @@ {% extends "templates/web.html" %} -{% block style %} - -{% endblock %} - {% block page_content %}
From cb2b07ceae75d7eeb06681c6d6652643b3305a94 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 18 Aug 2020 15:41:59 +0530 Subject: [PATCH 005/375] refactor: rename Minutes to First Response to First Response Time --- frappe/core/doctype/communication/communication.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 232d485f36..fc929351d4 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -455,18 +455,18 @@ def update_parent_document_on_communication(doc): # update the modified date for document parent.update_modified() - update_mins_to_first_communication(parent, doc) + update_first_response_time(parent, doc) set_avg_response_time(parent, doc) parent.run_method("notify_communication", doc) parent.notify_update() -def update_mins_to_first_communication(parent, communication): - if parent.meta.has_field("mins_to_first_response") and not parent.get("mins_to_first_response"): +def update_first_response_time(parent, communication): + if parent.meta.has_field("first_response_time") and not parent.get("first_response_time"): if is_system_user(communication.sender): first_responded_on = communication.creation if parent.meta.has_field("first_responded_on") and communication.sent_or_received == "Sent": parent.db_set("first_responded_on", first_responded_on) - parent.db_set("mins_to_first_response", round(time_diff_in_seconds(first_responded_on, parent.creation) / 60), 2) + parent.db_set("first_response_time", round(time_diff_in_seconds(first_responded_on, parent.creation), 2)) def set_avg_response_time(parent, communication): if parent.meta.has_field("avg_response_time") and communication.sent_or_received == "Sent": From aae0309cedf567ef4a2f3c4dd7c9ca5eb79096e9 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 19 Aug 2020 14:19:52 +0530 Subject: [PATCH 006/375] feat: Allow me to execute anything console would allow --- frappe/commands/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 721376016c..17e6e53c51 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -133,6 +133,7 @@ def reset_perms(context): def execute(context, method, args=None, kwargs=None, profile=False): "Execute a function" for site in context.sites: + ret = "" try: frappe.init(site=site) frappe.connect() @@ -154,7 +155,10 @@ def execute(context, method, args=None, kwargs=None, profile=False): pr = cProfile.Profile() pr.enable() - ret = frappe.get_attr(method)(*args, **kwargs) + try: + ret = frappe.get_attr(method)(*args, **kwargs) + except Exception: + ret = frappe.safe_eval(method + "()", eval_globals=globals(), eval_locals=locals()) if profile: pr.disable() From 022b6e356b4f4ebd95224818acffe3fa6a353227 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 19 Aug 2020 15:11:39 +0530 Subject: [PATCH 007/375] fix: Create backups folder if doesn't exist Backups fail silently if the default `backups` folder doesn't exist under private --- frappe/utils/backups.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index a64ba8c3a7..c7b1523ac0 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -48,10 +48,23 @@ class BackupGenerator: site = frappe.local.site or frappe.generate_hash(length=8) self.site_slug = site.replace('.', '_') - self.verbose = verbose + self.setup_backup_directory() _verbose = verbose + def setup_backup_directory(self): + specified = self.backup_path_db or self.backup_path_files or self.backup_path_private_files + + if not specified: + backups_folder = get_backup_path() + if not os.path.exists(backups_folder): + os.makedirs(backups_folder) + else: + for file_path in [self.backup_path_files, self.backup_path_db, self.backup_path_private_files]: + dir = os.path.dirname(file_path) + os.makedirs(dir) + + def get_backup(self, older_than=24, ignore_files=False, force=False): """ Takes a new dump if existing file is old From a7af5d0a09aa55a0d806631d7f32e44ba636bedf Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 19 Aug 2020 15:45:10 +0530 Subject: [PATCH 008/375] fix: Re-connect to database in case connection is closed --- frappe/database/mariadb/setup_db.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/database/mariadb/setup_db.py b/frappe/database/mariadb/setup_db.py index 6370990098..a4e4d624ae 100644 --- a/frappe/database/mariadb/setup_db.py +++ b/frappe/database/mariadb/setup_db.py @@ -92,6 +92,8 @@ def bootstrap_database(db_name, verbose, source_sql=None): sys.exit(1) import_db_from_sql(source_sql, verbose) + + frappe.connect(db_name=db_name) if not 'tabDefaultValue' in frappe.db.get_tables(): print('''Database not installed, this can due to lack of permission, or that the database name exists. Check your mysql root password, or use --force to reinstall''') From ee0bb635deb8bd63e375c55085e421fb0ef55c2c Mon Sep 17 00:00:00 2001 From: Anupam K Date: Wed, 19 Aug 2020 17:53:09 +0530 Subject: [PATCH 009/375] fix: newsletter fix --- .../email/doctype/newsletter/newsletter.json | 39 ++++++++++++++++--- frappe/email/doctype/newsletter/newsletter.py | 34 ++++++++-------- frappe/patches.txt | 1 + .../v13_0/update_newsletter_content_type.py | 12 ++++++ 4 files changed, 65 insertions(+), 21 deletions(-) create mode 100644 frappe/patches/v13_0/update_newsletter_content_type.py diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json index 01f75be954..6babf212aa 100644 --- a/frappe/email/doctype/newsletter/newsletter.json +++ b/frappe/email/doctype/newsletter/newsletter.json @@ -15,7 +15,10 @@ "email_sent", "newsletter_content", "subject", + "content_type", "message", + "message_md", + "message_html", "send_unsubscribe_link", "send_attachments", "published", @@ -50,7 +53,8 @@ }, { "fieldname": "newsletter_content", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Content" }, { "fieldname": "subject", @@ -61,11 +65,12 @@ "reqd": 1 }, { + "depends_on": "eval: doc.content_type === 'Rich Text'", "fieldname": "message", "fieldtype": "Text Editor", "in_list_view": 1, "label": "Message", - "reqd": 1 + "mandatory_depends_on": "eval: doc.content_type === 'Rich Text'" }, { "default": "1", @@ -87,16 +92,20 @@ "read_only": 1 }, { + "collapsible": 1, "fieldname": "test_the_newsletter", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Testing" }, { "description": "A Lead with this Email Address should exist", "fieldname": "test_email_id", "fieldtype": "Data", - "label": "Test Email Address" + "label": "Test Email Address", + "options": "Email" }, { + "depends_on": "eval: doc.test_email_id", "fieldname": "test_send", "fieldtype": "Button", "label": "Test", @@ -127,6 +136,26 @@ "fieldname": "send_attachments", "fieldtype": "Check", "label": "Send Attachments" + }, + { + "fieldname": "content_type", + "fieldtype": "Select", + "label": "Content Type", + "options": "Rich Text\nMarkdown\nHTML" + }, + { + "depends_on": "eval:doc.content_type === 'Markdown'", + "fieldname": "message_md", + "fieldtype": "Markdown Editor", + "label": "Message (Markdown)", + "mandatory_depends_on": "eval:doc.content_type === 'Markdown'" + }, + { + "depends_on": "eval:doc.content_type === 'HTML'", + "fieldname": "message_html", + "fieldtype": "HTML Editor", + "label": "Message (HTML)", + "mandatory_depends_on": "eval:doc.content_type === 'HTML'" } ], "has_web_view": 1, @@ -135,7 +164,7 @@ "is_published_field": "published", "links": [], "max_attachments": 3, - "modified": "2020-05-12 18:09:40.137138", + "modified": "2020-08-19 17:54:13.290593", "modified_by": "Administrator", "module": "Email", "name": "Newsletter", diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 48688afdb6..08a0e3d247 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -11,7 +11,7 @@ from frappe.utils.verified_command import get_signed_params, verify_request from frappe.utils.background_jobs import enqueue from frappe.email.queue import send from frappe.email.doctype.email_group.email_group import add_subscribers -from frappe.utils import parse_addr, now_datetime +from frappe.utils import parse_addr, now_datetime, markdown from frappe.utils import validate_email_address @@ -29,8 +29,8 @@ class Newsletter(WebsiteGenerator): def test_send(self, doctype="Lead"): self.recipients = frappe.utils.split_emails(self.test_email_id) - self.queue_all() - frappe.msgprint(_("Scheduled to send to {0}").format(self.test_email_id)) + self.queue_all(test_email=True) + frappe.msgprint(_("Test email is send to {0}").format(self.test_email_id)) def send_emails(self): """send emails to leads and customers""" @@ -41,20 +41,13 @@ class Newsletter(WebsiteGenerator): if self.recipients: if getattr(frappe.local, "is_ajax", False): - self.validate_send() - # using default queue with a longer timeout as this isn't a scheduled task - enqueue(send_newsletter, queue='default', timeout=6000, event='send_newsletter', - newsletter=self.name) - - else: self.queue_all() - - frappe.msgprint(_("Scheduled to send to {0} recipients").format(len(self.recipients))) + frappe.msgprint(_("Email queued to {0} recipients").format(len(self.recipients))) else: frappe.msgprint(_("Newsletter should have atleast one recipient")) - def queue_all(self): + def queue_all(self, test_email=False): if not self.get("recipients"): # in case it is called via worker self.recipients = self.get_recipients() @@ -80,7 +73,7 @@ class Newsletter(WebsiteGenerator): frappe.throw(_("Unable to find attachment {0}").format(file.name)) send(recipients=self.recipients, sender=sender, - subject=self.subject, message=self.message, + subject=self.subject, message=self.get_message(), reference_doctype=self.doctype, reference_name=self.name, add_unsubscribe_link=self.send_unsubscribe_link, attachments=attachments, unsubscribe_method="/unsubscribe", @@ -90,9 +83,18 @@ class Newsletter(WebsiteGenerator): if not frappe.flags.in_test: frappe.db.auto_commit_on_many_writes = False - self.db_set("email_sent", 1) - self.db_set("schedule_send", now_datetime()) - self.db_set("scheduled_to_send", len(self.recipients)) + if not test_email: + self.db_set("email_sent", 1) + self.db_set("schedule_send", now_datetime()) + self.db_set("scheduled_to_send", len(self.recipients)) + + def get_message(self): + if self.content_type == 'Rich Text': + return self.message + elif self.content_type == 'Markdown': + return markdown(self.message_md) + elif self.content_type == 'HTML': + return self.message_html def get_recipients(self): """Get recipients from Email Group""" diff --git a/frappe/patches.txt b/frappe/patches.txt index 75750ab59c..bf92486cbf 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -297,3 +297,4 @@ frappe.patches.v13_0.replace_old_data_import # 2020-06-24 frappe.patches.v13_0.create_custom_dashboards_cards_and_charts frappe.patches.v13_0.rename_is_custom_field_in_dashboard_chart frappe.patches.v13_0.generate_theme_files_in_public_folder +frappe.patches.v13_0.update_newsletter_content_type \ No newline at end of file diff --git a/frappe/patches/v13_0/update_newsletter_content_type.py b/frappe/patches/v13_0/update_newsletter_content_type.py new file mode 100644 index 0000000000..0b32fb49ed --- /dev/null +++ b/frappe/patches/v13_0/update_newsletter_content_type.py @@ -0,0 +1,12 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('email', 'doctype', 'Newsletter') + frappe.db.sql(""" + UPDATE tabNewsletter + SET content_type = 'Rich Text' + """) \ No newline at end of file From f48470bb72428a7a9e60c689de2bb03f4094c8ac Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 20 Aug 2020 11:51:56 +0530 Subject: [PATCH 010/375] refactor: Schedule Job Type * Updating Schedule Job Type when scheduler_events in hooks change * Doesn't update objects defined in other functions. Eliminating possible bugs --- .../scheduled_job_type/scheduled_job_type.py | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py index 765ae5fe93..f8b50ea292 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -124,42 +124,49 @@ def run_scheduled_job(job_type): def sync_jobs(): frappe.reload_doc('core', 'doctype', 'scheduled_job_type') - all_events = [] - scheduler_events = frappe.get_hooks("scheduler_events") - insert_events(all_events, scheduler_events) - clear_events(all_events, scheduler_events) + scheduler_events = frappe.get_hooks('scheduler_events') + all_events = insert_events(scheduler_events) + clear_events(all_events) -def insert_events(all_events, scheduler_events): +def insert_events(scheduler_events): + cron_jobs, event_jobs = [], [] for event_type in scheduler_events: events = scheduler_events.get(event_type) if isinstance(events, dict): - insert_cron_event(events, all_events) + cron_jobs += insert_cron_jobs(events) else: # hourly, daily etc - insert_event_list(events, event_type, all_events) + event_jobs += insert_event_jobs(events, event_type) + return cron_jobs + event_jobs -def insert_cron_event(events, all_events): +def insert_cron_jobs(events): + cron_jobs = [] for cron_format in events: for event in events.get(cron_format): - all_events.append(event) + cron_jobs.append(event) insert_single_event('Cron', event, cron_format) + return cron_jobs -def insert_event_list(events, event_type, all_events): +def insert_event_jobs(events, event_type): + event_jobs = [] for event in events: - all_events.append(event) + event_jobs.append(event) frequency = event_type.replace('_', ' ').title() insert_single_event(frequency, event) + return event_jobs -def insert_single_event(frequency, event, cron_format = None): - if not frappe.db.exists('Scheduled Job Type', dict(method=event)): - frappe.get_doc(dict( - doctype = 'Scheduled Job Type', - method = event, - cron_format = cron_format, - frequency = frequency - )).insert() +def insert_single_event(frequency, event, cron_format=None): + cron_expr = {'cron_format': cron_format} if cron_format else {} -def clear_events(all_events, scheduler_events): + if not frappe.db.exists('Scheduled Job Type', {'method': event, 'frequency': frequency, **cron_expr }): + frappe.get_doc({ + 'doctype': 'Scheduled Job Type', + 'method': event, + 'cron_format': cron_format, + 'frequency': frequency + }).insert() + +def clear_events(all_events): for event in frappe.get_all('Scheduled Job Type', ('name', 'method')): if event.method not in all_events: frappe.delete_doc('Scheduled Job Type', event.name) From 7bcb0b53773b48a173861bff5e70dbabc62a12f0 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 20 Aug 2020 11:54:29 +0530 Subject: [PATCH 011/375] style: Consistent spacing and quotes --- .../scheduled_job_type/scheduled_job_type.py | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py index f8b50ea292..649c79df88 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -109,12 +109,14 @@ class ScheduledJobType(Document): def on_trash(self): frappe.db.sql('delete from `tabScheduled Job Log` where scheduled_job_type=%s', self.name) + @frappe.whitelist() def execute_event(doc): frappe.only_for('System Manager') doc = json.loads(doc) frappe.get_doc('Scheduled Job Type', doc.get('name')).enqueue() + def run_scheduled_job(job_type): '''This is a wrapper function that runs a hooks.scheduler_events method''' try: @@ -122,12 +124,14 @@ def run_scheduled_job(job_type): except Exception: print(frappe.get_traceback()) + def sync_jobs(): - frappe.reload_doc('core', 'doctype', 'scheduled_job_type') - scheduler_events = frappe.get_hooks('scheduler_events') + frappe.reload_doc("core", "doctype", "scheduled_job_type") + scheduler_events = frappe.get_hooks("scheduler_events") all_events = insert_events(scheduler_events) clear_events(all_events) + def insert_events(scheduler_events): cron_jobs, event_jobs = [], [] for event_type in scheduler_events: @@ -139,14 +143,16 @@ def insert_events(scheduler_events): event_jobs += insert_event_jobs(events, event_type) return cron_jobs + event_jobs + def insert_cron_jobs(events): cron_jobs = [] for cron_format in events: for event in events.get(cron_format): cron_jobs.append(event) - insert_single_event('Cron', event, cron_format) + insert_single_event("Cron", event, cron_format) return cron_jobs + def insert_event_jobs(events, event_type): event_jobs = [] for event in events: @@ -155,18 +161,20 @@ def insert_event_jobs(events, event_type): insert_single_event(frequency, event) return event_jobs -def insert_single_event(frequency, event, cron_format=None): - cron_expr = {'cron_format': cron_format} if cron_format else {} - if not frappe.db.exists('Scheduled Job Type', {'method': event, 'frequency': frequency, **cron_expr }): +def insert_single_event(frequency, event, cron_format=None): + cron_expr = {"cron_format": cron_format} if cron_format else {} + + if not frappe.db.exists("Scheduled Job Type", {"method": event, "frequency": frequency, **cron_expr }): frappe.get_doc({ - 'doctype': 'Scheduled Job Type', - 'method': event, - 'cron_format': cron_format, - 'frequency': frequency + "doctype": "Scheduled Job Type", + "method": event, + "cron_format": cron_format, + "frequency": frequency }).insert() + def clear_events(all_events): - for event in frappe.get_all('Scheduled Job Type', ('name', 'method')): + for event in frappe.get_all("Scheduled Job Type", ("name", "method")): if event.method not in all_events: - frappe.delete_doc('Scheduled Job Type', event.name) + frappe.delete_doc("Scheduled Job Type", event.name) From 557bceed027a032c1ec2c3ba6941a4b381b64948 Mon Sep 17 00:00:00 2001 From: gavin Date: Thu, 20 Aug 2020 12:03:03 +0530 Subject: [PATCH 012/375] fix: Allow args and kwargs too Co-authored-by: Aditya Hase --- frappe/commands/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 17e6e53c51..acd25eb166 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -158,7 +158,7 @@ def execute(context, method, args=None, kwargs=None, profile=False): try: ret = frappe.get_attr(method)(*args, **kwargs) except Exception: - ret = frappe.safe_eval(method + "()", eval_globals=globals(), eval_locals=locals()) + ret = frappe.safe_eval(method + "(*args, **kwargs)", eval_globals=globals(), eval_locals=locals()) if profile: pr.disable() From 7c2510eb6ad71b88733938e58695afc1e9fb24ca Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 20 Aug 2020 18:19:23 +0530 Subject: [PATCH 013/375] feat: Mini Test Suite for commands --- frappe/tests/test_commands.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 frappe/tests/test_commands.py diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py new file mode 100644 index 0000000000..b89079889f --- /dev/null +++ b/frappe/tests/test_commands.py @@ -0,0 +1,25 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors + +from __future__ import unicode_literals + +import shlex +import subprocess +import unittest + +import frappe + + +def clean(value): + if isinstance(value, (bytes, str)): + value = value.decode().strip() + return value + + +class BaseTestCommands: + def execute(self, command): + command = command.format(**{"site": frappe.local.site}) + command = shlex.split(command) + self._proc = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + self.stdout = clean(self._proc.stdout) + self.stderr = clean(self._proc.stderr) + self.returncode = clean(self._proc.returncode) From 99496d97f3e00284840d2127556bba0e21d1a99e Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 20 Aug 2020 18:20:02 +0530 Subject: [PATCH 014/375] test: Add tests for bench execute --- frappe/tests/test_commands.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index b89079889f..fe0129566b 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -23,3 +23,21 @@ class BaseTestCommands: self.stdout = clean(self._proc.stdout) self.stderr = clean(self._proc.stderr) self.returncode = clean(self._proc.returncode) + + +class TestCommands(BaseTestCommands, unittest.TestCase): + def test_execute(self): + # execute a command expecting a numeric output + self.execute("bench --site {site} execute frappe.db.get_database_size") + self.assertEquals(self.returncode, 0) + self.assertIsInstance(float(self.stdout), float) + + # execute a command expecting an errored output as local won't exist + self.execute("bench --site {site} execute frappe.local.site") + self.assertEquals(self.returncode, 1) + self.assertIsNotNone(self.stderr) + + # execute a command with kwargs + self.execute("""bench --site {site} execute frappe.bold --kwargs '{{"text": "DocType"}}'""") + self.assertEquals(self.returncode, 0) + self.assertEquals(self.stdout[1:-1], frappe.bold(text='DocType')) From 5f299b191d4c9e5773142f2298aef2df1328048c Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 20 Aug 2020 18:26:18 +0530 Subject: [PATCH 015/375] chore: Added comments and style --- frappe/tests/test_commands.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index fe0129566b..82c0cdce5c 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -1,11 +1,11 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -from __future__ import unicode_literals - +# imports - standard imports import shlex import subprocess import unittest +# imports - module imports import frappe @@ -27,17 +27,20 @@ class BaseTestCommands: class TestCommands(BaseTestCommands, unittest.TestCase): def test_execute(self): - # execute a command expecting a numeric output + # test 1: execute a command expecting a numeric output self.execute("bench --site {site} execute frappe.db.get_database_size") self.assertEquals(self.returncode, 0) self.assertIsInstance(float(self.stdout), float) - # execute a command expecting an errored output as local won't exist + # test 2: execute a command expecting an errored output as local won't exist self.execute("bench --site {site} execute frappe.local.site") self.assertEquals(self.returncode, 1) self.assertIsNotNone(self.stderr) - # execute a command with kwargs + # test 3: execute a command with kwargs + # Note: + # terminal command has been escaped to avoid .format string replacement + # The returned value has quotes which have been trimmed for the test self.execute("""bench --site {site} execute frappe.bold --kwargs '{{"text": "DocType"}}'""") self.assertEquals(self.returncode, 0) self.assertEquals(self.stdout[1:-1], frappe.bold(text='DocType')) From bb11a55b5666957ac2777bec0aed6fb8d5d43c31 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 21 Aug 2020 16:23:20 +0530 Subject: [PATCH 016/375] test: Added tests to check if sync jobs updates on changes in hooks --- .../scheduled_job_type/scheduled_job_type.py | 4 ++-- .../scheduled_job_type/test_scheduled_job_type.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py index 649c79df88..445cf91649 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -125,9 +125,9 @@ def run_scheduled_job(job_type): print(frappe.get_traceback()) -def sync_jobs(): +def sync_jobs(hooks=None): frappe.reload_doc("core", "doctype", "scheduled_job_type") - scheduler_events = frappe.get_hooks("scheduler_events") + scheduler_events = hooks or frappe.get_hooks("scheduler_events") all_events = insert_events(scheduler_events) clear_events(all_events) diff --git a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py index ec1e70ad6a..3ff639e854 100644 --- a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py @@ -32,6 +32,20 @@ class TestScheduledJobType(unittest.TestCase): self.assertEqual(cron_job.frequency, 'Cron') self.assertEqual(cron_job.cron_format, '0/15 * * * *') + # check if jobs are synced after change in hooks + scheduler_events = { "cron": { "0/15 * * * *": ["frappe.email.queue.flush"] } } + sync_jobs(scheduler_events) + frappe.db.commit() + scheduled_job = frappe.get_doc("Scheduled Job Type", {"method": "frappe.email.queue.flush"}) + self.assertEqual(scheduled_job.frequency, "Cron") + self.assertEqual(scheduled_job.cron_format, "0/15 * * * *") + + updated_scheduler_events = { "hourly": ["frappe.email.queue.flush"] } + sync_jobs(updated_scheduler_events) + frappe.db.commit() + updated_scheduled_job = frappe.get_doc("Scheduled Job Type", {"method": "frappe.email.queue.flush"}) + self.assertEqual(scheduled_job.frequency, "Hourly") + def test_daily_job(self): job = frappe.get_doc('Scheduled Job Type', dict(method = 'frappe.email.queue.clear_outbox')) job.db_set('last_execution', '2019-01-01 00:00:00') From 5948248740c7880e30cb7c988155f5265ff1f101 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 21 Aug 2020 16:27:52 +0530 Subject: [PATCH 017/375] fix: Use correct document to test --- .../core/doctype/scheduled_job_type/test_scheduled_job_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py index 3ff639e854..e5e92f9e5b 100644 --- a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py @@ -44,7 +44,7 @@ class TestScheduledJobType(unittest.TestCase): sync_jobs(updated_scheduler_events) frappe.db.commit() updated_scheduled_job = frappe.get_doc("Scheduled Job Type", {"method": "frappe.email.queue.flush"}) - self.assertEqual(scheduled_job.frequency, "Hourly") + self.assertEqual(updated_scheduled_job.frequency, "Hourly") def test_daily_job(self): job = frappe.get_doc('Scheduled Job Type', dict(method = 'frappe.email.queue.clear_outbox')) From e9429aacff56a4fa0cb0a1c6095517926e47b811 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 21 Aug 2020 16:42:26 +0530 Subject: [PATCH 018/375] fix: Remove unnecessary addition --- .../doctype/scheduled_job_type/test_scheduled_job_type.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py index e5e92f9e5b..e580837810 100644 --- a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py @@ -33,13 +33,6 @@ class TestScheduledJobType(unittest.TestCase): self.assertEqual(cron_job.cron_format, '0/15 * * * *') # check if jobs are synced after change in hooks - scheduler_events = { "cron": { "0/15 * * * *": ["frappe.email.queue.flush"] } } - sync_jobs(scheduler_events) - frappe.db.commit() - scheduled_job = frappe.get_doc("Scheduled Job Type", {"method": "frappe.email.queue.flush"}) - self.assertEqual(scheduled_job.frequency, "Cron") - self.assertEqual(scheduled_job.cron_format, "0/15 * * * *") - updated_scheduler_events = { "hourly": ["frappe.email.queue.flush"] } sync_jobs(updated_scheduler_events) frappe.db.commit() From 88e4ab2bcaa74e7f13b89923a332688999d26c4e Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 21 Aug 2020 17:12:01 +0530 Subject: [PATCH 019/375] fix: Delete if DuplicateEntry and insert again Sometimes I add a fix for something. After some time, I forget the reason for it. And when something breaks, I remember why I added it in the first place. This is one of those times. --- .../scheduled_job_type/scheduled_job_type.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py index 445cf91649..fa854f579e 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -164,14 +164,19 @@ def insert_event_jobs(events, event_type): def insert_single_event(frequency, event, cron_format=None): cron_expr = {"cron_format": cron_format} if cron_format else {} + doc = frappe.get_doc({ + "doctype": "Scheduled Job Type", + "method": event, + "cron_format": cron_format, + "frequency": frequency + }) if not frappe.db.exists("Scheduled Job Type", {"method": event, "frequency": frequency, **cron_expr }): - frappe.get_doc({ - "doctype": "Scheduled Job Type", - "method": event, - "cron_format": cron_format, - "frequency": frequency - }).insert() + try: + doc.insert() + except frappe.DuplicateEntryError: + doc.delete() + doc.insert() def clear_events(all_events): From e3580357820653946b3875c32e90db56fccfdb9c Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 21 Aug 2020 17:44:55 +0530 Subject: [PATCH 020/375] test: Rollback and sync jobs for each test --- .../scheduled_job_type/test_scheduled_job_type.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py index e580837810..e7db6f9045 100644 --- a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py @@ -11,11 +11,10 @@ from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs class TestScheduledJobType(unittest.TestCase): def setUp(self): - if not frappe.get_all('Scheduled Job Type', limit=1): - frappe.db.rollback() - frappe.db.sql('truncate `tabScheduled Job Type`') - sync_jobs() - frappe.db.commit() + frappe.db.rollback() + frappe.db.sql('truncate `tabScheduled Job Type`') + sync_jobs() + frappe.db.commit() def test_sync_jobs(self): all_job = frappe.get_doc('Scheduled Job Type', @@ -35,7 +34,6 @@ class TestScheduledJobType(unittest.TestCase): # check if jobs are synced after change in hooks updated_scheduler_events = { "hourly": ["frappe.email.queue.flush"] } sync_jobs(updated_scheduler_events) - frappe.db.commit() updated_scheduled_job = frappe.get_doc("Scheduled Job Type", {"method": "frappe.email.queue.flush"}) self.assertEqual(updated_scheduled_job.frequency, "Hourly") From 613b91735ef79449232b38874d6b562cfb084478 Mon Sep 17 00:00:00 2001 From: KanchanChauhan Date: Fri, 21 Aug 2020 17:58:37 +0530 Subject: [PATCH 021/375] fix: Image shown as broken in comment if private --- frappe/utils/file_manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py index b1eb2b9ab3..e165a4e338 100644 --- a/frappe/utils/file_manager.py +++ b/frappe/utils/file_manager.py @@ -406,6 +406,10 @@ def extract_images_from_html(doc, content): doctype = doc.parenttype if doc.parent else doc.doctype name = doc.parent or doc.name + if doc.doctype == "Comment": + doctype = doc.reference_doctype + name = doc.reference_name + # TODO fix this file_url = save_file(filename, content, doctype, name, decode=True).get("file_url") if not frappe.flags.has_dataurl: From acabfd0b1663fc81d3780b2c6f8bcba7274ec51d Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 24 Aug 2020 00:41:24 +0530 Subject: [PATCH 022/375] fix: group dashboard cards based on label --- frappe/desk/desktop.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 148ae87249..94a38a5304 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -203,7 +203,7 @@ class Workspace: cards = cards + get_custom_reports_and_doctypes(self.doc.module) if len(self.extended_cards): - cards = cards + self.extended_cards + cards = merge_cards_based_on_label(cards + self.extended_cards) default_country = frappe.db.get_default("country") def _doctype_contains_a_record(name): @@ -579,3 +579,16 @@ def update_onboarding_step(name, field, value): """ frappe.db.set_value("Onboarding Step", name, field, value) + +def merge_cards_based_on_label(cards): + """Merge cards with common label.""" + cards_dict = {} + for card in cards: + if card.label in cards_dict: + links = loads(cards_dict[card.label].links) + loads(card.links) + cards_dict[card.label].update(dict(links=dumps(links))) + cards_dict[card.label] = cards_dict.pop(card.label) + else: + cards_dict[card.label] = card + + return list(cards_dict.values()) \ No newline at end of file From 178a576b6a39a252415c9ef5b49a57a7cef2c034 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Mon, 24 Aug 2020 20:11:25 +0530 Subject: [PATCH 023/375] fix: review changes --- frappe/email/doctype/newsletter/newsletter.js | 4 ---- frappe/email/doctype/newsletter/newsletter.json | 8 +++++--- frappe/email/doctype/newsletter/newsletter.py | 14 +++++++------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/frappe/email/doctype/newsletter/newsletter.js b/frappe/email/doctype/newsletter/newsletter.js index 3d69c0cfad..3277d8e9ee 100644 --- a/frappe/email/doctype/newsletter/newsletter.js +++ b/frappe/email/doctype/newsletter/newsletter.js @@ -14,10 +14,6 @@ frappe.ui.form.on('Newsletter', { }); }, "fa fa-play", "btn-success"); } - if (!doc.__islocal && cint(doc.email_sent)) { - frm.set_df_property('schedule_send', "read_only", 1); - frm.set_df_property('schedule_sending', "read_only", 1); - } frm.events.setup_dashboard(frm); diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json index fac211ab49..1dd6115b43 100644 --- a/frappe/email/doctype/newsletter/newsletter.json +++ b/frappe/email/doctype/newsletter/newsletter.json @@ -125,7 +125,8 @@ "depends_on": "eval: doc.schedule_sending", "fieldname": "schedule_send", "fieldtype": "Datetime", - "label": "Schedule Send" + "label": "Schedule Send", + "read_only_depends_on": "eval: doc.email_sent" }, { "default": "0", @@ -157,7 +158,8 @@ "default": "0", "fieldname": "schedule_sending", "fieldtype": "Check", - "label": "Schedule Sending" + "label": "Schedule Sending", + "read_only_depends_on": "eval: doc.email_sent" } ], "has_web_view": 1, @@ -167,7 +169,7 @@ "is_published_field": "published", "links": [], "max_attachments": 3, - "modified": "2020-08-20 10:22:24.560288", + "modified": "2020-08-24 19:59:37.262500", "modified_by": "Administrator", "module": "Email", "name": "Newsletter", diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index afc0fa638b..929855ea30 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -27,7 +27,7 @@ class Newsletter(WebsiteGenerator): def test_send(self, doctype="Lead"): self.recipients = frappe.utils.split_emails(self.test_email_id) self.queue_all(test_email=True) - frappe.msgprint(_("Test email is send to {0}").format(self.test_email_id)) + frappe.msgprint(_("Test email sent to {0}").format(self.test_email_id)) def send_emails(self): """send emails to leads and customers""" @@ -86,12 +86,12 @@ class Newsletter(WebsiteGenerator): self.db_set("scheduled_to_send", len(self.recipients)) def get_message(self): - if self.content_type == 'Rich Text': - return self.message - elif self.content_type == 'Markdown': - return markdown(self.message_md) - elif self.content_type == 'HTML': - return self.message_html + + return { + 'Rich Text': self.message, + 'Markdown': markdown(self.message_md), + 'HTML': self.message_html + }[self.content_type] def get_recipients(self): """Get recipients from Email Group""" From 1c56732de57f4846e88065623a728108eafe24c6 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 24 Aug 2020 22:06:12 +0530 Subject: [PATCH 024/375] feat: add option to disable custom script --- .../doctype/custom_script/custom_script.json | 252 ++++++------------ frappe/desk/form/meta.py | 2 +- frappe/patches.txt | 1 + frappe/patches/v13_0/enable_custom_script.py | 13 + 4 files changed, 93 insertions(+), 175 deletions(-) create mode 100644 frappe/patches/v13_0/enable_custom_script.py diff --git a/frappe/custom/doctype/custom_script/custom_script.json b/frappe/custom/doctype/custom_script/custom_script.json index fc086e4b0b..328b247c49 100644 --- a/frappe/custom/doctype/custom_script/custom_script.json +++ b/frappe/custom/doctype/custom_script/custom_script.json @@ -1,187 +1,91 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2013-01-10 16:34:01", - "custom": 0, - "description": "Adds a client custom script to a DocType", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, - "engine": "InnoDB", + "actions": [], + "allow_import": 1, + "creation": "2013-01-10 16:34:01", + "description": "Adds a client custom script to a DocType", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "dt", + "enabled", + "script", + "sample" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "dt", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "DocType", - "length": 0, - "no_copy": 0, - "oldfieldname": "dt", - "oldfieldtype": "Link", - "options": "DocType", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "dt", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "DocType", + "oldfieldname": "dt", + "oldfieldtype": "Link", + "options": "DocType", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "script", - "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Script", - "length": 0, - "no_copy": 0, - "oldfieldname": "script", - "oldfieldtype": "Code", - "options": "JS", - "permlevel": 0, - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "script", + "fieldtype": "Code", + "label": "Script", + "oldfieldname": "script", + "oldfieldtype": "Code", + "options": "JS", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "sample", - "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sample", - "length": 0, - "no_copy": 0, - "options": "

Custom Script Help

\n

Custom Scripts are executed only on the client-side (i.e. in Forms). Here are some examples to get you started

\n
\n\n// fetch local_tax_no on selection of customer \n// cur_frm.add_fetch(link_field,  source_fieldname,  target_fieldname); \ncur_frm.add_fetch('customer',  'local_tax_no',  'local_tax_no');\n\n// additional validation on dates \nfrappe.ui.form.on('Task',  'validate',  function(frm) {\n    if (frm.doc.from_date < get_today()) {\n        msgprint('You can not select past date in From Date');\n        validated = false;\n    } \n});\n\n// make a field read-only after saving \nfrappe.ui.form.on('Task',  {\n    refresh: function(frm) {\n        // use the __islocal value of doc,  to check if the doc is saved or not\n        frm.set_df_property('myfield',  'read_only',  frm.doc.__islocal ? 0 : 1);\n    } \n});\n\n// additional permission check\nfrappe.ui.form.on('Task',  {\n    validate: function(frm) {\n        if(user=='user1@example.com' && frm.doc.purpose!='Material Receipt') {\n            msgprint('You are only allowed Material Receipt');\n            validated = false;\n        }\n    } \n});\n\n// calculate sales incentive\nfrappe.ui.form.on('Sales Invoice',  {\n    validate: function(frm) {\n        // calculate incentives for each person on the deal\n        total_incentive = 0\n        $.each(frm.doc.sales_team,  function(i,  d) {\n            // calculate incentive\n            var incentive_percent = 2;\n            if(frm.doc.base_grand_total > 400) incentive_percent = 4;\n            // actual incentive\n            d.incentives = flt(frm.doc.base_grand_total) * incentive_percent / 100;\n            total_incentive += flt(d.incentives)\n        });\n        frm.doc.total_incentive = total_incentive;\n    } \n})\n\n
", - "permlevel": 0, - "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, - "translatable": 0, - "unique": 0 + "fieldname": "sample", + "fieldtype": "HTML", + "label": "Sample", + "options": "

Custom Script Help

\n

Custom Scripts are executed only on the client-side (i.e. in Forms). Here are some examples to get you started

\n
\n\n// fetch local_tax_no on selection of customer \n// cur_frm.add_fetch(link_field,  source_fieldname,  target_fieldname); \ncur_frm.add_fetch('customer',  'local_tax_no',  'local_tax_no');\n\n// additional validation on dates \nfrappe.ui.form.on('Task',  'validate',  function(frm) {\n    if (frm.doc.from_date < get_today()) {\n        msgprint('You can not select past date in From Date');\n        validated = false;\n    } \n});\n\n// make a field read-only after saving \nfrappe.ui.form.on('Task',  {\n    refresh: function(frm) {\n        // use the __islocal value of doc,  to check if the doc is saved or not\n        frm.set_df_property('myfield',  'read_only',  frm.doc.__islocal ? 0 : 1);\n    } \n});\n\n// additional permission check\nfrappe.ui.form.on('Task',  {\n    validate: function(frm) {\n        if(user=='user1@example.com' && frm.doc.purpose!='Material Receipt') {\n            msgprint('You are only allowed Material Receipt');\n            validated = false;\n        }\n    } \n});\n\n// calculate sales incentive\nfrappe.ui.form.on('Sales Invoice',  {\n    validate: function(frm) {\n        // calculate incentives for each person on the deal\n        total_incentive = 0\n        $.each(frm.doc.sales_team,  function(i,  d) {\n            // calculate incentive\n            var incentive_percent = 2;\n            if(frm.doc.base_grand_total > 400) incentive_percent = 4;\n            // actual incentive\n            d.incentives = flt(frm.doc.base_grand_total) * incentive_percent / 100;\n            total_incentive += flt(d.incentives)\n        });\n        frm.doc.total_incentive = total_incentive;\n    } \n})\n\n
", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "0", + "fieldname": "enabled", + "fieldtype": "Check", + "label": "Enabled", + "show_days": 1, + "show_seconds": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-glass", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2019-03-21 14:26:57.402994", - "modified_by": "Administrator", - "module": "Custom", - "name": "Custom Script", - "owner": "Administrator", + ], + "icon": "fa fa-glass", + "idx": 1, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-08-24 21:56:07.719579", + "modified_by": "Administrator", + "module": "Custom", + "name": "Custom Script", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Administrator", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_order": "ASC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "sort_order": "ASC", + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py index ba0e5c2216..c28a40657f 100644 --- a/frappe/desk/form/meta.py +++ b/frappe/desk/form/meta.py @@ -130,7 +130,7 @@ class FormMeta(Meta): def add_custom_script(self): """embed all require files""" # custom script - custom = frappe.db.get_value("Custom Script", {"dt": self.name}, "script") or "" + custom = frappe.db.get_value("Custom Script", {"dt": self.name, "enabled": 1}, "script") or "" self.set("__custom_js", custom) diff --git a/frappe/patches.txt b/frappe/patches.txt index afbd007abc..a00d93fdcd 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -302,3 +302,4 @@ frappe.patches.v13_0.increase_password_length frappe.patches.v13_0.add_toggle_width_in_navbar_settings frappe.patches.v13_0.rename_notification_fields frappe.patches.v13_0.remove_duplicate_navbar_items +frappe.patches.v13_0.enable_custom_script diff --git a/frappe/patches/v13_0/enable_custom_script.py b/frappe/patches/v13_0/enable_custom_script.py new file mode 100644 index 0000000000..92284e6dcc --- /dev/null +++ b/frappe/patches/v13_0/enable_custom_script.py @@ -0,0 +1,13 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + """Enable all the existing custom script""" + frappe.reload_doc("Custom", "doctype", "Custom Script") + + frappe.db.sql(""" + UPDATE `tabCustom Script` SET enabled=1 + """) \ No newline at end of file From 8143f551663cf4c9c478dcedfc41928022acbc73 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 24 Aug 2020 22:22:18 +0530 Subject: [PATCH 025/375] fix: Bundle minified version of vue in libs.min.js --- frappe/public/build.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/build.json b/frappe/public/build.json index 997a3092ad..844e436e43 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -128,7 +128,7 @@ "public/js/lib/Sortable.min.js", "public/js/lib/jquery/jquery.hotkeys.js", "public/js/lib/bootstrap.min.js", - "node_modules/vue/dist/vue.js", + "node_modules/vue/dist/vue.min.js", "node_modules/moment/min/moment-with-locales.min.js", "node_modules/moment-timezone/builds/moment-timezone-with-data.min.js", "public/js/lib/socket.io.min.js", From eab928a7110be295a81b1945fb5d8993fbbea137 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 25 Aug 2020 17:40:18 +0530 Subject: [PATCH 026/375] fix: translation --- frappe/translations/fa.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/translations/fa.csv b/frappe/translations/fa.csv index e93fcc4a21..0698897880 100644 --- a/frappe/translations/fa.csv +++ b/frappe/translations/fa.csv @@ -3304,7 +3304,7 @@ Daily Long,روزانه طولانی, Data Import Beta,واردات داده بتا, Default Role on Creation,نقش پیش فرض در آفرینش, Default Theme,موضوع پیش فرض, -Default {0},پیش فرض {0, +Default {0},پیش فرض {0}, Delete All,حذف همه, "Determines the order of the slide in the wizard. If the slide is not to be displayed, priority should be set to 0.",ترتیب اسلاید در جادوگر را تعیین می کند. اگر اسلاید نمایش داده نمی شود ، اولویت باید بر روی 0 تنظیم شود., Do you want to cancel all linked documents?,آیا می خواهید کلیه اسناد مرتبط را لغو کنید؟, From 77bb439429ac0a625395db280d2639ba34a4577b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 4 Aug 2020 13:54:54 +0530 Subject: [PATCH 027/375] feat: don't let users override route for blog category --- frappe/website/doctype/blog_category/blog_category.py | 3 +-- frappe/website/doctype/blog_post/blog_post.py | 2 +- frappe/website/doctype/blog_post/templates/blog_post.html | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/frappe/website/doctype/blog_category/blog_category.py b/frappe/website/doctype/blog_category/blog_category.py index a293158a09..aa5287bcb6 100644 --- a/frappe/website/doctype/blog_category/blog_category.py +++ b/frappe/website/doctype/blog_category/blog_category.py @@ -14,6 +14,5 @@ class BlogCategory(WebsiteGenerator): clear_cache() def validate(self): - if not self.route: - self.route = 'blog/' + self.scrub(self.name) + self.route = 'blog/' + self.scrub(self.name) super(BlogCategory, self).validate() diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index beffcdca25..bd9c801b08 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -99,7 +99,7 @@ class BlogPost(WebsiteGenerator): self.load_comments(context) context.category = frappe.db.get_value("Blog Category", - context.doc.blog_category, ["title", "route"], as_dict=1) + context.doc.blog_category, ["title", "name"], as_dict=1) context.parents = [{"name": _("Home"), "route":"/"}, {"name": "Blog", "route": "/blog"}, {"label": context.category.title, "route":context.category.route}] diff --git a/frappe/website/doctype/blog_post/templates/blog_post.html b/frappe/website/doctype/blog_post/templates/blog_post.html index dd3e59c3c1..c5580603d0 100644 --- a/frappe/website/doctype/blog_post/templates/blog_post.html +++ b/frappe/website/doctype/blog_post/templates/blog_post.html @@ -12,7 +12,7 @@

{{ title }}

From 494e95d0735debc9e75504721197d5b6ddec0a72 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 4 Aug 2020 14:01:54 +0530 Subject: [PATCH 028/375] feat: enhancements to blog category * changed order of fields * make route read only * add description to category name --- .../website/doctype/blog_category/blog_category.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frappe/website/doctype/blog_category/blog_category.json b/frappe/website/doctype/blog_category/blog_category.json index b2180047cd..ec5b48cdf1 100644 --- a/frappe/website/doctype/blog_category/blog_category.json +++ b/frappe/website/doctype/blog_category/blog_category.json @@ -8,13 +8,14 @@ "document_type": "Setup", "engine": "InnoDB", "field_order": [ - "category_name", "title", + "category_name", "published", "route" ], "fields": [ { + "description": "Name used in URLs, example \"product-updates\"", "fieldname": "category_name", "fieldtype": "Data", "in_list_view": 1, @@ -31,7 +32,7 @@ "reqd": 1 }, { - "default": "0", + "default": "1", "fieldname": "published", "fieldtype": "Check", "in_list_view": 1, @@ -42,15 +43,17 @@ "fieldname": "route", "fieldtype": "Data", "label": "Route", + "read_only": 1, "unique": 1 } ], "has_web_view": 1, "icon": "fa fa-tag", "idx": 1, + "index_web_pages_for_search": 1, "is_published_field": "published", "links": [], - "modified": "2020-07-29 21:14:47.210446", + "modified": "2020-08-04 14:00:19.842606", "modified_by": "Administrator", "module": "Website", "name": "Blog Category", @@ -78,5 +81,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "title_field": "title", "track_changes": 1 } \ No newline at end of file From 2e81bccd3c693a97ccb3f519d1b3882cb13b8865 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 4 Aug 2020 14:02:14 +0530 Subject: [PATCH 029/375] feat: don't let users override route --- frappe/website/doctype/blog_category/blog_category.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/website/doctype/blog_category/blog_category.py b/frappe/website/doctype/blog_category/blog_category.py index aa5287bcb6..47b363383a 100644 --- a/frappe/website/doctype/blog_category/blog_category.py +++ b/frappe/website/doctype/blog_category/blog_category.py @@ -13,6 +13,5 @@ class BlogCategory(WebsiteGenerator): def on_update(self): clear_cache() - def validate(self): + def set_route(self): self.route = 'blog/' + self.scrub(self.name) - super(BlogCategory, self).validate() From 88c4838458a19672853862bd85cf6f99b457e2c0 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 4 Aug 2020 14:07:43 +0530 Subject: [PATCH 030/375] feat: patch to set route for blog category --- frappe/patches.txt | 1 + frappe/patches/v13_0/set_route_for_blog_category.py | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 frappe/patches/v13_0/set_route_for_blog_category.py diff --git a/frappe/patches.txt b/frappe/patches.txt index 985911314e..310f88baa9 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -304,3 +304,4 @@ frappe.patches.v13_0.increase_password_length frappe.patches.v13_0.add_toggle_width_in_navbar_settings frappe.patches.v13_0.rename_notification_fields frappe.patches.v13_0.remove_duplicate_navbar_items +frappe.patches.v13_0.set_route_for_blog_category diff --git a/frappe/patches/v13_0/set_route_for_blog_category.py b/frappe/patches/v13_0/set_route_for_blog_category.py new file mode 100644 index 0000000000..7ea26bc2c0 --- /dev/null +++ b/frappe/patches/v13_0/set_route_for_blog_category.py @@ -0,0 +1,8 @@ +import frappe + +def execute(): + categories = frappe.get_list("Blog Category") + for category in categories: + doc = frappe.get_doc("Blog Category", category["name"]) + doc.set_route() + doc.save() From de0bde046a0546c89295855960055e1d43ed7e69 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 4 Aug 2020 14:07:57 +0530 Subject: [PATCH 031/375] feat: add test to check blog route --- frappe/website/doctype/blog_category/blog_category.py | 1 + .../website/doctype/blog_category/test_blog_category.py | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/frappe/website/doctype/blog_category/blog_category.py b/frappe/website/doctype/blog_category/blog_category.py index 47b363383a..b036b561d0 100644 --- a/frappe/website/doctype/blog_category/blog_category.py +++ b/frappe/website/doctype/blog_category/blog_category.py @@ -14,4 +14,5 @@ class BlogCategory(WebsiteGenerator): clear_cache() def set_route(self): + # Override blog route since it has to been templated self.route = 'blog/' + self.scrub(self.name) diff --git a/frappe/website/doctype/blog_category/test_blog_category.py b/frappe/website/doctype/blog_category/test_blog_category.py index d033b84786..6b0bd60c82 100644 --- a/frappe/website/doctype/blog_category/test_blog_category.py +++ b/frappe/website/doctype/blog_category/test_blog_category.py @@ -4,4 +4,11 @@ from __future__ import unicode_literals import frappe -test_records = frappe.get_test_records('Blog Category') \ No newline at end of file +class TestBlogCategory(unittest.TestCase): + def test_route(self): + cat = frappe.new_doc("Blog Categroy", { + "title": "_Yet Another Category", + "category_name": "test-category-yet-another-category", + }) + cat.insert() + self.assertEqual(cat.route, 'blog/test-category-yet-another-category') From 57b5f2fbbbf67206538a77a2d48be9bafa26365e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 11 Aug 2020 11:51:51 +0530 Subject: [PATCH 032/375] feat: don't scrub name when setting route --- frappe/website/doctype/blog_category/blog_category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/doctype/blog_category/blog_category.py b/frappe/website/doctype/blog_category/blog_category.py index b036b561d0..323217dad1 100644 --- a/frappe/website/doctype/blog_category/blog_category.py +++ b/frappe/website/doctype/blog_category/blog_category.py @@ -15,4 +15,4 @@ class BlogCategory(WebsiteGenerator): def set_route(self): # Override blog route since it has to been templated - self.route = 'blog/' + self.scrub(self.name) + self.route = 'blog/' + self.name From 2dc96252bd7126a6af1a98b3be5774470abbe6f5 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 11 Aug 2020 14:23:47 +0530 Subject: [PATCH 033/375] feat(blog): remove category name field --- .../doctype/blog_category/blog_category.json | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/frappe/website/doctype/blog_category/blog_category.json b/frappe/website/doctype/blog_category/blog_category.json index ec5b48cdf1..c5114a811b 100644 --- a/frappe/website/doctype/blog_category/blog_category.json +++ b/frappe/website/doctype/blog_category/blog_category.json @@ -2,27 +2,16 @@ "actions": [], "allow_guest_to_view": 1, "allow_import": 1, - "autoname": "field:category_name", "creation": "2013-03-08 09:41:11", "doctype": "DocType", "document_type": "Setup", "engine": "InnoDB", "field_order": [ "title", - "category_name", "published", "route" ], "fields": [ - { - "description": "Name used in URLs, example \"product-updates\"", - "fieldname": "category_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Category Name", - "reqd": 1, - "unique": 1 - }, { "fieldname": "title", "fieldtype": "Data", @@ -53,7 +42,7 @@ "index_web_pages_for_search": 1, "is_published_field": "published", "links": [], - "modified": "2020-08-04 14:00:19.842606", + "modified": "2020-08-11 11:57:19.672807", "modified_by": "Administrator", "module": "Website", "name": "Blog Category", From 5df22dcdcc26ebcca71253798ea9d67a2f588c76 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 11 Aug 2020 14:29:06 +0530 Subject: [PATCH 034/375] feat: update test records --- frappe/core/doctype/user_permission/test_user_permission.py | 3 +-- frappe/website/doctype/blog_category/test_blog_category.py | 3 +-- frappe/website/doctype/blog_category/test_records.json | 3 --- frappe/website/doctype/blog_post/test_blog_post.py | 3 +-- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/frappe/core/doctype/user_permission/test_user_permission.py b/frappe/core/doctype/user_permission/test_user_permission.py index 5ccc8752cf..82dd2ab27e 100644 --- a/frappe/core/doctype/user_permission/test_user_permission.py +++ b/frappe/core/doctype/user_permission/test_user_permission.py @@ -26,8 +26,7 @@ class TestUserPermission(unittest.TestCase): user = create_user('test_user_perm1@example.com', 'Website Manager') for category in ['general', 'public']: if not frappe.db.exists('Blog Category', category): - frappe.get_doc({'doctype': 'Blog Category', - 'category_name': category, 'title': category}).insert() + frappe.get_doc({'doctype': 'Blog Category', 'title': category}).insert() param = get_params(user, 'Blog Category', 'general', is_default=1) add_user_permissions(param) diff --git a/frappe/website/doctype/blog_category/test_blog_category.py b/frappe/website/doctype/blog_category/test_blog_category.py index 6b0bd60c82..d9c001ee73 100644 --- a/frappe/website/doctype/blog_category/test_blog_category.py +++ b/frappe/website/doctype/blog_category/test_blog_category.py @@ -7,8 +7,7 @@ import frappe class TestBlogCategory(unittest.TestCase): def test_route(self): cat = frappe.new_doc("Blog Categroy", { - "title": "_Yet Another Category", - "category_name": "test-category-yet-another-category", + "title": "Test Category Yet Another Category", }) cat.insert() self.assertEqual(cat.route, 'blog/test-category-yet-another-category') diff --git a/frappe/website/doctype/blog_category/test_records.json b/frappe/website/doctype/blog_category/test_records.json index 3334bbc4f9..4bd4ac35b7 100644 --- a/frappe/website/doctype/blog_category/test_records.json +++ b/frappe/website/doctype/blog_category/test_records.json @@ -1,18 +1,15 @@ [ { - "category_name": "_Test Blog Category", "doctype": "Blog Category", "parent_website_route": "blog", "title": "_Test Blog Category" }, { - "category_name": "_Test Blog Category 1", "doctype": "Blog Category", "parent_website_route": "blog", "title": "_Test Blog Category 1" }, { - "category_name": "_Test Blog Category 2", "doctype": "Blog Category", "parent_website_route": "blog", "title": "_Test Blog Category 2" diff --git a/frappe/website/doctype/blog_post/test_blog_post.py b/frappe/website/doctype/blog_post/test_blog_post.py index 15634a7caf..c73cf2caa0 100644 --- a/frappe/website/doctype/blog_post/test_blog_post.py +++ b/frappe/website/doctype/blog_post/test_blog_post.py @@ -36,7 +36,6 @@ def make_test_blog(): if not frappe.db.exists('Blog Category', 'Test Blog Category'): frappe.get_doc(dict( doctype = 'Blog Category', - category_name = 'Test Blog Category', title='Test Blog Category')).insert() if not frappe.db.exists('Blogger', 'test-blogger'): frappe.get_doc(dict( @@ -45,7 +44,7 @@ def make_test_blog(): full_name='Test Blogger')).insert() test_blog = frappe.get_doc(dict( doctype = 'Blog Post', - blog_category = 'Test Blog Category', + blog_category = 'test-blog-category', blogger = 'test-blogger', title = random_string(20), route = random_string(20), From fa6ef9731d33c8477c92dcc7d4edb2edcc85df33 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 11 Aug 2020 14:29:18 +0530 Subject: [PATCH 035/375] feat: set name using scrubbed title --- frappe/website/doctype/blog_category/blog_category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/doctype/blog_category/blog_category.py b/frappe/website/doctype/blog_category/blog_category.py index 323217dad1..375ba5b6a3 100644 --- a/frappe/website/doctype/blog_category/blog_category.py +++ b/frappe/website/doctype/blog_category/blog_category.py @@ -8,7 +8,7 @@ from frappe.website.render import clear_cache class BlogCategory(WebsiteGenerator): def autoname(self): # to override autoname of WebsiteGenerator - self.name = self.category_name + self.name = self.scrub(self.title) def on_update(self): clear_cache() From 425d29f314e61833aa1aea72758c42ab9ec36140 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Sat, 18 Jul 2020 16:56:07 +0530 Subject: [PATCH 036/375] test(blog): Blog Post should have correct link to the Blog Category page --- .../doctype/blog_post/test_blog_post.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/frappe/website/doctype/blog_post/test_blog_post.py b/frappe/website/doctype/blog_post/test_blog_post.py index c73cf2caa0..4b4d062a2d 100644 --- a/frappe/website/doctype/blog_post/test_blog_post.py +++ b/frappe/website/doctype/blog_post/test_blog_post.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe import unittest +from bs4 import BeautifulSoup from frappe.utils import set_request from frappe.website.render import render @@ -32,8 +33,36 @@ class TestBlogPost(unittest.TestCase): self.assertTrue(response.status_code, 404) + def test_category_link(self): + # Make a temporary Blog Post (and a Blog Category) + blog = make_test_blog() + + # Visit the blog post page + set_request(path=blog.route) + blog_page_response = render() + blog_page_html = frappe.safe_decode(blog_page_response.get_data()) + + # On blog post page find link to the category page + soup = BeautifulSoup(blog_page_html, "lxml") + category_title = frappe.db.get_value("Blog Category", blog.blog_category, "title") + category_page_link = list(soup.find_all('a', string=category_title))[0] + category_page_url = category_page_link["href"] + + # Visit the category page (by following the link found in above stage) + set_request(path=category_page_url) + category_page_response = render() + category_page_html = frappe.safe_decode(category_page_response.get_data()) + + # Category page should contain the blog post title + self.assertIn(blog.title, category_page_html) + + # Cleanup afterwords + frappe.delete_doc("Blog Post", blog.name) + frappe.delete_doc("Blog Category", blog.blog_category) + def make_test_blog(): if not frappe.db.exists('Blog Category', 'Test Blog Category'): + # Set different title and name for the category frappe.get_doc(dict( doctype = 'Blog Category', title='Test Blog Category')).insert() From 534cdb4c83b9ae784c02f3daa95deeaeda55ab97 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 21 Aug 2020 12:06:24 +0530 Subject: [PATCH 037/375] feat: allow renaming blog category --- frappe/website/doctype/blog_category/blog_category.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/website/doctype/blog_category/blog_category.json b/frappe/website/doctype/blog_category/blog_category.json index c5114a811b..67e17f49fb 100644 --- a/frappe/website/doctype/blog_category/blog_category.json +++ b/frappe/website/doctype/blog_category/blog_category.json @@ -2,6 +2,7 @@ "actions": [], "allow_guest_to_view": 1, "allow_import": 1, + "allow_rename": 1, "creation": "2013-03-08 09:41:11", "doctype": "DocType", "document_type": "Setup", @@ -42,7 +43,7 @@ "index_web_pages_for_search": 1, "is_published_field": "published", "links": [], - "modified": "2020-08-11 11:57:19.672807", + "modified": "2020-08-21 11:40:36.919321", "modified_by": "Administrator", "module": "Website", "name": "Blog Category", From 43c8d9886c49802dbba2759fcf70ddf579da796a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 21 Aug 2020 12:06:43 +0530 Subject: [PATCH 038/375] feat: remove test --- frappe/website/doctype/blog_category/test_blog_category.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/frappe/website/doctype/blog_category/test_blog_category.py b/frappe/website/doctype/blog_category/test_blog_category.py index d9c001ee73..38b7ea31a0 100644 --- a/frappe/website/doctype/blog_category/test_blog_category.py +++ b/frappe/website/doctype/blog_category/test_blog_category.py @@ -5,9 +5,4 @@ from __future__ import unicode_literals import frappe class TestBlogCategory(unittest.TestCase): - def test_route(self): - cat = frappe.new_doc("Blog Categroy", { - "title": "Test Category Yet Another Category", - }) - cat.insert() - self.assertEqual(cat.route, 'blog/test-category-yet-another-category') + pass From 5d15dff428716646f90ec7c14fdb873da164f578 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 21 Aug 2020 12:07:05 +0530 Subject: [PATCH 039/375] feat: use category route directly --- frappe/website/doctype/blog_post/blog_post.py | 2 +- frappe/website/doctype/blog_post/templates/blog_post.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index bd9c801b08..beffcdca25 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -99,7 +99,7 @@ class BlogPost(WebsiteGenerator): self.load_comments(context) context.category = frappe.db.get_value("Blog Category", - context.doc.blog_category, ["title", "name"], as_dict=1) + context.doc.blog_category, ["title", "route"], as_dict=1) context.parents = [{"name": _("Home"), "route":"/"}, {"name": "Blog", "route": "/blog"}, {"label": context.category.title, "route":context.category.route}] diff --git a/frappe/website/doctype/blog_post/templates/blog_post.html b/frappe/website/doctype/blog_post/templates/blog_post.html index c5580603d0..8244b9e6c2 100644 --- a/frappe/website/doctype/blog_post/templates/blog_post.html +++ b/frappe/website/doctype/blog_post/templates/blog_post.html @@ -12,7 +12,7 @@

{{ title }}

From 877260610330c7c4ba8bc0cb3f85bd6350dbb3e4 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 21 Aug 2020 12:11:36 +0530 Subject: [PATCH 040/375] fix: document None is not subscriptable --- frappe/search/full_text_search.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/search/full_text_search.py b/frappe/search/full_text_search.py index dd6e69111d..ecb018dbb4 100644 --- a/frappe/search/full_text_search.py +++ b/frappe/search/full_text_search.py @@ -46,7 +46,8 @@ class FullTextSearch: doc_name (str): name of the document to be updated """ document = self.get_document_to_index(doc_name) - self.update_index(document) + if document: + self.update_index(document) def remove_document_from_index(self, doc_name): """Remove document from search index From 87c6d413fab29440c9b1fe3f89d278869447505d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 24 Aug 2020 14:04:22 +0530 Subject: [PATCH 041/375] fix: import error --- frappe/website/doctype/blog_category/test_blog_category.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/website/doctype/blog_category/test_blog_category.py b/frappe/website/doctype/blog_category/test_blog_category.py index 38b7ea31a0..fe8f4544cd 100644 --- a/frappe/website/doctype/blog_category/test_blog_category.py +++ b/frappe/website/doctype/blog_category/test_blog_category.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe +import unittest class TestBlogCategory(unittest.TestCase): pass From 1356609f06fc9ff66839484466bda9c838249893 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 24 Aug 2020 15:26:49 +0530 Subject: [PATCH 042/375] test: add name explicitly to blog cateogry --- frappe/website/doctype/blog_category/test_records.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frappe/website/doctype/blog_category/test_records.json b/frappe/website/doctype/blog_category/test_records.json index 4bd4ac35b7..89ff56d4fd 100644 --- a/frappe/website/doctype/blog_category/test_records.json +++ b/frappe/website/doctype/blog_category/test_records.json @@ -2,16 +2,19 @@ { "doctype": "Blog Category", "parent_website_route": "blog", - "title": "_Test Blog Category" + "title": "_Test Blog Category", + "name": "_Test Blog Category" }, { "doctype": "Blog Category", "parent_website_route": "blog", - "title": "_Test Blog Category 1" + "title": "_Test Blog Category 1", + "name": "_Test Blog Category 1" }, { "doctype": "Blog Category", "parent_website_route": "blog", - "title": "_Test Blog Category 2" + "title": "_Test Blog Category 2", + "name": "_Test Blog Category 2" } ] \ No newline at end of file From faffec0f37cb53f04e48679476fa0944cd7b170b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 26 Aug 2020 11:37:58 +0530 Subject: [PATCH 043/375] feat: update name in test records --- frappe/tests/test_form_load.py | 2 +- frappe/tests/test_permissions.py | 34 +++++++++---------- .../doctype/blog_category/test_records.json | 9 ++--- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/frappe/tests/test_form_load.py b/frappe/tests/test_form_load.py index 34fc58465e..459a3f52bd 100644 --- a/frappe/tests/test_form_load.py +++ b/frappe/tests/test_form_load.py @@ -24,7 +24,7 @@ class TestFormLoad(unittest.TestCase): def test_fieldlevel_permissions_in_load(self): blog = frappe.get_doc({ "doctype": "Blog Post", - "blog_category": "_Test Blog Category 1", + "blog_category": "-test-blog-category-1", "blog_intro": "Test Blog Intro", "blogger": "_Test Blogger 1", "content": "Test Blog Content", diff --git a/frappe/tests/test_permissions.py b/frappe/tests/test_permissions.py index 364469f168..dddc790c94 100644 --- a/frappe/tests/test_permissions.py +++ b/frappe/tests/test_permissions.py @@ -59,7 +59,7 @@ class TestPermissions(unittest.TestCase): self.assertTrue(post.has_permission("read")) def test_user_permissions_in_doc(self): - add_user_permission("Blog Category", "_Test Blog Category 1", + add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") frappe.set_user("test2@example.com") @@ -73,7 +73,7 @@ class TestPermissions(unittest.TestCase): self.assertTrue(get_doc_permissions(post1).get("read")) def test_user_permissions_in_report(self): - add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com") + add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") frappe.set_user("test2@example.com") names = [d.name for d in frappe.get_list("Blog Post", fields=["name", "blog_category"])] @@ -86,23 +86,23 @@ class TestPermissions(unittest.TestCase): self.assertFalse(doc.get("blog_category")) # Fetch default based on single user permission - add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com") + add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") frappe.set_user("test2@example.com") doc = frappe.new_doc("Blog Post") - self.assertEqual(doc.get("blog_category"), "_Test Blog Category 1") + self.assertEqual(doc.get("blog_category"), "-test-blog-category-1") # Don't fetch default if user permissions is more than 1 - add_user_permission("Blog Category", "_Test Blog Category", "test2@example.com", ignore_permissions=True) + add_user_permission("Blog Category", "-test-blog-category", "test2@example.com", ignore_permissions=True) frappe.clear_cache() doc = frappe.new_doc("Blog Post") self.assertFalse(doc.get("blog_category")) # Fetch user permission set as default from multiple user permission - add_user_permission("Blog Category", "_Test Blog Category 2", "test2@example.com", ignore_permissions=True, is_default=1) + add_user_permission("Blog Category", "-test-blog-category-2", "test2@example.com", ignore_permissions=True, is_default=1) frappe.clear_cache() doc = frappe.new_doc("Blog Post") - self.assertEqual(doc.get("blog_category"), "_Test Blog Category 2") + self.assertEqual(doc.get("blog_category"), "-test-blog-category-2") def test_user_link_match_doc(self): blogger = frappe.get_doc("Blogger", "_Test Blogger 1") @@ -215,7 +215,7 @@ class TestPermissions(unittest.TestCase): frappe.clear_cache(doctype='DocType') def test_user_permission_doctypes(self): - add_user_permission("Blog Category", "_Test Blog Category 1", + add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") add_user_permission("Blogger", "_Test Blogger 1", "test2@example.com") @@ -235,7 +235,7 @@ class TestPermissions(unittest.TestCase): def if_owner_setup(self): update('Blog Post', 'Blogger', 0, 'if_owner', 1) - add_user_permission("Blog Category", "_Test Blog Category 1", + add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") add_user_permission("Blogger", "_Test Blogger 1", "test2@example.com") @@ -254,7 +254,7 @@ class TestPermissions(unittest.TestCase): doc = frappe.get_doc({ "doctype": "Blog Post", - "blog_category": "_Test Blog Category", + "blog_category": "-test-blog-category", "blogger": "_Test Blogger 1", "title": "_Test Blog Post Title", "content": "_Test Blog Post Content" @@ -263,14 +263,14 @@ class TestPermissions(unittest.TestCase): self.assertRaises(frappe.PermissionError, doc.insert) frappe.set_user('test1@example.com') - add_user_permission("Blog Category", "_Test Blog Category", + add_user_permission("Blog Category", "-test-blog-category", "test2@example.com") frappe.set_user("test2@example.com") doc.insert() frappe.set_user("Administrator") - remove_user_permission("Blog Category", "_Test Blog Category", + remove_user_permission("Blog Category", "-test-blog-category", "test2@example.com") frappe.set_user("test2@example.com") @@ -286,13 +286,13 @@ class TestPermissions(unittest.TestCase): def test_ignore_user_permissions_if_missing(self): """If there are no user permissions, then allow as per role""" - add_user_permission("Blog Category", "_Test Blog Category", + add_user_permission("Blog Category", "-test-blog-category", "test2@example.com") frappe.set_user("test2@example.com") doc = frappe.get_doc({ "doctype": "Blog Post", - "blog_category": "_Test Blog Category 2", + "blog_category": "-test-blog-category-2", "blogger": "_Test Blogger 1", "title": "_Test Blog Post Title", "content": "_Test Blog Post Content" @@ -301,7 +301,7 @@ class TestPermissions(unittest.TestCase): self.assertFalse(doc.has_permission("write")) frappe.set_user("Administrator") - remove_user_permission("Blog Category", "_Test Blog Category", + remove_user_permission("Blog Category", "-test-blog-category", "test2@example.com") frappe.set_user("test2@example.com") @@ -420,7 +420,7 @@ class TestPermissions(unittest.TestCase): doc = frappe.get_doc({ "doctype": "Blog Post", - "blog_category": "_Test Blog Category", + "blog_category": "-test-blog-category", "blogger": "_Test Blogger 1", "title": "_Test Blog Post Title", "content": "_Test Blog Post Content" @@ -454,7 +454,7 @@ class TestPermissions(unittest.TestCase): add_user_permission('Blog Post', '-test-blog-post-1', 'test2@example.com') add_user_permission('Blog Post', '-test-blog-post-2', 'test2@example.com') - add_user_permission("Blog Category", '_Test Blog Category 1', 'test2@example.com') + add_user_permission("Blog Category", '-test-blog-category-1', 'test2@example.com') deleted_user_permission_count = clear_user_permissions('test2@example.com', 'Blog Post') diff --git a/frappe/website/doctype/blog_category/test_records.json b/frappe/website/doctype/blog_category/test_records.json index 89ff56d4fd..4bd4ac35b7 100644 --- a/frappe/website/doctype/blog_category/test_records.json +++ b/frappe/website/doctype/blog_category/test_records.json @@ -2,19 +2,16 @@ { "doctype": "Blog Category", "parent_website_route": "blog", - "title": "_Test Blog Category", - "name": "_Test Blog Category" + "title": "_Test Blog Category" }, { "doctype": "Blog Category", "parent_website_route": "blog", - "title": "_Test Blog Category 1", - "name": "_Test Blog Category 1" + "title": "_Test Blog Category 1" }, { "doctype": "Blog Category", "parent_website_route": "blog", - "title": "_Test Blog Category 2", - "name": "_Test Blog Category 2" + "title": "_Test Blog Category 2" } ] \ No newline at end of file From cbaa795c200b82414b109fd3824582e8a16bb522 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 26 Aug 2020 11:57:03 +0530 Subject: [PATCH 044/375] fix(backups): Allow naming conf file via BackupGenerator --- frappe/utils/backups.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index a64ba8c3a7..abf2a5fc81 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -27,7 +27,7 @@ class BackupGenerator: To initialize, specify (db_name, user, password, db_file_name=None, db_host="localhost") If specifying db_file_name, also append ".sql.gz" """ - def __init__(self, db_name, user, password, backup_path_db=None, backup_path_files=None, + def __init__(self, db_name, user, password, backup_path_conf=None, backup_path_db=None, backup_path_files=None, backup_path_private_files=None, db_host="localhost", db_port=None, verbose=False, db_type='mariadb'): global _verbose @@ -37,8 +37,9 @@ class BackupGenerator: self.db_type = db_type self.user = user self.password = password - self.backup_path_files = backup_path_files + self.backup_path_conf = backup_path_conf self.backup_path_db = backup_path_db + self.backup_path_files = backup_path_files self.backup_path_private_files = backup_path_private_files if not self.db_port and self.db_type == 'mariadb': @@ -83,11 +84,14 @@ class BackupGenerator: def set_backup_file_name(self): #Generate a random name using today's date and a 8 digit random number + for_conf = self.todays_date + "-" + self.site_slug + "-site_config_backup.json" for_db = self.todays_date + "-" + self.site_slug + "-database.sql.gz" for_public_files = self.todays_date + "-" + self.site_slug + "-files.tar" for_private_files = self.todays_date + "-" + self.site_slug + "-private-files.tar" backup_path = get_backup_path() + if not self.backup_path_conf: + self.backup_path_conf = os.path.join(backup_path, for_conf) if not self.backup_path_db: self.backup_path_db = os.path.join(backup_path, for_db) if not self.backup_path_files: @@ -150,19 +154,11 @@ class BackupGenerator: print('Backed up files', os.path.abspath(backup_path)) def copy_site_config(self): - site_config_backup_path = os.path.join( - get_backup_path(), - "{time_stamp}-{site_slug}-site_config_backup.json".format( - time_stamp=self.todays_date, - site_slug=self.site_slug)) + site_config_backup_path = self.backup_path_conf site_config_path = os.path.join(frappe.get_site_path(), "site_config.json") - site_config = {} - if os.path.exists(site_config_path): - site_config.update(frappe.get_file_json(site_config_path)) + with open(site_config_backup_path, "w") as f: - f.write(json.dumps(site_config, indent=2)) - f.flush() - self.site_config_backup_path = site_config_backup_path + json.dump(frappe.get_file_json(site_config_path), f, indent=2) def take_dump(self): import frappe.utils From ae8201cc1c8a8f430919d3832476479ce92c0414 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 26 Aug 2020 12:08:51 +0530 Subject: [PATCH 045/375] fix: Just copy the contents of the files instead of parsing it --- frappe/utils/backups.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index abf2a5fc81..3d683f6c0a 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -157,8 +157,8 @@ class BackupGenerator: site_config_backup_path = self.backup_path_conf site_config_path = os.path.join(frappe.get_site_path(), "site_config.json") - with open(site_config_backup_path, "w") as f: - json.dump(frappe.get_file_json(site_config_path), f, indent=2) + with open(site_config_backup_path, "w") as n, open(site_config_path) as c: + n.write(c.read()) def take_dump(self): import frappe.utils From 2ae5ce7d0e157a3ca5e3b04cc71f4b6ccb997d6f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 26 Aug 2020 20:48:29 +0530 Subject: [PATCH 046/375] fix: Subtitle in Section With Tabs web template --- .../web_template/section_with_tabs/section_with_tabs.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/website/web_template/section_with_tabs/section_with_tabs.html b/frappe/website/web_template/section_with_tabs/section_with_tabs.html index 0c206b1c48..bb90611b4c 100644 --- a/frappe/website/web_template/section_with_tabs/section_with_tabs.html +++ b/frappe/website/web_template/section_with_tabs/section_with_tabs.html @@ -1,5 +1,8 @@

{{ title }}

+ +{%- if subtitle-%}

{{ subtitle }}

+{%- endif -%}
{% set ns = namespace(tabs=[]) %} From 2414dd386c24dcd0334a8c491eeb21d65d21bb14 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 27 Aug 2020 11:36:43 +0530 Subject: [PATCH 047/375] fix: formatting --- .../web_template/section_with_tabs/section_with_tabs.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/web_template/section_with_tabs/section_with_tabs.html b/frappe/website/web_template/section_with_tabs/section_with_tabs.html index bb90611b4c..9a5bb20e0f 100644 --- a/frappe/website/web_template/section_with_tabs/section_with_tabs.html +++ b/frappe/website/web_template/section_with_tabs/section_with_tabs.html @@ -1,6 +1,6 @@

{{ title }}

-{%- if subtitle-%} +{%- if subtitle -%}

{{ subtitle }}

{%- endif -%} From 5c7f34b15dd91a00bee7c44cca72881f4ece0de5 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 27 Aug 2020 14:22:48 +0530 Subject: [PATCH 048/375] test: update records --- frappe/website/doctype/blog_post/test_records.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/website/doctype/blog_post/test_records.json b/frappe/website/doctype/blog_post/test_records.json index 79a3525801..4b29eadfa4 100644 --- a/frappe/website/doctype/blog_post/test_records.json +++ b/frappe/website/doctype/blog_post/test_records.json @@ -1,6 +1,6 @@ [ { - "blog_category": "_Test Blog Category", + "blog_category": "-test-blog-category", "blog_intro": "Test Blog Intro", "blogger": "_Test Blogger", "content": "Test Blog Content", @@ -9,7 +9,7 @@ "published": 1 }, { - "blog_category": "_Test Blog Category 1", + "blog_category": "-test-blog-category-1", "blog_intro": "Test Blog Intro", "blogger": "_Test Blogger", "content": "Test Blog Content", @@ -18,7 +18,7 @@ "published": 1 }, { - "blog_category": "_Test Blog Category 1", + "blog_category": "-test-blog-category-1", "blog_intro": "Test Blog Intro", "blogger": "_Test Blogger 1", "content": "Test Blog Content", @@ -27,7 +27,7 @@ "published": 0 }, { - "blog_category": "_Test Blog Category 1", + "blog_category": "-test-blog-category-1", "blog_intro": "Test Blog Intro", "blogger": "_Test Blogger 2", "content": "Test Blog Content", @@ -35,4 +35,4 @@ "title": "_Test Blog Post 3", "published": 0 } -] \ No newline at end of file +] From a4217f2e387ea1ce00cfc4984c208bbb911f5653 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 27 Aug 2020 14:48:17 +0530 Subject: [PATCH 049/375] fix: Safer is_downgrade checks --- frappe/installer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/installer.py b/frappe/installer.py index 4baf0929f0..4994646890 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -359,8 +359,11 @@ def is_downgrade(sql_file_path, verbose=False): if head in line: # 'line' (str) format: ('2056588823','2020-05-11 18:21:31.488367','2020-06-12 11:49:31.079506','Administrator','Administrator',0,'Installed Applications','installed_applications','Installed Applications',1,'frappe','v10.1.71-74 (3c50d5e) (v10.x.x)','v10.x.x'),('855c640b8e','2020-05-11 18:21:31.488367','2020-06-12 11:49:31.079506','Administrator','Administrator',0,'Installed Applications','installed_applications','Installed Applications',2,'your_custom_app','0.0.1','master') line = line.strip().lstrip(head).rstrip(";").strip() + app_rows = frappe.safe_eval(line) + # check if iterable consists of tuples before trying to transform + apps_list = app_rows if all(isinstance(app_row, (tuple, list, set)) for app_row in app_rows) else (app_rows, ) # 'all_apps' (list) format: [('frappe', '12.x.x-develop ()', 'develop'), ('your_custom_app', '0.0.1', 'master')] - all_apps = [ x[-3:] for x in frappe.safe_eval(line) ] + all_apps = [ x[-3:] for x in apps_list ] for app in all_apps: app_name = app[0] From e7d21a813c579078cdd61f13313c109dad032a6d Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 27 Aug 2020 18:05:45 +0200 Subject: [PATCH 050/375] feat: hook for website_theme_scss --- frappe/utils/boilerplate.py | 3 +++ frappe/website/doctype/website_theme/website_theme.py | 7 ++++--- .../doctype/website_theme/website_theme_template.scss | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/frappe/utils/boilerplate.py b/frappe/utils/boilerplate.py index d3b206559f..550302774c 100755 --- a/frappe/utils/boilerplate.py +++ b/frappe/utils/boilerplate.py @@ -157,6 +157,9 @@ app_license = "{app_license}" # web_include_css = "/assets/{app_name}/css/{app_name}.css" # web_include_js = "/assets/{app_name}/js/{app_name}.js" +# include custom scss in every website theme (without file extension ".scss") +# website_theme_scss = "{app_name}/public/scss/website" + # include js, css files in header of web form # webform_include_js = {{"doctype": "public/js/doctype.js"}} # webform_include_css = {{"doctype": "public/css/doctype.css"}} diff --git a/frappe/website/doctype/website_theme/website_theme.py b/frappe/website/doctype/website_theme/website_theme.py index c2c4a6e2c8..6e95c7901b 100644 --- a/frappe/website/doctype/website_theme/website_theme.py +++ b/frappe/website/doctype/website_theme/website_theme.py @@ -73,7 +73,7 @@ class WebsiteTheme(Document): file_name = frappe.scrub(self.name) + '_' + suffix + '.css' output_path = join_path(folder_path, file_name) - content = get_scss(self) + self.theme_scss = content = get_scss(self) content = content.replace('\n', '\\n') command = ['node', 'generate_bootstrap_theme.js', output_path, content] @@ -128,5 +128,6 @@ def get_active_theme(): def get_scss(doc): - return frappe.render_template('frappe/website/doctype/website_theme/website_theme_template.scss', doc.as_dict()) - + opts = doc.as_dict() + opts['website_theme_scss'] = frappe.get_hooks('website_theme_scss') + return frappe.render_template('frappe/website/doctype/website_theme/website_theme_template.scss', opts) diff --git a/frappe/website/doctype/website_theme/website_theme_template.scss b/frappe/website/doctype/website_theme/website_theme_template.scss index b46184361b..43fdea354c 100644 --- a/frappe/website/doctype/website_theme/website_theme_template.scss +++ b/frappe/website/doctype/website_theme/website_theme_template.scss @@ -26,3 +26,7 @@ body { // Custom Theme {{ custom_scss or '' }} + +{% for app_scss in website_theme_scss %} +@import "{{ app_scss }}" +{% endfor %} From 8398b8b6d84def1a48bd48c00c309df0e83c797d Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 27 Aug 2020 18:11:29 +0200 Subject: [PATCH 051/375] feat: add autoprefixer --- package.json | 1 + rollup/config.js | 1 + yarn.lock | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/package.json b/package.json index f893d03ad3..fb3e507bd5 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "dependencies": { "ace-builds": "^1.4.8", "air-datepicker": "http://github.com/frappe/air-datepicker", + "autoprefixer": "^9.8.6", "awesomplete": "^1.1.5", "bootstrap": "^4.4.1", "cookie": "^0.4.0", diff --git a/rollup/config.js b/rollup/config.js index 460780bc1b..b1816cb4c6 100644 --- a/rollup/config.js +++ b/rollup/config.js @@ -117,6 +117,7 @@ function get_rollup_options_for_css(output_file, input_files) { // less -> css postcss({ plugins: [ + starts_with_css ? require('autoprefixer')() : null, starts_with_css && production ? require('cssnano')({ preset: 'default' }) : null ].filter(Boolean), extract: output_path, diff --git a/yarn.lock b/yarn.lock index c3808f680a..41fd1926c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -836,6 +836,19 @@ atob@^2.1.1: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +autoprefixer@^9.8.6: + version "9.8.6" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" + integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== + dependencies: + browserslist "^4.12.0" + caniuse-lite "^1.0.30001109" + colorette "^1.2.1" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.32" + postcss-value-parser "^4.1.0" + available-typed-arrays@^1.0.0, available-typed-arrays@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" @@ -1048,6 +1061,16 @@ browserslist@^4.0.0: electron-to-chromium "^1.3.113" node-releases "^1.1.8" +browserslist@^4.12.0: + version "4.14.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.0.tgz#2908951abfe4ec98737b72f34c3bcedc8d43b000" + integrity sha512-pUsXKAF2lVwhmtpeA3LJrZ76jXuusrNyhduuQs7CDFf9foT4Y38aQOserd2lMe5DSSrjf3fx34oHwryuvxAUgQ== + dependencies: + caniuse-lite "^1.0.30001111" + electron-to-chromium "^1.3.523" + escalade "^3.0.2" + node-releases "^1.1.60" + buble@^0.19.6: version "0.19.6" resolved "https://registry.yarnpkg.com/buble/-/buble-0.19.6.tgz#915909b6bd5b11ee03b1c885ec914a8b974d34d3" @@ -1203,6 +1226,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000939: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001116.tgz" integrity sha512-f2lcYnmAI5Mst9+g0nkMIznFGsArRmZ0qU+dnq8l91hymdc2J3SFbiPhOJEeDqC1vtE8nc1qNQyklzB8veJefQ== +caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001111: + version "1.0.30001118" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001118.tgz#116a9a670e5264aec895207f5e918129174c6f62" + integrity sha512-RNKPLojZo74a0cP7jFMidQI7nvLER40HgNfgKQEJ2PFm225L0ectUungNQoK3Xk3StQcFbpBPNEvoWD59436Hg== + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -1427,6 +1455,11 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.2" +colorette@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" + integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== + combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -2134,6 +2167,11 @@ electron-to-chromium@^1.3.113: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz#b1ccf619df7295aea17bc6951dc689632629e4a9" integrity sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g== +electron-to-chromium@^1.3.523: + version "1.3.551" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.551.tgz#a94d243a4ca90705189bd4a5eca4e0f56b745a4f" + integrity sha512-11qcm2xvf2kqeFO5EIejaBx5cKXsW1quAyv3VctCMYwofnyVZLs97y6LCekss3/ghQpr7PYkSO3uId5FmxZsdw== + elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" @@ -2309,6 +2347,11 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" +escalade@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" + integrity sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ== + escape-goat@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" @@ -4816,6 +4859,11 @@ node-gyp@^3.8.0: tar "^2.0.0" which "1" +node-releases@^1.1.60: + version "1.1.60" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084" + integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA== + node-releases@^1.1.8: version "1.1.9" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.9.tgz#70d0985ec4bf7de9f08fc481f5dae111889ca482" @@ -4878,6 +4926,11 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + normalize-url@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" @@ -4912,6 +4965,11 @@ nth-check@^1.0.2: dependencies: boolbase "~1.0.0" +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -5731,6 +5789,11 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.1: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== +postcss-value-parser@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" + integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== + postcss@6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.1.tgz#000dbd1f8eef217aa368b9a212c5fc40b2a8f3f2" @@ -5768,6 +5831,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.5: source-map "^0.6.1" supports-color "^6.1.0" +postcss@^7.0.32: + version "7.0.32" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" + integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" From 458087b3ea65effec610482f0f4914f5f3982735 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 28 Aug 2020 11:31:51 +0530 Subject: [PATCH 052/375] fix: skip table fields in link preview --- frappe/desk/link_preview.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frappe/desk/link_preview.py b/frappe/desk/link_preview.py index f380f96e26..9b4471aa8d 100644 --- a/frappe/desk/link_preview.py +++ b/frappe/desk/link_preview.py @@ -1,5 +1,5 @@ import frappe -from frappe.model import no_value_fields +from frappe.model import no_value_fields, table_fields import json @frappe.whitelist() @@ -9,11 +9,13 @@ def get_preview_data(doctype, docname): if not meta.show_preview_popup: return preview_fields = [field.fieldname for field in meta.fields \ - if field.in_preview and field.fieldtype not in no_value_fields] + if field.in_preview and field.fieldtype not in no_value_fields \ + and field.fieldtype not in table_fields] # no preview fields defined, build list from mandatory fields if not preview_fields: - preview_fields = [field.fieldname for field in meta.fields if field.reqd] + preview_fields = [field.fieldname for field in meta.fields if field.reqd \ + and field.fieldtype not in table_fields] title_field = meta.get_title_field() image_field = meta.image_field From cff99e81588cd156ebf9abf5d2ef05c321cd2cf5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 28 Aug 2020 11:44:41 +0530 Subject: [PATCH 053/375] fix: show 'In Preview' option only if fieldtype is not table field --- frappe/core/doctype/docfield/docfield.json | 4 +- .../doctype/custom_field/custom_field.json | 540 +++++++++--------- .../customize_form_field.json | 512 ++++++++--------- 3 files changed, 531 insertions(+), 525 deletions(-) diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index aab59a5a0a..e420d3b775 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -163,6 +163,7 @@ }, { "default": "0", + "depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);", "fieldname": "in_preview", "fieldtype": "Check", "label": "In Preview" @@ -475,9 +476,10 @@ } ], "idx": 1, + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-02-06 09:06:25.224413", + "modified": "2020-08-28 11:28:21.252853", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 6fa7b29161..3946568bb6 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -58,382 +58,384 @@ ], "fields": [ { - "bold": 1, - "fieldname": "dt", - "fieldtype": "Link", - "in_filter": 1, - "in_list_view": 1, - "label": "Document", - "oldfieldname": "dt", - "oldfieldtype": "Link", - "options": "DocType", - "reqd": 1, - "search_index": 1 + "bold": 1, + "fieldname": "dt", + "fieldtype": "Link", + "in_filter": 1, + "in_list_view": 1, + "label": "Document", + "oldfieldname": "dt", + "oldfieldtype": "Link", + "options": "DocType", + "reqd": 1, + "search_index": 1 }, { - "bold": 1, - "fieldname": "label", - "fieldtype": "Data", - "in_filter": 1, - "label": "Label", - "no_copy": 1, - "oldfieldname": "label", - "oldfieldtype": "Data" + "bold": 1, + "fieldname": "label", + "fieldtype": "Data", + "in_filter": 1, + "label": "Label", + "no_copy": 1, + "oldfieldname": "label", + "oldfieldtype": "Data" }, { - "fieldname": "label_help", - "fieldtype": "HTML", - "label": "Label Help", - "oldfieldtype": "HTML" + "fieldname": "label_help", + "fieldtype": "HTML", + "label": "Label Help", + "oldfieldtype": "HTML" }, { - "fieldname": "fieldname", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Fieldname", - "no_copy": 1, - "oldfieldname": "fieldname", - "oldfieldtype": "Data", - "read_only": 1 + "fieldname": "fieldname", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Fieldname", + "no_copy": 1, + "oldfieldname": "fieldname", + "oldfieldtype": "Data", + "read_only": 1 }, { - "description": "Select the label after which you want to insert new field.", - "fieldname": "insert_after", - "fieldtype": "Select", - "label": "Insert After", - "no_copy": 1, - "oldfieldname": "insert_after", - "oldfieldtype": "Select" + "description": "Select the label after which you want to insert new field.", + "fieldname": "insert_after", + "fieldtype": "Select", + "label": "Insert After", + "no_copy": 1, + "oldfieldname": "insert_after", + "oldfieldtype": "Select" }, { - "fieldname": "column_break_6", - "fieldtype": "Column Break" + "fieldname": "column_break_6", + "fieldtype": "Column Break" }, { - "bold": 1, - "default": "Data", - "fieldname": "fieldtype", - "fieldtype": "Select", - "in_filter": 1, - "in_list_view": 1, - "label": "Field Type", - "oldfieldname": "fieldtype", - "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", - "reqd": 1 + "bold": 1, + "default": "Data", + "fieldname": "fieldtype", + "fieldtype": "Select", + "in_filter": 1, + "in_list_view": 1, + "label": "Field Type", + "oldfieldname": "fieldtype", + "oldfieldtype": "Select", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", + "reqd": 1 }, { - "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", - "description": "Set non-standard precision for a Float or Currency field", - "fieldname": "precision", - "fieldtype": "Select", - "label": "Precision", - "options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9" + "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", + "description": "Set non-standard precision for a Float or Currency field", + "fieldname": "precision", + "fieldtype": "Select", + "label": "Precision", + "options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9" }, { - "fieldname": "options", - "fieldtype": "Small Text", - "in_list_view": 1, - "label": "Options", - "oldfieldname": "options", - "oldfieldtype": "Text" + "fieldname": "options", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Options", + "oldfieldname": "options", + "oldfieldtype": "Text" }, { - "fieldname": "fetch_from", - "fieldtype": "Small Text", - "label": "Fetch From" + "fieldname": "fetch_from", + "fieldtype": "Small Text", + "label": "Fetch From" }, { - "default": "0", - "description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.", - "fieldname": "fetch_if_empty", - "fieldtype": "Check", - "label": "Fetch If Empty" + "default": "0", + "description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.", + "fieldname": "fetch_if_empty", + "fieldtype": "Check", + "label": "Fetch If Empty" }, { - "fieldname": "options_help", - "fieldtype": "HTML", - "label": "Options Help", - "oldfieldtype": "HTML" + "fieldname": "options_help", + "fieldtype": "HTML", + "label": "Options Help", + "oldfieldtype": "HTML" }, { - "fieldname": "section_break_11", - "fieldtype": "Section Break" + "fieldname": "section_break_11", + "fieldtype": "Section Break" }, { - "default": "0", - "depends_on": "eval:doc.fieldtype==\"Section Break\"", - "fieldname": "collapsible", - "fieldtype": "Check", - "label": "Collapsible" + "default": "0", + "depends_on": "eval:doc.fieldtype==\"Section Break\"", + "fieldname": "collapsible", + "fieldtype": "Check", + "label": "Collapsible" }, { - "depends_on": "eval:doc.fieldtype==\"Section Break\"", - "fieldname": "collapsible_depends_on", - "fieldtype": "Code", - "label": "Collapsible Depends On" + "depends_on": "eval:doc.fieldtype==\"Section Break\"", + "fieldname": "collapsible_depends_on", + "fieldtype": "Code", + "label": "Collapsible Depends On" }, { - "fieldname": "default", - "fieldtype": "Text", - "label": "Default Value", - "oldfieldname": "default", - "oldfieldtype": "Text" + "fieldname": "default", + "fieldtype": "Text", + "label": "Default Value", + "oldfieldname": "default", + "oldfieldtype": "Text" }, { - "fieldname": "depends_on", - "fieldtype": "Code", - "label": "Depends On", - "length": 255 + "fieldname": "depends_on", + "fieldtype": "Code", + "label": "Depends On", + "length": 255 }, { - "fieldname": "description", - "fieldtype": "Text", - "label": "Field Description", - "oldfieldname": "description", - "oldfieldtype": "Text", - "print_width": "300px", - "width": "300px" + "fieldname": "description", + "fieldtype": "Text", + "label": "Field Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "print_width": "300px", + "width": "300px" }, { - "default": "0", - "fieldname": "permlevel", - "fieldtype": "Int", - "label": "Permission Level", - "oldfieldname": "permlevel", - "oldfieldtype": "Int" + "default": "0", + "fieldname": "permlevel", + "fieldtype": "Int", + "label": "Permission Level", + "oldfieldname": "permlevel", + "oldfieldtype": "Int" }, { - "fieldname": "width", - "fieldtype": "Data", - "label": "Width", - "oldfieldname": "width", - "oldfieldtype": "Data" + "fieldname": "width", + "fieldtype": "Data", + "label": "Width", + "oldfieldname": "width", + "oldfieldtype": "Data" }, { - "description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)", - "fieldname": "columns", - "fieldtype": "Int", - "label": "Columns" + "description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)", + "fieldname": "columns", + "fieldtype": "Int", + "label": "Columns" }, { - "fieldname": "properties", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "print_width": "50%", - "width": "50%" + "fieldname": "properties", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "print_width": "50%", + "width": "50%" }, { - "default": "0", - "fieldname": "reqd", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Is Mandatory Field", - "oldfieldname": "reqd", - "oldfieldtype": "Check" + "default": "0", + "fieldname": "reqd", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Mandatory Field", + "oldfieldname": "reqd", + "oldfieldtype": "Check" }, { - "default": "0", - "fieldname": "unique", - "fieldtype": "Check", - "label": "Unique" + "default": "0", + "fieldname": "unique", + "fieldtype": "Check", + "label": "Unique" }, { - "default": "0", - "fieldname": "read_only", - "fieldtype": "Check", - "label": "Read Only" + "default": "0", + "fieldname": "read_only", + "fieldtype": "Check", + "label": "Read Only" }, { - "default": "0", - "depends_on": "eval:doc.fieldtype===\"Link\"", - "fieldname": "ignore_user_permissions", - "fieldtype": "Check", - "label": "Ignore User Permissions" + "default": "0", + "depends_on": "eval:doc.fieldtype===\"Link\"", + "fieldname": "ignore_user_permissions", + "fieldtype": "Check", + "label": "Ignore User Permissions" }, { - "default": "0", - "fieldname": "hidden", - "fieldtype": "Check", - "label": "Hidden" + "default": "0", + "fieldname": "hidden", + "fieldtype": "Check", + "label": "Hidden" }, { - "default": "0", - "fieldname": "print_hide", - "fieldtype": "Check", - "label": "Print Hide", - "oldfieldname": "print_hide", - "oldfieldtype": "Check" + "default": "0", + "fieldname": "print_hide", + "fieldtype": "Check", + "label": "Print Hide", + "oldfieldname": "print_hide", + "oldfieldtype": "Check" }, { - "default": "0", - "depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1", - "fieldname": "print_hide_if_no_value", - "fieldtype": "Check", - "label": "Print Hide If No Value" + "default": "0", + "depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1", + "fieldname": "print_hide_if_no_value", + "fieldtype": "Check", + "label": "Print Hide If No Value" }, { - "fieldname": "print_width", - "fieldtype": "Data", - "hidden": 1, - "label": "Print Width", - "no_copy": 1, - "print_hide": 1 + "fieldname": "print_width", + "fieldtype": "Data", + "hidden": 1, + "label": "Print Width", + "no_copy": 1, + "print_hide": 1 }, { - "default": "0", - "fieldname": "no_copy", - "fieldtype": "Check", - "label": "No Copy", - "oldfieldname": "no_copy", - "oldfieldtype": "Check" + "default": "0", + "fieldname": "no_copy", + "fieldtype": "Check", + "label": "No Copy", + "oldfieldname": "no_copy", + "oldfieldtype": "Check" }, { - "default": "0", - "fieldname": "allow_on_submit", - "fieldtype": "Check", - "label": "Allow on Submit", - "oldfieldname": "allow_on_submit", - "oldfieldtype": "Check" + "default": "0", + "fieldname": "allow_on_submit", + "fieldtype": "Check", + "label": "Allow on Submit", + "oldfieldname": "allow_on_submit", + "oldfieldtype": "Check" }, { - "default": "0", - "fieldname": "in_list_view", - "fieldtype": "Check", - "label": "In List View" + "default": "0", + "fieldname": "in_list_view", + "fieldtype": "Check", + "label": "In List View" }, { - "default": "0", - "fieldname": "in_standard_filter", - "fieldtype": "Check", - "label": "In Standard Filter" + "default": "0", + "fieldname": "in_standard_filter", + "fieldtype": "Check", + "label": "In Standard Filter" }, { - "default": "0", - "depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)", - "fieldname": "in_global_search", - "fieldtype": "Check", - "label": "In Global Search" + "default": "0", + "depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)", + "fieldname": "in_global_search", + "fieldtype": "Check", + "label": "In Global Search" }, { - "default": "0", - "fieldname": "bold", - "fieldtype": "Check", - "label": "Bold" + "default": "0", + "fieldname": "bold", + "fieldtype": "Check", + "label": "Bold" }, { - "default": "0", - "fieldname": "report_hide", - "fieldtype": "Check", - "label": "Report Hide", - "oldfieldname": "report_hide", - "oldfieldtype": "Check" + "default": "0", + "fieldname": "report_hide", + "fieldtype": "Check", + "label": "Report Hide", + "oldfieldname": "report_hide", + "oldfieldtype": "Check" }, { - "default": "0", - "fieldname": "search_index", - "fieldtype": "Check", - "hidden": 1, - "label": "Index", - "no_copy": 1, - "print_hide": 1 + "default": "0", + "fieldname": "search_index", + "fieldtype": "Check", + "hidden": 1, + "label": "Index", + "no_copy": 1, + "print_hide": 1 }, { - "default": "0", - "description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field", - "fieldname": "ignore_xss_filter", - "fieldtype": "Check", - "label": "Ignore XSS Filter" + "default": "0", + "description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field", + "fieldname": "ignore_xss_filter", + "fieldtype": "Check", + "label": "Ignore XSS Filter" }, { - "default": "1", - "depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)", - "fieldname": "translatable", - "fieldtype": "Check", - "label": "Translatable" + "default": "1", + "depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)", + "fieldname": "translatable", + "fieldtype": "Check", + "label": "Translatable" }, { - "depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)", - "fieldname": "length", - "fieldtype": "Int", - "label": "Length" + "depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)", + "fieldname": "length", + "fieldtype": "Int", + "label": "Length" }, { - "fieldname": "mandatory_depends_on", - "fieldtype": "Code", - "label": "Mandatory Depends On", - "length": 255 + "fieldname": "mandatory_depends_on", + "fieldtype": "Code", + "label": "Mandatory Depends On", + "length": 255 }, { - "fieldname": "read_only_depends_on", - "fieldtype": "Code", - "label": "Read Only Depends On", - "length": 255 + "fieldname": "read_only_depends_on", + "fieldtype": "Code", + "label": "Read Only Depends On", + "length": 255 }, { - "default": "0", - "fieldname": "allow_in_quick_entry", - "fieldtype": "Check", - "label": "Allow in Quick Entry" + "default": "0", + "fieldname": "allow_in_quick_entry", + "fieldtype": "Check", + "label": "Allow in Quick Entry" }, { - "default": "0", - "fieldname": "in_preview", - "fieldtype": "Check", - "label": "In Preview" + "default": "0", + "depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);", + "fieldname": "in_preview", + "fieldtype": "Check", + "label": "In Preview" }, { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Duration'", - "fieldname": "hide_seconds", - "fieldtype": "Check", - "label": "Hide Seconds" + "default": "0", + "depends_on": "eval:doc.fieldtype=='Duration'", + "fieldname": "hide_seconds", + "fieldtype": "Check", + "label": "Hide Seconds" }, { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Duration'", - "fieldname": "hide_days", - "fieldtype": "Check", - "label": "Hide Days" + "default": "0", + "depends_on": "eval:doc.fieldtype=='Duration'", + "fieldname": "hide_days", + "fieldtype": "Check", + "label": "Hide Days" }, { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Section Break'", - "fieldname": "hide_border", - "fieldtype": "Check", - "label": "Hide Border" + "default": "0", + "depends_on": "eval:doc.fieldtype=='Section Break'", + "fieldname": "hide_border", + "fieldtype": "Check", + "label": "Hide Border" } ], "icon": "fa fa-glass", "idx": 1, + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-02-06 23:43:00.123575", + "modified": "2020-08-28 11:28:44.377753", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", "owner": "Administrator", "permissions": [ { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Administrator", - "share": 1, - "write": 1 + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Administrator", + "share": 1, + "write": 1 }, { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 } ], "search_fields": "dt,label,fieldtype,options", diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index 267213517c..1c7349ef01 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -60,364 +60,366 @@ ], "fields": [ { - "fieldname": "label_and_type", - "fieldtype": "Section Break", - "label": "Label and Type" + "fieldname": "label_and_type", + "fieldtype": "Section Break", + "label": "Label and Type" }, { - "fieldname": "label", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Label", - "oldfieldname": "label", - "oldfieldtype": "Data", - "search_index": 1 + "fieldname": "label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Label", + "oldfieldname": "label", + "oldfieldtype": "Data", + "search_index": 1 }, { - "default": "Data", - "fieldname": "fieldtype", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Type", - "oldfieldname": "fieldtype", - "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime", - "reqd": 1, - "search_index": 1 + "default": "Data", + "fieldname": "fieldtype", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Type", + "oldfieldname": "fieldtype", + "oldfieldtype": "Select", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime", + "reqd": 1, + "search_index": 1 }, { - "fieldname": "fieldname", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Name", - "oldfieldname": "fieldname", - "oldfieldtype": "Data", - "read_only": 1, - "search_index": 1 + "fieldname": "fieldname", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Name", + "oldfieldname": "fieldname", + "oldfieldtype": "Data", + "read_only": 1, + "search_index": 1 }, { - "default": "0", - "depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)", - "fieldname": "reqd", - "fieldtype": "Check", - "label": "Mandatory", - "oldfieldname": "reqd", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" + "default": "0", + "depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)", + "fieldname": "reqd", + "fieldtype": "Check", + "label": "Mandatory", + "oldfieldname": "reqd", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" }, { - "default": "0", - "fieldname": "unique", - "fieldtype": "Check", - "label": "Unique" + "default": "0", + "fieldname": "unique", + "fieldtype": "Check", + "label": "Unique" }, { - "default": "0", - "fieldname": "in_list_view", - "fieldtype": "Check", - "label": "In List View" + "default": "0", + "fieldname": "in_list_view", + "fieldtype": "Check", + "label": "In List View" }, { - "default": "0", - "fieldname": "in_standard_filter", - "fieldtype": "Check", - "label": "In Standard Filter" + "default": "0", + "fieldname": "in_standard_filter", + "fieldtype": "Check", + "label": "In Standard Filter" }, { - "default": "0", - "depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)", - "fieldname": "in_global_search", - "fieldtype": "Check", - "label": "In Global Search" + "default": "0", + "depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)", + "fieldname": "in_global_search", + "fieldtype": "Check", + "label": "In Global Search" }, { - "default": "0", - "fieldname": "bold", - "fieldtype": "Check", - "label": "Bold" + "default": "0", + "fieldname": "bold", + "fieldtype": "Check", + "label": "Bold" }, { - "default": "1", - "depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)", - "fieldname": "translatable", - "fieldtype": "Check", - "label": "Translatable" + "default": "1", + "depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)", + "fieldname": "translatable", + "fieldtype": "Check", + "label": "Translatable" }, { - "fieldname": "column_break_7", - "fieldtype": "Column Break" + "fieldname": "column_break_7", + "fieldtype": "Column Break" }, { - "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", - "description": "Set non-standard precision for a Float or Currency field", - "fieldname": "precision", - "fieldtype": "Select", - "label": "Precision", - "options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9" + "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", + "description": "Set non-standard precision for a Float or Currency field", + "fieldname": "precision", + "fieldtype": "Select", + "label": "Precision", + "options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9" }, { - "depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'], doc.fieldtype)", - "fieldname": "length", - "fieldtype": "Int", - "label": "Length" + "depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'], doc.fieldtype)", + "fieldname": "length", + "fieldtype": "Int", + "label": "Length" }, { - "description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.", - "fieldname": "options", - "fieldtype": "Small Text", - "in_list_view": 1, - "label": "Options", - "oldfieldname": "options", - "oldfieldtype": "Text" + "description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.", + "fieldname": "options", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Options", + "oldfieldname": "options", + "oldfieldtype": "Text" }, { - "fieldname": "fetch_from", - "fieldtype": "Small Text", - "label": "Fetch From" + "fieldname": "fetch_from", + "fieldtype": "Small Text", + "label": "Fetch From" }, { - "default": "0", - "description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.", - "fieldname": "fetch_if_empty", - "fieldtype": "Check", - "label": "Fetch If Empty" + "default": "0", + "description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.", + "fieldname": "fetch_if_empty", + "fieldtype": "Check", + "label": "Fetch If Empty" }, { - "fieldname": "permissions", - "fieldtype": "Section Break", - "label": "Permissions" + "fieldname": "permissions", + "fieldtype": "Section Break", + "label": "Permissions" }, { - "description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): \nmyfield\neval:doc.myfield=='My Value'\neval:doc.age>18", - "fieldname": "depends_on", - "fieldtype": "Code", - "label": "Depends On", - "oldfieldname": "depends_on", - "oldfieldtype": "Data", - "options": "JS" + "description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): \nmyfield\neval:doc.myfield=='My Value'\neval:doc.age>18", + "fieldname": "depends_on", + "fieldtype": "Code", + "label": "Depends On", + "oldfieldname": "depends_on", + "oldfieldtype": "Data", + "options": "JS" }, { - "default": "0", - "fieldname": "permlevel", - "fieldtype": "Int", - "in_list_view": 1, - "label": "Perm Level", - "oldfieldname": "permlevel", - "oldfieldtype": "Int" + "default": "0", + "fieldname": "permlevel", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Perm Level", + "oldfieldname": "permlevel", + "oldfieldtype": "Int" }, { - "default": "0", - "fieldname": "hidden", - "fieldtype": "Check", - "label": "Hidden", - "oldfieldname": "hidden", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" + "default": "0", + "fieldname": "hidden", + "fieldtype": "Check", + "label": "Hidden", + "oldfieldname": "hidden", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" }, { - "default": "0", - "fieldname": "read_only", - "fieldtype": "Check", - "label": "Read Only" + "default": "0", + "fieldname": "read_only", + "fieldtype": "Check", + "label": "Read Only" }, { - "default": "0", - "depends_on": "eval:doc.fieldtype==\"Section Break\"", - "fieldname": "collapsible", - "fieldtype": "Check", - "label": "Collapsible" + "default": "0", + "depends_on": "eval:doc.fieldtype==\"Section Break\"", + "fieldname": "collapsible", + "fieldtype": "Check", + "label": "Collapsible" }, { - "default": "0", - "depends_on": "eval: doc.fieldtype == \"Table\"", - "fieldname": "allow_bulk_edit", - "fieldtype": "Check", - "label": "Allow Bulk Edit" + "default": "0", + "depends_on": "eval: doc.fieldtype == \"Table\"", + "fieldname": "allow_bulk_edit", + "fieldtype": "Check", + "label": "Allow Bulk Edit" }, { - "depends_on": "eval:doc.fieldtype==\"Section Break\"", - "fieldname": "collapsible_depends_on", - "fieldtype": "Code", - "label": "Collapsible Depends On", - "options": "JS" + "depends_on": "eval:doc.fieldtype==\"Section Break\"", + "fieldname": "collapsible_depends_on", + "fieldtype": "Code", + "label": "Collapsible Depends On", + "options": "JS" }, { - "fieldname": "column_break_14", - "fieldtype": "Column Break" + "fieldname": "column_break_14", + "fieldtype": "Column Break" }, { - "default": "0", - "fieldname": "ignore_user_permissions", - "fieldtype": "Check", - "label": "Ignore User Permissions" + "default": "0", + "fieldname": "ignore_user_permissions", + "fieldtype": "Check", + "label": "Ignore User Permissions" }, { - "default": "0", - "fieldname": "allow_on_submit", - "fieldtype": "Check", - "label": "Allow on Submit", - "oldfieldname": "allow_on_submit", - "oldfieldtype": "Check" + "default": "0", + "fieldname": "allow_on_submit", + "fieldtype": "Check", + "label": "Allow on Submit", + "oldfieldname": "allow_on_submit", + "oldfieldtype": "Check" }, { - "default": "0", - "fieldname": "report_hide", - "fieldtype": "Check", - "label": "Report Hide", - "oldfieldname": "report_hide", - "oldfieldtype": "Check" + "default": "0", + "fieldname": "report_hide", + "fieldtype": "Check", + "label": "Report Hide", + "oldfieldname": "report_hide", + "oldfieldtype": "Check" }, { - "default": "0", - "depends_on": "eval:(doc.fieldtype == 'Link')", - "fieldname": "remember_last_selected_value", - "fieldtype": "Check", - "label": "Remember Last Selected Value" + "default": "0", + "depends_on": "eval:(doc.fieldtype == 'Link')", + "fieldname": "remember_last_selected_value", + "fieldtype": "Check", + "label": "Remember Last Selected Value" }, { - "fieldname": "display", - "fieldtype": "Section Break", - "label": "Display" + "fieldname": "display", + "fieldtype": "Section Break", + "label": "Display" }, { - "fieldname": "default", - "fieldtype": "Text", - "label": "Default", - "oldfieldname": "default", - "oldfieldtype": "Text" + "fieldname": "default", + "fieldtype": "Text", + "label": "Default", + "oldfieldname": "default", + "oldfieldtype": "Text" }, { - "default": "0", - "fieldname": "in_filter", - "fieldtype": "Check", - "label": "In Filter", - "oldfieldname": "in_filter", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" + "default": "0", + "fieldname": "in_filter", + "fieldtype": "Check", + "label": "In Filter", + "oldfieldname": "in_filter", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" }, { - "fieldname": "column_break_21", - "fieldtype": "Column Break" + "fieldname": "column_break_21", + "fieldtype": "Column Break" }, { - "fieldname": "description", - "fieldtype": "Text", - "label": "Description", - "oldfieldname": "description", - "oldfieldtype": "Text", - "print_width": "300px", - "width": "300px" + "fieldname": "description", + "fieldtype": "Text", + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "print_width": "300px", + "width": "300px" }, { - "default": "0", - "fieldname": "print_hide", - "fieldtype": "Check", - "label": "Print Hide", - "oldfieldname": "print_hide", - "oldfieldtype": "Check" + "default": "0", + "fieldname": "print_hide", + "fieldtype": "Check", + "label": "Print Hide", + "oldfieldname": "print_hide", + "oldfieldtype": "Check" }, { - "default": "0", - "depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1", - "fieldname": "print_hide_if_no_value", - "fieldtype": "Check", - "label": "Print Hide If No Value" + "default": "0", + "depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1", + "fieldname": "print_hide_if_no_value", + "fieldtype": "Check", + "label": "Print Hide If No Value" }, { - "description": "Print Width of the field, if the field is a column in a table", - "fieldname": "print_width", - "fieldtype": "Data", - "label": "Print Width", - "print_width": "50px", - "width": "50px" + "description": "Print Width of the field, if the field is a column in a table", + "fieldname": "print_width", + "fieldtype": "Data", + "label": "Print Width", + "print_width": "50px", + "width": "50px" }, { - "depends_on": "eval:cur_frm.doc.istable", - "description": "Number of columns for a field in a Grid (Total Columns in a grid should be less than 11)", - "fieldname": "columns", - "fieldtype": "Int", - "label": "Columns" + "depends_on": "eval:cur_frm.doc.istable", + "description": "Number of columns for a field in a Grid (Total Columns in a grid should be less than 11)", + "fieldname": "columns", + "fieldtype": "Int", + "label": "Columns" }, { - "fieldname": "width", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Width", - "oldfieldname": "width", - "oldfieldtype": "Data", - "print_width": "50px", - "width": "50px" + "fieldname": "width", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Width", + "oldfieldname": "width", + "oldfieldtype": "Data", + "print_width": "50px", + "width": "50px" }, { - "default": "0", - "fieldname": "is_custom_field", - "fieldtype": "Check", - "hidden": 1, - "label": "Is Custom Field", - "read_only": 1 + "default": "0", + "fieldname": "is_custom_field", + "fieldtype": "Check", + "hidden": 1, + "label": "Is Custom Field", + "read_only": 1 }, { - "default": "0", - "fieldname": "allow_in_quick_entry", - "fieldtype": "Check", - "label": "Allow in Quick Entry" + "default": "0", + "fieldname": "allow_in_quick_entry", + "fieldtype": "Check", + "label": "Allow in Quick Entry" }, { - "fieldname": "property_depends_on_section", - "fieldtype": "Section Break", - "label": "Property Depends On" + "fieldname": "property_depends_on_section", + "fieldtype": "Section Break", + "label": "Property Depends On" }, { - "fieldname": "mandatory_depends_on", - "fieldtype": "Code", - "label": "Mandatory Depends On", - "options": "JS" + "fieldname": "mandatory_depends_on", + "fieldtype": "Code", + "label": "Mandatory Depends On", + "options": "JS" }, { - "fieldname": "column_break_33", - "fieldtype": "Column Break" + "fieldname": "column_break_33", + "fieldtype": "Column Break" }, { - "fieldname": "read_only_depends_on", - "fieldtype": "Code", - "label": "Read Only Depends On", - "options": "JS" + "fieldname": "read_only_depends_on", + "fieldtype": "Code", + "label": "Read Only Depends On", + "options": "JS" }, { - "default": "0", - "fieldname": "in_preview", - "fieldtype": "Check", - "label": "In Preview" + "default": "0", + "depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);", + "fieldname": "in_preview", + "fieldtype": "Check", + "label": "In Preview" }, { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Duration'", - "fieldname": "hide_seconds", - "fieldtype": "Check", - "label": "Hide Seconds" + "default": "0", + "depends_on": "eval:doc.fieldtype=='Duration'", + "fieldname": "hide_seconds", + "fieldtype": "Check", + "label": "Hide Seconds" }, { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Duration'", - "fieldname": "hide_days", - "fieldtype": "Check", - "label": "Hide Days" + "default": "0", + "depends_on": "eval:doc.fieldtype=='Duration'", + "fieldname": "hide_days", + "fieldtype": "Check", + "label": "Hide Days" }, { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Section Break'", - "fieldname": "hide_border", - "fieldtype": "Check", - "label": "Hide Border" + "default": "0", + "depends_on": "eval:doc.fieldtype=='Section Break'", + "fieldname": "hide_border", + "fieldtype": "Check", + "label": "Hide Border" } ], "idx": 1, + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-06-02 23:45:46.810868", + "modified": "2020-08-28 11:28:59.084060", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", From 89ddeab996433a028ceab8c5289a16dffdd586ae Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 28 Aug 2020 11:49:11 +0530 Subject: [PATCH 054/375] fix(Activity Log): Handle delete and new actions via Permissions --- .../doctype/activity_log/activity_log.json | 649 ++---------------- .../core/doctype/activity_log/activity_log.py | 3 - 2 files changed, 51 insertions(+), 601 deletions(-) diff --git a/frappe/core/doctype/activity_log/activity_log.json b/frappe/core/doctype/activity_log/activity_log.json index 580882968c..0d85855504 100644 --- a/frappe/core/doctype/activity_log/activity_log.json +++ b/frappe/core/doctype/activity_log/activity_log.json @@ -1,734 +1,187 @@ - { - "allow_copy": 0, - "allow_guest_to_view": 0, +{ + "actions": [], "allow_import": 1, - "allow_rename": 0, - "autoname": "", - "beta": 0, "creation": "2017-10-05 11:10:38.780133", - "custom": 0, "description": "Keep track of all update feeds", - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "subject", + "section_break_8", + "content", + "column_break_5", + "additional_info", + "communication_date", + "column_break_7", + "operation", + "status", + "reference_section", + "reference_doctype", + "reference_name", + "reference_owner", + "column_break_14", + "timeline_doctype", + "timeline_name", + "link_doctype", + "link_name", + "user", + "full_name" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "subject", "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, "in_list_view": 1, - "in_standard_filter": 0, "label": "Subject", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_8", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "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, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "content", "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Message", - "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, - "translatable": 0, - "unique": 0, "width": "400" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_5", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "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, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "additional_info", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "More Information", - "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, - "translatable": 0, - "unique": 0 + "label": "More Information" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Now", "fieldname": "communication_date", "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Date", - "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, - "translatable": 0, - "unique": 0 + "label": "Date" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_7", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "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, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "operation", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Operation", - "length": 0, - "no_copy": 0, - "options": "\nLogin\nLogout", - "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, - "translatable": 0, - "unique": 0 + "options": "\nLogin\nLogout" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "status", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Status", - "length": 0, - "no_copy": 0, - "options": "\nSuccess\nFailed\nLinked\nClosed", - "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, - "translatable": 0, - "unique": 0 + "options": "\nSuccess\nFailed\nLinked\nClosed" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "reference_section", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reference", - "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, - "translatable": 0, - "unique": 0 + "label": "Reference" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "reference_doctype", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Reference Document Type", - "length": 0, - "no_copy": 0, - "options": "DocType", - "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, - "translatable": 0, - "unique": 0 + "options": "DocType" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "reference_name", "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Reference Name", - "length": 0, - "no_copy": 0, - "options": "reference_doctype", - "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, - "translatable": 0, - "unique": 0 + "options": "reference_doctype" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "reference_name.owner", "fieldname": "reference_owner", "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Reference Owner", - "length": 0, - "no_copy": 0, - "options": "", - "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": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_14", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "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, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "timeline_doctype", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Timeline DocType", - "length": 0, - "no_copy": 0, - "options": "DocType", - "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, - "translatable": 0, - "unique": 0 + "options": "DocType" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "timeline_name", "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Timeline Name", - "length": 0, - "no_copy": 0, - "options": "timeline_doctype", - "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, - "translatable": 0, - "unique": 0 + "options": "timeline_doctype" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "link_doctype", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Link DocType", - "length": 0, - "no_copy": 0, "options": "DocType", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "link_name", "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Link Name", - "length": 0, - "no_copy": 0, "options": "link_doctype", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "__user", "fieldname": "user", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "User", - "length": 0, - "no_copy": 0, "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "full_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, - "label": "Full Name", - "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, - "translatable": 0, - "unique": 0 + "label": "Full Name" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-comment", - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-09-05 14:22:27.664645", - "modified_by": "Administrator", + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-08-28 11:43:57.504565", + "modified_by": "gavin18d@gmail.com", "module": "Core", "name": "Activity Log", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 + "share": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 1, + "if_owner": 1, "print": 1, "read": 1, "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 1, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, "role": "All", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "share": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, "search_fields": "subject", - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", "title_field": "subject", "track_changes": 1, "track_seen": 1 -} +} \ No newline at end of file diff --git a/frappe/core/doctype/activity_log/activity_log.py b/frappe/core/doctype/activity_log/activity_log.py index 8b7941c086..27a2892ca8 100644 --- a/frappe/core/doctype/activity_log/activity_log.py +++ b/frappe/core/doctype/activity_log/activity_log.py @@ -25,9 +25,6 @@ class ActivityLog(Document): if self.reference_doctype and self.reference_name: self.status = "Linked" - def on_trash(self): # pylint: disable=no-self-use - frappe.throw(_("Sorry! You cannot delete auto-generated comments")) - def on_doctype_update(): """Add indexes in `tabActivity Log`""" frappe.db.add_index("Activity Log", ["reference_doctype", "reference_name"]) From 1637c787e050c3c82d487d551bc2cb8210cf2960 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 28 Aug 2020 14:51:04 +0530 Subject: [PATCH 055/375] Reload module def --- frappe/patches.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/patches.txt b/frappe/patches.txt index 985911314e..afe5a30abd 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -15,7 +15,7 @@ execute:frappe.reload_doc('core', 'doctype', 'custom_docperm') execute:frappe.reload_doc('core', 'doctype', 'docperm') #2018-05-29 execute:frappe.reload_doc('core', 'doctype', 'comment') frappe.patches.v8_0.drop_is_custom_from_docperm -execute:frappe.reload_doc('core', 'doctype', 'module_def') #2017-09-22 +execute:frappe.reload_doc('core', 'doctype', 'module_def') #2020-08-28 execute:frappe.reload_doc('core', 'doctype', 'version') #2017-04-01 execute:frappe.reload_doc('email', 'doctype', 'document_follow') execute:frappe.reload_doc('core', 'doctype', 'communication_link') #2019-10-02 From 65ae9036ae069690893e77f8f567c6e2c96114e8 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 28 Aug 2020 14:47:19 +0200 Subject: [PATCH 056/375] feat: allow to ignore standard app themes --- frappe/hooks.py | 1 + .../doctype/website_theme/website_theme.json | 10 +++++++++- .../doctype/website_theme/website_theme.py | 6 +++++- .../website_theme/website_theme_template.scss | 18 ++++++++++-------- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/frappe/hooks.py b/frappe/hooks.py index 7ecc199814..b139788c65 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -48,6 +48,7 @@ web_include_js = [ ] web_include_css = [] +website_theme_scss = "frappe/public/scss/website" website_route_rules = [ {"from_route": "/blog/", "to_route": "Blog Post"}, diff --git a/frappe/website/doctype/website_theme/website_theme.json b/frappe/website/doctype/website_theme/website_theme.json index 7af6c91281..e02f4e9cc2 100644 --- a/frappe/website/doctype/website_theme/website_theme.json +++ b/frappe/website/doctype/website_theme/website_theme.json @@ -26,6 +26,7 @@ "stylesheet_section", "custom_overrides", "custom_scss", + "imports_to_ignore", "theme_scss", "theme_url", "custom_js_section", @@ -167,10 +168,17 @@ "fieldtype": "Code", "label": "Custom Overrides", "options": "SCSS" + }, + { + "description": "Comma-separated list of import paths", + "fieldname": "imports_to_ignore", + "fieldtype": "Data", + "label": "Imports To Ignore" } ], + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-05-16 18:36:22.203519", + "modified": "2020-08-28 13:31:32.846474", "modified_by": "Administrator", "module": "Website", "name": "Website Theme", diff --git a/frappe/website/doctype/website_theme/website_theme.py b/frappe/website/doctype/website_theme/website_theme.py index 6e95c7901b..5491bdd83c 100644 --- a/frappe/website/doctype/website_theme/website_theme.py +++ b/frappe/website/doctype/website_theme/website_theme.py @@ -128,6 +128,10 @@ def get_active_theme(): def get_scss(doc): + def trim_list(list_of_strings): + return [s.strip() for s in list_of_strings] + opts = doc.as_dict() - opts['website_theme_scss'] = frappe.get_hooks('website_theme_scss') + opts['website_theme_scss'] = trim_list(frappe.get_hooks('website_theme_scss', [])) + opts['imports_to_ignore'] = trim_list((opts.get('imports_to_ignore') or '').split(',')) return frappe.render_template('frappe/website/doctype/website_theme/website_theme_template.scss', opts) diff --git a/frappe/website/doctype/website_theme/website_theme_template.scss b/frappe/website/doctype/website_theme/website_theme_template.scss index 43fdea354c..0174996683 100644 --- a/frappe/website/doctype/website_theme/website_theme_template.scss +++ b/frappe/website/doctype/website_theme/website_theme_template.scss @@ -1,7 +1,8 @@ {% if google_font %} -@import url('https://fonts.googleapis.com/css2?family={{ google_font.replace(' ', '+') }}:{{ font_properties }}&display=swap'); -$font-family-sans-serif: "{{ google_font }}", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; - +@import url("https://fonts.googleapis.com/css2?family={{ google_font.replace(' ', '+') }}:{{ font_properties }}&display=swap"); +$font-family-sans-serif: "{{ google_font }}", -apple-system, BlinkMacSystemFont, + "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", + "Droid Sans", "Helvetica Neue", sans-serif; {% endif -%} {% if primary_color %}$primary: {{ frappe.db.get_value('Color', primary_color, 'color') }};{% endif -%} @@ -16,7 +17,12 @@ $enable-rounded: {{ button_rounded_corners and "true" or "false" }}; // Bootstrap Variable Overrides {{ custom_overrides or '' }} -@import "frappe/public/scss/website"; +// Import themes from installed apps +{% for import_path in website_theme_scss -%} +{% if import_path not in imports_to_ignore -%} +@import "{{ import_path }}"; +{%- endif %} +{% endfor %} {% if font_size -%} body { @@ -26,7 +32,3 @@ body { // Custom Theme {{ custom_scss or '' }} - -{% for app_scss in website_theme_scss %} -@import "{{ app_scss }}" -{% endfor %} From 0139de6c4e8fc526de44e67cd25224fd661a06fd Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 28 Aug 2020 14:48:01 +0200 Subject: [PATCH 057/375] tests: create and update test cases --- .../doctype/website_theme/test_records.json | 1 - .../website_theme/test_website_theme.py | 33 ++++--- package.json | 4 +- yarn.lock | 92 +++++++------------ 4 files changed, 56 insertions(+), 74 deletions(-) delete mode 100644 frappe/website/doctype/website_theme/test_records.json diff --git a/frappe/website/doctype/website_theme/test_records.json b/frappe/website/doctype/website_theme/test_records.json deleted file mode 100644 index fe51488c70..0000000000 --- a/frappe/website/doctype/website_theme/test_records.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/frappe/website/doctype/website_theme/test_website_theme.py b/frappe/website/doctype/website_theme/test_website_theme.py index 0402ec1e52..ef3a924905 100644 --- a/frappe/website/doctype/website_theme/test_website_theme.py +++ b/frappe/website/doctype/website_theme/test_website_theme.py @@ -7,25 +7,30 @@ import os import frappe import unittest -test_records = frappe.get_test_records('Website Theme') - class TestWebsiteTheme(unittest.TestCase): - def test_website_theme(self): - if os.environ.get('CI'): - # no node-sass on travis (?) - return + def test_website_theme(self): + frappe.delete_doc_if_exists('Website Theme', 'test-theme') + theme = frappe.get_doc(dict( + doctype='Website Theme', + theme='test-theme', + google_font='Inter', + custom_scss='body { font-size: 16.5px; }' # this will get minified! + )).insert() + + theme_path = frappe.get_site_path('public', theme.theme_url[1:]) + with open(theme_path) as theme_file: + css = theme_file.read() + + self.assertTrue('body{font-size:16.5px}' in css) + self.assertTrue('fonts.googleapis.com' in css) + + def test_imports_to_ignore(self): frappe.delete_doc_if_exists('Website Theme', 'test-theme') theme = frappe.get_doc(dict( doctype = 'Website Theme', theme = 'test-theme', - google_font = 'Inter', - custom_scss = 'body { font-size: 16.5px; }' + imports_to_ignore = 'frappe/public/scss/website' )).insert() - with open(theme.theme_url[1:]) as f: - css = f.read() - - self.assertTrue(theme.custom_scss in css) - self.assertTrue('fonts.googleapis.com' in css) - + self.assertTrue('@import "frappe/public/scss/website"' not in theme.theme_scss) diff --git a/package.json b/package.json index f893d03ad3..2a8a88823a 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,8 @@ "superagent": "^3.8.2", "touch": "^3.1.0", "vue": "^2.6.11", - "vue-router": "^2.0.0" + "vue-router": "^2.0.0", + "node-sass": "^4.14.1" }, "devDependencies": { "babel-runtime": "^6.26.0", @@ -57,7 +58,6 @@ "cypress-file-upload": "^3.1.0", "graphlib": "^2.1.8", "less": "^3.11.1", - "node-sass": "^4.13.1", "rollup": "^1.2.2", "rollup-plugin-buble": "^0.19.2", "rollup-plugin-commonjs": "^8.3.0", diff --git a/yarn.lock b/yarn.lock index c3808f680a..6b72e0981a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1178,11 +1178,6 @@ camelcase@^2.0.0, camelcase@^2.0.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= -camelcase@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" - integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= - camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -1330,7 +1325,7 @@ clipanion@^2.4.2: resolved "https://registry.yarnpkg.com/clipanion/-/clipanion-2.4.4.tgz#d70244c6f60feb3f4cbd509d2fcbe829fc619061" integrity sha512-KjyCBz8xplftHjIK/nOqq/9b3hPlXbAAo/AxoITrO4yySpQ6a9QSJDAfOx9PfcRUHteeqbdNxZKSPfeFqQ7plg== -cliui@^3.0.3, cliui@^3.2.0: +cliui@^3.0.3: version "3.2.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= @@ -2837,11 +2832,6 @@ generic-names@^1.0.2, generic-names@^1.0.3: dependencies: loader-utils "^0.2.16" -get-caller-file@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== - get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -4823,10 +4813,10 @@ node-releases@^1.1.8: dependencies: semver "^5.3.0" -node-sass@^4.13.1: - version "4.13.1" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.13.1.tgz#9db5689696bb2eec2c32b98bfea4c7a2e992d0a3" - integrity sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw== +node-sass@^4.14.1: + version "4.14.1" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.14.1.tgz#99c87ec2efb7047ed638fb4c9db7f3a42e2217b5" + integrity sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g== dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -4842,7 +4832,7 @@ node-sass@^4.13.1: node-gyp "^3.8.0" npmlog "^4.0.0" request "^2.88.0" - sass-graph "^2.2.4" + sass-graph "2.2.5" stdout-stream "^1.4.0" "true-case-path" "^1.0.2" @@ -6304,11 +6294,6 @@ require-from-string@^2.0.1: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= - require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -6557,15 +6542,15 @@ safe-regex@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass-graph@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" - integrity sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k= +sass-graph@2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.5.tgz#a981c87446b8319d96dce0671e487879bd24c2e8" + integrity sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag== dependencies: glob "^7.0.0" lodash "^4.0.0" scss-tokenizer "^0.2.3" - yargs "^7.0.0" + yargs "^13.3.2" sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4: version "1.2.4" @@ -7367,7 +7352,7 @@ string-hash@^1.1.0, string-hash@^1.1.1: resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" integrity sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs= -string-width@^1.0.1, string-width@^1.0.2: +string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= @@ -8135,11 +8120,6 @@ which-collection@^1.0.1: is-weakmap "^2.0.1" is-weakset "^2.0.1" -which-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" - integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= - which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" @@ -8279,7 +8259,7 @@ xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -y18n@^3.2.0, y18n@^3.2.1: +y18n@^3.2.0: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= @@ -8309,6 +8289,14 @@ yaml@^1.9.2: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== +yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@^15.0.0: version "15.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-15.0.0.tgz#cdd7a97490ec836195f59f3f4dbe5ea9e8f75f08" @@ -8317,12 +8305,21 @@ yargs-parser@^15.0.0: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" - integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo= +yargs@^13.3.2: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== dependencies: - camelcase "^3.0.0" + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" yargs@^14.2: version "14.2.2" @@ -8354,25 +8351,6 @@ yargs@^3.19.0: window-size "^0.1.4" y18n "^3.2.0" -yargs@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" - integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg= - dependencies: - camelcase "^3.0.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^1.4.0" - read-pkg-up "^1.0.1" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^1.0.2" - which-module "^1.0.0" - y18n "^3.2.1" - yargs-parser "^5.0.0" - yauzl@2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" From c19bc430280392fa9426340bc5c8c9210ceddfcd Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Fri, 28 Aug 2020 15:54:13 +0200 Subject: [PATCH 058/375] test: fix spacing --- frappe/website/doctype/website_theme/test_website_theme.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/website/doctype/website_theme/test_website_theme.py b/frappe/website/doctype/website_theme/test_website_theme.py index ef3a924905..03fab88da3 100644 --- a/frappe/website/doctype/website_theme/test_website_theme.py +++ b/frappe/website/doctype/website_theme/test_website_theme.py @@ -28,9 +28,9 @@ class TestWebsiteTheme(unittest.TestCase): def test_imports_to_ignore(self): frappe.delete_doc_if_exists('Website Theme', 'test-theme') theme = frappe.get_doc(dict( - doctype = 'Website Theme', - theme = 'test-theme', - imports_to_ignore = 'frappe/public/scss/website' + doctype='Website Theme', + theme='test-theme', + imports_to_ignore='frappe/public/scss/website' )).insert() self.assertTrue('@import "frappe/public/scss/website"' not in theme.theme_scss) From 3c57041f74eb1089def0a09162aa168e0772e494 Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Fri, 28 Aug 2020 18:06:43 +0200 Subject: [PATCH 059/375] fix(User): merge sections "Email Settings" and "Email Inbox" into "Email" --- frappe/core/doctype/user/user.json | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 9e6781ba64..2073f41fdd 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -356,7 +356,7 @@ "depends_on": "enabled", "fieldname": "email_settings", "fieldtype": "Section Break", - "label": "Email Settings" + "label": "Email" }, { "default": "1", @@ -382,12 +382,6 @@ "label": "Email Signature", "no_copy": 1 }, - { - "collapsible": 1, - "fieldname": "email_inbox", - "fieldtype": "Section Break", - "label": "Email Inbox" - }, { "fieldname": "user_emails", "fieldtype": "Table", @@ -651,7 +645,7 @@ } ], "max_attachments": 5, - "modified": "2020-08-06 19:48:49.677800", + "modified": "2020-08-26 19:48:49.677800", "modified_by": "Administrator", "module": "Core", "name": "User", @@ -685,4 +679,4 @@ "sort_order": "DESC", "title_field": "full_name", "track_changes": 1 -} \ No newline at end of file +} From 6067751e5f90077f562688464532b583e483679c Mon Sep 17 00:00:00 2001 From: michellealva Date: Sat, 29 Aug 2020 11:39:46 +0530 Subject: [PATCH 060/375] fix: Typo in Print Format Help --- frappe/printing/doctype/print_format/print_format.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frappe/printing/doctype/print_format/print_format.json b/frappe/printing/doctype/print_format/print_format.json index 951a863776..d0c488565c 100644 --- a/frappe/printing/doctype/print_format/print_format.json +++ b/frappe/printing/doctype/print_format/print_format.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_rename": 1, "autoname": "Prompt", "creation": "2013-01-23 19:54:43", @@ -181,7 +182,7 @@ "fieldname": "print_format_help", "fieldtype": "HTML", "label": "Print Format Help", - "options": "

Print Format Help

\n
\n

Introduction

\n

Print itemsFormats are rendered on the server side using the Jinja Templating Language. All forms have access to the doc object which contains information about the document that is being formatted. You can also access common utilities via the frappe module.

\n

For styling, the Boostrap CSS framework is provided and you can enjoy the full range of classes.

\n
\n

References

\n
    \n\t
  1. Jinja Tempalting Language: Reference
  2. \n\t
  3. Bootstrap CSS Framework
  4. \n
\n
\n

Example

\n
<h3>{{ doc.select_print_heading or \"Invoice\" }}</h3>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Customer Name</div>\n\t<div class=\"col-md-9\">{{ doc.customer_name }}</div>\n</div>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Date</div>\n\t<div class=\"col-md-9\">{{ doc.get_formatted(\"invoice_date\") }}</div>\n</div>\n<table class=\"table table-bordered\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<th>Sr</th>\n\t\t\t<th>Item Name</th>\n\t\t\t<th>Description</th>\n\t\t\t<th class=\"text-right\">Qty</th>\n\t\t\t<th class=\"text-right\">Rate</th>\n\t\t\t<th class=\"text-right\">Amount</th>\n\t\t</tr>\n\t\t{%- for row in doc.items -%}\n\t\t<tr>\n\t\t\t<td style=\"width: 3%;\">{{ row.idx }}</td>\n\t\t\t<td style=\"width: 20%;\">\n\t\t\t\t{{ row.item_name }}\n\t\t\t\t{% if row.item_code != row.item_name -%}\n\t\t\t\t<br>Item Code: {{ row.item_code}}\n\t\t\t\t{%- endif %}\n\t\t\t</td>\n\t\t\t<td style=\"width: 37%;\">\n\t\t\t\t<div style=\"border: 0px;\">{{ row.description }}</div></td>\n\t\t\t<td style=\"width: 10%; text-align: right;\">{{ row.qty }} {{ row.uom or row.stock_uom }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"rate\", doc) }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"amount\", doc) }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>
\n
\n

Common Functions

\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n
doc.get_formatted(\"[fieldname]\", [parent_doc])Get document value formatted as Date, Currency etc. Pass parent doc for curreny type fields.
frappe.db.get_value(\"[doctype]\", \"[name]\", \"fieldname\")Get value from another document.
\n" + "options": "

Print Format Help

\n
\n

Introduction

\n

Print Formats are rendered on the server side using the Jinja Templating Language. All forms have access to the doc object which contains information about the document that is being formatted. You can also access common utilities via the frappe module.

\n

For styling, the Boostrap CSS framework is provided and you can enjoy the full range of classes.

\n
\n

References

\n
    \n\t
  1. Jinja Templating Language: Reference
  2. \n\t
  3. Bootstrap CSS Framework
  4. \n
\n
\n

Example

\n
<h3>{{ doc.select_print_heading or \"Invoice\" }}</h3>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Customer Name</div>\n\t<div class=\"col-md-9\">{{ doc.customer_name }}</div>\n</div>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Date</div>\n\t<div class=\"col-md-9\">{{ doc.get_formatted(\"invoice_date\") }}</div>\n</div>\n<table class=\"table table-bordered\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<th>Sr</th>\n\t\t\t<th>Item Name</th>\n\t\t\t<th>Description</th>\n\t\t\t<th class=\"text-right\">Qty</th>\n\t\t\t<th class=\"text-right\">Rate</th>\n\t\t\t<th class=\"text-right\">Amount</th>\n\t\t</tr>\n\t\t{%- for row in doc.items -%}\n\t\t<tr>\n\t\t\t<td style=\"width: 3%;\">{{ row.idx }}</td>\n\t\t\t<td style=\"width: 20%;\">\n\t\t\t\t{{ row.item_name }}\n\t\t\t\t{% if row.item_code != row.item_name -%}\n\t\t\t\t<br>Item Code: {{ row.item_code}}\n\t\t\t\t{%- endif %}\n\t\t\t</td>\n\t\t\t<td style=\"width: 37%;\">\n\t\t\t\t<div style=\"border: 0px;\">{{ row.description }}</div></td>\n\t\t\t<td style=\"width: 10%; text-align: right;\">{{ row.qty }} {{ row.uom or row.stock_uom }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"rate\", doc) }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"amount\", doc) }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>
\n
\n

Common Functions

\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n
doc.get_formatted(\"[fieldname]\", [parent_doc])Get document value formatted as Date, Currency, etc. Pass parent doc for currency type fields.
frappe.db.get_value(\"[doctype]\", \"[name]\", \"fieldname\")Get value from another document.
\n" }, { "fieldname": "format_data", @@ -199,8 +200,10 @@ ], "icon": "fa fa-print", "idx": 1, - "modified": "2019-11-28 12:40:40.364699", - "modified_by": "faris@erpnext.com", + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-08-29 11:39:22.312438", + "modified_by": "Administrator", "module": "Printing", "name": "Print Format", "owner": "Administrator", From 9605e828b1c6a86ea07072cc1aa68c647c161abb Mon Sep 17 00:00:00 2001 From: michellealva Date: Sat, 29 Aug 2020 11:45:53 +0530 Subject: [PATCH 061/375] fix: Change link text --- frappe/printing/doctype/print_format/print_format.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/printing/doctype/print_format/print_format.json b/frappe/printing/doctype/print_format/print_format.json index d0c488565c..74ee56cb54 100644 --- a/frappe/printing/doctype/print_format/print_format.json +++ b/frappe/printing/doctype/print_format/print_format.json @@ -182,7 +182,7 @@ "fieldname": "print_format_help", "fieldtype": "HTML", "label": "Print Format Help", - "options": "

Print Format Help

\n
\n

Introduction

\n

Print Formats are rendered on the server side using the Jinja Templating Language. All forms have access to the doc object which contains information about the document that is being formatted. You can also access common utilities via the frappe module.

\n

For styling, the Boostrap CSS framework is provided and you can enjoy the full range of classes.

\n
\n

References

\n
    \n\t
  1. Jinja Templating Language: Reference
  2. \n\t
  3. Bootstrap CSS Framework
  4. \n
\n
\n

Example

\n
<h3>{{ doc.select_print_heading or \"Invoice\" }}</h3>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Customer Name</div>\n\t<div class=\"col-md-9\">{{ doc.customer_name }}</div>\n</div>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Date</div>\n\t<div class=\"col-md-9\">{{ doc.get_formatted(\"invoice_date\") }}</div>\n</div>\n<table class=\"table table-bordered\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<th>Sr</th>\n\t\t\t<th>Item Name</th>\n\t\t\t<th>Description</th>\n\t\t\t<th class=\"text-right\">Qty</th>\n\t\t\t<th class=\"text-right\">Rate</th>\n\t\t\t<th class=\"text-right\">Amount</th>\n\t\t</tr>\n\t\t{%- for row in doc.items -%}\n\t\t<tr>\n\t\t\t<td style=\"width: 3%;\">{{ row.idx }}</td>\n\t\t\t<td style=\"width: 20%;\">\n\t\t\t\t{{ row.item_name }}\n\t\t\t\t{% if row.item_code != row.item_name -%}\n\t\t\t\t<br>Item Code: {{ row.item_code}}\n\t\t\t\t{%- endif %}\n\t\t\t</td>\n\t\t\t<td style=\"width: 37%;\">\n\t\t\t\t<div style=\"border: 0px;\">{{ row.description }}</div></td>\n\t\t\t<td style=\"width: 10%; text-align: right;\">{{ row.qty }} {{ row.uom or row.stock_uom }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"rate\", doc) }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"amount\", doc) }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>
\n
\n

Common Functions

\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n
doc.get_formatted(\"[fieldname]\", [parent_doc])Get document value formatted as Date, Currency, etc. Pass parent doc for currency type fields.
frappe.db.get_value(\"[doctype]\", \"[name]\", \"fieldname\")Get value from another document.
\n" + "options": "

Print Format Help

\n
\n

Introduction

\n

Print Formats are rendered on the server side using the Jinja Templating Language. All forms have access to the doc object which contains information about the document that is being formatted. You can also access common utilities via the frappe module.

\n

For styling, the Boostrap CSS framework is provided and you can enjoy the full range of classes.

\n
\n

References

\n
    \n\t
  1. Jinja Templating Language
  2. \n\t
  3. Bootstrap CSS Framework
  4. \n
\n
\n

Example

\n
<h3>{{ doc.select_print_heading or \"Invoice\" }}</h3>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Customer Name</div>\n\t<div class=\"col-md-9\">{{ doc.customer_name }}</div>\n</div>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Date</div>\n\t<div class=\"col-md-9\">{{ doc.get_formatted(\"invoice_date\") }}</div>\n</div>\n<table class=\"table table-bordered\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<th>Sr</th>\n\t\t\t<th>Item Name</th>\n\t\t\t<th>Description</th>\n\t\t\t<th class=\"text-right\">Qty</th>\n\t\t\t<th class=\"text-right\">Rate</th>\n\t\t\t<th class=\"text-right\">Amount</th>\n\t\t</tr>\n\t\t{%- for row in doc.items -%}\n\t\t<tr>\n\t\t\t<td style=\"width: 3%;\">{{ row.idx }}</td>\n\t\t\t<td style=\"width: 20%;\">\n\t\t\t\t{{ row.item_name }}\n\t\t\t\t{% if row.item_code != row.item_name -%}\n\t\t\t\t<br>Item Code: {{ row.item_code}}\n\t\t\t\t{%- endif %}\n\t\t\t</td>\n\t\t\t<td style=\"width: 37%;\">\n\t\t\t\t<div style=\"border: 0px;\">{{ row.description }}</div></td>\n\t\t\t<td style=\"width: 10%; text-align: right;\">{{ row.qty }} {{ row.uom or row.stock_uom }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"rate\", doc) }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"amount\", doc) }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>
\n
\n

Common Functions

\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n
doc.get_formatted(\"[fieldname]\", [parent_doc])Get document value formatted as Date, Currency, etc. Pass parent doc for currency type fields.
frappe.db.get_value(\"[doctype]\", \"[name]\", \"fieldname\")Get value from another document.
\n" }, { "fieldname": "format_data", @@ -202,7 +202,7 @@ "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-08-29 11:39:22.312438", + "modified": "2020-08-29 11:44:59.082797", "modified_by": "Administrator", "module": "Printing", "name": "Print Format", From 1b73602a1731f718f3a1cac86116292e18df40a4 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 19 Aug 2020 14:31:38 +0530 Subject: [PATCH 062/375] feat(system console): Added a System Console to help in debugging and Console Log --- .../doctype_action/doctype_action.json | 10 +-- frappe/database/mariadb/framework_mariadb.sql | 2 +- .../database/postgres/framework_postgres.sql | 2 +- frappe/desk/doctype/console_log/__init__.py | 0 .../desk/doctype/console_log/console_log.js | 8 +++ .../desk/doctype/console_log/console_log.json | 52 +++++++++++++++ .../desk/doctype/console_log/console_log.py | 10 +++ .../doctype/console_log/test_console_log.py | 10 +++ .../desk/doctype/system_console/__init__.py | 0 .../doctype/system_console/system_console.js | 8 +++ .../system_console/system_console.json | 66 +++++++++++++++++++ .../doctype/system_console/system_console.py | 36 ++++++++++ .../system_console/test_system_console.py | 10 +++ frappe/model/__init__.py | 3 +- frappe/model/naming.py | 2 + frappe/public/js/frappe/form/form.js | 12 +++- frappe/utils/safe_exec.py | 1 + 17 files changed, 224 insertions(+), 8 deletions(-) create mode 100644 frappe/desk/doctype/console_log/__init__.py create mode 100644 frappe/desk/doctype/console_log/console_log.js create mode 100644 frappe/desk/doctype/console_log/console_log.json create mode 100644 frappe/desk/doctype/console_log/console_log.py create mode 100644 frappe/desk/doctype/console_log/test_console_log.py create mode 100644 frappe/desk/doctype/system_console/__init__.py create mode 100644 frappe/desk/doctype/system_console/system_console.js create mode 100644 frappe/desk/doctype/system_console/system_console.json create mode 100644 frappe/desk/doctype/system_console/system_console.py create mode 100644 frappe/desk/doctype/system_console/test_system_console.py diff --git a/frappe/core/doctype/doctype_action/doctype_action.json b/frappe/core/doctype/doctype_action/doctype_action.json index 7a1b845af3..87ee6fc12f 100644 --- a/frappe/core/doctype/doctype_action/doctype_action.json +++ b/frappe/core/doctype/doctype_action/doctype_action.json @@ -31,20 +31,22 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Action Type", - "options": "Server Action", + "options": "Server Action\nRoute", "reqd": 1 }, { "columns": 4, "fieldname": "action", - "fieldtype": "Data", + "fieldtype": "Small Text", "in_list_view": 1, - "label": "Action", + "label": "Action / Route", "reqd": 1 } ], + "index_web_pages_for_search": 1, "istable": 1, - "modified": "2019-09-24 09:11:39.860100", + "links": [], + "modified": "2020-08-18 20:03:42.479436", "modified_by": "Administrator", "module": "Core", "name": "DocType Action", diff --git a/frappe/database/mariadb/framework_mariadb.sql b/frappe/database/mariadb/framework_mariadb.sql index 1e3749e030..15b0bed699 100644 --- a/frappe/database/mariadb/framework_mariadb.sql +++ b/frappe/database/mariadb/framework_mariadb.sql @@ -128,7 +128,7 @@ CREATE TABLE `tabDocType Action` ( `label` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `group` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `action_type` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `action` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `action` text COLLATE utf8mb4_unicode_ci DEFAULT NULL, PRIMARY KEY (`name`), KEY `parent` (`parent`), KEY `modified` (`modified`) diff --git a/frappe/database/postgres/framework_postgres.sql b/frappe/database/postgres/framework_postgres.sql index a946a7ee5c..eeb0eecd3f 100644 --- a/frappe/database/postgres/framework_postgres.sql +++ b/frappe/database/postgres/framework_postgres.sql @@ -128,7 +128,7 @@ CREATE TABLE "tabDocType Action" ( "parenttype" varchar(255) DEFAULT NULL, "idx" bigint NOT NULL DEFAULT 0, "label" varchar(140) NOT NULL, - "group" varchar(140) DEFAULT NULL, + "group" text DEFAULT NULL, "action_type" varchar(140) NOT NULL, "action" varchar(140) NOT NULL, PRIMARY KEY ("name") diff --git a/frappe/desk/doctype/console_log/__init__.py b/frappe/desk/doctype/console_log/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/desk/doctype/console_log/console_log.js b/frappe/desk/doctype/console_log/console_log.js new file mode 100644 index 0000000000..1ef4fdce59 --- /dev/null +++ b/frappe/desk/doctype/console_log/console_log.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Console Log', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/desk/doctype/console_log/console_log.json b/frappe/desk/doctype/console_log/console_log.json new file mode 100644 index 0000000000..a9ae9717fd --- /dev/null +++ b/frappe/desk/doctype/console_log/console_log.json @@ -0,0 +1,52 @@ +{ + "actions": [], + "autoname": "format:Log on {timestamp}", + "creation": "2020-08-18 19:56:12.336427", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "script", + "output" + ], + "fields": [ + { + "fieldname": "script", + "fieldtype": "Code", + "in_list_view": 1, + "label": "Script", + "read_only": 1 + }, + { + "fieldname": "output", + "fieldtype": "Code", + "label": "Output", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-08-18 20:07:57.587344", + "modified_by": "Administrator", + "module": "Desk", + "name": "Console Log", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/desk/doctype/console_log/console_log.py b/frappe/desk/doctype/console_log/console_log.py new file mode 100644 index 0000000000..635c4c1ba7 --- /dev/null +++ b/frappe/desk/doctype/console_log/console_log.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class ConsoleLog(Document): + pass diff --git a/frappe/desk/doctype/console_log/test_console_log.py b/frappe/desk/doctype/console_log/test_console_log.py new file mode 100644 index 0000000000..04dc4f241f --- /dev/null +++ b/frappe/desk/doctype/console_log/test_console_log.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestConsoleLog(unittest.TestCase): + pass diff --git a/frappe/desk/doctype/system_console/__init__.py b/frappe/desk/doctype/system_console/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/desk/doctype/system_console/system_console.js b/frappe/desk/doctype/system_console/system_console.js new file mode 100644 index 0000000000..20c1257c10 --- /dev/null +++ b/frappe/desk/doctype/system_console/system_console.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('System Console', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/desk/doctype/system_console/system_console.json b/frappe/desk/doctype/system_console/system_console.json new file mode 100644 index 0000000000..8c56792abb --- /dev/null +++ b/frappe/desk/doctype/system_console/system_console.json @@ -0,0 +1,66 @@ +{ + "actions": [ + { + "action": "#List/Console Log/List", + "action_type": "Route", + "label": "Logs" + }, + { + "action": "frappe.desk.doctype.system_console.system_console.execute_code", + "action_type": "Server Action", + "label": "Execute" + } + ], + "creation": "2020-08-18 17:44:35.647815", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "console", + "commit", + "output" + ], + "fields": [ + { + "description": "To print output use log(text)", + "fieldname": "console", + "fieldtype": "Code", + "label": "Console" + }, + { + "fieldname": "output", + "fieldtype": "Code", + "label": "Output", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "commit", + "fieldtype": "Check", + "label": "Commit" + } + ], + "hide_toolbar": 1, + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2020-08-18 20:05:36.936664", + "modified_by": "Administrator", + "module": "Desk", + "name": "System Console", + "owner": "Administrator", + "permissions": [ + { + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/desk/doctype/system_console/system_console.py b/frappe/desk/doctype/system_console/system_console.py new file mode 100644 index 0000000000..3d7b3db2c2 --- /dev/null +++ b/frappe/desk/doctype/system_console/system_console.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +import json + +import frappe +from frappe.utils.safe_exec import safe_exec +from frappe.model.document import Document + +class SystemConsole(Document): + pass + +@frappe.whitelist() +def execute_code(doc): + doc = json.loads(doc) + frappe.only_for('System Manager') + try: + frappe.debug_log = [] + safe_exec(doc['console']) + doc['output'] = '\n'.join(frappe.debug_log) + except: + doc['output'] = frappe.get_traceback() + + if doc.get('commit'): + frappe.db.commit() + else: + frappe.db.rollback() + + frappe.get_doc(dict(doctype='Console Log', script=doc['console'], output=doc['output'])).insert() + frappe.db.commit() + + + return doc \ No newline at end of file diff --git a/frappe/desk/doctype/system_console/test_system_console.py b/frappe/desk/doctype/system_console/test_system_console.py new file mode 100644 index 0000000000..2635b5a210 --- /dev/null +++ b/frappe/desk/doctype/system_console/test_system_console.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestSystemConsole(unittest.TestCase): + pass diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index e59d325c9a..c39a73ccd7 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -134,7 +134,8 @@ log_types = ( 'Notification Log', 'Email Queue', 'DocShare', - 'Document Follow' + 'Document Follow', + 'Console Log' ) def delete_fields(args_dict, delete=0): diff --git a/frappe/model/naming.py b/frappe/model/naming.py index ffaf84e2b3..f2c918113b 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -142,6 +142,8 @@ def parse_naming_series(parts, doctype='', doc=''): part = today.strftime("%d") elif e == 'YYYY': part = today.strftime('%Y') + elif e == 'timestamp': + part = str(today) elif e == 'FY': part = frappe.defaults.get_user_default("fiscal_year") elif e.startswith('{') and doc: diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index ff48ad2f60..d417d37c08 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -323,12 +323,22 @@ frappe.ui.form.Form = class FrappeForm { if (!this.is_new()) { this.add_custom_button(action.label, () => { if (action.action_type==='Server Action') { - frappe.xcall(action.action, {doc: this.doc}).then(() => { + frappe.xcall(action.action, {doc: this.doc}).then((doc) => { + if (doc.doctype) { + // document is returned by the method, + // apply the changes locally and refresh + frappe.model.sync(doc); + this.refresh(); + } + + // feedback frappe.msgprint({ message: __('{} Complete', [action.label]), alert: true }); }); + } else if (action.action_type==='Route') { + frappe.set_route(action.action); } }, action.group); } diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index c95b7e4699..548bd0baf7 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -48,6 +48,7 @@ def get_safe_globals(): # make available limited methods of frappe json=json, dict=dict, + log=frappe.log, _dict=frappe._dict, frappe=frappe._dict( flags=frappe.flags, From 12be3d26ff26fa798000504547325251e474820a Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 21 Aug 2020 15:03:57 +0530 Subject: [PATCH 063/375] fix(minor): primary button --- .../doctype_action/doctype_action.json | 11 +++- .../doctype/system_console/system_console.js | 9 ++- .../system_console/system_console.json | 3 +- frappe/public/js/frappe/form/form.js | 60 ++++++++++++------- 4 files changed, 57 insertions(+), 26 deletions(-) diff --git a/frappe/core/doctype/doctype_action/doctype_action.json b/frappe/core/doctype/doctype_action/doctype_action.json index 87ee6fc12f..0f9da802eb 100644 --- a/frappe/core/doctype/doctype_action/doctype_action.json +++ b/frappe/core/doctype/doctype_action/doctype_action.json @@ -8,7 +8,8 @@ "label", "action_type", "action", - "group" + "group", + "hidden" ], "fields": [ { @@ -41,12 +42,18 @@ "in_list_view": 1, "label": "Action / Route", "reqd": 1 + }, + { + "default": "0", + "fieldname": "hidden", + "fieldtype": "Check", + "label": "Hidden" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-08-18 20:03:42.479436", + "modified": "2020-08-21 14:44:03.845315", "modified_by": "Administrator", "module": "Core", "name": "DocType Action", diff --git a/frappe/desk/doctype/system_console/system_console.js b/frappe/desk/doctype/system_console/system_console.js index 20c1257c10..0aae8bd519 100644 --- a/frappe/desk/doctype/system_console/system_console.js +++ b/frappe/desk/doctype/system_console/system_console.js @@ -2,7 +2,10 @@ // For license information, please see license.txt frappe.ui.form.on('System Console', { - // refresh: function(frm) { - - // } + refresh: function(frm) { + frm.disable_save(); + frm.page.set_primary_action(__("Execute"), () => { + frm.execute_action('Execute'); + }); + } }); diff --git a/frappe/desk/doctype/system_console/system_console.json b/frappe/desk/doctype/system_console/system_console.json index 8c56792abb..296647a17a 100644 --- a/frappe/desk/doctype/system_console/system_console.json +++ b/frappe/desk/doctype/system_console/system_console.json @@ -8,6 +8,7 @@ { "action": "frappe.desk.doctype.system_console.system_console.execute_code", "action_type": "Server Action", + "hidden": 1, "label": "Execute" } ], @@ -44,7 +45,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-08-18 20:05:36.936664", + "modified": "2020-08-21 14:44:35.296877", "modified_by": "Administrator", "module": "Desk", "name": "System Console", diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index d417d37c08..9e221f7131 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -321,32 +321,52 @@ frappe.ui.form.Form = class FrappeForm { for (let action of this.meta.actions) { frappe.ui.form.on(this.doctype, 'refresh', () => { if (!this.is_new()) { - this.add_custom_button(action.label, () => { - if (action.action_type==='Server Action') { - frappe.xcall(action.action, {doc: this.doc}).then((doc) => { - if (doc.doctype) { - // document is returned by the method, - // apply the changes locally and refresh - frappe.model.sync(doc); - this.refresh(); - } - - // feedback - frappe.msgprint({ - message: __('{} Complete', [action.label]), - alert: true - }); - }); - } else if (action.action_type==='Route') { - frappe.set_route(action.action); - } - }, action.group); + if (!action.hidden) { + this.add_custom_button(action.label, () => { + this.execute_action(action); + }, action.group); + } } }); } } } + execute_action(action) { + if (typeof action === 'string') { + // called by label - maybe via custom script + // frm.execute_action('Action') + for (let _action of this.meta.actions) { + if (_action.label === action) { + action = _action; + break; + } + } + + if (typeof action === 'string') { + frappe.throw(`Action ${action} not found`); + } + } + if (action.action_type==='Server Action') { + frappe.xcall(action.action, {doc: this.doc}).then((doc) => { + if (doc.doctype) { + // document is returned by the method, + // apply the changes locally and refresh + frappe.model.sync(doc); + this.refresh(); + } + + // feedback + frappe.msgprint({ + message: __('{} Complete', [action.label]), + alert: true + }); + }); + } else if (action.action_type==='Route') { + frappe.set_route(action.action); + } + } + switch_doc(docname) { // record switch if(this.docname != docname && (!this.meta.in_dialog || this.in_form) && !this.meta.istable) { From 5a61558820d1c05892c27320a6b455b112aefbd5 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 24 Aug 2020 17:45:20 +0530 Subject: [PATCH 064/375] fix(minor): return value in frappe.flags --- frappe/core/doctype/server_script/server_script.json | 7 ++++--- frappe/core/doctype/server_script/server_script.py | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/server_script/server_script.json b/frappe/core/doctype/server_script/server_script.json index 3ed4076430..cc3995ad1d 100644 --- a/frappe/core/doctype/server_script/server_script.json +++ b/frappe/core/doctype/server_script/server_script.json @@ -7,12 +7,12 @@ "engine": "InnoDB", "field_order": [ "script_type", - "disabled", - "column_break_3", "reference_doctype", "doctype_event", "api_method", "allow_guest", + "column_break_3", + "disabled", "section_break_8", "script", "help_section", @@ -85,8 +85,9 @@ "fieldtype": "HTML" } ], + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-08-07 13:13:02.483963", + "modified": "2020-08-24 16:44:41.060350", "modified_by": "Administrator", "module": "Core", "name": "Server Script", diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index 539ae8eb01..55d7a33b8c 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -25,6 +25,7 @@ class ServerScript(Document): if frappe.session.user == 'Guest' and not self.allow_guest: raise frappe.PermissionError safe_exec(self.script) + return frappe.flags # output can be stored in flags else: # wrong report type! raise frappe.DoesNotExistError From 079a0e4af0fb38d8773b4f0d6669cbd090cba26e Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 25 Aug 2020 11:14:03 +0530 Subject: [PATCH 065/375] wip: refactor System Console --- .../doctype/system_console/system_console.py | 42 ++++++++++--------- frappe/utils/safe_exec.py | 8 +++- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/frappe/desk/doctype/system_console/system_console.py b/frappe/desk/doctype/system_console/system_console.py index 3d7b3db2c2..2715cd1425 100644 --- a/frappe/desk/doctype/system_console/system_console.py +++ b/frappe/desk/doctype/system_console/system_console.py @@ -11,26 +11,28 @@ from frappe.utils.safe_exec import safe_exec from frappe.model.document import Document class SystemConsole(Document): - pass + def run(self): + frappe.only_for('System Manager') + try: + frappe.debug_log = [] + safe_exec(self.console) + self.output = '\n'.join(frappe.debug_log) + except: + self.output = frappe.get_traceback() + + if self.commit: + frappe.db.commit() + else: + frappe.db.rollback() + + frappe.get_doc(dict( + doctype='Console Log', + script=self.console, + output=self.output)).insert() + frappe.db.commit() @frappe.whitelist() def execute_code(doc): - doc = json.loads(doc) - frappe.only_for('System Manager') - try: - frappe.debug_log = [] - safe_exec(doc['console']) - doc['output'] = '\n'.join(frappe.debug_log) - except: - doc['output'] = frappe.get_traceback() - - if doc.get('commit'): - frappe.db.commit() - else: - frappe.db.rollback() - - frappe.get_doc(dict(doctype='Console Log', script=doc['console'], output=doc['output'])).insert() - frappe.db.commit() - - - return doc \ No newline at end of file + console = frappe.get_doc(json.loads(doc)) + console.run() + return console.as_dict() \ No newline at end of file diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 548bd0baf7..a070d287da 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -100,7 +100,8 @@ def get_safe_globals(): scrub=scrub, guess_mimetype=mimetypes.guess_type, html2text=html2text, - dev_server=1 if os.environ.get('DEV_SERVER', False) else 0 + dev_server=1 if os.environ.get('DEV_SERVER', False) else 0, + run_script=run_script ) add_module_properties(frappe.exceptions, out.frappe, lambda obj: inspect.isclass(obj) and issubclass(obj, Exception)) @@ -143,6 +144,11 @@ def read_sql(query, *args, **kwargs): else: raise frappe.PermissionError('Only SELECT SQL allowed in scripting') +def run_script(script): + '''run another server script''' + frappe.get_doc('Server Script', script).execute_method() + return frappe.flags + def _getitem(obj, key): # guard function for RestrictedPython # allow any key to be accessed as long as it does not start with underscore From 39e36e01cde80e37ba0e758f60b4492700dbd177 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 25 Aug 2020 15:09:33 +0530 Subject: [PATCH 066/375] fix(test): test for system console; --- .../doctype/system_console/test_system_console.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/system_console/test_system_console.py b/frappe/desk/doctype/system_console/test_system_console.py index 2635b5a210..55ef199122 100644 --- a/frappe/desk/doctype/system_console/test_system_console.py +++ b/frappe/desk/doctype/system_console/test_system_console.py @@ -3,8 +3,18 @@ # See license.txt from __future__ import unicode_literals -# import frappe +import frappe import unittest class TestSystemConsole(unittest.TestCase): - pass + def test_system_console(self): + system_console = frappe.get_doc('System Console') + system_console.console = 'log("hello")' + system_console.run() + + self.assertEqual(system_console.output, 'hello') + + system_console.console = 'log(frappe.db.get_value("DocType", "DocType", "module"))' + system_console.run() + + self.assertEqual(system_console.output, 'Core') From 5e4ee1a6bcfd77d10ace5b810d43244c25394fc7 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 31 Aug 2020 11:29:06 +0530 Subject: [PATCH 067/375] fix(minor): quote the column names in alter table --- frappe/database/postgres/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/database/postgres/schema.py b/frappe/database/postgres/schema.py index b5129b60bb..58153ca6ce 100644 --- a/frappe/database/postgres/schema.py +++ b/frappe/database/postgres/schema.py @@ -49,7 +49,7 @@ class PostgresTable(DBTable): elif col.fieldtype in ("Check"): using_clause = "USING {}::smallint".format(col.fieldname) - query.append("ALTER COLUMN {0} TYPE {1} {2}".format( + query.append("ALTER COLUMN `{0}` TYPE {1} {2}".format( col.fieldname, get_definition(col.fieldtype, precision=col.precision, length=col.length), using_clause) From a360d0e806dc1dec7de79965dab81b29150bad56 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 31 Aug 2020 11:31:13 +0530 Subject: [PATCH 068/375] feat: escape html before rendering --- frappe/public/js/frappe/form/grid_row.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 733c1bea5f..8e0510ae7b 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -301,7 +301,7 @@ export default class GridRow { }); $col.field_area = $('
').appendTo($col).toggle(false); - $col.static_area = $('
').appendTo($col).html(txt); + $col.static_area = $('
').appendTo($col).html(frappe.utils.escape_html(txt)); $col.df = df; $col.column_index = ci; @@ -595,7 +595,7 @@ export default class GridRow { // reset static value var column = this.columns[fieldname]; if(column) { - column.static_area.html(txt || ""); + column.static_area.html(frappe.utils.escape_html(txt) || ""); if(df && df.reqd) { column.toggleClass('error', !!(txt===null || txt==='')); } From a032e9fc3d0d87835f22d4a2990a57ad247244b5 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 31 Aug 2020 11:31:44 +0530 Subject: [PATCH 069/375] fix(flake8): ignore bare except --- frappe/desk/doctype/system_console/system_console.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/system_console/system_console.py b/frappe/desk/doctype/system_console/system_console.py index 2715cd1425..6c87ca8c36 100644 --- a/frappe/desk/doctype/system_console/system_console.py +++ b/frappe/desk/doctype/system_console/system_console.py @@ -17,7 +17,7 @@ class SystemConsole(Document): frappe.debug_log = [] safe_exec(self.console) self.output = '\n'.join(frappe.debug_log) - except: + except: # noqa: E722 self.output = frappe.get_traceback() if self.commit: From 11a8f759f1f961c25418cc723585ec8249cb733b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 31 Aug 2020 11:32:18 +0530 Subject: [PATCH 070/375] chore: format functions --- frappe/public/js/frappe/form/grid_row.js | 34 ++++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 8e0510ae7b..366f746dc5 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -276,25 +276,25 @@ export default class GridRow { make_column(df, colsize, txt, ci) { let me = this; - var add_class = ((["Text", "Small Text"].indexOf(df.fieldtype)!==-1) ? + var add_class = ((["Text", "Small Text"].indexOf(df.fieldtype) !== -1) ? " grid-overflow-no-ellipsis" : ""); - add_class += (["Int", "Currency", "Float", "Percent"].indexOf(df.fieldtype)!==-1) ? - " text-right": ""; - add_class += (["Check"].indexOf(df.fieldtype)!==-1) ? - " text-center": ""; + add_class += (["Int", "Currency", "Float", "Percent"].indexOf(df.fieldtype) !== -1) ? + " text-right" : ""; + add_class += (["Check"].indexOf(df.fieldtype) !== -1) ? + " text-center" : ""; - var $col = $('
') + var $col = $('
') .attr("data-fieldname", df.fieldname) .attr("data-fieldtype", df.fieldtype) .data("df", df) .appendTo(this.row) - .on('click', function() { - if(frappe.ui.form.editable_row===me) { + .on('click', function () { + if (frappe.ui.form.editable_row === me) { return; } var out = me.toggle_editable_row(); var col = this; - setTimeout(function() { + setTimeout(function () { $(col).find('input[type="Text"]:first').focus(); }, 500); return out; @@ -577,39 +577,39 @@ export default class GridRow { var df = this.grid.get_docfield(fieldname) || undefined; // format values if no frm - if(!df) { + if (!df) { df = this.grid.visible_columns.find((col) => { return col[0].fieldname === fieldname; }); - if(df && this.doc) { + if (df && this.doc) { var txt = frappe.format(this.doc[fieldname], df[0], null, this.doc); } } - if(txt===undefined && this.frm) { + if (txt === undefined && this.frm) { var txt = frappe.format(this.doc[fieldname], df, null, this.frm.doc); } // reset static value var column = this.columns[fieldname]; - if(column) { + if (column) { column.static_area.html(frappe.utils.escape_html(txt) || ""); - if(df && df.reqd) { - column.toggleClass('error', !!(txt===null || txt==='')); + if (df && df.reqd) { + column.toggleClass('error', !!(txt === null || txt === '')); } } // reset field value var field = this.on_grid_fields_dict[fieldname]; - if(field) { + if (field) { field.docname = this.doc.name; field.refresh(); } // in form - if(this.grid_form) { + if (this.grid_form) { this.grid_form.refresh_field(fieldname); } } From a5959df752f8cea57037d7e832a01e55e0b628db Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 31 Aug 2020 11:44:37 +0530 Subject: [PATCH 071/375] fix: don't override column (#11374) --- frappe/public/js/frappe/views/reports/query_report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 706cde13b7..7958ff46cc 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1041,7 +1041,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { if (column.colIndex === index && !value) { value = "Total"; - column.fieldtype = "Data"; // avoid type issues for value if Date column + column = { fieldtype: "Data" }; // avoid type issues for value if Date column } else if (in_list(["Currency", "Float"], column.fieldtype)) { // proxy for currency and float data = this.data[0]; From dcce45b558f1a7d602214540f81f53a1ad3b888d Mon Sep 17 00:00:00 2001 From: Anupam K Date: Mon, 31 Aug 2020 11:54:56 +0530 Subject: [PATCH 072/375] fix: test case --- frappe/email/doctype/newsletter/test_newsletter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/email/doctype/newsletter/test_newsletter.py b/frappe/email/doctype/newsletter/test_newsletter.py index bb339165d3..ee7f123b7e 100644 --- a/frappe/email/doctype/newsletter/test_newsletter.py +++ b/frappe/email/doctype/newsletter/test_newsletter.py @@ -67,6 +67,7 @@ class TestNewsletter(unittest.TestCase): "doctype": "Newsletter", "subject": "_Test Newsletter", "send_from": "Test Sender ", + "content_type": "Rich Text", "message": "Testing my news.", "published": published, "schedule_sending": bool(schedule_send), From afe5959963d8277f97c93b37ffd15d55ef58b8c1 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 31 Aug 2020 14:30:33 +0530 Subject: [PATCH 073/375] fix: Unbreaking change for kwarg backup_path_conf --- frappe/utils/backups.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index 3d683f6c0a..77d3e83dd9 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -27,9 +27,9 @@ class BackupGenerator: To initialize, specify (db_name, user, password, db_file_name=None, db_host="localhost") If specifying db_file_name, also append ".sql.gz" """ - def __init__(self, db_name, user, password, backup_path_conf=None, backup_path_db=None, backup_path_files=None, + def __init__(self, db_name, user, password, backup_path_db=None, backup_path_files=None, backup_path_private_files=None, db_host="localhost", db_port=None, verbose=False, - db_type='mariadb'): + db_type='mariadb', backup_path_conf=None): global _verbose self.db_host = db_host self.db_port = db_port From e860ac110802ee282b66b6f1b4f3be187d668512 Mon Sep 17 00:00:00 2001 From: gavin Date: Mon, 31 Aug 2020 15:14:20 +0530 Subject: [PATCH 074/375] fix: Don't raise FileExistsError if directory already exists Co-authored-by: Aditya Hase --- frappe/utils/backups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index c7b1523ac0..4611fdc04e 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -62,7 +62,7 @@ class BackupGenerator: else: for file_path in [self.backup_path_files, self.backup_path_db, self.backup_path_private_files]: dir = os.path.dirname(file_path) - os.makedirs(dir) + os.makedirs(dir, exist_ok=True) def get_backup(self, older_than=24, ignore_files=False, force=False): From 6e3d3c5b74de828b9cc9339fae35dd37c3dc37cf Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 31 Aug 2020 16:56:41 +0530 Subject: [PATCH 075/375] fix(minor): allow tags in markdown --- frappe/website/doctype/blog_post/blog_post.json | 3 ++- frappe/website/doctype/web_page/web_page.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/website/doctype/blog_post/blog_post.json b/frappe/website/doctype/blog_post/blog_post.json index c3220788b7..25bca28e85 100644 --- a/frappe/website/doctype/blog_post/blog_post.json +++ b/frappe/website/doctype/blog_post/blog_post.json @@ -110,6 +110,7 @@ "depends_on": "eval:doc.content_type === 'Markdown'", "fieldname": "content_md", "fieldtype": "Markdown Editor", + "ignore_xss_filter": 1, "label": "Content (Markdown)" }, { @@ -193,7 +194,7 @@ "is_published_field": "published", "links": [], "max_attachments": 5, - "modified": "2020-07-21 16:25:17.154911", + "modified": "2020-08-31 16:55:03.687862", "modified_by": "Administrator", "module": "Website", "name": "Blog Post", diff --git a/frappe/website/doctype/web_page/web_page.json b/frappe/website/doctype/web_page/web_page.json index 955b34f44e..42ca81391e 100644 --- a/frappe/website/doctype/web_page/web_page.json +++ b/frappe/website/doctype/web_page/web_page.json @@ -126,6 +126,7 @@ "depends_on": "eval:doc.content_type==='Markdown'", "fieldname": "main_section_md", "fieldtype": "Markdown Editor", + "ignore_xss_filter": 1, "label": "Main Section (Markdown)" }, { @@ -294,7 +295,7 @@ "is_published_field": "published", "links": [], "max_attachments": 20, - "modified": "2020-08-07 10:55:54.885448", + "modified": "2020-08-31 16:55:52.015249", "modified_by": "Administrator", "module": "Website", "name": "Web Page", From 4de73810765f43d149b8aff6432452c0b154672c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 31 Aug 2020 17:23:41 +0530 Subject: [PATCH 076/375] feat: update category in tests --- frappe/website/doctype/blog_post/test_blog_post.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/website/doctype/blog_post/test_blog_post.py b/frappe/website/doctype/blog_post/test_blog_post.py index 4b4d062a2d..eccc1258f3 100644 --- a/frappe/website/doctype/blog_post/test_blog_post.py +++ b/frappe/website/doctype/blog_post/test_blog_post.py @@ -61,7 +61,7 @@ class TestBlogPost(unittest.TestCase): frappe.delete_doc("Blog Category", blog.blog_category) def make_test_blog(): - if not frappe.db.exists('Blog Category', 'Test Blog Category'): + if not frappe.db.exists('Blog Category', '-test-blog-category'): # Set different title and name for the category frappe.get_doc(dict( doctype = 'Blog Category', @@ -73,7 +73,7 @@ def make_test_blog(): full_name='Test Blogger')).insert() test_blog = frappe.get_doc(dict( doctype = 'Blog Post', - blog_category = 'test-blog-category', + blog_category = '-test-blog-category', blogger = 'test-blogger', title = random_string(20), route = random_string(20), From c6f6ff6b5cee9345a650695e667beb11b8c0734c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 31 Aug 2020 17:33:03 +0530 Subject: [PATCH 077/375] feat: use blog category name --- frappe/website/doctype/blog_post/test_blog_post.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/website/doctype/blog_post/test_blog_post.py b/frappe/website/doctype/blog_post/test_blog_post.py index eccc1258f3..ea1b6125db 100644 --- a/frappe/website/doctype/blog_post/test_blog_post.py +++ b/frappe/website/doctype/blog_post/test_blog_post.py @@ -44,8 +44,8 @@ class TestBlogPost(unittest.TestCase): # On blog post page find link to the category page soup = BeautifulSoup(blog_page_html, "lxml") - category_title = frappe.db.get_value("Blog Category", blog.blog_category, "title") - category_page_link = list(soup.find_all('a', string=category_title))[0] + + category_page_link = list(soup.find_all('a', string=blog.blog_category))[0] category_page_url = category_page_link["href"] # Visit the category page (by following the link found in above stage) From 1eb1cba6664774aeb693b0ca53bc715eb806c141 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 31 Aug 2020 19:54:19 +0530 Subject: [PATCH 078/375] feat: Show Status Section in Query Report --- frappe/public/js/frappe/views/reports/query_report.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 7958ff46cc..875be7d29e 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -592,6 +592,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.render_summary(data.report_summary); } + if(data.message && !data.prepared_report) this.show_status(data.message); + this.toggle_message(false); if (data.result && data.result.length) { this.prepare_report_data(data); From 4b2c82b9c90bb02432a76b34c2611cd1b51acd09 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 31 Aug 2020 20:14:34 +0530 Subject: [PATCH 079/375] fix: Set blog route if title and category are set Set blog route on client side as soon as title and category are selected. This behavious is in line with Web Page, where the route is set as soon as the title is entered. This will ensure blog routes are consistent and contain category as the part of the route. The user can change the route ofcourse, but this behaviour is the most common expectation. --- frappe/website/doctype/blog_post/blog_post.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/frappe/website/doctype/blog_post/blog_post.js b/frappe/website/doctype/blog_post/blog_post.js index 7aa83f536d..bfff947948 100644 --- a/frappe/website/doctype/blog_post/blog_post.js +++ b/frappe/website/doctype/blog_post/blog_post.js @@ -11,16 +11,29 @@ frappe.ui.form.on('Blog Post', { }, title: function(frm) { generate_google_search_preview(frm); + frm.trigger('set_route'); }, meta_description: function(frm) { generate_google_search_preview(frm); }, blog_intro: function(frm) { generate_google_search_preview(frm); + }, + blog_category(frm) { + frm.trigger('set_route'); + }, + set_route(frm) { + if (frm.doc.route) return; + if (frm.doc.title && frm.doc.blog_category) { + frm.call('make_route').then(r => { + frm.set_value('route', r.message); + }); + } } }); function generate_google_search_preview(frm) { + if (!frm.doc.title) return; let google_preview = frm.get_field("google_preview"); let seo_title = (frm.doc.title).slice(0, 60); let seo_description = (frm.doc.meta_description || frm.doc.blog_intro || "").slice(0, 160); From 04dbb4c2613f345007ab992cd5b610fb1bc31f3d Mon Sep 17 00:00:00 2001 From: Marica Date: Mon, 31 Aug 2020 20:16:02 +0530 Subject: [PATCH 080/375] fix: Spacing --- frappe/public/js/frappe/views/reports/query_report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 875be7d29e..1bec65e460 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -592,7 +592,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.render_summary(data.report_summary); } - if(data.message && !data.prepared_report) this.show_status(data.message); + if (data.message && !data.prepared_report) this.show_status(data.message); this.toggle_message(false); if (data.result && data.result.length) { From 14d70b274317a703c9a97ab351151c3c4ed7b8e9 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 31 Aug 2020 20:51:25 +0530 Subject: [PATCH 081/375] fix: Refresh datatable when sidebar is toggled --- frappe/public/js/frappe/views/reports/report_view.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index c7d001ed94..aec24d9d13 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -48,6 +48,10 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { setup_view() { this.setup_columns(); super.setup_new_doc_event(); + // refresh datatable when sidebar is toggled to accomodate extra space + $(document.body) + .off('toggleListSidebar.report_view') + .on('toggleListSidebar.report_view', () => this.render(true)); } setup_result_area() { From f9e6f0e8e41a64e9eed308c54037f2b3dd5509d9 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 31 Aug 2020 21:07:51 +0530 Subject: [PATCH 082/375] fix(Blog): Ability to set meta_title separately --- frappe/website/doctype/blog_post/blog_post.js | 3 ++- frappe/website/doctype/blog_post/blog_post.json | 10 ++++++++-- frappe/website/doctype/blog_post/blog_post.py | 7 ++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/frappe/website/doctype/blog_post/blog_post.js b/frappe/website/doctype/blog_post/blog_post.js index 7aa83f536d..545051db7f 100644 --- a/frappe/website/doctype/blog_post/blog_post.js +++ b/frappe/website/doctype/blog_post/blog_post.js @@ -21,8 +21,9 @@ frappe.ui.form.on('Blog Post', { }); function generate_google_search_preview(frm) { + if (!(frm.doc.meta_title && frm.doc.title)) return; let google_preview = frm.get_field("google_preview"); - let seo_title = (frm.doc.title).slice(0, 60); + let seo_title = (frm.doc.meta_title || frm.doc.title).slice(0, 60); let seo_description = (frm.doc.meta_description || frm.doc.blog_intro || "").slice(0, 160); let date = frm.doc.published_on ? new frappe.datetime.datetime(frm.doc.published_on).moment.format('ll') + ' - ' : ''; let route_array = frm.doc.route ? frm.doc.route.split('/') : []; diff --git a/frappe/website/doctype/blog_post/blog_post.json b/frappe/website/doctype/blog_post/blog_post.json index 25bca28e85..48e9a18e71 100644 --- a/frappe/website/doctype/blog_post/blog_post.json +++ b/frappe/website/doctype/blog_post/blog_post.json @@ -26,6 +26,7 @@ "content_html", "email_sent", "meta_tags", + "meta_title", "meta_description", "column_break_18", "meta_image", @@ -110,7 +111,6 @@ "depends_on": "eval:doc.content_type === 'Markdown'", "fieldname": "content_md", "fieldtype": "Markdown Editor", - "ignore_xss_filter": 1, "label": "Content (Markdown)" }, { @@ -185,6 +185,12 @@ "fieldtype": "Check", "hidden": 1, "label": "Hide CTA" + }, + { + "fieldname": "meta_title", + "fieldtype": "Data", + "label": "Meta Title", + "length": 60 } ], "has_web_view": 1, @@ -194,7 +200,7 @@ "is_published_field": "published", "links": [], "max_attachments": 5, - "modified": "2020-08-31 16:55:03.687862", + "modified": "2020-08-31 21:01:51.100349", "modified_by": "Administrator", "module": "Website", "name": "Blog Post", diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index beffcdca25..1a07dbed86 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -36,6 +36,11 @@ class BlogPost(WebsiteGenerator): if self.blog_intro: self.blog_intro = self.blog_intro[:200] + if not self.meta_title: + self.meta_title = self.title[:60] + else: + self.meta_title = self.meta_title[:60] + if not self.meta_description: self.meta_description = self.blog_intro[:140] else: @@ -88,7 +93,7 @@ class BlogPost(WebsiteGenerator): context.description = self.meta_description or self.blog_intro or strip_html_tags(context.content[:140]) context.metatags = { - "name": self.title, + "name": self.meta_title, "description": context.description, } From 8f819da4ff7afdf9c218d0bf45417ee9fe167704 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Tue, 1 Sep 2020 12:43:58 +0530 Subject: [PATCH 083/375] fix: failed test case --- frappe/email/doctype/newsletter/newsletter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 929855ea30..14bd7eb1dc 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -37,8 +37,8 @@ class Newsletter(WebsiteGenerator): self.recipients = self.get_recipients() if self.recipients: + self.queue_all() if getattr(frappe.local, "is_ajax", False): - self.queue_all() frappe.msgprint(_("Email queued to {0} recipients").format(len(self.recipients))) else: From f772aee2110b2383d42c2ee276c766baa19a7dca Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 1 Sep 2020 12:48:26 +0530 Subject: [PATCH 084/375] fix(minor): don't allow access to global flags --- frappe/utils/safe_exec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index c95b7e4699..b393939f92 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -50,7 +50,7 @@ def get_safe_globals(): dict=dict, _dict=frappe._dict, frappe=frappe._dict( - flags=frappe.flags, + flags=frappe._dict(), format=frappe.format_value, format_value=frappe.format_value, date_format=date_format, From c53813950fde81bd884f601690a9c5c15c070c1c Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 1 Sep 2020 12:59:22 +0530 Subject: [PATCH 085/375] fix(minor): Server Script can return values in frappe.flags --- frappe/core/doctype/server_script/server_script.py | 4 ++-- .../core/doctype/server_script/test_server_script.py | 12 ++++++++++++ frappe/utils/safe_exec.py | 5 +++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index 55d7a33b8c..839b784651 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -24,8 +24,8 @@ class ServerScript(Document): # validate if guest is allowed if frappe.session.user == 'Guest' and not self.allow_guest: raise frappe.PermissionError - safe_exec(self.script) - return frappe.flags # output can be stored in flags + _globals, _locals = safe_exec(self.script) + return _globals.frappe.flags # output can be stored in flags else: # wrong report type! raise frappe.DoesNotExistError diff --git a/frappe/core/doctype/server_script/test_server_script.py b/frappe/core/doctype/server_script/test_server_script.py index 5c12858e8a..3356e584af 100644 --- a/frappe/core/doctype/server_script/test_server_script.py +++ b/frappe/core/doctype/server_script/test_server_script.py @@ -36,6 +36,15 @@ if "validate" in doc.description: allow_guest = 1, script = ''' frappe.response['message'] = 'hello' +''' + ), + dict( + name='test_return_value', + script_type = 'API', + api_method = 'test_return_value', + allow_guest = 1, + script = ''' +frappe.flags = 'hello' ''' ) ] @@ -73,3 +82,6 @@ class TestServerScript(unittest.TestCase): response = requests.post(get_site_url(frappe.local.site) + "/api/method/test_server_script") self.assertEqual(response.status_code, 200) self.assertEqual("hello", response.json()["message"]) + + def test_api_return(self): + self.assertEqual(frappe.get_doc('Server Script', 'test_return_value').execute_method(), 'hello') diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index a070d287da..56c5fe90c8 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -28,6 +28,8 @@ def safe_exec(script, _globals=None, _locals=None): # execute script compiled by RestrictedPython exec(compile_restricted(script), exec_globals, _locals) # pylint: disable=exec-used + return exec_globals, _locals + def get_safe_globals(): datautils = frappe._dict() if frappe.db: @@ -146,8 +148,7 @@ def read_sql(query, *args, **kwargs): def run_script(script): '''run another server script''' - frappe.get_doc('Server Script', script).execute_method() - return frappe.flags + return frappe.get_doc('Server Script', script).execute_method() def _getitem(obj, key): # guard function for RestrictedPython From f533acabe6927cfe296197ecb05272be2e3131cf Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 1 Sep 2020 13:46:08 +0530 Subject: [PATCH 086/375] test: use regex to search for link --- frappe/website/doctype/blog_post/test_blog_post.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/website/doctype/blog_post/test_blog_post.py b/frappe/website/doctype/blog_post/test_blog_post.py index ea1b6125db..7c6e64dc5a 100644 --- a/frappe/website/doctype/blog_post/test_blog_post.py +++ b/frappe/website/doctype/blog_post/test_blog_post.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe import unittest from bs4 import BeautifulSoup +import re from frappe.utils import set_request from frappe.website.render import render @@ -44,8 +45,7 @@ class TestBlogPost(unittest.TestCase): # On blog post page find link to the category page soup = BeautifulSoup(blog_page_html, "lxml") - - category_page_link = list(soup.find_all('a', string=blog.blog_category))[0] + category_page_link = list(soup.find_all('a', href=re.compile(blog.blog_category)))[0] category_page_url = category_page_link["href"] # Visit the category page (by following the link found in above stage) @@ -61,7 +61,7 @@ class TestBlogPost(unittest.TestCase): frappe.delete_doc("Blog Category", blog.blog_category) def make_test_blog(): - if not frappe.db.exists('Blog Category', '-test-blog-category'): + if not frappe.db.exists('Blog Category', 'test-blog-category'): # Set different title and name for the category frappe.get_doc(dict( doctype = 'Blog Category', @@ -73,7 +73,7 @@ def make_test_blog(): full_name='Test Blogger')).insert() test_blog = frappe.get_doc(dict( doctype = 'Blog Post', - blog_category = '-test-blog-category', + blog_category = 'test-blog-category', blogger = 'test-blogger', title = random_string(20), route = random_string(20), From 7efe1db6287043f3d6d2f34c07ba447daf7d079d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 1 Sep 2020 14:07:16 +0530 Subject: [PATCH 087/375] Revert "fix: child table rendering with escape_html" --- frappe/public/js/frappe/form/grid_row.js | 38 ++++++++++++------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 366f746dc5..733c1bea5f 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -276,32 +276,32 @@ export default class GridRow { make_column(df, colsize, txt, ci) { let me = this; - var add_class = ((["Text", "Small Text"].indexOf(df.fieldtype) !== -1) ? + var add_class = ((["Text", "Small Text"].indexOf(df.fieldtype)!==-1) ? " grid-overflow-no-ellipsis" : ""); - add_class += (["Int", "Currency", "Float", "Percent"].indexOf(df.fieldtype) !== -1) ? - " text-right" : ""; - add_class += (["Check"].indexOf(df.fieldtype) !== -1) ? - " text-center" : ""; + add_class += (["Int", "Currency", "Float", "Percent"].indexOf(df.fieldtype)!==-1) ? + " text-right": ""; + add_class += (["Check"].indexOf(df.fieldtype)!==-1) ? + " text-center": ""; - var $col = $('
') + var $col = $('
') .attr("data-fieldname", df.fieldname) .attr("data-fieldtype", df.fieldtype) .data("df", df) .appendTo(this.row) - .on('click', function () { - if (frappe.ui.form.editable_row === me) { + .on('click', function() { + if(frappe.ui.form.editable_row===me) { return; } var out = me.toggle_editable_row(); var col = this; - setTimeout(function () { + setTimeout(function() { $(col).find('input[type="Text"]:first').focus(); }, 500); return out; }); $col.field_area = $('
').appendTo($col).toggle(false); - $col.static_area = $('
').appendTo($col).html(frappe.utils.escape_html(txt)); + $col.static_area = $('
').appendTo($col).html(txt); $col.df = df; $col.column_index = ci; @@ -577,39 +577,39 @@ export default class GridRow { var df = this.grid.get_docfield(fieldname) || undefined; // format values if no frm - if (!df) { + if(!df) { df = this.grid.visible_columns.find((col) => { return col[0].fieldname === fieldname; }); - if (df && this.doc) { + if(df && this.doc) { var txt = frappe.format(this.doc[fieldname], df[0], null, this.doc); } } - if (txt === undefined && this.frm) { + if(txt===undefined && this.frm) { var txt = frappe.format(this.doc[fieldname], df, null, this.frm.doc); } // reset static value var column = this.columns[fieldname]; - if (column) { - column.static_area.html(frappe.utils.escape_html(txt) || ""); - if (df && df.reqd) { - column.toggleClass('error', !!(txt === null || txt === '')); + if(column) { + column.static_area.html(txt || ""); + if(df && df.reqd) { + column.toggleClass('error', !!(txt===null || txt==='')); } } // reset field value var field = this.on_grid_fields_dict[fieldname]; - if (field) { + if(field) { field.docname = this.doc.name; field.refresh(); } // in form - if (this.grid_form) { + if(this.grid_form) { this.grid_form.refresh_field(fieldname); } } From 428080bc56003427a113826c488f2d55459c1841 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 1 Sep 2020 21:04:57 +0530 Subject: [PATCH 088/375] fix: Send email notification to all document assignees --- .../email/doctype/notification/notification.json | 13 +++++++++++-- frappe/email/doctype/notification/notification.py | 15 +++++++++++++-- .../notification_recipient.json | 3 ++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/frappe/email/doctype/notification/notification.json b/frappe/email/doctype/notification/notification.json index 95f218ad73..8918709fa0 100644 --- a/frappe/email/doctype/notification/notification.json +++ b/frappe/email/doctype/notification/notification.json @@ -34,6 +34,7 @@ "set_property_after_alert", "property_value", "column_break_5", + "send_to_all_assignees", "recipients", "message_sb", "message", @@ -216,7 +217,7 @@ "fieldname": "recipients", "fieldtype": "Table", "label": "Recipients", - "mandatory_depends_on": "eval:doc.channel!=='Slack'", + "mandatory_depends_on": "eval:doc.channel!=='Slack' && !doc.send_to_all_assignees", "options": "Notification Recipient" }, { @@ -277,11 +278,19 @@ "fieldname": "send_system_notification", "fieldtype": "Check", "label": "Send System Notification" + }, + { + "default": "0", + "depends_on": "eval:doc.channel == 'Email'", + "fieldname": "send_to_all_assignees", + "fieldtype": "Check", + "label": "Send To All Assignees" } ], "icon": "fa fa-envelope", + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-08-11 19:24:35.479373", + "modified": "2020-09-01 18:36:22.550891", "modified_by": "Administrator", "module": "Email", "name": "Notification", diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 2ec208c89d..cfa3c43ff7 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -189,6 +189,7 @@ def get_context(context): recipients, cc, bcc = self.get_list_of_recipients(doc, context) if not (recipients or cc or bcc): return + sender = None if self.sender and self.sender_email: sender = formataddr((self.sender, self.sender_email)) @@ -239,8 +240,6 @@ def get_context(context): email_ids = email_ids_value.replace(",", "\n") recipients = recipients + email_ids.split("\n") - # else: - # print "invalid email" if recipient.cc and "{" in recipient.cc: recipient.cc = frappe.render_template(recipient.cc, context) @@ -262,6 +261,9 @@ def get_context(context): for email in emails: recipients = recipients + email.split("\n") + if self.send_to_all_assignees: + recipients = recipients + get_assignees(doc) + if not recipients and not cc and not bcc: return None, None, None return list(set(recipients)), list(set(cc)), list(set(bcc)) @@ -405,3 +407,12 @@ def evaluate_alert(doc, alert, event): def get_context(doc): return {"doc": doc, "nowdate": nowdate, "frappe": frappe._dict(utils=frappe.utils)} + +def get_assignees(doc): + assignees = [] + assignees = frappe.get_all('ToDo', filters={'status': 'Open', 'reference_name': doc.name, + 'reference_type': doc.doctype}, fields=['owner']) + + recipients = [d.owner for d in assignees] + + return recipients \ No newline at end of file diff --git a/frappe/email/doctype/notification_recipient/notification_recipient.json b/frappe/email/doctype/notification_recipient/notification_recipient.json index 201899cd57..0670320a77 100644 --- a/frappe/email/doctype/notification_recipient/notification_recipient.json +++ b/frappe/email/doctype/notification_recipient/notification_recipient.json @@ -46,9 +46,10 @@ "options": "Role" } ], + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-02-21 11:18:40.125233", + "modified": "2020-09-01 17:40:27.289105", "modified_by": "Administrator", "module": "Email", "name": "Notification Recipient", From 3eb0e5ce8a88f39bc79f61415f2af6bc0a7c45e2 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 2 Sep 2020 09:21:24 +0530 Subject: [PATCH 089/375] Update frappe/desk/doctype/system_console/system_console.js Co-authored-by: Aditya Hase --- frappe/desk/doctype/system_console/system_console.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frappe/desk/doctype/system_console/system_console.js b/frappe/desk/doctype/system_console/system_console.js index 0aae8bd519..c7eac39490 100644 --- a/frappe/desk/doctype/system_console/system_console.js +++ b/frappe/desk/doctype/system_console/system_console.js @@ -2,6 +2,16 @@ // For license information, please see license.txt frappe.ui.form.on('System Console', { + onload: function(frm) { + frappe.ui.keys.add_shortcut({ + shortcut: 'shift+enter', + action: () => frm.execute_action('Execute'), + page: frm.page, + description: __('Execute Console script'), + ignore_inputs: true, + }); + }, + refresh: function(frm) { frm.disable_save(); frm.page.set_primary_action(__("Execute"), () => { From ac76c551600408f2020c192ceb4dfa1016746ccf Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 2 Sep 2020 09:22:28 +0530 Subject: [PATCH 090/375] Update frappe/desk/doctype/system_console/system_console.json Co-authored-by: Aditya Hase --- frappe/desk/doctype/system_console/system_console.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/system_console/system_console.json b/frappe/desk/doctype/system_console/system_console.json index 296647a17a..14e36e6fd3 100644 --- a/frappe/desk/doctype/system_console/system_console.json +++ b/frappe/desk/doctype/system_console/system_console.json @@ -26,7 +26,8 @@ "description": "To print output use log(text)", "fieldname": "console", "fieldtype": "Code", - "label": "Console" + "label": "Console", + "options": "Python" }, { "fieldname": "output", @@ -64,4 +65,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From aecb77ce9b5023dab233e55ee918cb87f169812e Mon Sep 17 00:00:00 2001 From: Anupam K Date: Wed, 2 Sep 2020 12:19:34 +0530 Subject: [PATCH 091/375] fix: removing condition --- frappe/email/doctype/newsletter/newsletter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 14bd7eb1dc..0a0a13a6ce 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -38,8 +38,7 @@ class Newsletter(WebsiteGenerator): if self.recipients: self.queue_all() - if getattr(frappe.local, "is_ajax", False): - frappe.msgprint(_("Email queued to {0} recipients").format(len(self.recipients))) + frappe.msgprint(_("Email queued to {0} recipients").format(len(self.recipients))) else: frappe.msgprint(_("Newsletter should have atleast one recipient")) From 0b8ac586c0f98c8d69777ac1b6b52dd402cd7348 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Wed, 2 Sep 2020 12:51:15 +0530 Subject: [PATCH 092/375] fix: newsletter issues (#11321) * fix: newsletter fix * fix: review changes * fix: test case * fix: failed test case * fix: removing condition Co-authored-by: Prssanna Desai --- frappe/email/doctype/newsletter/newsletter.js | 3 -- .../email/doctype/newsletter/newsletter.json | 48 +++++++++++++++---- frappe/email/doctype/newsletter/newsletter.py | 40 ++++++++-------- .../doctype/newsletter/newsletter_list.js | 6 ++- .../doctype/newsletter/test_newsletter.py | 1 + frappe/patches.txt | 1 + .../v13_0/update_newsletter_content_type.py | 12 +++++ 7 files changed, 76 insertions(+), 35 deletions(-) create mode 100644 frappe/patches/v13_0/update_newsletter_content_type.py diff --git a/frappe/email/doctype/newsletter/newsletter.js b/frappe/email/doctype/newsletter/newsletter.js index 0f1e8dc57c..3277d8e9ee 100644 --- a/frappe/email/doctype/newsletter/newsletter.js +++ b/frappe/email/doctype/newsletter/newsletter.js @@ -14,9 +14,6 @@ frappe.ui.form.on('Newsletter', { }); }, "fa fa-play", "btn-success"); } - if (!doc.__islocal && cint(doc.email_sent)) { - frm.set_df_property('schedule_send', "read_only", 1); - } frm.events.setup_dashboard(frm); diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json index 4804b3d6fa..1dd6115b43 100644 --- a/frappe/email/doctype/newsletter/newsletter.json +++ b/frappe/email/doctype/newsletter/newsletter.json @@ -15,7 +15,10 @@ "email_sent", "newsletter_content", "subject", + "content_type", "message", + "message_md", + "message_html", "send_unsubscribe_link", "send_attachments", "published", @@ -37,8 +40,7 @@ "fieldname": "send_from", "fieldtype": "Data", "ignore_xss_filter": 1, - "label": "Sender", - "no_copy": 1 + "label": "Sender" }, { "default": "0", @@ -50,7 +52,8 @@ }, { "fieldname": "newsletter_content", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Content" }, { "fieldname": "subject", @@ -61,11 +64,12 @@ "reqd": 1 }, { + "depends_on": "eval: doc.content_type === 'Rich Text'", "fieldname": "message", "fieldtype": "Text Editor", "in_list_view": 1, "label": "Message", - "reqd": 1 + "mandatory_depends_on": "eval: doc.content_type === 'Rich Text'" }, { "default": "1", @@ -87,16 +91,20 @@ "read_only": 1 }, { + "collapsible": 1, "fieldname": "test_the_newsletter", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Testing" }, { "description": "A Lead with this Email Address should exist", "fieldname": "test_email_id", "fieldtype": "Data", - "label": "Test Email Address" + "label": "Test Email Address", + "options": "Email" }, { + "depends_on": "eval: doc.test_email_id", "fieldname": "test_send", "fieldtype": "Button", "label": "Test", @@ -117,7 +125,8 @@ "depends_on": "eval: doc.schedule_sending", "fieldname": "schedule_send", "fieldtype": "Datetime", - "label": "Schedule Send" + "label": "Schedule Send", + "read_only_depends_on": "eval: doc.email_sent" }, { "default": "0", @@ -125,11 +134,32 @@ "fieldtype": "Check", "label": "Send Attachments" }, + { + "fieldname": "content_type", + "fieldtype": "Select", + "label": "Content Type", + "options": "Rich Text\nMarkdown\nHTML" + }, + { + "depends_on": "eval:doc.content_type === 'Markdown'", + "fieldname": "message_md", + "fieldtype": "Markdown Editor", + "label": "Message (Markdown)", + "mandatory_depends_on": "eval:doc.content_type === 'Markdown'" + }, + { + "depends_on": "eval:doc.content_type === 'HTML'", + "fieldname": "message_html", + "fieldtype": "HTML Editor", + "label": "Message (HTML)", + "mandatory_depends_on": "eval:doc.content_type === 'HTML'" + }, { "default": "0", "fieldname": "schedule_sending", "fieldtype": "Check", - "label": "Schedule Sending" + "label": "Schedule Sending", + "read_only_depends_on": "eval: doc.email_sent" } ], "has_web_view": 1, @@ -139,7 +169,7 @@ "is_published_field": "published", "links": [], "max_attachments": 3, - "modified": "2020-08-17 18:11:59.541686", + "modified": "2020-08-24 19:59:37.262500", "modified_by": "Administrator", "module": "Email", "name": "Newsletter", diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 849c21f768..0a0a13a6ce 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -8,12 +8,9 @@ import frappe.utils from frappe import throw, _ from frappe.website.website_generator import WebsiteGenerator from frappe.utils.verified_command import get_signed_params, verify_request -from frappe.utils.background_jobs import enqueue from frappe.email.queue import send from frappe.email.doctype.email_group.email_group import add_subscribers -from frappe.utils import parse_addr, now_datetime -from frappe.utils import validate_email_address - +from frappe.utils import parse_addr, now_datetime, markdown, validate_email_address class Newsletter(WebsiteGenerator): def onload(self): @@ -29,8 +26,8 @@ class Newsletter(WebsiteGenerator): def test_send(self, doctype="Lead"): self.recipients = frappe.utils.split_emails(self.test_email_id) - self.queue_all() - frappe.msgprint(_("Scheduled to send to {0}").format(self.test_email_id)) + self.queue_all(test_email=True) + frappe.msgprint(_("Test email sent to {0}").format(self.test_email_id)) def send_emails(self): """send emails to leads and customers""" @@ -40,21 +37,13 @@ class Newsletter(WebsiteGenerator): self.recipients = self.get_recipients() if self.recipients: - if getattr(frappe.local, "is_ajax", False): - self.validate_send() - # using default queue with a longer timeout as this isn't a scheduled task - enqueue(send_newsletter, queue='default', timeout=6000, event='send_newsletter', - newsletter=self.name) - - else: - self.queue_all() - - frappe.msgprint(_("Scheduled to send to {0} recipients").format(len(self.recipients))) + self.queue_all() + frappe.msgprint(_("Email queued to {0} recipients").format(len(self.recipients))) else: frappe.msgprint(_("Newsletter should have atleast one recipient")) - def queue_all(self): + def queue_all(self, test_email=False): if not self.get("recipients"): # in case it is called via worker self.recipients = self.get_recipients() @@ -80,7 +69,7 @@ class Newsletter(WebsiteGenerator): frappe.throw(_("Unable to find attachment {0}").format(file.name)) send(recipients=self.recipients, sender=sender, - subject=self.subject, message=self.message, + subject=self.subject, message=self.get_message(), reference_doctype=self.doctype, reference_name=self.name, add_unsubscribe_link=self.send_unsubscribe_link, attachments=attachments, unsubscribe_method="/unsubscribe", @@ -90,9 +79,18 @@ class Newsletter(WebsiteGenerator): if not frappe.flags.in_test: frappe.db.auto_commit_on_many_writes = False - self.db_set("email_sent", 1) - self.db_set("schedule_send", now_datetime()) - self.db_set("scheduled_to_send", len(self.recipients)) + if not test_email: + self.db_set("email_sent", 1) + self.db_set("schedule_send", now_datetime()) + self.db_set("scheduled_to_send", len(self.recipients)) + + def get_message(self): + + return { + 'Rich Text': self.message, + 'Markdown': markdown(self.message_md), + 'HTML': self.message_html + }[self.content_type] def get_recipients(self): """Get recipients from Email Group""" diff --git a/frappe/email/doctype/newsletter/newsletter_list.js b/frappe/email/doctype/newsletter/newsletter_list.js index e95d29545d..9ded6148e0 100644 --- a/frappe/email/doctype/newsletter/newsletter_list.js +++ b/frappe/email/doctype/newsletter/newsletter_list.js @@ -1,8 +1,10 @@ frappe.listview_settings['Newsletter'] = { - add_fields: ["subject", "email_sent"], + add_fields: ["subject", "email_sent", "schedule_sending"], get_indicator: function(doc) { - if(doc.email_sent) { + if (doc.email_sent) { return [__("Sent"), "green", "email_sent,=,Yes"]; + } else if (doc.schedule_sending) { + return [__("Scheduled"), "orange", "email_sent,=,No|schedule_sending,=,Yes"]; } else { return [__("Not Sent"), "orange", "email_sent,=,No"]; } diff --git a/frappe/email/doctype/newsletter/test_newsletter.py b/frappe/email/doctype/newsletter/test_newsletter.py index bb339165d3..ee7f123b7e 100644 --- a/frappe/email/doctype/newsletter/test_newsletter.py +++ b/frappe/email/doctype/newsletter/test_newsletter.py @@ -67,6 +67,7 @@ class TestNewsletter(unittest.TestCase): "doctype": "Newsletter", "subject": "_Test Newsletter", "send_from": "Test Sender ", + "content_type": "Rich Text", "message": "Testing my news.", "published": published, "schedule_sending": bool(schedule_send), diff --git a/frappe/patches.txt b/frappe/patches.txt index 9c9fd71661..35389eee43 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -306,3 +306,4 @@ frappe.patches.v13_0.add_toggle_width_in_navbar_settings frappe.patches.v13_0.rename_notification_fields frappe.patches.v13_0.remove_duplicate_navbar_items frappe.patches.v13_0.enable_custom_script +frappe.patches.v13_0.update_newsletter_content_type diff --git a/frappe/patches/v13_0/update_newsletter_content_type.py b/frappe/patches/v13_0/update_newsletter_content_type.py new file mode 100644 index 0000000000..0b32fb49ed --- /dev/null +++ b/frappe/patches/v13_0/update_newsletter_content_type.py @@ -0,0 +1,12 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('email', 'doctype', 'Newsletter') + frappe.db.sql(""" + UPDATE tabNewsletter + SET content_type = 'Rich Text' + """) \ No newline at end of file From fda7999bdd6b67561e7ded71f12e9264f417dd27 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 2 Sep 2020 15:48:49 +0530 Subject: [PATCH 093/375] fix: Send email notification by child table fields --- .../doctype/notification/notification.js | 22 ++++++++++++++----- .../doctype/notification/notification.py | 15 +++++++++---- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/frappe/email/doctype/notification/notification.js b/frappe/email/doctype/notification/notification.js index 454514f922..5eae016573 100644 --- a/frappe/email/doctype/notification/notification.js +++ b/frappe/email/doctype/notification/notification.js @@ -19,9 +19,11 @@ frappe.notification = { } frappe.model.with_doctype(frm.doc.document_type, function() { - let get_select_options = function(df) { + let get_select_options = function(df, parent_field) { + let select_value = parent_field ? df.fieldname + ',' + parent_field : df.fieldname; + return { - value: df.fieldname, + value: select_value, label: df.fieldname + ' (' + __(df.label) + ')' }; }; @@ -59,9 +61,19 @@ frappe.notification = { let receiver_fields = []; if (frm.doc.channel === 'Email') { receiver_fields = $.map(fields, function(d) { - return d.options == 'Email' || - (d.options == 'User' && d.fieldtype == 'Link') - ? get_select_options(d) : null; + + if (d.fieldtype == 'Table') { + let child_fields = frappe.get_doc('DocType', d.options).fields; + return $.map(child_fields, function(df) { + return df.options == 'Email' || + (df.options == 'User' && df.fieldtype == 'Link') + ? get_select_options(df, d.fieldname) : null; + }); + } else { + return d.options == 'Email' || + (d.options == 'User' && d.fieldtype == 'Link') + ? get_select_options(d) : null; + } }); } else if (in_list(['WhatsApp', 'SMS'], frm.doc.channel)) { receiver_fields = $.map(fields, function(d) { diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index cfa3c43ff7..a51afbf7aa 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -235,10 +235,17 @@ def get_context(context): if not frappe.safe_eval(recipient.condition, None, context): continue if recipient.receiver_by_document_field: - email_ids_value = doc.get(recipient.receiver_by_document_field) - if validate_email_address(email_ids_value): - email_ids = email_ids_value.replace(",", "\n") - recipients = recipients + email_ids.split("\n") + fields = recipient.receiver_by_document_field.split(',') + if len(fields) > 1: + for d in doc.get(fields[1]): + email_id = d.get(fields[0]) + if validate_email_address(email_id): + recipients.append(email_id) + else: + email_ids_value = doc.get(fields[0]) + if validate_email_address(email_ids_value): + email_ids = email_ids_value.replace(",", "\n") + recipients = recipients + email_ids.split("\n") if recipient.cc and "{" in recipient.cc: recipient.cc = frappe.render_template(recipient.cc, context) From 07728df16af98858accc34cb339bce6e92ebc26f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 2 Sep 2020 15:49:55 +0530 Subject: [PATCH 094/375] fix: Add test cases --- .../doctype/notification/test_notification.py | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/frappe/email/doctype/notification/test_notification.py b/frappe/email/doctype/notification/test_notification.py index 9bdf09375d..71d46cbdfa 100644 --- a/frappe/email/doctype/notification/test_notification.py +++ b/frappe/email/doctype/notification/test_notification.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe, frappe.utils, frappe.utils.scheduler +from frappe.desk.form import assign_to import unittest test_records = frappe.get_test_records('Notification') @@ -177,3 +178,92 @@ class TestNotification(unittest.TestCase): frappe.db.sql("""delete from `tabUser` where email='test_jinja@example.com'""") frappe.db.sql("""delete from `tabEmail Queue`""") frappe.db.sql("""delete from `tabEmail Queue Recipient`""") + + def test_notification_to_assignee(self): + frappe.set_user("Administrator") + + todo = frappe.new_doc('ToDo') + todo.description = 'Test Notification' + todo.save() + + assign_to.add({ + "assign_to": ["test2@example.com"], + "doctype": todo.doctype, + "name": todo.name, + "description": "Close this Todo" + }) + + assign_to.add({ + "assign_to": ["test1@example.com"], + "doctype": todo.doctype, + "name": todo.name, + "description": "Close this Todo" + }) + + if not frappe.db.exists('Notification', {'name': 'ToDo Status Update'}, 'name'): + notification = frappe.new_doc('Notification') + notification.name = 'ToDo Status Update' + notification.subject = 'ToDo Status Update' + notification.document_type = 'ToDo' + notification.event = 'Value Change' + notification.value_changed = 'status' + notification.send_to_all_assignees = 1 + notification.save() + + #change status of todo + todo.status = 'Closed' + todo.save() + + email_queue = frappe.get_doc('Email Queue', {'reference_doctype': 'ToDo', + 'reference_name': todo.name}) + + self.assertTrue(email_queue) + + recipients = [d.recipient for d in email_queue.recipients] + self.assertTrue('test2@example.com' in recipients) + self.assertTrue('test1@example.com' in recipients) + + def test_notification_by_child_table_field(self): + frappe.set_user("Administrator") + + if not frappe.db.exists('Notification', {'name': 'Contact Status Update'}, 'name'): + notification = frappe.new_doc('Notification') + notification.name = 'Contact Status Update' + notification.subject = 'Contact Status Update' + notification.document_type = 'Contact' + notification.event = 'Value Change' + notification.value_changed = 'status' + notification.message = 'Test Contact Update' + notification.append('recipients', { + 'receiver_by_document_field': 'email_id,email_ids' + }) + notification.save() + + contact = frappe.new_doc('Contact') + contact.first_name = 'John Doe' + contact.status = 'Open' + contact.append('email_ids', { + 'email_id': 'test2@example.com', + 'is_primary': 1 + }) + + contact.append('email_ids', { + 'email_id': 'test1@example.com' + }) + + contact.save() + + #change status of todo + contact.status = 'Replied' + contact.save() + + email_queue = frappe.get_doc('Email Queue', {'reference_doctype': 'Contact', + 'reference_name': contact.name}) + + self.assertTrue(email_queue) + + recipients = [d.recipient for d in email_queue.recipients] + self.assertTrue('test2@example.com' in recipients) + self.assertTrue('test1@example.com' in recipients) + + From c92b5d01660d47f3c145f9f5333ec7c58d019871 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 24 Aug 2020 19:12:26 +0530 Subject: [PATCH 095/375] fix: Handle duration fieldtype during export --- frappe/core/doctype/data_export/exporter.py | 4 ++- frappe/desk/query_report.py | 31 +++++++++++++++++++-- frappe/desk/reportview.py | 27 +++++++++++++++++- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/data_export/exporter.py b/frappe/core/doctype/data_export/exporter.py index e4d2ff2af6..bec8cde7ea 100644 --- a/frappe/core/doctype/data_export/exporter.py +++ b/frappe/core/doctype/data_export/exporter.py @@ -8,7 +8,7 @@ from frappe import _ import frappe.permissions import re, csv, os from frappe.utils.csvutils import UnicodeWriter -from frappe.utils import cstr, formatdate, format_datetime, parse_json, cint +from frappe.utils import cstr, formatdate, format_datetime, parse_json, cint, format_duration from frappe.core.doctype.data_import_legacy.importer import get_data_keys from six import string_types from frappe.core.doctype.access_log.access_log import make_access_log @@ -330,6 +330,8 @@ class DataExporter: value = formatdate(value) elif fieldtype == "Datetime": value = format_datetime(value) + elif fieldtype == "Duration": + value = format_duration(value, df.hide_days) row[_column_start_end.start + i + 1] = value diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index a1cfd02132..8e1c98c2af 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -8,14 +8,13 @@ import os, json from frappe import _ from frappe.modules import scrub, get_module_path -from frappe.utils import flt, cint, get_html_format, get_url_to_form +from frappe.utils import flt, cint, get_html_format, get_url_to_form, gzip_decompress, format_duration from frappe.model.utils import render_include from frappe.translate import send_translations import frappe.desk.reportview from frappe.permissions import get_role_permissions from six import string_types, iteritems from datetime import timedelta -from frappe.utils import gzip_decompress from frappe.core.utils import ljust_list def get_report_doc(report_name): @@ -83,6 +82,8 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None) if cint(report.add_total_row) and result and not skip_total_row: result = add_total_row(result, columns) + result = handle_duration_fieldtype_values(columns, result) + return { "result": result, "columns": columns, @@ -266,6 +267,32 @@ def get_columns_from_dict(columns, result): return reordered_result + +def handle_duration_fieldtype_values(columns, result, meta=None): + for i, col in enumerate(columns): + fieldtype, fieldname = None, None + if isinstance(col, string_types): + col = col.split(":") + if len(col) > 1: + if col[1]: + fieldtype = col[1] + if "/" in fieldtype: + fieldtype, options = fieldtype.split("/") + else: + fieldtype = "Data" + else: + fieldtype = col.get("fieldtype") + fieldname = col.get("fieldname") + + if fieldtype == "Duration": + for entry in range(0, len(result)): + val_in_seconds = result[entry][i] + if val_in_seconds: + duration_val = format_duration(val_in_seconds) + result[entry][i] = duration_val + return result + + def get_prepared_report_result(report, filters, dn="", user=None): latest_report_data = {} doc = None diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 6102be61ce..340e447f19 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -11,7 +11,7 @@ from frappe.model.db_query import DatabaseQuery from frappe import _ from six import string_types, StringIO from frappe.core.doctype.access_log.access_log import make_access_log -from frappe.utils import cstr +from frappe.utils import cstr, format_duration @frappe.whitelist() @@ -166,6 +166,8 @@ def export_query(): for i, row in enumerate(ret): data.append([i+1] + list(row)) + data = handle_duration_fieldtype_values(doctype, data, db_query.fields) + if file_format_type == "CSV": # convert to csv @@ -235,6 +237,29 @@ def get_labels(fields, doctype): return labels +def handle_duration_fieldtype_values(doctype, data, fields): + for field in fields: + key = field.split(" as ")[0] + + if key.startswith(('count(', 'sum(', 'avg(')): continue + + if "." in key: + parenttype, fieldname = key.split(".")[0][4:-1], key.split(".")[1].strip("`") + else: + parenttype = doctype + fieldname = field.strip("`") + + df = frappe.get_meta(parenttype).get_field(fieldname) + + if df and df.fieldtype == 'Duration': + index = fields.index(field) + 1 + for i in range(1, len(data)): + val_in_seconds = data[i][index] + if val_in_seconds: + duration_val = format_duration(val_in_seconds, df.hide_days) + data[i][index] = duration_val + return data + @frappe.whitelist() def delete_items(): """delete selected items""" From c0b4532ea510c3e905ab3052d63d867badf87edd Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 24 Aug 2020 19:12:59 +0530 Subject: [PATCH 096/375] fix: Handle duration fieldtype for Data Import --- frappe/core/doctype/data_import/exporter.py | 5 +++ frappe/core/doctype/data_import/importer.py | 4 ++- .../doctype/data_import_legacy/importer.py | 5 +-- frappe/utils/data.py | 33 +++++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/data_import/exporter.py b/frappe/core/doctype/data_import/exporter.py index 3eef6ce016..2ef206f56a 100644 --- a/frappe/core/doctype/data_import/exporter.py +++ b/frappe/core/doctype/data_import/exporter.py @@ -8,6 +8,7 @@ from frappe.model import ( no_value_fields, table_fields as table_fieldtypes, ) +from frappe.utils import flt, format_duration from frappe.utils.csvutils import build_csv_response from frappe.utils.xlsxutils import build_xlsx_response @@ -148,6 +149,10 @@ class Exporter: continue row[i] = doc.get(df.fieldname, "") + if df.fieldtype == "Duration": + value = flt(doc.get(df.fieldname, 0)) + row[i] = format_duration(value, df.hide_days) + return rows def get_data_as_docs(self): diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index 2c10c6b0a5..301c356e45 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -9,7 +9,7 @@ import timeit import json from datetime import datetime, date from frappe import _ -from frappe.utils import cint, flt, update_progress_bar, cstr +from frappe.utils import cint, flt, update_progress_bar, cstr, duration_to_seconds from frappe.utils.csvutils import read_csv_content, get_csv_content_from_google_sheets from frappe.utils.xlsxutils import ( read_xlsx_file_from_attached_file, @@ -692,6 +692,8 @@ class Row: value = flt(value) elif df.fieldtype in ["Date", "Datetime"]: value = self.get_date(value, col) + elif df.fieldtype == "Duration": + value = duration_to_seconds(value) return value diff --git a/frappe/core/doctype/data_import_legacy/importer.py b/frappe/core/doctype/data_import_legacy/importer.py index 5bd0daf32b..f7f196da61 100644 --- a/frappe/core/doctype/data_import_legacy/importer.py +++ b/frappe/core/doctype/data_import_legacy/importer.py @@ -15,7 +15,7 @@ from frappe import _ from frappe.utils.csvutils import getlink from frappe.utils.dateutils import parse_date -from frappe.utils import cint, cstr, flt, getdate, get_datetime, get_url, get_absolute_url +from frappe.utils import cint, cstr, flt, getdate, get_datetime, get_url, get_absolute_url, duration_to_seconds from six import string_types @@ -164,7 +164,8 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, d[fieldname] = get_datetime(_date + " " + _time) else: d[fieldname] = None - + elif df.fieldtype == "Duration": + d[fieldname] = duration_to_seconds(cstr(d[fieldname])) elif fieldtype in ("Image", "Attach Image", "Attach"): # added file to attachments list attachments.append(d[fieldname]) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index fd5c838b57..2a5ab8d5a3 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -346,6 +346,11 @@ def format_datetime(datetime_string, format_string=None): return formatted_datetime def format_duration(seconds, hide_days=False): + """Converts the given duration value in float(seconds) to duration format + + example: converts 12885 to '3h 34m 45s' where 12885 = seconds in float + """ + total_duration = { 'days': math.floor(seconds / (3600 * 24)), 'hours': math.floor(seconds % (3600 * 24) / 3600), @@ -373,6 +378,34 @@ def format_duration(seconds, hide_days=False): return duration +def duration_to_seconds(duration): + """Converts the given duration formatted value to duration value in seconds + + example: converts '3h 34m 45s' to 12885 (value in seconds) + """ + value = 0 + if 'd' in duration: + val = duration.split('d') + days = val[0] + value += cint(days) * 24 * 60 * 60 + duration = val[1] + if 'h' in duration: + val = duration.split('h') + hours = val[0] + value += cint(hours) * 60 * 60 + duration = val[1] + if 'm' in duration: + val = duration.split('m') + mins = val[0] + value += cint(mins) * 60 + duration = val[1] + if 's' in duration: + val = duration.split('s') + secs = val[0] + value += cint(secs) + + return value + def get_weekdays(): return ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] From 6ef4536c697885d1fdd617edafd30ab21733b578 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 2 Sep 2020 16:20:32 +0530 Subject: [PATCH 097/375] fix: Update comment --- frappe/email/doctype/notification/test_notification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/doctype/notification/test_notification.py b/frappe/email/doctype/notification/test_notification.py index 71d46cbdfa..e370bac9e5 100644 --- a/frappe/email/doctype/notification/test_notification.py +++ b/frappe/email/doctype/notification/test_notification.py @@ -253,7 +253,7 @@ class TestNotification(unittest.TestCase): contact.save() - #change status of todo + #change status of contact contact.status = 'Replied' contact.save() From 22a8cc2ddaee2db053dfc7255dc92d72a9454269 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 2 Sep 2020 16:31:02 +0530 Subject: [PATCH 098/375] fix: Uninstall App even if it doesn't exist on bench --- frappe/commands/site.py | 2 +- frappe/installer.py | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index d343d10126..f85a9b2474 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -452,7 +452,7 @@ def uninstall(context, app, dry_run, yes, no_backup, force): try: frappe.init(site=site) frappe.connect() - remove_app(app_name=app, dry_run=dry_run, yes=yes, no_backup=no_backup, force=force) + remove_app(app_name=app, dry_run=dry_run, yes=yes, no_backup=no_backup, force=force, verbose=context.verbose) finally: frappe.destroy() if not context.sites: diff --git a/frappe/installer.py b/frappe/installer.py index 4994646890..53a8d878c7 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -8,7 +8,7 @@ from __future__ import unicode_literals, print_function from six.moves import input -import os, json, subprocess, shutil +import os, json, subprocess, shutil, sys import click import frappe import frappe.database @@ -119,9 +119,12 @@ def remove_from_installed_apps(app_name): if frappe.flags.in_install: post_install() -def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False): +def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False, verbose=True): """Remove app and all linked to the app's module with the app from a site.""" + if not (verbose or dry_run): + sys.stdout = open(os.devnull, "w") + # dont allow uninstall app if not installed unless forced if not force: if app_name not in frappe.get_installed_apps(): @@ -143,11 +146,12 @@ def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False) frappe.flags.in_uninstall = True drop_doctypes = [] - # remove modules, doctypes, roles - for module_name in frappe.get_module_list(app_name): - for doctype in frappe.get_list("DocType", filters={"module": module_name}, - fields=["name", "issingle"]): - print("removing DocType {0}...".format(doctype.name)) + modules = (x.name for x in frappe.get_all("Module Def", filters={"app_name": app_name})) + for module_name in modules: + print("Deleting Module '{0}'".format(module_name)) + + for doctype in frappe.get_list("DocType", filters={"module": module_name}, fields=["name", "issingle"]): + print("* removing DocType '{0}'...".format(doctype.name)) if not dry_run: frappe.delete_doc("DocType", doctype.name) @@ -155,24 +159,22 @@ def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False) if not doctype.issingle: drop_doctypes.append(doctype.name) - linked_doctypes = frappe.get_all("DocField", filters={"fieldtype": "Link", "options": "Module Def"}, fields=['parent']) ordered_doctypes = ["Desk Page", "Report", "Page", "Web Form"] doctypes_with_linked_modules = ordered_doctypes + [doctype.parent for doctype in linked_doctypes if doctype.parent not in ordered_doctypes] for doctype in doctypes_with_linked_modules: for record in frappe.get_list(doctype, filters={"module": module_name}): - print("removing {0} {1}...".format(doctype, record.name)) + print("* removing {0} '{1}'...".format(doctype, record.name)) if not dry_run: frappe.delete_doc(doctype, record.name) - print("removing Module {0}...".format(module_name)) + print("* removing Module Def '{0}'...".format(module_name)) if not dry_run: frappe.delete_doc("Module Def", module_name) - remove_from_installed_apps(app_name) - if not dry_run: + remove_from_installed_apps(app_name) # drop tables after a commit frappe.db.commit() From dbea31943a6f58582c116423f4a170f0b0935f94 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 2 Sep 2020 17:08:00 +0530 Subject: [PATCH 099/375] fix: Add verbosity --- frappe/commands/site.py | 5 +++-- frappe/installer.py | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index f85a9b2474..9de06b3723 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -444,15 +444,16 @@ def remove_from_installed_apps(context, app): @click.option('--dry-run', help='List all doctypes that will be deleted', is_flag=True, default=False) @click.option('--no-backup', help='Do not backup the site', is_flag=True, default=False) @click.option('--force', help='Force remove app from site', is_flag=True, default=False) +@click.option('--verbose', help='Add verbosity', is_flag=True, default=False) @pass_context -def uninstall(context, app, dry_run, yes, no_backup, force): +def uninstall(context, app, dry_run, yes, no_backup, force, verbose): "Remove app and linked modules from site" from frappe.installer import remove_app for site in context.sites: try: frappe.init(site=site) frappe.connect() - remove_app(app_name=app, dry_run=dry_run, yes=yes, no_backup=no_backup, force=force, verbose=context.verbose) + remove_app(app_name=app, dry_run=dry_run, yes=yes, no_backup=no_backup, force=force, verbose=context.verbose or verbose) finally: frappe.destroy() if not context.sites: diff --git a/frappe/installer.py b/frappe/installer.py index 53a8d878c7..d0b6db77d4 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -122,9 +122,6 @@ def remove_from_installed_apps(app_name): def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False, verbose=True): """Remove app and all linked to the app's module with the app from a site.""" - if not (verbose or dry_run): - sys.stdout = open(os.devnull, "w") - # dont allow uninstall app if not installed unless forced if not force: if app_name not in frappe.get_installed_apps(): @@ -138,6 +135,9 @@ def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False, if not confirm: return + if not (verbose or dry_run): + sys.stdout = open(os.devnull, "w") + if not no_backup: from frappe.utils.backups import scheduled_backup print("Backing up...") @@ -181,6 +181,7 @@ def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False, for doctype in set(drop_doctypes): frappe.db.sql("drop table `tab{0}`".format(doctype)) + sys.stdout = sys.__stdout__ click.secho("Uninstalled App {0} from Site {1}".format(app_name, frappe.local.site), fg="green") frappe.flags.in_uninstall = False From 87c06ef5a49a86150680f9102f81f0883a567285 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Wed, 2 Sep 2020 17:19:00 +0530 Subject: [PATCH 100/375] fix: tabs --- frappe/patches/v13_0/update_newsletter_content_type.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/patches/v13_0/update_newsletter_content_type.py b/frappe/patches/v13_0/update_newsletter_content_type.py index 0b32fb49ed..01f4bacd16 100644 --- a/frappe/patches/v13_0/update_newsletter_content_type.py +++ b/frappe/patches/v13_0/update_newsletter_content_type.py @@ -5,8 +5,8 @@ from __future__ import unicode_literals import frappe def execute(): - frappe.reload_doc('email', 'doctype', 'Newsletter') - frappe.db.sql(""" - UPDATE tabNewsletter - SET content_type = 'Rich Text' - """) \ No newline at end of file + frappe.reload_doc('email', 'doctype', 'Newsletter') + frappe.db.sql(""" + UPDATE tabNewsletter + SET content_type = 'Rich Text' + """) \ No newline at end of file From 5c01c7145a75f84474e05ea826dceacd905bea70 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 2 Sep 2020 17:38:21 +0530 Subject: [PATCH 101/375] fix: handle total for duration fieldtype --- frappe/desk/query_report.py | 32 ++------------------------ frappe/public/js/frappe/model/model.js | 2 +- 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 8e1c98c2af..fa1df349c3 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -8,7 +8,7 @@ import os, json from frappe import _ from frappe.modules import scrub, get_module_path -from frappe.utils import flt, cint, get_html_format, get_url_to_form, gzip_decompress, format_duration +from frappe.utils import flt, cint, get_html_format, get_url_to_form, gzip_decompress, format_duration, cstr from frappe.model.utils import render_include from frappe.translate import send_translations import frappe.desk.reportview @@ -82,8 +82,6 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None) if cint(report.add_total_row) and result and not skip_total_row: result = add_total_row(result, columns) - result = handle_duration_fieldtype_values(columns, result) - return { "result": result, "columns": columns, @@ -267,32 +265,6 @@ def get_columns_from_dict(columns, result): return reordered_result - -def handle_duration_fieldtype_values(columns, result, meta=None): - for i, col in enumerate(columns): - fieldtype, fieldname = None, None - if isinstance(col, string_types): - col = col.split(":") - if len(col) > 1: - if col[1]: - fieldtype = col[1] - if "/" in fieldtype: - fieldtype, options = fieldtype.split("/") - else: - fieldtype = "Data" - else: - fieldtype = col.get("fieldtype") - fieldname = col.get("fieldname") - - if fieldtype == "Duration": - for entry in range(0, len(result)): - val_in_seconds = result[entry][i] - if val_in_seconds: - duration_val = format_duration(val_in_seconds) - result[entry][i] = duration_val - return result - - def get_prepared_report_result(report, filters, dn="", user=None): latest_report_data = {} doc = None @@ -454,7 +426,7 @@ def add_total_row(result, columns, meta = None): if i >= len(row): continue cell = row.get(fieldname) if isinstance(row, dict) else row[i] - if fieldtype in ["Currency", "Int", "Float", "Percent"] and flt(cell): + if fieldtype in ["Currency", "Int", "Float", "Percent", "Duration"] and flt(cell): total_row[i] = flt(total_row[i]) + flt(cell) if fieldtype == "Percent" and i not in has_percent: diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index 663850d08c..308d9bd5f8 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -31,7 +31,7 @@ $.extend(frappe.model, { {fieldname:'docstatus', fieldtype:'Int', label:__('Document Status')}, ], - numeric_fieldtypes: ["Int", "Float", "Currency", "Percent"], + numeric_fieldtypes: ["Int", "Float", "Currency", "Percent", "Duration"], std_fields_table: [ {fieldname:'parent', fieldtype:'Data', label:__('Parent')}, From 0888fb48a73d42cd3f2eaae2eb77bbd4a9947ebe Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 2 Sep 2020 19:01:42 +0530 Subject: [PATCH 102/375] feat: validate duration format in data import --- frappe/core/doctype/data_import/importer.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index 301c356e45..5271690527 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -664,6 +664,20 @@ class Row: } ) return + elif df.fieldtype == "Duration": + import re + is_valid_duration = re.match("^(?:(\d+d)?((^|\s)\d+h)?((^|\s)\d+m)?((^|\s)\d+s)?)$", value) + if not is_valid_duration: + self.warnings.append( + { + "row": self.row_number, + "col": col.column_number, + "field": df_as_json(df), + "message": _("Value {0} must be in the valid duration format: d h m s").format( + frappe.bold(value) + ) + } + ) return value From 32e864bb6c55d5f84f3cf7be56294510e8d12b30 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 2 Sep 2020 19:21:56 +0530 Subject: [PATCH 103/375] test: duration fieldtype import --- .../data_import/fixtures/sample_import_file.csv | 10 +++++----- frappe/core/doctype/data_import/test_importer.py | 7 ++++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/frappe/core/doctype/data_import/fixtures/sample_import_file.csv b/frappe/core/doctype/data_import/fixtures/sample_import_file.csv index ef5b96df58..693f400878 100644 --- a/frappe/core/doctype/data_import/fixtures/sample_import_file.csv +++ b/frappe/core/doctype/data_import/fixtures/sample_import_file.csv @@ -1,5 +1,5 @@ -Title ,Description ,Number ,another_number ,ID (Table Field 1) ,Child Title (Table Field 1) ,Child Description (Table Field 1) ,Child 2 Title (Table Field 2) ,Child 2 Date (Table Field 2) ,Child 2 Number (Table Field 2) ,Child Title (Table Field 1 Again) ,Child Date (Table Field 1 Again) ,Child Number (Table Field 1 Again) ,table_field_1_again.child_another_number -Test ,test description ,1 ,2 ,"" ,child title ,child description ,child title ,14-08-2019 ,4 ,child title again ,22-09-2020 ,5 , 7 - , , , , ,child title 2 ,child description 2 ,title child ,30-10-2019 ,5 ,child title again 2 ,22-09-2021 , , -Test 2 ,test description 2 ,1 ,2 , ,child mandatory title , ,title child man , , ,child mandatory again , , , -Test 3 ,test description 3 ,4 ,5 ,"" ,child title asdf ,child description asdf ,child title asdf adsf ,15-08-2019 ,6 ,child title again asdf ,22-09-2022 ,9 , 71 +Title ,Description ,Number ,Duration,another_number ,ID (Table Field 1),Child Title (Table Field 1),Child Description (Table Field 1),Child 2 Title (Table Field 2),Child 2 Date (Table Field 2),Child 2 Number (Table Field 2),Child Title (Table Field 1 Again),Child Date (Table Field 1 Again),Child Number (Table Field 1 Again),table_field_1_again.child_another_number +Test ,test description ,1,3h,2, ,child title ,child description ,child title ,14-08-2019,4,child title again ,22-09-2020,5,7 + , , ,, , ,child title 2,child description 2,title child ,30-10-2019,5,child title again 2,22-09-2021, , +Test 2,test description 2,1,4d 3h,2, ,child mandatory title , ,title child man , , ,child mandatory again , , , +Test 3,test description 3,4,5d 5h 45m,5, ,child title asdf ,child description asdf ,child title asdf adsf ,15-08-2019,6,child title again asdf ,22-09-2022,9,71 \ No newline at end of file diff --git a/frappe/core/doctype/data_import/test_importer.py b/frappe/core/doctype/data_import/test_importer.py index bdadad7890..3b573e64a1 100644 --- a/frappe/core/doctype/data_import/test_importer.py +++ b/frappe/core/doctype/data_import/test_importer.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import unittest import frappe -from frappe.utils import getdate +from frappe.utils import getdate, format_duration doctype_name = 'DocType for Import' @@ -24,6 +24,7 @@ class TestImporter(unittest.TestCase): self.assertEqual(doc1.description, 'test description') self.assertEqual(doc1.number, 1) + self.assertEqual(format_duration(doc1.duration), '3h') self.assertEqual(doc1.table_field_1[0].child_title, 'child title') self.assertEqual(doc1.table_field_1[0].child_description, 'child description') @@ -40,7 +41,10 @@ class TestImporter(unittest.TestCase): self.assertEqual(doc1.table_field_1_again[1].child_date, getdate('2021-09-22')) self.assertEqual(doc2.description, 'test description 2') + self.assertEqual(format_duration(doc2.duration), '4d 3h') + self.assertEqual(doc3.another_number, 5) + self.assertEqual(format_duration(doc3.duration), '5d 5h 45m') def test_data_import_preview(self): import_file = get_import_file('sample_import_file') @@ -146,6 +150,7 @@ def create_doctype_if_not_exists(doctype_name, force=False): {'label': 'Title', 'fieldname': 'title', 'reqd': 1, 'fieldtype': 'Data'}, {'label': 'Description', 'fieldname': 'description', 'fieldtype': 'Small Text'}, {'label': 'Date', 'fieldname': 'date', 'fieldtype': 'Date'}, + {'label': 'Duration', 'fieldname': 'duration', 'fieldtype': 'Duration'}, {'label': 'Number', 'fieldname': 'number', 'fieldtype': 'Int'}, {'label': 'Number', 'fieldname': 'another_number', 'fieldtype': 'Int'}, {'label': 'Table Field 1', 'fieldname': 'table_field_1', 'fieldtype': 'Table', 'options': table_1_name}, From b373f10432cc5be1df2f5922eeae1eaecd227890 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 2 Sep 2020 20:00:36 +0530 Subject: [PATCH 104/375] fix: Add sleep to fix test cases --- frappe/email/doctype/notification/test_notification.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/email/doctype/notification/test_notification.py b/frappe/email/doctype/notification/test_notification.py index e370bac9e5..5b8ceb03d4 100644 --- a/frappe/email/doctype/notification/test_notification.py +++ b/frappe/email/doctype/notification/test_notification.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe, frappe.utils, frappe.utils.scheduler from frappe.desk.form import assign_to import unittest +import time test_records = frappe.get_test_records('Notification') @@ -214,6 +215,8 @@ class TestNotification(unittest.TestCase): todo.status = 'Closed' todo.save() + # adding sleep so that email queue is fetched once its created + time.sleep(10) email_queue = frappe.get_doc('Email Queue', {'reference_doctype': 'ToDo', 'reference_name': todo.name}) @@ -257,6 +260,8 @@ class TestNotification(unittest.TestCase): contact.status = 'Replied' contact.save() + # adding sleep so that email queue is fetched once its created + time.sleep(10) email_queue = frappe.get_doc('Email Queue', {'reference_doctype': 'Contact', 'reference_name': contact.name}) From b65e6588282eb29843b91cb6a868ccb945751f93 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 2 Sep 2020 20:42:43 +0530 Subject: [PATCH 105/375] fix: Move notification creation in setup --- .../doctype/notification/test_notification.py | 58 ++++++++----------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/frappe/email/doctype/notification/test_notification.py b/frappe/email/doctype/notification/test_notification.py index 5b8ceb03d4..45a1587c1a 100644 --- a/frappe/email/doctype/notification/test_notification.py +++ b/frappe/email/doctype/notification/test_notification.py @@ -6,7 +6,6 @@ from __future__ import unicode_literals import frappe, frappe.utils, frappe.utils.scheduler from frappe.desk.form import assign_to import unittest -import time test_records = frappe.get_test_records('Notification') @@ -15,7 +14,31 @@ test_dependencies = ["User"] class TestNotification(unittest.TestCase): def setUp(self): frappe.db.sql("""delete from `tabEmail Queue`""") - frappe.set_user("test1@example.com") + frappe.set_user("test@example.com") + + if not frappe.db.exists('Notification', {'name': 'ToDo Status Update'}, 'name'): + notification = frappe.new_doc('Notification') + notification.name = 'ToDo Status Update' + notification.subject = 'ToDo Status Update' + notification.document_type = 'ToDo' + notification.event = 'Value Change' + notification.value_changed = 'status' + notification.send_to_all_assignees = 1 + notification.save() + + if not frappe.db.exists('Notification', {'name': 'Contact Status Update'}, 'name'): + notification = frappe.new_doc('Notification') + notification.name = 'Contact Status Update' + notification.subject = 'Contact Status Update' + notification.document_type = 'Contact' + notification.event = 'Value Change' + notification.value_changed = 'status' + notification.message = 'Test Contact Update' + notification.append('recipients', { + 'receiver_by_document_field': 'email_id,email_ids' + }) + notification.save() + def tearDown(self): frappe.set_user("Administrator") @@ -181,8 +204,6 @@ class TestNotification(unittest.TestCase): frappe.db.sql("""delete from `tabEmail Queue Recipient`""") def test_notification_to_assignee(self): - frappe.set_user("Administrator") - todo = frappe.new_doc('ToDo') todo.description = 'Test Notification' todo.save() @@ -201,22 +222,10 @@ class TestNotification(unittest.TestCase): "description": "Close this Todo" }) - if not frappe.db.exists('Notification', {'name': 'ToDo Status Update'}, 'name'): - notification = frappe.new_doc('Notification') - notification.name = 'ToDo Status Update' - notification.subject = 'ToDo Status Update' - notification.document_type = 'ToDo' - notification.event = 'Value Change' - notification.value_changed = 'status' - notification.send_to_all_assignees = 1 - notification.save() - #change status of todo todo.status = 'Closed' todo.save() - # adding sleep so that email queue is fetched once its created - time.sleep(10) email_queue = frappe.get_doc('Email Queue', {'reference_doctype': 'ToDo', 'reference_name': todo.name}) @@ -227,21 +236,6 @@ class TestNotification(unittest.TestCase): self.assertTrue('test1@example.com' in recipients) def test_notification_by_child_table_field(self): - frappe.set_user("Administrator") - - if not frappe.db.exists('Notification', {'name': 'Contact Status Update'}, 'name'): - notification = frappe.new_doc('Notification') - notification.name = 'Contact Status Update' - notification.subject = 'Contact Status Update' - notification.document_type = 'Contact' - notification.event = 'Value Change' - notification.value_changed = 'status' - notification.message = 'Test Contact Update' - notification.append('recipients', { - 'receiver_by_document_field': 'email_id,email_ids' - }) - notification.save() - contact = frappe.new_doc('Contact') contact.first_name = 'John Doe' contact.status = 'Open' @@ -260,8 +254,6 @@ class TestNotification(unittest.TestCase): contact.status = 'Replied' contact.save() - # adding sleep so that email queue is fetched once its created - time.sleep(10) email_queue = frappe.get_doc('Email Queue', {'reference_doctype': 'Contact', 'reference_name': contact.name}) From 0bdb4c8b4e00e37a5353561419dff82337fe3f97 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 2 Sep 2020 21:12:26 +0530 Subject: [PATCH 106/375] fix: Linting issues --- frappe/email/doctype/notification/notification.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/doctype/notification/notification.js b/frappe/email/doctype/notification/notification.js index 5eae016573..cb49232906 100644 --- a/frappe/email/doctype/notification/notification.js +++ b/frappe/email/doctype/notification/notification.js @@ -65,7 +65,7 @@ frappe.notification = { if (d.fieldtype == 'Table') { let child_fields = frappe.get_doc('DocType', d.options).fields; return $.map(child_fields, function(df) { - return df.options == 'Email' || + return df.options == 'Email' || (df.options == 'User' && df.fieldtype == 'Link') ? get_select_options(df, d.fieldname) : null; }); From 66934b311fd9a0275252b71f7b43c61f7cc1ae22 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2020 22:18:05 +0530 Subject: [PATCH 107/375] chore(deps): [security] bump bl from 3.0.0 to 3.0.1 (#11403) Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- yarn.lock | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/yarn.lock b/yarn.lock index 41fd1926c2..e5c975c357 100644 --- a/yarn.lock +++ b/yarn.lock @@ -937,9 +937,9 @@ big.js@^3.1.3: integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== bl@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" - integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== + version "3.0.1" + resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.1.tgz#1cbb439299609e419b5a74d7fce2f8b37d8e5c6f" + integrity sha512-jrCW5ZhfQ/Vt07WX1Ngs+yn9BDqPL/gw28S7s9H6QK/gupnizNzJAss5akW20ISgOrbLTlXOOCTJeNUQqruAWQ== dependencies: readable-stream "^3.0.1" @@ -6157,7 +6157,7 @@ readable-stream@1.1.x: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.2.2, readable-stream@~2.3.6: +readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.5, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -6170,19 +6170,6 @@ readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.2.2, readable-stre string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^2.0.1, readable-stream@^2.0.6, readable-stream@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - readable-stream@^3.0.1, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" @@ -6612,11 +6599,16 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.2: version "5.2.0" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" From 9eb903038144e6823f7857d180788a01e33b0da8 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 3 Sep 2020 08:42:42 +0530 Subject: [PATCH 108/375] fix(minor): move get_source_value to data_migration_mapper --- .../data_migration_mapping/data_migration_mapping.py | 8 +++++++- .../doctype/data_migration_run/data_migration_run.py | 3 ++- frappe/utils/data.py | 7 ------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py index e89282885f..b346864f02 100644 --- a/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py +++ b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from frappe.utils import get_source_value class DataMigrationMapping(Document): def get_filters(self): @@ -70,3 +69,10 @@ def get_value_from_fieldname(field_map, fieldname_field, doc): else: value = get_source_value(doc, field_name) return value + +def get_source_value(source, key): + '''Get value from source (object or dict) based on key''' + if isinstance(source, dict): + return source.get(key) + else: + return getattr(source, key) diff --git a/frappe/data_migration/doctype/data_migration_run/data_migration_run.py b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py index b2ce4606f8..473acfb3d0 100644 --- a/frappe/data_migration/doctype/data_migration_run/data_migration_run.py +++ b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py @@ -6,7 +6,8 @@ from __future__ import unicode_literals import frappe, json, math from frappe.model.document import Document from frappe import _ -from frappe.utils import get_source_value, cstr +from frappe.utils import cstr +from frappe.data_migration.doctype.data_migration_mapping.data_migration_mapping import get_source_value class DataMigrationRun(Document): def run(self): diff --git a/frappe/utils/data.py b/frappe/utils/data.py index fd5c838b57..8dad5f3e92 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -1226,13 +1226,6 @@ def md_to_html(markdown_text): return html -def get_source_value(source, key): - '''Get value from source (object or dict) based on key''' - if isinstance(source, dict): - return source.get(key) - else: - return getattr(source, key) - def is_subset(list_a, list_b): '''Returns whether list_a is a subset of list_b''' return len(list(set(list_a) & set(list_b))) == len(list_a) From 4d91d72d1ace10725cb74a146af911e6b6275d45 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 3 Sep 2020 09:00:58 +0530 Subject: [PATCH 109/375] refactor: Client-side get_role_permissions --- frappe/public/js/frappe/form/form.js | 11 +++---- frappe/public/js/frappe/model/perm.js | 43 ++++++++++++--------------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index ff48ad2f60..cae0cb3c4a 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1405,19 +1405,16 @@ frappe.ui.form.Form = class FrappeForm { } set_read_only() { - var perm = []; - var docperms = frappe.perm.get_perm(this.doc.doctype); - for (var i=0, l=docperms.length; i { + return { read: p.read, cancel: p.cancel, share: p.share, print: p.print, email: p.email }; - } - this.perm = perm; + }); } trigger(event, doctype, docname) { diff --git a/frappe/public/js/frappe/model/perm.js b/frappe/public/js/frappe/model/perm.js index f30368bbc3..2a1f52fbfb 100644 --- a/frappe/public/js/frappe/model/perm.js +++ b/frappe/public/js/frappe/model/perm.js @@ -42,7 +42,7 @@ $.extend(frappe.perm, { }, get_perm: (doctype, doc) => { - let perm = [{ read: 0 }]; + let perm = [{ read: 0, permlevel: 0 }]; let meta = frappe.get_doc("DocType", doctype); const user = frappe.session.user; @@ -53,7 +53,7 @@ $.extend(frappe.perm, { if (!meta) return perm; - frappe.perm.build_role_permissions(perm, meta); + perm = frappe.perm.get_role_permissions(meta); if (doc) { // apply user permissions via docinfo (which is processed server-side) @@ -107,35 +107,30 @@ $.extend(frappe.perm, { return perm; }, - build_role_permissions: (perm, meta) => { + get_role_permissions: (meta) => { + let perm = [{ read: 0, permlevel: 0 }]; // Returns a `dict` of evaluated Role Permissions - $.each(meta.permissions || [], (i, p) => { + (meta.permissions || []).forEach(p => { // if user has this role - if (frappe.user_roles.includes(p.role)) { - let permlevel = cint(p.permlevel); - if (!perm[permlevel]) { - perm[permlevel] = {}; - perm[permlevel]["permlevel"] = permlevel - } + let permlevel = cint(p.permlevel); + if (!perm[permlevel]) { + perm[permlevel] = {}; + perm[permlevel]["permlevel"] = permlevel; + } - $.each(frappe.perm.rights, (i, key) => { - perm[permlevel][key] = perm[permlevel][key] || (p[key] || 0); + if (frappe.user_roles.includes(p.role)) { + frappe.perm.rights.forEach(right => { + let value = perm[permlevel][right] || (p[right] || 0); + if (value) { + perm[permlevel][right] = value; + } }); } }); - // remove values with 0 - $.each(perm[0], (key, val) => { - if (!val) { - delete perm[0][key]; - } - }); - - $.each(perm, (i, v) => { - if (v === undefined) { - perm[i] = {}; - } - }); + // fill gaps with empty object + perm = perm.map(p => p || {}); + return perm; }, get_match_rules: (doctype, ptype) => { From 1359c1c8b6aaa2cef6c4fc784ac563a5adfbb206 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 3 Sep 2020 12:35:12 +0530 Subject: [PATCH 110/375] fix: add correct slack webhook url link --- frappe/email/doctype/notification/notification.js | 1 + frappe/email/doctype/notification/notification.json | 4 ++-- .../integrations/doctype/twilio_settings/twilio_settings.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/email/doctype/notification/notification.js b/frappe/email/doctype/notification/notification.js index 454514f922..02fc6b31ca 100644 --- a/frappe/email/doctype/notification/notification.js +++ b/frappe/email/doctype/notification/notification.js @@ -151,6 +151,7 @@ frappe.ui.form.on('Notification', { }, refresh: function(frm) { frappe.notification.setup_fieldname_select(frm); + frappe.notification.setup_example_message(frm); frm.get_field('is_standard').toggle(frappe.boot.developer_mode); frm.trigger('event'); }, diff --git a/frappe/email/doctype/notification/notification.json b/frappe/email/doctype/notification/notification.json index 95f218ad73..1e3c0d5b14 100644 --- a/frappe/email/doctype/notification/notification.json +++ b/frappe/email/doctype/notification/notification.json @@ -66,7 +66,7 @@ }, { "depends_on": "eval:doc.channel=='Slack'", - "description": "To use Slack Channel, add a Slack Webhook URL.", + "description": "To use Slack Channel, add a Slack Webhook URL.", "fieldname": "slack_webhook_url", "fieldtype": "Link", "label": "Slack Channel", @@ -281,7 +281,7 @@ ], "icon": "fa fa-envelope", "links": [], - "modified": "2020-08-11 19:24:35.479373", + "modified": "2020-09-03 08:45:21.289300", "modified_by": "Administrator", "module": "Email", "name": "Notification", diff --git a/frappe/integrations/doctype/twilio_settings/twilio_settings.py b/frappe/integrations/doctype/twilio_settings/twilio_settings.py index 6c698d719a..80c5162987 100644 --- a/frappe/integrations/doctype/twilio_settings/twilio_settings.py +++ b/frappe/integrations/doctype/twilio_settings/twilio_settings.py @@ -11,7 +11,7 @@ from frappe.utils.password import get_decrypted_password from six import string_types class TwilioSettings(Document): - def validate(self): + def on_update(self): self.validate_twilio_credentials() def validate_twilio_credentials(self): From 21d58f3acd15ea45347e0da17c41a9880e00ea7a Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 3 Sep 2020 12:47:00 +0530 Subject: [PATCH 111/375] fix: update message to be more concised --- frappe/email/doctype/notification/notification.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/doctype/notification/notification.js b/frappe/email/doctype/notification/notification.js index 02fc6b31ca..24ebf8d01b 100644 --- a/frappe/email/doctype/notification/notification.js +++ b/frappe/email/doctype/notification/notification.js @@ -87,7 +87,7 @@ frappe.notification = {
Message Example
-Your {{ doc.name }} order of {{ doc.total }} has shipped and should be delivered on {{ doc.date }}. Details : {{doc.customer}}
+Your appointment is coming up on {{ doc.date }} at {{ doc.time }}
 
`; } else if (frm.doc.channel === 'Email') { template = `
Message Example
From 5f51c201773bee983c6f36869eefcfed120e4677 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 3 Sep 2020 13:36:30 +0530 Subject: [PATCH 112/375] fix(reports): handle duration fieldtype during export --- .../doctype/report_column/report_column.json | 4 +-- frappe/desk/query_report.py | 29 +++++++++++++++++++ frappe/public/js/frappe/utils/utils.js | 8 ++++- .../js/frappe/views/reports/query_report.js | 3 ++ 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/report_column/report_column.json b/frappe/core/doctype/report_column/report_column.json index 53b5dff9b6..2e6a22d29a 100644 --- a/frappe/core/doctype/report_column/report_column.json +++ b/frappe/core/doctype/report_column/report_column.json @@ -31,7 +31,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Fieldtype", - "options": "Check\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nInt\nLink\nSelect\nTime", + "options": "Check\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nInt\nLink\nSelect\nTime", "reqd": 1 }, { @@ -48,7 +48,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-08-17 14:32:17.174796", + "modified": "2020-09-03 10:52:03.895817", "modified_by": "Administrator", "module": "Core", "name": "Report Column", diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index fa1df349c3..dc42228f81 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -359,6 +359,7 @@ def export_query(): columns = get_columns_dict(data.columns) from frappe.utils.xlsxutils import make_xlsx + data['result'] = handle_duration_fieldtype_values(data.get('result'), data.get('columns')) xlsx_data = build_xlsx_data(columns, data, visible_idx, include_indentation) xlsx_file = make_xlsx(xlsx_data, "Query Report") @@ -366,6 +367,30 @@ def export_query(): frappe.response['filecontent'] = xlsx_file.getvalue() frappe.response['type'] = 'binary' +def handle_duration_fieldtype_values(result, columns): + for i, col in enumerate(columns): + fieldtype, fieldname = None, None + if isinstance(col, string_types): + col = col.split(":") + if len(col) > 1: + if col[1]: + fieldtype = col[1] + if "/" in fieldtype: + fieldtype, options = fieldtype.split("/") + else: + fieldtype = "Data" + else: + fieldtype = col.get("fieldtype") + fieldname = col.get("fieldname") + + if fieldtype == "Duration": + for entry in range(0, len(result)): + val_in_seconds = result[entry][i] + if val_in_seconds: + duration_val = format_duration(val_in_seconds) + result[entry][i] = duration_val + + return result def build_xlsx_data(columns, data, visible_idx, include_indentation): result = [[]] @@ -385,7 +410,11 @@ def build_xlsx_data(columns, data, visible_idx, include_indentation): for idx in range(len(data.columns)): label = columns[idx]["label"] fieldname = columns[idx]["fieldname"] + fieldtype = columns[idx]["fieldtype"] cell_value = row.get(fieldname, row.get(label, "")) + if fieldtype == "Duration": + cell_value = format_duration(value) + if cint(include_indentation) and 'indent' in row and idx == 0: cell_value = (' ' * cint(row['indent'])) + cell_value row_data.append(cell_value) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 38c22c9c9f..d64be06869 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -824,8 +824,14 @@ Object.assign(frappe.utils, { }; }, - get_formatted_duration(value, duration_options) { + get_formatted_duration(value, duration_options=null) { let duration = ''; + if (!duration_options) { + duration_options = { + hide_days: 0, + hide_seconds: 0 + } + } if (value) { let total_duration = frappe.utils.seconds_to_duration(value, duration_options); diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 1bec65e460..0817d8cfa5 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1322,6 +1322,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { return row .slice(standard_column_count) .map((cell, i) => { + if (cell.column.fieldtype === "Duration") { + cell.content = frappe.utils.get_formatted_duration(cell.content) + } if (include_indentation && i===0) { cell.content = ' '.repeat(row.meta.indent) + (cell.content || ''); } From ba9c7bdc09b658238391c913b45922b06340a57c Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 3 Sep 2020 13:55:55 +0530 Subject: [PATCH 113/375] fix: Also check if name is set, since empty object is truthy --- frappe/public/js/frappe/form/controls/data.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index 355f35891a..bbf9a89072 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -61,7 +61,7 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ // check if name exists frappe.db.get_value(this.doctype, this.$input.val(), 'name', (val) => { - if (val) { + if (val && val.name) { this.set_description(__('{0} already exists. Select another name', [val.name])); } }, From 14af05037abe8e24c66a1907eda37f15d09e2890 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 17 Aug 2020 13:15:00 +0530 Subject: [PATCH 114/375] feat: Section with Collapsible Content --- frappe/public/scss/page-builder.scss | 38 ++++++++++++++ .../section_with_collapsible_content.html | 21 ++++++++ .../section_with_collapsible_content.json | 51 +++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 frappe/website/web_template/section_with_collapsible_content/section_with_collapsible_content.html create mode 100644 frappe/website/web_template/section_with_collapsible_content/section_with_collapsible_content.json diff --git a/frappe/public/scss/page-builder.scss b/frappe/public/scss/page-builder.scss index f6446a9ba9..28db0b5a85 100644 --- a/frappe/public/scss/page-builder.scss +++ b/frappe/public/scss/page-builder.scss @@ -409,3 +409,41 @@ } } } + + +/* Section with Collapsible Content */ + +.collapsible-items { + max-width: 46rem; +} + +.collapsible-item { + padding: 1.75rem 0; + + &:not(:last-child) { + border-bottom: 1px solid $border-color; + } +} + +.collapsible-item a { + text-decoration: none; +} + +.collapsible-item h3 { + margin-bottom: 0; +} + +.collapsible-content { + margin-top: 1rem; + margin-bottom: 0; + color: $gray-700; +} + +.section-with-collapsible-content.align-center { + .section-title, .section-description { + text-align: center; + } + .section-description, .collapsible-items { + margin: 0 auto; + } +} diff --git a/frappe/website/web_template/section_with_collapsible_content/section_with_collapsible_content.html b/frappe/website/web_template/section_with_collapsible_content/section_with_collapsible_content.html new file mode 100644 index 0000000000..2b86e7f992 --- /dev/null +++ b/frappe/website/web_template/section_with_collapsible_content/section_with_collapsible_content.html @@ -0,0 +1,21 @@ +
+

{{ title }}

+ {%- if subtitle -%} +

{{ subtitle }}

+ {%- endif -%} + +
+ {%- for item in items -%} +
+ {%- set collapse_id = 'id-' + frappe.utils.generate_hash('Collapse', 12) -%} + +
+ {{ frappe.utils.md_to_html(item.content) }} +
+
+ {%- endfor -%} +
+
diff --git a/frappe/website/web_template/section_with_collapsible_content/section_with_collapsible_content.json b/frappe/website/web_template/section_with_collapsible_content/section_with_collapsible_content.json new file mode 100644 index 0000000000..f35b8d793e --- /dev/null +++ b/frappe/website/web_template/section_with_collapsible_content/section_with_collapsible_content.json @@ -0,0 +1,51 @@ +{ + "creation": "2020-08-07 16:27:38.265089", + "docstatus": 0, + "doctype": "Web Template", + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "reqd": 0 + }, + { + "fieldname": "subtitle", + "fieldtype": "Data", + "label": "Subtitle", + "reqd": 0 + }, + { + "fieldname": "align", + "fieldtype": "Select", + "label": "Align", + "options": "Left\nCenter", + "reqd": 0 + }, + { + "fieldname": "items", + "fieldtype": "Table Break", + "label": "Items", + "reqd": 0 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "reqd": 0 + }, + { + "fieldname": "content", + "fieldtype": "Markdown Editor", + "label": "Content", + "reqd": 0 + } + ], + "idx": 0, + "modified": "2020-08-13 15:51:23.728803", + "modified_by": "Administrator", + "name": "Section with Collapsible Content", + "owner": "Administrator", + "standard": 1, + "template": "" +} \ No newline at end of file From c0734921de5d30e335c9dedd6282dbcfff321abe Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 17 Aug 2020 13:23:03 +0530 Subject: [PATCH 115/375] feat: Section with Image align center --- frappe/public/scss/page-builder.scss | 14 +++++++++++-- .../section_with_image.html | 16 +++++++------- .../section_with_image.json | 21 ++++++++++++++----- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/frappe/public/scss/page-builder.scss b/frappe/public/scss/page-builder.scss index 28db0b5a85..81c06420bc 100644 --- a/frappe/public/scss/page-builder.scss +++ b/frappe/public/scss/page-builder.scss @@ -1,6 +1,6 @@ .hero-content { .btn-primary { - margin-top: 1rem; + margin-top: 1rem; margin-right: 0.5rem; @include media-breakpoint-up(lg) { @@ -35,6 +35,15 @@ } } +.section-with-image.align-center { + text-align: center; + + .section-description, .section-image { + margin-left: auto; + margin-right: auto; + } +} + .section-image { margin-top: 2rem; border-radius: 0.75rem; @@ -444,6 +453,7 @@ text-align: center; } .section-description, .collapsible-items { - margin: 0 auto; + margin-left: auto; + margin-right: auto; } } diff --git a/frappe/website/web_template/section_with_image/section_with_image.html b/frappe/website/web_template/section_with_image/section_with_image.html index ffa47d089e..cfd98064ac 100644 --- a/frappe/website/web_template/section_with_image/section_with_image.html +++ b/frappe/website/web_template/section_with_image/section_with_image.html @@ -1,8 +1,10 @@ -

{{ title }}

-

{{ subtitle }}

+
+

{{ title }}

+

{{ subtitle }}

-{{ frappe.render_template('templates/includes/image_with_blur.html', { - "src": image, - "alt": image_description, - "class": "section-image" -}) }} + {{ frappe.render_template('templates/includes/image_with_blur.html', { + "src": image, + "alt": image_description, + "class": "section-image" + }) }} +
diff --git a/frappe/website/web_template/section_with_image/section_with_image.json b/frappe/website/web_template/section_with_image/section_with_image.json index 5f610e5e2f..46169a8cc3 100644 --- a/frappe/website/web_template/section_with_image/section_with_image.json +++ b/frappe/website/web_template/section_with_image/section_with_image.json @@ -6,26 +6,37 @@ { "fieldname": "title", "fieldtype": "Data", - "label": "Title" + "label": "Title", + "reqd": 0 }, { "fieldname": "subtitle", "fieldtype": "Small Text", - "label": "Subtitle" + "label": "Subtitle", + "reqd": 0 }, { "fieldname": "image", "fieldtype": "Attach Image", - "label": "Image" + "label": "Image", + "reqd": 0 }, { "fieldname": "image_description", "fieldtype": "Data", - "label": "Image Description" + "label": "Image Description", + "reqd": 0 + }, + { + "fieldname": "align", + "fieldtype": "Select", + "label": "Align", + "options": "Left\nCenter", + "reqd": 0 } ], "idx": 0, - "modified": "2020-04-17 19:31:33.474017", + "modified": "2020-08-06 16:08:12.005764", "modified_by": "Administrator", "name": "Section with Image", "owner": "Administrator", From 40a0c69255b7a3db0f5f1a8807ac90e576aaffb8 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 17 Aug 2020 17:00:26 +0530 Subject: [PATCH 116/375] feat: Footer - Split footer in files - Footer grouped links styling - Put footer logo and extension in one row - Delete unused footer_items.html - Uncheck Right when adding Footer Items in Website Settings --- frappe/public/scss/footer.scss | 77 +++++++++++++++++++ frappe/public/scss/website.scss | 63 +-------------- frappe/templates/includes/footer/footer.html | 44 ++--------- .../includes/footer/footer_grouped_links.html | 56 ++++++++------ .../includes/footer/footer_info.html | 19 +++++ .../includes/footer/footer_items.html | 28 ------- .../includes/footer/footer_links.html | 8 +- .../footer/footer_logo_extension.html | 16 ++++ .../website_settings/website_settings.js | 6 +- 9 files changed, 158 insertions(+), 159 deletions(-) create mode 100644 frappe/public/scss/footer.scss create mode 100644 frappe/templates/includes/footer/footer_info.html delete mode 100644 frappe/templates/includes/footer/footer_items.html create mode 100644 frappe/templates/includes/footer/footer_logo_extension.html diff --git a/frappe/public/scss/footer.scss b/frappe/public/scss/footer.scss new file mode 100644 index 0000000000..9214907fbb --- /dev/null +++ b/frappe/public/scss/footer.scss @@ -0,0 +1,77 @@ +.web-footer { + padding: 5rem 0; + min-height: 140px; +} + +.footer-logo { + min-width: 5rem; + height: 1.5rem; + object-fit: contain; + object-position: left; +} + +.footer-child-item { + margin-top: 0.5rem; +} + +.footer-link, .footer-child-item a { + font-size: $font-size-sm; + font-weight: 500; + color: $gray-700; + + &:hover { + color: $primary; + text-decoration: none; + } +} + +.footer-col-left, .footer-col-right { + padding-top: 0.8rem; + padding-bottom: 1rem; + line-height: 2; + + &:empty { + padding: 0; + } +} + +.footer-col-right { + @include media-breakpoint-up(sm) { + text-align: right; + } +} + +.footer-col-left .footer-link { + margin-right: 1rem; +} + +.footer-col-right .footer-link { + margin-right: 1rem; + @include media-breakpoint-up(sm) { + margin-right: 0; + margin-left: 1rem; + } +} + +.footer-group { + margin-top: 2rem; +} + +.footer-group-label { + color: $text-muted; + font-size: $font-size-sm; + margin-bottom: 0.5rem; +} + +.footer-group-links { + display: flex; + flex-direction: column; + flex-wrap: wrap; + max-height: 10rem; +} + +.footer-info { + border-top: 1px solid $border-color; + color: $text-muted; + font-size: $font-size-sm; +} diff --git a/frappe/public/scss/website.scss b/frappe/public/scss/website.scss index e64c090ea8..59bbe4f19d 100644 --- a/frappe/public/scss/website.scss +++ b/frappe/public/scss/website.scss @@ -12,6 +12,7 @@ @import 'portal'; @import 'search'; @import 'doc'; +@import 'footer'; .ql-editor.read-mode { padding: 0; @@ -162,68 +163,6 @@ a.card { color: #d1d8dd !important; } -// footer - -.web-footer { - padding: 5rem 0; - min-height: 140px; -} - -.footer-logo { - width: 5rem; - height: 2rem; - object-fit: contain; - object-position: left; -} - -.footer-link, .footer-child-item a { - font-weight: 500; - color: $gray-700; - - &:hover { - color: $primary; - text-decoration: none; - } -} - -.footer-col-left, .footer-col-right { - padding-top: 0.8rem; - padding-bottom: 1rem; - line-height: 2; -} - -.footer-col-right { - @include media-breakpoint-up(sm) { - text-align: right; - } -} - -.footer-col-left .footer-link { - margin-right: 1rem; -} - -.footer-col-right .footer-link { - margin-right: 1rem; - @include media-breakpoint-up(sm) { - margin-right: 0; - margin-left: 1rem; - } -} - -.footer-group-label { - color: $text-muted; -} - -.footer-parent-item { - margin-bottom: 0.5rem; -} - -.footer-info { - border-top: 1px solid $border-color; - color: $text-muted; - font-size: $font-size-sm; -} - .no-underline { text-decoration: none !important; } diff --git a/frappe/templates/includes/footer/footer.html b/frappe/templates/includes/footer/footer.html index 671e928d32..2016c7e3d9 100644 --- a/frappe/templates/includes/footer/footer.html +++ b/frappe/templates/includes/footer/footer.html @@ -1,46 +1,12 @@
- {%- if footer_logo -%} -
- -
- {%- endif -%} -
-
- {% if footer_items -%} -
- {% include ["templates/includes/footer/footer_grouped_links.html", "templates/includes/footer/footer_items.html"] %} -
- {% endif %} -
+ {% include "templates/includes/footer/footer_logo_extension.html" %} -
- {% block extension %} - {% include "templates/includes/footer/footer_extension.html" %} - {% endblock %} -
-
+ {% if footer_items -%} + {% include "templates/includes/footer/footer_grouped_links.html" %} + {% endif %} {% include "templates/includes/footer/footer_links.html" %} - - + {% include "templates/includes/footer/footer_info.html" %}
diff --git a/frappe/templates/includes/footer/footer_grouped_links.html b/frappe/templates/includes/footer/footer_grouped_links.html index 6e20c51279..22cdb10824 100644 --- a/frappe/templates/includes/footer/footer_grouped_links.html +++ b/frappe/templates/includes/footer/footer_grouped_links.html @@ -1,28 +1,34 @@ -{% for page in footer_items if page.child_items %} -