From 0f8e164e8e1f3a6ca9a8c119822d5cadb106326d Mon Sep 17 00:00:00 2001 From: David Angulo Date: Thu, 25 Mar 2021 19:38:56 -0600 Subject: [PATCH 01/89] feat: Add 'Automatic' option for desk theme --- frappe/core/doctype/user/user.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 747ace5de6..10e9e63d0e 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -594,7 +594,7 @@ "fieldname": "desk_theme", "fieldtype": "Select", "label": "Desk Theme", - "options": "Light\nDark" + "options": "Light\nDark\nAutomatic" }, { "fieldname": "module_profile", From bfce0ca52f4d6695955c4ae950b2ab1d50fde5c1 Mon Sep 17 00:00:00 2001 From: David Angulo Date: Thu, 25 Mar 2021 19:39:15 -0600 Subject: [PATCH 02/89] feat: Add desk_theme to bootinfo --- frappe/sessions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/sessions.py b/frappe/sessions.py index 3babf1db12..bb54418a17 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -159,6 +159,8 @@ def get(): bootinfo["setup_complete"] = cint(frappe.db.get_single_value('System Settings', 'setup_complete')) bootinfo["is_first_startup"] = cint(frappe.db.get_single_value('System Settings', 'is_first_startup')) + bootinfo['desk_theme'] = frappe.db.get_value("User", frappe.session.user, "desk_theme") or 'Light' + return bootinfo def get_csrf_token(): From 72126a8645e0112c4ce5925d4e54efcc534483fe Mon Sep 17 00:00:00 2001 From: David Angulo Date: Thu, 25 Mar 2021 19:41:19 -0600 Subject: [PATCH 03/89] feat: Change default desk theme behaviour --- frappe/www/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/www/app.py b/frappe/www/app.py index 6088c413dc..656ebbe45c 100644 --- a/frappe/www/app.py +++ b/frappe/www/app.py @@ -46,7 +46,7 @@ def get_context(context): "include_css": hooks["app_include_css"], "sounds": hooks["sounds"], "boot": boot if context.get("for_mobile") else boot_json, - "desk_theme": desk_theme or "Light", + "desk_theme": desk_theme if not desk_theme == 'Automatic' else 'Light', "csrf_token": csrf_token, "google_analytics_id": frappe.conf.get("google_analytics_id"), "google_analytics_anonymize_ip": frappe.conf.get("google_analytics_anonymize_ip"), From 1100180576a3560774f6b7f210479e87ae43297d Mon Sep 17 00:00:00 2001 From: David Angulo Date: Thu, 25 Mar 2021 19:43:51 -0600 Subject: [PATCH 04/89] feat: Add listener to system theme change, to update desk theme accordingly --- frappe/public/js/frappe/desk.js | 6 +++++ frappe/public/js/frappe/ui/theme_switcher.js | 26 +++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 250d308b7e..8094c0261b 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -63,6 +63,12 @@ frappe.Application = Class.extend({ } }); + if(frappe.boot.desk_theme == 'Automatic') { + frappe.ui.add_system_theme_switch_listener(); + const startup_theme = frappe.ui.dark_theme_media_query.matches ? 'dark' : 'light'; + frappe.ui.toggle_theme(startup_theme); + } + this.set_rtl(); // page container diff --git a/frappe/public/js/frappe/ui/theme_switcher.js b/frappe/public/js/frappe/ui/theme_switcher.js index 317198bca5..d78dd6455c 100644 --- a/frappe/public/js/frappe/ui/theme_switcher.js +++ b/frappe/public/js/frappe/ui/theme_switcher.js @@ -82,11 +82,15 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher { return preview; } - toggle_theme(theme) { + toggle_theme(theme, save_preferences=true) { this.current_theme = theme.toLowerCase(); document.documentElement.setAttribute("data-theme", this.current_theme); frappe.show_alert("Theme Changed", 3); + if(!save_preferences) { + return; + } + frappe.xcall("frappe.core.doctype.user.user.switch_theme", { theme: toTitle(theme) }); @@ -99,3 +103,23 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher { this.dialog.hide(); } }; + +frappe.ui.add_system_theme_switch_listener = function() { + const toggle_theme = frappe.ui.toggle_theme; + + frappe.ui.dark_theme_media_query.addEventListener('change', function(e) { + if (e.matches) { + toggle_theme('dark'); + return; + } + + toggle_theme('light'); + }) +} + +frappe.ui.dark_theme_media_query = window.matchMedia("(prefers-color-scheme: dark)"); + +frappe.ui.toggle_theme = function(theme) { + const theme_switcher = new frappe.ui.ThemeSwitcher(); + theme_switcher.toggle_theme(theme, false); +} \ No newline at end of file From c1fd70b997484797a6f3d464ab4ab807c08fc935 Mon Sep 17 00:00:00 2001 From: David Angulo Date: Thu, 25 Mar 2021 20:51:58 -0600 Subject: [PATCH 05/89] feat: Add options to ThemeSwitcher.toggle_theme to configure if we want to show an alert and save preferences on change --- frappe/public/js/frappe/ui/theme_switcher.js | 22 ++++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/frappe/public/js/frappe/ui/theme_switcher.js b/frappe/public/js/frappe/ui/theme_switcher.js index d78dd6455c..fb43c1fae0 100644 --- a/frappe/public/js/frappe/ui/theme_switcher.js +++ b/frappe/public/js/frappe/ui/theme_switcher.js @@ -82,18 +82,19 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher { return preview; } - toggle_theme(theme, save_preferences=true) { + toggle_theme(theme, options = {save_preferences:true, show_alert:true}) { this.current_theme = theme.toLowerCase(); document.documentElement.setAttribute("data-theme", this.current_theme); - frappe.show_alert("Theme Changed", 3); - if(!save_preferences) { - return; + if (options && options.show_alert) { + frappe.show_alert("Theme Changed", 3); + } + + if(options && options.save_preferences) { + frappe.xcall("frappe.core.doctype.user.user.switch_theme", { + theme: toTitle(theme) + }); } - - frappe.xcall("frappe.core.doctype.user.user.switch_theme", { - theme: toTitle(theme) - }); } show() { this.dialog.show(); @@ -121,5 +122,8 @@ frappe.ui.dark_theme_media_query = window.matchMedia("(prefers-color-scheme: dar frappe.ui.toggle_theme = function(theme) { const theme_switcher = new frappe.ui.ThemeSwitcher(); - theme_switcher.toggle_theme(theme, false); + theme_switcher.toggle_theme(theme, { + save_preferences: false, + show_alert: false + }); } \ No newline at end of file From a1579db65dde95520705100176dd7ddff637e5a9 Mon Sep 17 00:00:00 2001 From: David Angulo Date: Thu, 25 Mar 2021 21:08:05 -0600 Subject: [PATCH 06/89] fix: fix sider validation --- frappe/public/js/frappe/desk.js | 2 +- frappe/public/js/frappe/ui/theme_switcher.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 8094c0261b..682f730d3c 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -63,7 +63,7 @@ frappe.Application = Class.extend({ } }); - if(frappe.boot.desk_theme == 'Automatic') { + if (frappe.boot.desk_theme == 'Automatic') { frappe.ui.add_system_theme_switch_listener(); const startup_theme = frappe.ui.dark_theme_media_query.matches ? 'dark' : 'light'; frappe.ui.toggle_theme(startup_theme); diff --git a/frappe/public/js/frappe/ui/theme_switcher.js b/frappe/public/js/frappe/ui/theme_switcher.js index fb43c1fae0..56263824dd 100644 --- a/frappe/public/js/frappe/ui/theme_switcher.js +++ b/frappe/public/js/frappe/ui/theme_switcher.js @@ -82,7 +82,7 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher { return preview; } - toggle_theme(theme, options = {save_preferences:true, show_alert:true}) { + toggle_theme(theme, options = { save_preferences:true, show_alert:true }) { this.current_theme = theme.toLowerCase(); document.documentElement.setAttribute("data-theme", this.current_theme); @@ -90,7 +90,7 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher { frappe.show_alert("Theme Changed", 3); } - if(options && options.save_preferences) { + if (options && options.save_preferences) { frappe.xcall("frappe.core.doctype.user.user.switch_theme", { theme: toTitle(theme) }); @@ -115,8 +115,8 @@ frappe.ui.add_system_theme_switch_listener = function() { } toggle_theme('light'); - }) -} + }); +}; frappe.ui.dark_theme_media_query = window.matchMedia("(prefers-color-scheme: dark)"); @@ -126,4 +126,4 @@ frappe.ui.toggle_theme = function(theme) { save_preferences: false, show_alert: false }); -} \ No newline at end of file +}; \ No newline at end of file From 2f32c4f19698c1031fa2fa83dcd3fd8b19051b4f Mon Sep 17 00:00:00 2001 From: David Angulo Date: Fri, 26 Mar 2021 10:01:18 -0600 Subject: [PATCH 07/89] fix: fix sider validation --- frappe/public/js/frappe/ui/theme_switcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/theme_switcher.js b/frappe/public/js/frappe/ui/theme_switcher.js index 56263824dd..fbf6575bc4 100644 --- a/frappe/public/js/frappe/ui/theme_switcher.js +++ b/frappe/public/js/frappe/ui/theme_switcher.js @@ -82,7 +82,7 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher { return preview; } - toggle_theme(theme, options = { save_preferences:true, show_alert:true }) { + toggle_theme(theme, options = { save_preferences: true, show_alert: true }) { this.current_theme = theme.toLowerCase(); document.documentElement.setAttribute("data-theme", this.current_theme); From e00eb958367fc7567cf1a51f0370e64a57b8f95c Mon Sep 17 00:00:00 2001 From: David Angulo Date: Tue, 6 Apr 2021 10:35:56 -0500 Subject: [PATCH 08/89] fix: Update modified timestamp so the update will pickup the changes --- frappe/core/doctype/user/user.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 10e9e63d0e..4d8d907ee6 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -669,7 +669,7 @@ } ], "max_attachments": 5, - "modified": "2021-02-01 16:11:06.037543", + "modified": "2021-04-06 16:11:06.037543", "modified_by": "Administrator", "module": "Core", "name": "User", From d328b65122aecdd1490251b671f5dc203539b848 Mon Sep 17 00:00:00 2001 From: mhbu50 Date: Sat, 24 Jul 2021 21:37:15 +0300 Subject: [PATCH 09/89] fix: Translate Strings --- frappe/templates/print_formats/standard_macros.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/templates/print_formats/standard_macros.html b/frappe/templates/print_formats/standard_macros.html index f8dc6c370c..a6238b65d2 100644 --- a/frappe/templates/print_formats/standard_macros.html +++ b/frappe/templates/print_formats/standard_macros.html @@ -192,9 +192,9 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}" {% else %} From 64c18a31870665b1d4e04b1b257a8bfe3db2f675 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Tue, 26 Oct 2021 16:06:32 +0530 Subject: [PATCH 10/89] feat: allow more print page size options --- frappe/__init__.py | 11 +- .../print_settings/print_settings.json | 16 ++- .../doctype/print_settings/print_settings.py | 15 ++- .../public/js/frappe/list/bulk_operations.js | 119 +++++++++++------- frappe/public/js/frappe/model/meta.js | 9 ++ frappe/utils/pdf.py | 19 ++- frappe/utils/print_format.py | 9 +- 7 files changed, 140 insertions(+), 58 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index c8245b0bf0..3d3acd99e2 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1512,8 +1512,8 @@ def format(*args, **kwargs): import frappe.utils.formatters return frappe.utils.formatters.format_value(*args, **kwargs) -def get_print(doctype=None, name=None, print_format=None, style=None, - html=None, as_pdf=False, doc=None, output=None, no_letterhead=0, password=None): +def get_print(doctype=None, name=None, print_format=None, style=None, html=None, + as_pdf=False, doc=None, output=None, no_letterhead=0, password=None, pdf_options=None): """Get Print Format for given document. :param doctype: DocType of document. @@ -1532,15 +1532,16 @@ def get_print(doctype=None, name=None, print_format=None, style=None, local.form_dict.doc = doc local.form_dict.no_letterhead = no_letterhead - options = None + if not pdf_options: + pdf_options = {} if password: - options = {'password': password} + pdf_options['password'] = password if not html: html = get_response_content("printview") if as_pdf: - return get_pdf(html, output = output, options = options) + return get_pdf(html, options=pdf_options, output=output) else: return html diff --git a/frappe/printing/doctype/print_settings/print_settings.json b/frappe/printing/doctype/print_settings/print_settings.json index babbae248d..f45de7637d 100644 --- a/frappe/printing/doctype/print_settings/print_settings.json +++ b/frappe/printing/doctype/print_settings/print_settings.json @@ -10,6 +10,8 @@ "repeat_header_footer", "column_break_4", "pdf_page_size", + "pdf_page_height", + "pdf_page_width", "view_link_in_email", "with_letterhead", "allow_print_for_draft", @@ -56,7 +58,7 @@ "fieldname": "pdf_page_size", "fieldtype": "Select", "label": "PDF Page Size", - "options": "A4\nLetter" + "options": "A0\nA1\nA2\nA3\nA4\nA5\nA6\nA7\nA8\nA9\nB0\nB1\nB2\nB3\nB4\nB5\nB6\nB7\nB8\nB9\nB10\nC5E\nComm10E\nDLE\nExecutive\nFolio\nLedger\nLegal\nLetter\nTabloid\nCustom" }, { "fieldname": "view_link_in_email", @@ -156,6 +158,18 @@ "fieldname": "font_size", "fieldtype": "Float", "label": "Font Size" + }, + { + "depends_on": "eval:doc.pdf_page_size == \"Custom\"", + "fieldname": "pdf_page_height", + "fieldtype": "Float", + "label": "PDF Page Height (in mm)" + }, + { + "depends_on": "eval:doc.pdf_page_size == \"Custom\"", + "fieldname": "pdf_page_width", + "fieldtype": "Float", + "label": "PDF Page Width (in mm)" } ], "icon": "fa fa-cog", diff --git a/frappe/printing/doctype/print_settings/print_settings.py b/frappe/printing/doctype/print_settings/print_settings.py index ff00317cf8..5eb8d8b215 100644 --- a/frappe/printing/doctype/print_settings/print_settings.py +++ b/frappe/printing/doctype/print_settings/print_settings.py @@ -8,14 +8,23 @@ from frappe.utils import cint from frappe.model.document import Document + class PrintSettings(Document): + def validate(self): + if self.pdf_page_size == "Custom" and ( + not self.pdf_page_height or not self.pdf_page_width + ): + frappe.throw(_("Page height and width cannot be zero")) + def on_update(self): frappe.clear_cache() + @frappe.whitelist() def is_print_server_enabled(): - if not hasattr(frappe.local, 'enable_print_server'): - frappe.local.enable_print_server = cint(frappe.db.get_single_value('Print Settings', - 'enable_print_server')) + if not hasattr(frappe.local, "enable_print_server"): + frappe.local.enable_print_server = cint( + frappe.db.get_single_value("Print Settings", "enable_print_server") + ) return frappe.local.enable_print_server diff --git a/frappe/public/js/frappe/list/bulk_operations.js b/frappe/public/js/frappe/list/bulk_operations.js index ee6e6d753c..94ec9d4e67 100644 --- a/frappe/public/js/frappe/list/bulk_operations.js +++ b/frappe/public/js/frappe/list/bulk_operations.js @@ -24,51 +24,84 @@ export default class BulkOperations { return; } - if (valid_docs.length > 0) { - const dialog = new frappe.ui.Dialog({ - title: __('Print Documents'), - fields: [ - { - 'fieldtype': 'Select', - 'label': __('Letter Head'), - 'fieldname': 'letter_sel', - 'default': __('No Letterhead'), - options: this.get_letterhead_options() - }, - { - 'fieldtype': 'Select', - 'label': __('Print Format'), - 'fieldname': 'print_sel', - options: frappe.meta.get_print_formats(this.doctype) - } - ] - }); - - dialog.set_primary_action(__('Print'), args => { - if (!args) return; - const default_print_format = frappe.get_meta(this.doctype).default_print_format; - const with_letterhead = args.letter_sel == __("No Letterhead") ? 0 : 1; - const print_format = args.print_sel ? args.print_sel : default_print_format; - const json_string = JSON.stringify(valid_docs); - const letterhead = args.letter_sel; - const w = window.open('/api/method/frappe.utils.print_format.download_multi_pdf?' + - 'doctype=' + encodeURIComponent(this.doctype) + - '&name=' + encodeURIComponent(json_string) + - '&format=' + encodeURIComponent(print_format) + - '&no_letterhead=' + (with_letterhead ? '0' : '1') + - '&letterhead=' + encodeURIComponent(letterhead) - ); - - if (!w) { - frappe.msgprint(__('Please enable pop-ups')); - return; - } - }); - - dialog.show(); - } else { + if (valid_docs.length === 0) { frappe.msgprint(__('Select atleast 1 record for printing')); + return; } + + const dialog = new frappe.ui.Dialog({ + title: __('Print Documents'), + fields: [{ + fieldtype: 'Select', + label: __('Letter Head'), + fieldname: 'letter_sel', + default: __('No Letterhead'), + options: this.get_letterhead_options() + }, + { + fieldtype: 'Select', + label: __('Print Format'), + fieldname: 'print_sel', + options: frappe.meta.get_print_formats(this.doctype) + }, + { + fieldtype: 'Select', + label: __('Page Size'), + fieldname: 'page_size', + options: frappe.meta.get_print_sizes(), + default: print_settings.pdf_page_size + }, + { + fieldtype: 'Float', + label: __('Page Height (in mm)'), + fieldname: 'page_height', + depends_on: 'eval:doc.page_size == "Custom"', + default: print_settings.pdf_page_height + }, + { + fieldtype: 'Float', + label: __('Page Width (in mm)'), + fieldname: 'page_width', + depends_on: 'eval:doc.page_size == "Custom"', + default: print_settings.pdf_page_width + }] + }); + + dialog.set_primary_action(__('Print'), args => { + if (!args) return; + const default_print_format = frappe.get_meta(this.doctype).default_print_format; + const with_letterhead = args.letter_sel == __("No Letterhead") ? 0 : 1; + const print_format = args.print_sel ? args.print_sel : default_print_format; + const json_string = JSON.stringify(valid_docs); + const letterhead = args.letter_sel; + + let pdf_options; + if (args.page_size === "Custom") { + if (args.page_height === 0 || args.page_width === 0) { + frappe.throw(__('Page height and width cannot be zero')); + } + pdf_options = JSON.stringify({ "page-height": args.page_height, "page-width": args.page_width }); + } else { + pdf_options = JSON.stringify({ "page-size": args.page_size }); + } + + const w = window.open( + '/api/method/frappe.utils.print_format.download_multi_pdf?' + + 'doctype=' + encodeURIComponent(this.doctype) + + '&name=' + encodeURIComponent(json_string) + + '&format=' + encodeURIComponent(print_format) + + '&no_letterhead=' + (with_letterhead ? '0' : '1') + + '&letterhead=' + encodeURIComponent(letterhead) + + '&options=' + encodeURIComponent(pdf_options) + ); + + if (!w) { + frappe.msgprint(__('Please enable pop-ups')); + return; + } + }); + + dialog.show(); } get_letterhead_options () { diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index 6ee9084adc..3c9ddc4d96 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -192,6 +192,15 @@ $.extend(frappe.meta, { } }, + get_print_sizes: function() { + return [ + "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", + "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", + "C5E", "Comm10E", "DLE", "Executive", "Folio", "Ledger", "Legal", + "Letter", "Tabloid", "Custom" + ]; + }, + get_print_formats: function(doctype) { var print_format_list = ["Standard"]; var default_print_format = locals.DocType[doctype].default_print_format; diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index 90bb4f63de..9a7c0889b5 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -95,7 +95,7 @@ def prepare_options(html, options): 'quiet': None, # 'no-outline': None, 'encoding': "UTF-8", - #'load-error-handling': 'ignore' + # 'load-error-handling': 'ignore' }) if not options.get("margin-right"): @@ -111,8 +111,21 @@ def prepare_options(html, options): options.update(get_cookie_options()) # page size - if not options.get("page-size"): - options['page-size'] = frappe.db.get_single_value("Print Settings", "pdf_page_size") or "A4" + pdf_page_size = ( + options.get("page-size") + or frappe.db.get_single_value("Print Settings", "pdf_page_size") + or "A4" + ) + + if pdf_page_size == "Custom": + options["page-height"] = options.get("page-height") or frappe.db.get_single_value( + "Print Settings", "pdf_page_height" + ) + options["page-width"] = options.get("page-width") or frappe.db.get_single_value( + "Print Settings", "pdf_page_width" + ) + else: + options["page-size"] = pdf_page_size return html, options diff --git a/frappe/utils/print_format.py b/frappe/utils/print_format.py index 6dfa3a350b..06f15ced27 100644 --- a/frappe/utils/print_format.py +++ b/frappe/utils/print_format.py @@ -11,7 +11,7 @@ base_template_path = "www/printview.html" standard_format = "templates/print_formats/standard.html" @frappe.whitelist() -def download_multi_pdf(doctype, name, format=None, no_letterhead=0): +def download_multi_pdf(doctype, name, format=None, no_letterhead=False, options=None): """ Concatenate multiple docs as PDF . @@ -54,18 +54,21 @@ def download_multi_pdf(doctype, name, format=None, no_letterhead=0): import json output = PdfFileWriter() + if isinstance(options, str): + options = json.loads(options) + if not isinstance(doctype, dict): result = json.loads(name) # Concatenating pdf files for i, ss in enumerate(result): - output = frappe.get_print(doctype, ss, format, as_pdf = True, output = output, no_letterhead=no_letterhead) + output = frappe.get_print(doctype, ss, format, as_pdf=True, output=output, no_letterhead=no_letterhead, pdf_options=options) frappe.local.response.filename = "{doctype}.pdf".format(doctype=doctype.replace(" ", "-").replace("/", "-")) else: for doctype_name in doctype: for doc_name in doctype[doctype_name]: try: - output = frappe.get_print(doctype_name, doc_name, format, as_pdf = True, output = output, no_letterhead=no_letterhead) + output = frappe.get_print(doctype_name, doc_name, format, as_pdf=True, output=output, no_letterhead=no_letterhead, pdf_options=options) except Exception: frappe.log_error("Permission Error on doc {} of doctype {}".format(doc_name, doctype_name)) frappe.local.response.filename = "{}.pdf".format(name) From 632e3acc01b3ea2965af684f6c383dc07fc72363 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 28 Oct 2021 20:49:34 +0530 Subject: [PATCH 11/89] refactor: New Feedback & Comment Design for Blog Post --- frappe/core/doctype/feedback/feedback.json | 39 ++-- frappe/public/icons/timeless/symbol-defs.svg | 20 ++ frappe/public/scss/website/blog.scss | 99 +++++++++ frappe/templates/includes/avatar_macro.html | 6 +- .../templates/includes/comments/comment.html | 30 ++- .../templates/includes/comments/comments.html | 138 ++++++++----- .../templates/includes/comments/comments.py | 3 + .../templates/includes/feedback/feedback.html | 195 +++++------------- .../templates/includes/feedback/feedback.py | 47 ++--- frappe/website/doctype/blog_post/blog_post.py | 30 ++- .../blog_post/templates/blog_post.html | 16 +- .../doctype/blog_settings/blog_settings.json | 26 ++- .../doctype/blog_settings/blog_settings.py | 5 +- 13 files changed, 368 insertions(+), 286 deletions(-) diff --git a/frappe/core/doctype/feedback/feedback.json b/frappe/core/doctype/feedback/feedback.json index b77e7a6677..998278e89e 100644 --- a/frappe/core/doctype/feedback/feedback.json +++ b/frappe/core/doctype/feedback/feedback.json @@ -8,34 +8,15 @@ "reference_doctype", "reference_name", "column_break_3", - "rating", - "ip_address", - "section_break_6", - "feedback" + "like", + "dislike", + "ip_address" ], "fields": [ { "fieldname": "column_break_3", "fieldtype": "Column Break" }, - { - "fieldname": "rating", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Rating", - "precision": "1", - "reqd": 1 - }, - { - "fieldname": "section_break_6", - "fieldtype": "Section Break" - }, - { - "fieldname": "feedback", - "fieldtype": "Small Text", - "label": "Feedback", - "reqd": 1 - }, { "fieldname": "reference_doctype", "fieldtype": "Select", @@ -57,11 +38,23 @@ "hidden": 1, "label": "IP Address", "read_only": 1 + }, + { + "default": "0", + "fieldname": "like", + "fieldtype": "Check", + "label": "Like" + }, + { + "default": "0", + "fieldname": "dislike", + "fieldtype": "Check", + "label": "Dislike" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-06-23 12:45:42.045696", + "modified": "2021-10-27 22:33:58.362849", "modified_by": "Administrator", "module": "Core", "name": "Feedback", diff --git a/frappe/public/icons/timeless/symbol-defs.svg b/frappe/public/icons/timeless/symbol-defs.svg index b878f713e9..593303e790 100644 --- a/frappe/public/icons/timeless/symbol-defs.svg +++ b/frappe/public/icons/timeless/symbol-defs.svg @@ -712,4 +712,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/frappe/public/scss/website/blog.scss b/frappe/public/scss/website/blog.scss index ea82efed21..c842554514 100644 --- a/frappe/public/scss/website/blog.scss +++ b/frappe/public/scss/website/blog.scss @@ -1,3 +1,7 @@ +:root { + --comment-timeline-height: 60px; +} + .blog-list { display: flex; flex-wrap: wrap; @@ -96,4 +100,99 @@ margin-top: 3rem; } } + + + .feedback-button svg, .comment-count svg { + vertical-align: sub; + } + + .blog-feedback { + .feedback-header { + display: flex; + justify-content: space-between; + + .like-dislike { + display: flex; + + .like-icon, .dislike-icon { + cursor: pointer; + } + } + } + } + + .blog-comments { + .add-comment-section { + .login-required { + padding: var(--padding-sm); + border-radius: var(--border-radius-sm); + box-shadow: var(--card-shadow); + } + + .new-comment { + display: flex; + padding: var(--padding-lg); + box-shadow: var(--card-shadow); + border-radius: var(--border-radius-md); + + .new-comment-fields { + flex: 1; + + .form-label { + font-weight: var(--text-bold); + } + + .comment-text-area textarea { + resize: none; + } + + @media (min-width: 576px) { + .comment-by { + padding-right: 0px !important; + padding-bottom: 0px !important; + } + } + } + } + } + + + #comment-list { + position: relative; + padding-left: var(--padding-xl); + padding-top: var(--padding-lg); + + &::before { + content: " "; + top: 0; + position: absolute; + bottom: var(--comment-timeline-height); + border-left: 1px solid var(--dark-border-color); + } + + .comment-row { + position: relative; + + .comment-avatar { + position: absolute; + top: 10px; + left: -17px; + border-radius: var(--border-radius-full); + box-shadow: var(--shadow-xs); + } + + .comment-content { + box-shadow: var(--card-shadow); + border-radius: var(--border-radius-md); + padding: var(--padding-md); + margin-left: 35px; + flex: 1; + + .content p{ + margin-bottom: 0px; + } + } + } + } + } } diff --git a/frappe/templates/includes/avatar_macro.html b/frappe/templates/includes/avatar_macro.html index 6983477f9c..b652b573b3 100644 --- a/frappe/templates/includes/avatar_macro.html +++ b/frappe/templates/includes/avatar_macro.html @@ -1,6 +1,6 @@ -{% macro avatar(user_id=None, css_style=None) %} +{% macro avatar(user_id=None, css_style=None, size="avatar-small") %} {% set user_info = frappe.utils.get_user_info_for_avatar(user_id) %} - + {% if user_info.image %} - {{ frappe.utils.get_abbr(user_info.name) }} + {{ frappe.utils.get_abbr(user_info.name).upper() }} {% endif %} diff --git a/frappe/templates/includes/comments/comment.html b/frappe/templates/includes/comments/comment.html index 08a2b79ee6..3ce5224db2 100644 --- a/frappe/templates/includes/comments/comment.html +++ b/frappe/templates/includes/comments/comment.html @@ -1,18 +1,14 @@ -{% from "frappe/templates/includes/macros.html" import square_image_with_fallback %} +{% from "frappe/templates/includes/avatar_macro.html" import avatar %} -
- {{ square_image_with_fallback(src=frappe.get_gravatar(comment.comment_email or comment.sender), size='extra-small', alt=comment.sender_full_name, class='align-self-start mr-4') }} -
-
- - {{ comment.sender_full_name or comment.comment_by }} - - - {{ comment.creation | global_date_format }} - -
-
- {{ comment.content | markdown }} -
-
-
+
+
+ {{ avatar(user_id=(comment.comment_email or comment.sender), size='avatar-medium') }} +
+
+
+ {{ comment.sender_full_name or comment.comment_by }} + {{ frappe.utils.pretty_date(comment.creation) }} +
+
{{ comment.content | markdown }}
+
+
\ No newline at end of file diff --git a/frappe/templates/includes/comments/comments.html b/frappe/templates/includes/comments/comments.html index 935fa5367e..c0bb33fd38 100644 --- a/frappe/templates/includes/comments/comments.html +++ b/frappe/templates/includes/comments/comments.html @@ -1,55 +1,82 @@
- {% if comment_text %} -
{{ comment_text }}
- {% endif %} {% if not comment_list %} -
-

