diff --git a/.github/helper/install.sh b/.github/helper/install.sh index 454cc89694..19a7c68e19 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -17,7 +17,7 @@ if [ "$TYPE" == "server" ]; then fi if [ "$DB" == "mariadb" ];then - sudo apt install mariadb-client-10.3 + sudo apt update && sudo apt install mariadb-client-10.3 mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"; mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"; diff --git a/.gitignore b/.gitignore index c9dd8f38f3..7e3d178630 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ dist/ frappe/docs/current frappe/public/dist .vscode +.vs node_modules .kdev4/ *.kdev4 diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index cd20a5c0f3..28880e7e38 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -199,7 +199,7 @@ class Importer: new_doc = frappe.new_doc(self.doctype) new_doc.update(doc) - if (meta.autoname or "").lower() != "prompt": + if not doc.name and (meta.autoname or "").lower() != "prompt": # name can only be set directly if autoname is prompt new_doc.set("name", None) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index af9c8a48fa..91090bdd77 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -569,6 +569,24 @@ class File(Document): frappe.local.rollback_observers.append(self) self.save() + @staticmethod + def zip_files(files): + from six import string_types + + zip_file = io.BytesIO() + zf = zipfile.ZipFile(zip_file, "w", zipfile.ZIP_DEFLATED) + for _file in files: + if isinstance(_file, string_types): + _file = frappe.get_doc("File", _file) + if not isinstance(_file, File): + continue + if _file.is_folder: + continue + zf.writestr(_file.file_name, _file.get_content()) + zf.close() + return zip_file.getvalue() + + def on_doctype_update(): frappe.db.add_index("File", ["attached_to_doctype", "attached_to_name"]) @@ -612,6 +630,16 @@ def move_file(file_list, new_parent, old_parent): frappe.get_doc("File", old_parent).save() frappe.get_doc("File", new_parent).save() + +@frappe.whitelist() +def zip_files(files): + files = frappe.parse_json(files) + zipped_files = File.zip_files(files) + frappe.response["filename"] = "files.zip" + frappe.response["filecontent"] = zipped_files + frappe.response["type"] = "download" + + def setup_folder_path(filename, new_parent): file = frappe.get_doc("File", filename) file.folder = new_parent diff --git a/frappe/desk/doctype/global_search_settings/global_search_settings.py b/frappe/desk/doctype/global_search_settings/global_search_settings.py index 9ffe9aaf06..e9a47cecd1 100644 --- a/frappe/desk/doctype/global_search_settings/global_search_settings.py +++ b/frappe/desk/doctype/global_search_settings/global_search_settings.py @@ -33,7 +33,7 @@ class GlobalSearchSettings(Document): def get_doctypes_for_global_search(): def get_from_db(): - doctypes = frappe.get_list("Global Search DocType", fields=["document_type"], order_by="idx ASC") + doctypes = frappe.get_all("Global Search DocType", fields=["document_type"], order_by="idx ASC") return [d.document_type for d in doctypes] or [] return frappe.cache().hget("global_search", "search_priorities", get_from_db) diff --git a/frappe/desk/form/linked_with.py b/frappe/desk/form/linked_with.py index cbf459e8ae..14ea2712e2 100644 --- a/frappe/desk/form/linked_with.py +++ b/frappe/desk/form/linked_with.py @@ -410,11 +410,11 @@ def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None): try: if link.get("filters"): - ret = frappe.get_list(doctype=dt, fields=fields, filters=link.get("filters")) + ret = frappe.get_all(doctype=dt, fields=fields, filters=link.get("filters")) elif link.get("get_parent"): if me and me.parent and me.parenttype == dt: - ret = frappe.get_list(doctype=dt, fields=fields, + ret = frappe.get_all(doctype=dt, fields=fields, filters=[[dt, "name", '=', me.parent]]) else: ret = None @@ -426,7 +426,7 @@ def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None): if link.get("doctype_fieldname"): filters.append([link.get('child_doctype'), link.get("doctype_fieldname"), "=", doctype]) - ret = frappe.get_list(doctype=dt, fields=fields, filters=filters, or_filters=or_filters, distinct=True) + ret = frappe.get_all(doctype=dt, fields=fields, filters=filters, or_filters=or_filters, distinct=True) else: link_fieldnames = link.get("fieldname") @@ -437,7 +437,7 @@ def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None): # dynamic link if link.get("doctype_fieldname"): filters.append([dt, link.get("doctype_fieldname"), "=", doctype]) - ret = frappe.get_list(doctype=dt, fields=fields, filters=filters, or_filters=or_filters) + ret = frappe.get_all(doctype=dt, fields=fields, filters=filters, or_filters=or_filters) else: ret = None diff --git a/frappe/desk/page/user_profile/user_profile_controller.js b/frappe/desk/page/user_profile/user_profile_controller.js index c1a89f316e..40b542d5c3 100644 --- a/frappe/desk/page/user_profile/user_profile_controller.js +++ b/frappe/desk/page/user_profile/user_profile_controller.js @@ -17,21 +17,15 @@ class UserProfile { show() { let route = frappe.get_route(); this.user_id = route[1] || frappe.session.user; - - //validate if user - if (route.length > 1) { - frappe.dom.freeze(__('Loading user profile') + '...'); - frappe.db.exists('User', this.user_id).then(exists => { - frappe.dom.unfreeze(); - if (exists) { - this.make_user_profile(); - } else { - frappe.msgprint(__('User does not exist')); - } - }); - } else { - frappe.set_route('user-profile', frappe.session.user); - } + frappe.dom.freeze(__('Loading user profile') + '...'); + frappe.db.exists('User', this.user_id).then(exists => { + frappe.dom.unfreeze(); + if (exists) { + this.make_user_profile(); + } else { + frappe.msgprint(__('User does not exist')); + } + }); } make_user_profile() { @@ -74,8 +68,7 @@ class UserProfile { primary_action_label: __('Go'), primary_action: ({ user }) => { dialog.hide(); - this.user_id = user; - this.make_user_profile(); + frappe.set_route('user-profile', user); } }); dialog.show(); diff --git a/frappe/desk/page/user_profile/user_profile_sidebar.html b/frappe/desk/page/user_profile/user_profile_sidebar.html index 4a35c6cf9c..9f8889fd03 100644 --- a/frappe/desk/page/user_profile/user_profile_sidebar.html +++ b/frappe/desk/page/user_profile/user_profile_sidebar.html @@ -51,10 +51,10 @@

