diff --git a/.mergify.yml b/.mergify.yml index d810898eee..b145834cc4 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,7 +1,7 @@ pull_request_rules: - name: Automatic merge on CI success and review conditions: - - status-success=Codacy/PR Quality Review + - status-success=Sider - status-success=Semantic Pull Request - status-success=Travis CI - Pull Request - status-success=security/snyk - package.json (frappe) @@ -14,7 +14,7 @@ pull_request_rules: method: merge - name: Automatic squash on CI success and review conditions: - - status-success=Codacy/PR Quality Review + - status-success=Sider - status-success=Semantic Pull Request - status-success=Travis CI - Pull Request - status-success=security/snyk - package.json (frappe) diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index 0c5b5f94b4..78f452db21 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -15,7 +15,8 @@ global_cache_keys = ("app_hooks", "installed_apps", "app_modules", "module_app", "system_settings", 'scheduler_events', 'time_zone', 'webhooks', 'active_domains', 'active_modules', 'assignment_rule', 'server_script_map', 'wkhtmltopdf_version', - 'domain_restricted_doctypes', 'domain_restricted_pages', 'information_schema:counts') + 'domain_restricted_doctypes', 'domain_restricted_pages', 'information_schema:counts', + 'sitemap_routes') user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang", "defaults", "user_permissions", "home_page", "linked_with", diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index 9b04ebb7ad..4614dd09c4 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -102,6 +102,7 @@ }, { "default": "0", + "depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)", "fieldname": "reqd", "fieldtype": "Check", "in_list_view": 1, @@ -452,7 +453,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-03-16 14:49:49.672099", + "modified": "2020-04-15 02:26:03.310781", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index f970f51419..f7c9cbe28a 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -206,7 +206,7 @@ class DocType(Document): if d.fieldtype: if (not getattr(d, "fieldname", None)): if d.label: - d.fieldname = d.label.strip().lower().replace(' ','_') + d.fieldname = d.label.strip().lower().replace(' ','_').strip('?') if d.fieldname in restricted: d.fieldname = d.fieldname + '1' if d.fieldtype=='Section Break': @@ -914,7 +914,7 @@ def validate_fields(meta): if not d.permlevel: d.permlevel = 0 if d.fieldtype not in table_fields: d.allow_bulk_edit = 0 if not d.fieldname: - d.fieldname = d.fieldname.lower() + d.fieldname = d.fieldname.lower().strip('?') check_illegal_characters(d.fieldname) check_invalid_fieldnames(meta.get("name"), d.fieldname) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 6633884bb3..7f763ea9fc 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -517,7 +517,7 @@ class File(Document): delete_file(self.thumbnail_url) def is_downloadable(self): - return self.is_private and has_permission(self, 'read') + return has_permission(self, 'read') def get_extension(self): '''returns split filename and extension''' @@ -712,7 +712,11 @@ def remove_all(dt, dn, from_delete=False): def has_permission(doc, ptype=None, user=None): - permission = True + has_access = False + user = user or frappe.session.user + + if not doc.is_private or doc.owner == user or user == 'Administrator': + has_access = True if doc.attached_to_doctype and doc.attached_to_name: attached_to_doctype = doc.attached_to_doctype @@ -722,20 +726,20 @@ def has_permission(doc, ptype=None, user=None): ref_doc = frappe.get_doc(attached_to_doctype, attached_to_name) if ptype in ['write', 'create', 'delete']: - permission = ref_doc.has_permission('write') + has_access = ref_doc.has_permission('write') - if ptype == 'delete' and permission == False: + if ptype == 'delete' and not has_access: frappe.throw(_("Cannot delete file as it belongs to {0} {1} for which you do not have permissions").format( doc.attached_to_doctype, doc.attached_to_name), frappe.PermissionError) else: - permission = ref_doc.has_permission('read') + has_access = ref_doc.has_permission('read') except frappe.DoesNotExistError: # if parent doc is not created before file is created - # we cannot check its permission so allow the file - permission = True + # we cannot check its permission so we will use file's permission + pass - return permission + return has_access def remove_file_by_url(file_url, doctype=None, name=None): diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 68848d26f6..ab005bd428 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -208,9 +208,11 @@ class CustomizeForm(Document): self.validate_fieldtype_change(df, meta_df[0].get(property), df.get(property)) elif property == "allow_on_submit" and df.get(property): - frappe.msgprint(_("Row {0}: Not allowed to enable Allow on Submit for standard fields")\ - .format(df.idx)) - continue + if not frappe.db.get_value("DocField", + {"parent": self.doc_type, "fieldname": df.fieldname}, "allow_on_submit"): + frappe.msgprint(_("Row {0}: Not allowed to enable Allow on Submit for standard fields")\ + .format(df.idx)) + continue elif property == "reqd" and \ ((frappe.db.get_value("DocField", 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 34778a76e9..350d159541 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -93,6 +93,7 @@ }, { "default": "0", + "depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)", "fieldname": "reqd", "fieldtype": "Check", "label": "Mandatory", @@ -385,7 +386,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-07 14:53:40.619043", + "modified": "2020-04-15 02:26:59.673750", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 1a110c46d9..f4a15930c4 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -278,9 +278,13 @@ def get_table_with_counts(): def get_custom_reports_and_doctypes(module): return [ _dict({ - "label": "Custom", - "links": get_custom_doctype_list(module) + get_custom_report_list(module) - }) + "label": _("Custom Documents"), + "links": get_custom_doctype_list(module) + }), + _dict({ + "label": _("Custom Reports"), + "links": get_custom_report_list(module) + }), ] def get_custom_doctype_list(module): diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 9bb12a4ec8..6102be61ce 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -367,8 +367,11 @@ def scrub_user_tags(tagcount): return rlist # used in building query in queries.py -def get_match_cond(doctype): - cond = DatabaseQuery(doctype).build_match_conditions() +def get_match_cond(doctype, as_condition=True): + cond = DatabaseQuery(doctype).build_match_conditions(as_condition=as_condition) + if not as_condition: + return cond + return ((' and ' + cond) if cond else "").replace("%", "%%") def build_match_conditions(doctype, user=None, as_condition=True): diff --git a/frappe/email/__init__.py b/frappe/email/__init__.py index f99536f9a8..d58b35040e 100644 --- a/frappe/email/__init__.py +++ b/frappe/email/__init__.py @@ -65,7 +65,7 @@ def get_communication_doctype(doctype, txt, searchfield, start, page_len, filter com_doctypes = [] if len(txt)<2: - for name in ["Customer", "Supplier"]: + for name in frappe.get_hooks("communication_doctypes"): try: module = load_doctype_module(name, suffix='_dashboard') if hasattr(module, 'get_data'): diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 6a3dd89873..8c011ade65 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -335,4 +335,4 @@ def evaluate_alert(doc, alert, event): frappe.utils.get_link_to_form('Error Log', error_log.name))) def get_context(doc): - return {"doc": doc, "nowdate": nowdate, "frappe.utils": frappe.utils} + return {"doc": doc, "nowdate": nowdate, "frappe": frappe._dict(utils=frappe.utils)} diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 13b2b61bef..9ab1ef7799 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -506,9 +506,13 @@ class BaseDocument(object): fetch_from_fieldname = _df.fetch_from.split('.')[-1] value = values[fetch_from_fieldname] if _df.fieldtype == 'Small Text' or _df.fieldtype == 'Text' or _df.fieldtype == 'Data': - fetch_from_df = frappe.get_meta(doctype).get_field(fetch_from_fieldname) - fetch_from_ft = fetch_from_df.get('fieldtype') + if fetch_from_fieldname in default_fields: + from frappe.model.meta import get_default_df + fetch_from_df = get_default_df(fetch_from_fieldname) + else: + fetch_from_df = frappe.get_meta(doctype).get_field(fetch_from_fieldname) + fetch_from_ft = fetch_from_df.get('fieldtype') if fetch_from_ft == 'Text Editor' and value: value = unescape_html(strip_html(value)) setattr(self, _df.fieldname, value) diff --git a/frappe/public/js/frappe/chat.js b/frappe/public/js/frappe/chat.js index 810de89874..8b1c09ac93 100644 --- a/frappe/public/js/frappe/chat.js +++ b/frappe/public/js/frappe/chat.js @@ -1566,7 +1566,11 @@ class extends Component { const alert = // TODO: ellipses content ` - ${frappe.user.first_name(r.user)}: ${r.content} + + + + + ${frappe.user.first_name(r.user)}: ${r.content} ` frappe.show_alert(alert, 15, { @@ -1575,6 +1579,11 @@ class extends Component { this.base.firstChild._component.toggle() }.bind(this, r) }) + frappe.notify(`${frappe.user.first_name(r.user)}`, { + body: r.content, + icon: frappe.user.image(r.user), + tag: r.user + }) } if ( r.room === state.room.name ) { diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index b55c822ba6..e714418375 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1052,7 +1052,7 @@ frappe.ui.form.Form = class FrappeForm { } is_dirty() { - return this.doc.__unsaved; + return !!this.doc.__unsaved; } is_new() { diff --git a/frappe/public/js/frappe/form/save.js b/frappe/public/js/frappe/form/save.js index fbc35634f4..d40b3ed341 100644 --- a/frappe/public/js/frappe/form/save.js +++ b/frappe/public/js/frappe/form/save.js @@ -21,7 +21,7 @@ frappe.ui.form.save = function (frm, action, callback, btn) { remove_empty_rows(); $(frm.wrapper).addClass('validated-form'); - if (check_mandatory()) { + if (frm.is_dirty() && check_mandatory()) { _call({ method: "frappe.desk.form.save.savedocs", args: { doc: frm.doc, action: action }, @@ -36,6 +36,7 @@ frappe.ui.form.save = function (frm, action, callback, btn) { freeze_message: freeze_message }); } else { + frappe.show_alert({message: __("Document not updated"), indicator: "yellow"}); $(btn).prop("disabled", false); } }; diff --git a/frappe/public/js/frappe/ui/group_by/group_by.js b/frappe/public/js/frappe/ui/group_by/group_by.js index 6936f25c18..dc81bbdf20 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.js +++ b/frappe/public/js/frappe/ui/group_by/group_by.js @@ -92,7 +92,6 @@ frappe.ui.GroupBy = class { } apply_settings(settings) { - if (!settings.group_by.startsWith('`tab')) { settings.group_by = '`tab' + this.doctype + '`.`' + settings.group_by + '`'; } diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index a2b03f180e..0f27e97178 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -675,7 +675,9 @@ Object.assign(frappe.utils, { return __(frappe.utils.to_title_case(route[0], true)); }, report_column_total: function(values, column, type) { - if (values.length > 0) { + if (column.column.disable_total) { + return ''; + } else if (values.length > 0) { if (column.column.fieldtype == "Percent" || type === "mean") { return values.reduce((a, b) => a + flt(b)) / values.length; } else if (column.column.fieldtype == "Int") { diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index b479c4c101..43540f449d 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -837,6 +837,9 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { fieldtype: 'MultiCheck', columns: 2, options: columns[this.doctype] + .filter(df => { + return !df.hidden; + }) .map(df => ({ label: __(df.label), value: df.fieldname, @@ -858,6 +861,9 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { fieldtype: 'MultiCheck', columns: 2, options: columns[cdt] + .filter(df => { + return !df.hidden; + }) .map(df => ({ label: __(df.label), value: df.fieldname, diff --git a/frappe/public/js/frappe/web_form/webform_script.js b/frappe/public/js/frappe/web_form/webform_script.js index 7bf7162101..688c0938c4 100644 --- a/frappe/public/js/frappe/web_form/webform_script.js +++ b/frappe/public/js/frappe/web_form/webform_script.js @@ -2,13 +2,13 @@ import WebFormList from './web_form_list' import WebForm from './web_form' frappe.ready(function() { + let query_params = frappe.utils.get_query_params(); let wrapper = $(".web-form-wrapper"); - let is_list = parseInt(wrapper.data('is-list')); + let is_list = parseInt(wrapper.data('is-list')) || query_params.is_list; let webform_doctype = wrapper.data('web-form-doctype'); let webform_name = wrapper.data('web-form'); let login_required = parseInt(wrapper.data('login-required')); let allow_delete = parseInt(wrapper.data('allow-delete')); - let query_params = frappe.utils.get_query_params(); let doc_name = query_params.name || ''; let is_new = query_params.new; @@ -38,7 +38,7 @@ frappe.ready(function() { settings: { allow_delete } - }) + }); } function show_form() { diff --git a/frappe/templates/base.html b/frappe/templates/base.html index 1c5f286442..2a241c4843 100644 --- a/frappe/templates/base.html +++ b/frappe/templates/base.html @@ -62,7 +62,11 @@ {%- endblock -%} {%- block navbar -%} - {% include "templates/includes/navbar/navbar.html" %} + {%- if navbar_content -%} + {{ navbar_content }} + {%- else -%} + {% include "templates/includes/navbar/navbar.html" %} + {%- endif -%} {%- endblock -%} {% block content %} @@ -70,7 +74,11 @@ {% endblock %} {%- block footer -%} - {% include "templates/includes/footer/footer.html" %} + {%- if footer_content -%} + {{ footer_content }} + {%- else -%} + {% include "templates/includes/footer/footer.html" %} + {%- endif -%} {%- endblock -%} {% block base_scripts %} diff --git a/frappe/templates/web.html b/frappe/templates/web.html index e61672124a..d2d38a6320 100644 --- a/frappe/templates/web.html +++ b/frappe/templates/web.html @@ -13,7 +13,7 @@ {% block page_container %} -
+