{{ _("No comments yet. Start a new discussion.") }}

-
+
+

{{ _("No comments yet. Start a new discussion.") }}

+
+ {% endif %} + + {% if not is_communication %} +
+ +
+
+
+
+ +
+
Add a comment
+ +
Ctrl+Enter to add comment
+
+ +
+
+
+
+
{% endif %}
{% for comment in comment_list %} -
{% include "templates/includes/comments/comment.html" %} -
{% endfor %}
-{% if not is_communication %} -
- - -
- {{ _("Add Comment") }} - -
-
-{% endif %} diff --git a/frappe/templates/includes/comments/comments.py b/frappe/templates/includes/comments/comments.py index 3bba388ac2..e352c7368b 100644 --- a/frappe/templates/includes/comments/comments.py +++ b/frappe/templates/includes/comments/comments.py @@ -3,11 +3,14 @@ import frappe import re from frappe.website.utils import clear_cache +from frappe.rate_limiter import rate_limit from frappe.utils import add_to_date, now +from frappe.website.doctype.blog_settings.blog_settings import get_comment_limit from frappe import _ @frappe.whitelist(allow_guest=True) +@rate_limit(key='reference_name', limit=get_comment_limit, seconds=60*60) def add_comment(comment, comment_email, comment_by, reference_doctype, reference_name, route): doc = frappe.get_doc(reference_doctype, reference_name) diff --git a/frappe/templates/includes/feedback/feedback.html b/frappe/templates/includes/feedback/feedback.html index f180fa5e42..fd509f9db3 100644 --- a/frappe/templates/includes/feedback/feedback.html +++ b/frappe/templates/includes/feedback/feedback.html @@ -1,160 +1,77 @@ -
- {% endif %} - {% if not disable_feedback %} -
- {% include 'templates/includes/feedback/feedback.html' %} -
- {% endif %}
-
-
- {{_("Page Missing or Moved")}} + +
+ +
+