{%=__("Edit Profile") %}

{%=__("User Settings") %}

- {%=__("Leaderboard") %}

- \ No newline at end of file + diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py index 077a5dd40b..d89a3d83be 100644 --- a/frappe/email/doctype/email_queue/email_queue.py +++ b/frappe/email/doctype/email_queue/email_queue.py @@ -18,7 +18,7 @@ from frappe import _, safe_encode, task from frappe.model.document import Document from frappe.email.queue import get_unsubcribed_url, get_unsubscribe_message from frappe.email.email_body import add_attachment, get_formatted_html, get_email -from frappe.utils import cint, split_emails, add_days, nowdate, cstr +from frappe.utils import cint, split_emails, add_days, nowdate, cstr, get_hook_method from frappe.email.doctype.email_account.email_account import EmailAccount @@ -121,9 +121,13 @@ class EmailQueue(Document): continue message = ctx.build_message(recipient.recipient) - if not frappe.flags.in_test: - ctx.smtp_session.sendmail(from_addr=self.sender, to_addrs=recipient.recipient, msg=message) - ctx.add_to_sent_list(recipient) + method = get_hook_method('override_email_send') + if method: + method(self, self.sender, recipient.recipient, message) + else: + if not frappe.flags.in_test: + ctx.smtp_session.sendmail(from_addr=self.sender, to_addrs=recipient.recipient, msg=message) + ctx.add_to_sent_list(recipient) if frappe.flags.in_test: frappe.flags.sent_mail = message diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index cf8ec46d76..50a56d9dbb 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -189,7 +189,7 @@ def update_modified(original_modified, doc): ).set( singles_table.value,original_modified ).where( - singles_table.field == "modified" + singles_table["field"] == "modified", # singles_table.field is a method of pypika Selectable ).where( singles_table.doctype == doc["name"] ).run() diff --git a/frappe/patches/v13_0/set_path_for_homepage_in_web_page_view.py b/frappe/patches/v13_0/set_path_for_homepage_in_web_page_view.py index 66f878e4bf..d4fe2d2f36 100644 --- a/frappe/patches/v13_0/set_path_for_homepage_in_web_page_view.py +++ b/frappe/patches/v13_0/set_path_for_homepage_in_web_page_view.py @@ -2,4 +2,4 @@ import frappe def execute(): frappe.reload_doc('website', 'doctype', 'web_page_view', force=True) - frappe.db.sql("""UPDATE `tabWeb Page View` set path="/" where path=''""") + frappe.db.sql("""UPDATE `tabWeb Page View` set path='/' where path=''""") diff --git a/frappe/patches/v13_0/update_date_filters_in_user_settings.py b/frappe/patches/v13_0/update_date_filters_in_user_settings.py index 3b1b07fe0a..fa13a6789e 100644 --- a/frappe/patches/v13_0/update_date_filters_in_user_settings.py +++ b/frappe/patches/v13_0/update_date_filters_in_user_settings.py @@ -10,7 +10,7 @@ def execute(): select * from `__UserSettings` where - user="{user}" + user='{user}' '''.format(user = user.user), as_dict=True) for setting in user_settings: diff --git a/frappe/patches/v14_0/copy_mail_data.py b/frappe/patches/v14_0/copy_mail_data.py index 362d23d0e1..d3a5c59209 100644 --- a/frappe/patches/v14_0/copy_mail_data.py +++ b/frappe/patches/v14_0/copy_mail_data.py @@ -3,7 +3,9 @@ import frappe def execute(): + frappe.reload_doc("email", "doctype", "imap_folder") frappe.reload_doc("email", "doctype", "email_account") + # patch for all Email Account with the flag use_imap for email_account in frappe.get_list("Email Account", filters={"enable_incoming": 1, "use_imap": 1}): # get all data from Email Account diff --git a/frappe/permissions.py b/frappe/permissions.py index 96e1910462..5faaf7dcfb 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -108,7 +108,7 @@ def get_doc_permissions(doc, user=None, ptype=None): meta = frappe.get_meta(doc.doctype) def is_user_owner(): - return (doc.get("owner") or "").lower() == frappe.session.user.lower() + return (doc.get("owner") or "").lower() == user.lower() if has_controller_permissions(doc, ptype, user=user) is False: push_perm_check_log('Not allowed via controller permission check') diff --git a/frappe/public/js/frappe/form/footer/form_timeline.js b/frappe/public/js/frappe/form/footer/form_timeline.js index b9ec9a5438..f278d1b64b 100644 --- a/frappe/public/js/frappe/form/footer/form_timeline.js +++ b/frappe/public/js/frappe/form/footer/form_timeline.js @@ -172,9 +172,11 @@ class FormTimeline extends BaseTimeline { get_communication_timeline_contents() { let communication_timeline_contents = []; + let icon_set = {Email: "mail", Phone: "call", Meeting: "calendar", Other: "dot-horizontal"}; (this.doc_info.communications|| []).forEach(communication => { + let medium = communication.communication_medium; communication_timeline_contents.push({ - icon: 'mail', + icon: icon_set[medium], icon_size: 'sm', creation: communication.creation, is_card: true, diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js index adc3bb5626..eff0391338 100644 --- a/frappe/public/js/frappe/request.js +++ b/frappe/public/js/frappe/request.js @@ -481,6 +481,24 @@ frappe.request.report_error = function(xhr, request_opts) { exc = ""; } + const copy_markdown_to_clipboard = () => { + const code_block = snippet => '```\n' + snippet + '\n```'; + const traceback_info = [ + '### App Versions', + code_block(JSON.stringify(frappe.boot.versions, null, "\t")), + '### Route', + code_block(frappe.get_route_str()), + '### Trackeback', + code_block(exc), + '### Request Data', + code_block(JSON.stringify(request_opts, null, "\t")), + '### Response Data', + code_block(JSON.stringify(data, null, '\t')), + ].join("\n"); + frappe.utils.copy_to_clipboard(traceback_info); + }; + + var show_communication = function() { var error_report_message = [ '
Please type some additional information that could help us reproduce this issue:
', @@ -532,6 +550,11 @@ frappe.request.report_error = function(xhr, request_opts) { frappe.msgprint(__('Support Email Address Not Specified')); } frappe.error_dialog.hide(); + }, + secondary_action_label: __('Copy error to clipboard'), + secondary_action: () => { + copy_markdown_to_clipboard(); + frappe.error_dialog.hide(); } }); frappe.error_dialog.wrapper.classList.add('msgprint-dialog'); diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 5f546c22da..8b374c44fd 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -316,7 +316,7 @@ Object.assign(frappe.utils, { } }, get_scroll_position: function(element, additional_offset) { - let header_offset = $(".navbar").height() + $(".page-head:visible").height(); + let header_offset = $(".navbar").height() + $(".page-head:visible").height() || $(".navbar").height(); let scroll_top = $(element).offset().top - header_offset - cint(additional_offset); return scroll_top; }, @@ -957,17 +957,24 @@ Object.assign(frappe.utils, { return decoded; }, copy_to_clipboard(string) { - let input = $(""); - $("body").append(input); - input.val(string).select(); + const show_success_alert = () => { + frappe.show_alert({ + indicator: 'green', + message: __('Copied to clipboard.') + }); + }; + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard.writeText(string).then(show_success_alert); + } else { + let input = $("