+ There's nothing here +

+
+ The page you are looking for have gone missing. +
+
-

{{_("The page you are looking for is missing. This could be because it is moved or there is a typo in the link.")}}

-
-

{{ _("Error Code: {0}").format('404') }}

- + {% endblock %} \ No newline at end of file From 9ee1613d05ea989607e1d59f0085f7251a33ca58 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 22 Nov 2021 14:36:17 +0530 Subject: [PATCH 73/89] fix: Load assets load required for geolocation control --- .../js/frappe/form/controls/geolocation.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/frappe/public/js/frappe/form/controls/geolocation.js b/frappe/public/js/frappe/form/controls/geolocation.js index 080a1cbb48..280eac3941 100644 --- a/frappe/public/js/frappe/form/controls/geolocation.js +++ b/frappe/public/js/frappe/form/controls/geolocation.js @@ -3,6 +3,11 @@ frappe.provide('frappe.utils.utils'); frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.form.ControlData { static horizontal = false + async make() { + await frappe.require(this.required_libs); + super.make(); + } + make_wrapper() { // Create the elements for map area super.make_wrapper(); @@ -196,4 +201,17 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f this.editableLayers.removeLayer(l); }); } + + get required_libs() { + return [ + "assets/frappe/js/lib/leaflet/easy-button.css", + "assets/frappe/js/lib/leaflet/L.Control.Locate.css", + "assets/frappe/js/lib/leaflet/leaflet.draw.css", + "assets/frappe/js/lib/leaflet/leaflet.css", + "assets/frappe/js/lib/leaflet/leaflet.js", + "assets/frappe/js/lib/leaflet/easy-button.js", + "assets/frappe/js/lib/leaflet/leaflet.draw.js", + "assets/frappe/js/lib/leaflet/L.Control.Locate.js", + ]; + } }; From 57614e9a6ee366d90fb48c66e7f808c785d113d0 Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Mon, 22 Nov 2021 18:49:56 +0530 Subject: [PATCH 74/89] fix: Give select permission to 'All' for workflow state (#15044) --- frappe/workflow/doctype/workflow_state/workflow_state.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/workflow/doctype/workflow_state/workflow_state.json b/frappe/workflow/doctype/workflow_state/workflow_state.json index a08f713bb1..be5804f390 100644 --- a/frappe/workflow/doctype/workflow_state/workflow_state.json +++ b/frappe/workflow/doctype/workflow_state/workflow_state.json @@ -112,7 +112,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-02-20 13:33:44.011509", + "modified": "2021-11-22 17:56:40.495232", "modified_by": "Administrator", "module": "Workflow", "name": "Workflow State", @@ -137,6 +137,10 @@ "share": 1, "submit": 0, "write": 1 + }, + { + "role": "All", + "select": 1 } ], "quick_entry": 1, From 6612bb2667064c0d652ab9ca65146909b618909b Mon Sep 17 00:00:00 2001 From: Summayya Date: Mon, 22 Nov 2021 22:16:42 +0530 Subject: [PATCH 75/89] refactor: make user facing strings translatable --- frappe/www/404.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/www/404.html b/frappe/www/404.html index 534805eebb..c03b5d3e96 100644 --- a/frappe/www/404.html +++ b/frappe/www/404.html @@ -12,10 +12,10 @@

- There's nothing here + {{ _("There's nothing here") }}

- The page you are looking for have gone missing. + {{ _("The page you are looking for have gone missing.") }}
From fca5f86a08af426305c1d1dabfe7b7858d87b3a7 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 23 Nov 2021 07:10:46 +0530 Subject: [PATCH 76/89] fix: Remove edit icon from quick entry to avoid browser crash --- frappe/public/js/frappe/form/quick_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/quick_entry.js b/frappe/public/js/frappe/form/quick_entry.js index 2cf2ac38a9..e412b1dec8 100644 --- a/frappe/public/js/frappe/form/quick_entry.js +++ b/frappe/public/js/frappe/form/quick_entry.js @@ -267,7 +267,7 @@ frappe.ui.form.QuickEntryForm = class QuickEntryForm { render_edit_in_full_page_link() { var me = this; this.dialog.add_custom_action( - `${frappe.utils.icon('edit', 'xs')} ${__("Edit in full page")}`, + `${__("Edit in full page")}`, () => me.open_doc(true) ); } From cb36bfaf045d7f4d18e85df07963567ec9074788 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Tue, 23 Nov 2021 08:43:35 +0530 Subject: [PATCH 77/89] style: Fix formatting --- frappe/public/scss/website/error-state.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/scss/website/error-state.scss b/frappe/public/scss/website/error-state.scss index f3fcc140d6..6f88009ecb 100644 --- a/frappe/public/scss/website/error-state.scss +++ b/frappe/public/scss/website/error-state.scss @@ -1,7 +1,7 @@ .error-page { text-align: center; - .img-404{ + .img-404 { width: 40%; margin: var(--margin-2xl) auto; From 5cfe3ad94624fd8d391a3972df12b3516df9db20 Mon Sep 17 00:00:00 2001 From: ritwik Date: Tue, 23 Nov 2021 09:41:06 +0530 Subject: [PATCH 78/89] feat: add no-git option in make-app command and boilerplate generation (#15028) --- frappe/commands/utils.py | 5 +- frappe/tests/test_boilerplate.py | 93 ++++++++++++++++++++++---------- frappe/tests/test_commands.py | 35 +++++++++++- frappe/utils/boilerplate.py | 18 ++++--- 4 files changed, 111 insertions(+), 40 deletions(-) diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index e311b8db6a..41b607b192 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -791,10 +791,11 @@ def request(context, args=None, path=None): @click.command('make-app') @click.argument('destination') @click.argument('app_name') -def make_app(destination, app_name): +@click.option('--no-git', is_flag=True, default=False, help='Do not initialize git repository for the app') +def make_app(destination, app_name, no_git=False): "Creates a boilerplate app" from frappe.utils.boilerplate import make_boilerplate - make_boilerplate(destination, app_name) + make_boilerplate(destination, app_name, no_git=no_git) @click.command('set-config') diff --git a/frappe/tests/test_boilerplate.py b/frappe/tests/test_boilerplate.py index 259d5a9194..6a9544b2e9 100644 --- a/frappe/tests/test_boilerplate.py +++ b/frappe/tests/test_boilerplate.py @@ -11,14 +11,7 @@ from frappe.utils.boilerplate import make_boilerplate class TestBoilerPlate(unittest.TestCase): @classmethod - def tearDownClass(cls): - - bench_path = frappe.utils.get_bench_path() - test_app_dir = os.path.join(bench_path, "apps", "test_app") - if os.path.exists(test_app_dir): - shutil.rmtree(test_app_dir) - - def test_create_app(self): + def setUpClass(cls): title = "Test App" description = "This app's description contains 'single quotes' and \"double quotes\"." publisher = "Test Publisher" @@ -27,7 +20,7 @@ class TestBoilerPlate(unittest.TestCase): color = "" app_license = "MIT" - user_input = [ + cls.user_input = [ title, description, publisher, @@ -37,22 +30,21 @@ class TestBoilerPlate(unittest.TestCase): app_license, ] - bench_path = frappe.utils.get_bench_path() - apps_dir = os.path.join(bench_path, "apps") - app_name = "test_app" + cls.bench_path = frappe.utils.get_bench_path() + cls.apps_dir = os.path.join(cls.bench_path, "apps") + cls.app_names = ("test_app", "test_app_no_git") + cls.gitignore_file = ".gitignore" + cls.git_folder = ".git" - with patch("builtins.input", side_effect=user_input): - make_boilerplate(apps_dir, app_name) - - root_paths = [ - app_name, + cls.root_paths = [ "requirements.txt", "README.md", "setup.py", "license.txt", - ".git", + cls.git_folder, + cls.gitignore_file ] - paths_inside_app = [ + cls.paths_inside_app = [ "__init__.py", "hooks.py", "patches.txt", @@ -60,25 +52,68 @@ class TestBoilerPlate(unittest.TestCase): "www", "config", "modules.txt", - "public", - app_name, + "public" ] - new_app_dir = os.path.join(bench_path, apps_dir, app_name) + @classmethod + def tearDownClass(cls): + test_app_dirs = (os.path.join(cls.bench_path, "apps", app_name) for app_name in cls.app_names) + for test_app_dir in test_app_dirs: + if os.path.exists(test_app_dir): + shutil.rmtree(test_app_dir) + def test_create_app(self): + with patch("builtins.input", side_effect=self.user_input): + make_boilerplate(self.apps_dir, self.app_names[0]) + + new_app_dir = os.path.join(self.bench_path, self.apps_dir, self.app_names[0]) + + paths = self.get_paths(new_app_dir, self.app_names[0]) + for path in paths: + self.assertTrue( + os.path.exists(path), + msg=f"{path} should exist in {self.app_names[0]} app" + ) + + self.check_parsable_python_files(new_app_dir) + + def test_create_app_without_git_init(self): + with patch("builtins.input", side_effect=self.user_input): + make_boilerplate(self.apps_dir, self.app_names[1], no_git=True) + + new_app_dir = os.path.join(self.apps_dir, self.app_names[1]) + + paths = self.get_paths(new_app_dir, self.app_names[1]) + for path in paths: + if os.path.basename(path) in (self.git_folder, self.gitignore_file): + self.assertFalse( + os.path.exists(path), + msg=f"{path} shouldn't exist in {self.app_names[1]} app" + ) + else: + self.assertTrue( + os.path.exists(path), + msg=f"{path} should exist in {self.app_names[1]} app" + ) + + self.check_parsable_python_files(new_app_dir) + + def get_paths(self, app_dir, app_name): all_paths = list() - for path in root_paths: - all_paths.append(os.path.join(new_app_dir, path)) + for path in self.root_paths: + all_paths.append(os.path.join(app_dir, path)) - for path in paths_inside_app: - all_paths.append(os.path.join(new_app_dir, app_name, path)) + all_paths.append(os.path.join(app_dir, app_name)) - for path in all_paths: - self.assertTrue(os.path.exists(path), msg=f"{path} should exist in new app") + for path in self.paths_inside_app: + all_paths.append(os.path.join(app_dir, app_name, path)) + return all_paths + + def check_parsable_python_files(self, app_dir): # check if python files are parsable - python_files = glob.glob(new_app_dir + "**/*.py", recursive=True) + python_files = glob.glob(app_dir + "**/*.py", recursive=True) for python_file in python_files: with open(python_file) as p: diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index c048e23949..94389cd7a3 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -5,6 +5,7 @@ import gzip import json import os import shlex +import shutil import subprocess import sys import unittest @@ -102,14 +103,24 @@ def exists_in_backup(doctypes, file): class BaseTestCommands(unittest.TestCase): def execute(self, command, kwargs=None): site = {"site": frappe.local.site} + cmd_input = None if kwargs: + cmd_input = kwargs.get("cmd_input", None) + if cmd_input: + if not isinstance(cmd_input, bytes): + raise Exception( + f"The input should be of type bytes, not {type(cmd_input).__name__}" + ) + + del kwargs["cmd_input"] kwargs.update(site) else: kwargs = site + self.command = " ".join(command.split()).format(**kwargs) print("{0}$ {1}{2}".format(color.silver, self.command, color.nc)) command = shlex.split(self.command) - self._proc = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + self._proc = subprocess.run(command, input=cmd_input, stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.stdout = clean(self._proc.stdout) self.stderr = clean(self._proc.stderr) self.returncode = clean(self._proc.returncode) @@ -466,6 +477,28 @@ class TestCommands(BaseTestCommands): self.assertEqual(self.returncode, 0) self.assertEqual(check_password('Administrator', 'test2'), 'Administrator') + def test_make_app(self): + user_input = [ + b"Test App", # title + b"This app's description contains 'single quotes' and \"double quotes\".", # description + b"Test Publisher", # publisher + b"example@example.org", # email + b"", # icon + b"", # color + b"MIT" # app_license + ] + app_name = "testapp0" + apps_path = os.path.join(frappe.utils.get_bench_path(), "apps") + test_app_path = os.path.join(apps_path, app_name) + self.execute(f"bench make-app {apps_path} {app_name}", {"cmd_input": b'\n'.join(user_input)}) + self.assertEqual(self.returncode, 0) + self.assertTrue( + os.path.exists(test_app_path) + ) + + # cleanup + shutil.rmtree(test_app_path) + class RemoveAppUnitTests(unittest.TestCase): def test_delete_modules(self): diff --git a/frappe/utils/boilerplate.py b/frappe/utils/boilerplate.py index d0dd1669b4..91f7dbb2f8 100755 --- a/frappe/utils/boilerplate.py +++ b/frappe/utils/boilerplate.py @@ -3,7 +3,7 @@ import frappe, os, re, git from frappe.utils import touch_file, cstr -def make_boilerplate(dest, app_name): +def make_boilerplate(dest, app_name, no_git=False): if not os.path.exists(dest): print("Destination directory does not exist") return @@ -63,9 +63,6 @@ def make_boilerplate(dest, app_name): with open(os.path.join(dest, hooks.app_name, "MANIFEST.in"), "w") as f: f.write(frappe.as_unicode(manifest_template.format(**hooks))) - with open(os.path.join(dest, hooks.app_name, ".gitignore"), "w") as f: - f.write(frappe.as_unicode(gitignore_template.format(app_name = hooks.app_name))) - with open(os.path.join(dest, hooks.app_name, "requirements.txt"), "w") as f: f.write("# frappe -- https://github.com/frappe/frappe is installed via 'bench init'") @@ -98,11 +95,16 @@ def make_boilerplate(dest, app_name): with open(os.path.join(dest, hooks.app_name, hooks.app_name, "config", "docs.py"), "w") as f: f.write(frappe.as_unicode(docs_template.format(**hooks))) - # initialize git repository app_directory = os.path.join(dest, hooks.app_name) - app_repo = git.Repo.init(app_directory) - app_repo.git.add(A=True) - app_repo.index.commit("feat: Initialize App") + + if not no_git: + with open(os.path.join(dest, hooks.app_name, ".gitignore"), "w") as f: + f.write(frappe.as_unicode(gitignore_template.format(app_name = hooks.app_name))) + + # initialize git repository + app_repo = git.Repo.init(app_directory) + app_repo.git.add(A=True) + app_repo.index.commit("feat: Initialize App") print("'{app}' created at {path}".format(app=app_name, path=app_directory)) From 4a730622fd1de852dd16e8b4cfed43488146d8ed Mon Sep 17 00:00:00 2001 From: Mitul David Date: Tue, 23 Nov 2021 10:39:34 +0530 Subject: [PATCH 79/89] fix: Invalid translation syntax --- frappe/public/js/frappe/file_uploader/FileUploader.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/file_uploader/FileUploader.vue b/frappe/public/js/frappe/file_uploader/FileUploader.vue index 8d93052cd3..167b4955fa 100644 --- a/frappe/public/js/frappe/file_uploader/FileUploader.vue +++ b/frappe/public/js/frappe/file_uploader/FileUploader.vue @@ -339,15 +339,15 @@ export default { if (!is_correct_type) { console.warn('File skipped because of invalid file type', file); frappe.show_alert({ - message:__(`File "${file.name}" was skipped because of invalid file type`), - indicator:'orange' + message: __('File "{0}" was skipped because of invalid file type', [file.name]), + indicator: 'orange' }); } if (!valid_file_size) { console.warn('File skipped because of invalid file size', file.size, file); frappe.show_alert({ - message:__(`File "${file.name}" was skipped because size exceeds ${max_file_size / (1024 * 1024)} MB`), - indicator:'orange' + message: __('File "{0}" was skipped because size exceeds {1} MB', [file.name, max_file_size / (1024 * 1024)]), + indicator: 'orange' }); } From 98da4b9388f1f3516110b8ac94b2feb760192296 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Tue, 23 Nov 2021 10:46:54 +0530 Subject: [PATCH 80/89] chore: Added description on email notification field --- frappe/website/doctype/blog_post/blog_post.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/website/doctype/blog_post/blog_post.json b/frappe/website/doctype/blog_post/blog_post.json index a7c21b7a48..b01115b818 100644 --- a/frappe/website/doctype/blog_post/blog_post.json +++ b/frappe/website/doctype/blog_post/blog_post.json @@ -201,6 +201,7 @@ }, { "default": "1", + "description": "Enable or Disable email notification when you get any comment or feedback on your Blog Post.", "fieldname": "enable_email_notification", "fieldtype": "Check", "label": "Enable Email Notification" @@ -213,7 +214,7 @@ "is_published_field": "published", "links": [], "max_attachments": 5, - "modified": "2021-11-16 16:19:18.135696", + "modified": "2021-11-23 10:42:01.759723", "modified_by": "Administrator", "module": "Website", "name": "Blog Post", From 7db461ad6dc9623bc8ebbd295844e1c891aac36b Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Tue, 23 Nov 2021 11:03:04 +0530 Subject: [PATCH 81/89] chore: description updated --- frappe/website/doctype/blog_post/blog_post.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/website/doctype/blog_post/blog_post.json b/frappe/website/doctype/blog_post/blog_post.json index b01115b818..b05293f28b 100644 --- a/frappe/website/doctype/blog_post/blog_post.json +++ b/frappe/website/doctype/blog_post/blog_post.json @@ -201,7 +201,7 @@ }, { "default": "1", - "description": "Enable or Disable email notification when you get any comment or feedback on your Blog Post.", + "description": "Enable email notification for any comment or feedback on your Blog Post.", "fieldname": "enable_email_notification", "fieldtype": "Check", "label": "Enable Email Notification" @@ -248,4 +248,4 @@ "sort_order": "ASC", "title_field": "title", "track_changes": 1 -} \ No newline at end of file +} From 1c410de6f5a806b4c50710d86694c557a211cedc Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 23 Nov 2021 12:45:28 +0100 Subject: [PATCH 82/89] fix: "send open in web" only if enabled --- frappe/email/doctype/newsletter/newsletter.py | 6 ++---- frappe/templates/emails/newsletter.html | 5 ++++- 2 files changed, 6 insertions(+), 5 deletions(-) mode change 100755 => 100644 frappe/email/doctype/newsletter/newsletter.py diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py old mode 100755 new mode 100644 index a118240488..7c0e2dfe87 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -159,11 +159,10 @@ class Newsletter(WebsiteGenerator): def send_newsletter(self, emails: List[str]): """Trigger email generation for `emails` and add it in Email Queue. """ - # TODO: get rid of this maybe? - message = self.get_message() attachments = self.get_newsletter_attachments() sender = self.send_from or frappe.utils.get_formatted_email(self.owner) - args = {"message": message, "name": self.name} + args = self.as_dict() + args["message"] = self.get_message() is_auto_commit_set = bool(frappe.db.auto_commit_on_many_writes) frappe.db.auto_commit_on_many_writes = not frappe.flags.in_test @@ -172,7 +171,6 @@ class Newsletter(WebsiteGenerator): subject=self.subject, sender=sender, recipients=emails, - message=message, attachments=attachments, template="newsletter", add_unsubscribe_link=self.send_unsubscribe_link, diff --git a/frappe/templates/emails/newsletter.html b/frappe/templates/emails/newsletter.html index a3afb906cf..051840ef69 100644 --- a/frappe/templates/emails/newsletter.html +++ b/frappe/templates/emails/newsletter.html @@ -3,8 +3,11 @@ {{ message }}
+ +{% if published and send_webview_link %}
Open in web
-
\ No newline at end of file + +{% endif %} \ No newline at end of file From 8c63bad25b01fd647e86c2c4257e594a6c9c1982 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Wed, 24 Nov 2021 00:58:33 +0530 Subject: [PATCH 83/89] refactor: move styles out of js add support for dark mode --- frappe/public/js/frappe/utils/diffview.js | 17 +-------------- frappe/public/scss/desk/global.scss | 25 ++++++++++++++++++++++- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/frappe/public/js/frappe/utils/diffview.js b/frappe/public/js/frappe/utils/diffview.js index 1537395e14..ebface7f05 100644 --- a/frappe/public/js/frappe/utils/diffview.js +++ b/frappe/public/js/frappe/utils/diffview.js @@ -80,22 +80,7 @@ frappe.ui.DiffView = class DiffView { } prettify_diff(diff) { - let html = ` - - `; + let html = ``; diff.forEach((line) => { let line_class = ""; diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss index d157a43bc3..a445fc269b 100644 --- a/frappe/public/scss/desk/global.scss +++ b/frappe/public/scss/desk/global.scss @@ -574,6 +574,29 @@ details > summary:focus { } } +.diffview { + font-family: monospace; + white-space: pre; + word-wrap: break-word; + color: var(--text-color); +} + + +.diffview .insert { + background-color: var(--green-100); +} + +.diffview .delete { + background-color: var(--red-100); +} + +[data-theme="dark"] { + .diffview .insert,.delete { + color: var(--gray-900); + + } +} + // REDESIGN TODO: Handling of broken images? // img.no-image:before { // .img-background(); @@ -603,4 +626,4 @@ details > summary:focus { .chart-container { direction: ltr; } -*/ \ No newline at end of file +*/ From 06aa8a10adb6dc05c91f2bc7dd589d1ea400b57d Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 24 Nov 2021 11:19:30 +0530 Subject: [PATCH 84/89] fix: Remove chart from report when saved without chart --- frappe/public/js/frappe/views/reports/report_view.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index c26b63a9f6..c70c64be0e 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -388,6 +388,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { this.$charts_wrapper.addClass('hidden'); this.save_view_user_settings( { chart_args: null }); + this.chart_args = null; } } From 731cb5796adeb926a1d7aa60abd6767d4ac0c46e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 24 Nov 2021 15:01:04 +0530 Subject: [PATCH 85/89] feat: Option to disable Event Reminders in Notification Settings --- frappe/desk/doctype/event/event.py | 8 +++++++- .../notification_settings/notification_settings.json | 12 ++++++++++-- .../test_notification_settings.py | 8 ++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 frappe/desk/doctype/notification_settings/test_notification_settings.py diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py index d4c185e56f..86f0656bc6 100644 --- a/frappe/desk/doctype/event/event.py +++ b/frappe/desk/doctype/event/event.py @@ -11,6 +11,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils.user import get_enabled_system_users from frappe.desk.reportview import get_filters_cond +from frappe.desk.doctype.notification_settings.notification_settings import is_email_notifications_enabled_for_type weekdays = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"] communication_mapping = {"": "Event", "Event": "Event", "Meeting": "Meeting", "Call": "Phone", "Sent/Received Email": "Email", "Other": "Other"} @@ -141,7 +142,12 @@ def has_permission(doc, user): def send_event_digest(): today = nowdate() - for user in get_enabled_system_users(): + + # select only those users that have event reminder email notifications enabled + users = [user for user in get_enabled_system_users() if + is_email_notifications_enabled_for_type(user.name, 'Event Reminders')] + + for user in users: events = get_events(today, today, user.name, for_reminder=True) if events: frappe.set_user_lang(user.name, user.language) diff --git a/frappe/desk/doctype/notification_settings/notification_settings.json b/frappe/desk/doctype/notification_settings/notification_settings.json index fc535fa405..1a6efd5a0d 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.json +++ b/frappe/desk/doctype/notification_settings/notification_settings.json @@ -14,6 +14,7 @@ "enable_email_assignment", "enable_email_energy_point", "enable_email_share", + "enable_email_event_reminders", "user", "seen", "system_notifications_section", @@ -97,12 +98,19 @@ "fieldname": "energy_points_system_notifications", "fieldtype": "Check", "label": "Energy Points" + }, + { + "default": "1", + "depends_on": "enable_email_notifications", + "fieldname": "enable_email_event_reminders", + "fieldtype": "Check", + "label": "Event Reminders" } ], "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2021-11-16 12:18:46.955501", + "modified": "2021-11-24 14:45:31.931154", "modified_by": "Administrator", "module": "Desk", "name": "Notification Settings", @@ -125,4 +133,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file diff --git a/frappe/desk/doctype/notification_settings/test_notification_settings.py b/frappe/desk/doctype/notification_settings/test_notification_settings.py new file mode 100644 index 0000000000..e3dac0af5f --- /dev/null +++ b/frappe/desk/doctype/notification_settings/test_notification_settings.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies and Contributors +# See license.txt + +# import frappe +import unittest + +class TestNotificationSettings(unittest.TestCase): + pass From 3be5c797ec61a8d47a4b00619e21587885934bfa Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Wed, 24 Nov 2021 15:08:03 +0530 Subject: [PATCH 86/89] fix: diffview styles --- frappe/public/scss/desk/global.scss | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss index a445fc269b..7c2ae3c8b1 100644 --- a/frappe/public/scss/desk/global.scss +++ b/frappe/public/scss/desk/global.scss @@ -591,9 +591,11 @@ details > summary:focus { } [data-theme="dark"] { - .diffview .insert,.delete { - color: var(--gray-900); - + .diffview .insert { + background-color: var(--green-800); + } + .diffview .delete { + background-color: var(--red-800); } } From bacfdaaf7c972dd8b004a48696cfd4f154057c63 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 24 Nov 2021 18:40:22 +0530 Subject: [PATCH 87/89] fix: delete prepared reports on deleting reports Co-authored-by: Dany Roberts --- frappe/core/doctype/report/report.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index be0346d869..266017dd71 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -51,6 +51,14 @@ class Report(Document): and not frappe.flags.in_patch): frappe.throw(_("You are not allowed to delete Standard Report")) delete_custom_role('report', self.name) + self.delete_prepared_reports() + + def delete_prepared_reports(self): + prepared_reports = frappe.get_all("Prepared Report", filters={'ref_report_doctype': self.name}, pluck='name') + + for report in prepared_reports: + frappe.delete_doc("Prepared Report", report, ignore_missing=True, force=True, + delete_permanently=True) def get_columns(self): return [d.as_dict(no_default_fields = True) for d in self.columns] From c30f20c064a1c8531bad4f59ba1069f0e84ccf14 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 24 Nov 2021 16:08:51 +0100 Subject: [PATCH 88/89] fix: navbar settings validation --- frappe/core/doctype/navbar_settings/navbar_settings.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/navbar_settings/navbar_settings.py b/frappe/core/doctype/navbar_settings/navbar_settings.py index 46eb5c3e7a..c46d0081b6 100644 --- a/frappe/core/doctype/navbar_settings/navbar_settings.py +++ b/frappe/core/doctype/navbar_settings/navbar_settings.py @@ -13,6 +13,9 @@ class NavbarSettings(Document): def validate_standard_navbar_items(self): doc_before_save = self.get_doc_before_save() + if not doc_before_save: + return + before_save_items = [item for item in \ doc_before_save.help_dropdown + doc_before_save.settings_dropdown if item.is_standard] @@ -32,7 +35,3 @@ def get_app_logo(): def get_navbar_settings(): navbar_settings = frappe.get_single('Navbar Settings') return navbar_settings - - - - From ae9731467ac459b73d685d745f3bb2220384cf39 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 25 Nov 2021 11:28:41 +0530 Subject: [PATCH 89/89] fix: Don't pass kwargs that make_access_log can't handle When people try to export reports, sometimes a cmd kwarg is passed across the three wrapped functions right to make_access_log. This happens because of the order and way we had to wrap the write_only, retry and whitelist. --- frappe/core/doctype/access_log/access_log.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/access_log/access_log.py b/frappe/core/doctype/access_log/access_log.py index 48c12fd93f..db2e64e868 100644 --- a/frappe/core/doctype/access_log/access_log.py +++ b/frappe/core/doctype/access_log/access_log.py @@ -11,11 +11,26 @@ class AccessLog(Document): @frappe.whitelist() +def make_access_log( + doctype=None, + document=None, + method=None, + file_type=None, + report_name=None, + filters=None, + page=None, + columns=None, +): + _make_access_log( + doctype, document, method, file_type, report_name, filters, page, columns, + ) + + @frappe.write_only() @retry( stop=stop_after_attempt(3), retry=retry_if_exception_type(frappe.DuplicateEntryError) ) -def make_access_log( +def _make_access_log( doctype=None, document=None, method=None, @@ -42,6 +57,7 @@ def make_access_log( }).db_insert() # `frappe.db.commit` added because insert doesnt `commit` when called in GET requests like `printview` - # dont commit in test mode + # dont commit in test mode. It must be tempting to put this block along with the in_request in the + # whitelisted method...yeah, don't do it. That part would be executed possibly on a read only DB conn if not frappe.flags.in_test or in_request: frappe.db.commit()