diff --git a/frappe/__init__.py b/frappe/__init__.py index 514f83561c..852ae72f77 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -23,7 +23,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '10.1.70' +__version__ = '10.1.71' __title__ = "Frappe Framework" local = Local() diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 917eb16be8..3346079208 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -571,7 +571,7 @@ def get_version(): @click.command('setup-global-help') @click.option('--mariadb_root_password') def setup_global_help(mariadb_root_password=None): - '''Removed: setup help table in a separate database that will be + '''Deprecated: setup help table in a separate database that will be shared by the whole bench and set `global_help_setup` as 1 in common_site_config.json''' print_in_app_help_deprecation() @@ -579,18 +579,18 @@ def setup_global_help(mariadb_root_password=None): @click.command('get-docs-app') @click.argument('app') def get_docs_app(app): - '''Removed: Get the docs app for given app''' + '''Deprecated: Get the docs app for given app''' print_in_app_help_deprecation() @click.command('get-all-docs-apps') def get_all_docs_apps(): - '''Removed: Get docs apps for all apps''' + '''Deprecated: Get docs apps for all apps''' print_in_app_help_deprecation() @click.command('setup-help') @pass_context def setup_help(context): - '''Removed: Setup help table in the current site (called after migrate)''' + '''Deprecated: Setup help table in the current site (called after migrate)''' print_in_app_help_deprecation() @click.command('rebuild-global-search') @@ -686,6 +686,5 @@ commands = [ watch, _bulk_rename, add_to_email_queue, - rebuild_global_search, - auto_deploy + rebuild_global_search ] diff --git a/frappe/custom/doctype/custom_field/custom_field.js b/frappe/custom/doctype/custom_field/custom_field.js index c430d12752..c59fabeaa6 100644 --- a/frappe/custom/doctype/custom_field/custom_field.js +++ b/frappe/custom/doctype/custom_field/custom_field.js @@ -9,6 +9,9 @@ frappe.ui.form.on('Custom Field', { frm.set_query('dt', function(doc) { var filters = [ ['DocType', 'issingle', '=', 0], + ['DocType', 'custom', '=', 0], + ['DocType', 'name', 'not in', frappe.model.core_doctypes_list], + ['DocType', 'restrict_to_domain', 'in', frappe.boot.active_domains] ]; if(frappe.session.user!=="Administrator") { filters.push(['DocType', 'module', 'not in', ['Core', 'Custom']]) @@ -32,15 +35,21 @@ frappe.ui.form.on('Custom Field', { return frappe.call({ method: 'frappe.custom.doctype.custom_field.custom_field.get_fields_label', args: { doctype: frm.doc.dt, fieldname: frm.doc.fieldname }, - callback: function(r, rt) { - set_field_options('insert_after', r.message); - var fieldnames = $.map(r.message, function(v) { return v.value; }); + callback: function(r) { + if(r) { + if(r._server_messages && r._server_messages.length) { + frm.set_value("dt", ""); + } else { + set_field_options('insert_after', r.message); + var fieldnames = $.map(r.message, function(v) { return v.value; }); - if(insert_after==null || !in_list(fieldnames, insert_after)) { - insert_after = fieldnames[-1]; + if(insert_after==null || !in_list(fieldnames, insert_after)) { + insert_after = fieldnames[-1]; + } + + frm.set_value('insert_after', insert_after); + } } - - frm.set_value('insert_after', insert_after); } }); diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index 10eca8a1b9..30ad859a1c 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -8,6 +8,7 @@ from frappe.utils import cstr from frappe import _ from frappe.model.document import Document from frappe.model.docfield import supports_translation +from frappe.model import core_doctypes_list class CustomField(Document): def autoname(self): @@ -86,6 +87,14 @@ class CustomField(Document): @frappe.whitelist() def get_fields_label(doctype=None): + meta = frappe.get_meta(doctype) + + if doctype in core_doctypes_list: + return frappe.msgprint(_("Custom Fields cannot be added to core DocTypes.")) + + if meta.custom: + return frappe.msgprint(_("Custom Fields can only be added to a standard DocType.")) + return [{"value": df.fieldname or "", "label": _(df.label or "")} for df in frappe.get_meta(doctype).get("fields")] diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js index 227c6de1ee..125db507ea 100644 --- a/frappe/custom/doctype/customize_form/customize_form.js +++ b/frappe/custom/doctype/customize_form/customize_form.js @@ -13,9 +13,7 @@ frappe.ui.form.on("Customize Form", { filters: [ ['DocType', 'issingle', '=', 0], ['DocType', 'custom', '=', 0], - ['DocType', 'name', 'not in', 'DocType, DocField, DocPerm, User, Role, Has Role, \ - Page, Has Role, Module Def, Print Format, Report, Customize Form, \ - Customize Form Field, Property Setter, Custom Field, Custom Script'], + ['DocType', 'name', 'not in', frappe.model.core_doctypes_list], ['DocType', 'restrict_to_domain', 'in', frappe.boot.active_domains] ] }; @@ -39,8 +37,14 @@ frappe.ui.form.on("Customize Form", { doc: frm.doc, freeze: true, callback: function(r) { - frm.refresh(); - frm.trigger("setup_sortable"); + if(r) { + if(r._server_messages && r._server_messages.length) { + frm.set_value("doc_type", ""); + } else { + frm.refresh(); + frm.trigger("setup_sortable"); + } + } } }); } else { diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 842db9c18c..6ddad0d984 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -11,7 +11,7 @@ import frappe.translate from frappe import _ from frappe.utils import cint from frappe.model.document import Document -from frappe.model import no_value_fields +from frappe.model import no_value_fields, core_doctypes_list from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype from frappe.model.docfield import supports_translation @@ -85,6 +85,12 @@ class CustomizeForm(Document): meta = frappe.get_meta(self.doc_type) + if self.doc_type in core_doctypes_list: + return frappe.msgprint(_("Core DocTypes cannot be customized.")) + + if meta.custom: + return frappe.msgprint(_("Only standard DocTypes are allowed to be customized from Customize Form.")) + # doctype properties for property in doctype_properties: self.set(property, meta.get(property)) diff --git a/frappe/custom/doctype/customize_form/test_customize_form.py b/frappe/custom/doctype/customize_form/test_customize_form.py index f76fc52609..7df3eedb5e 100644 --- a/frappe/custom/doctype/customize_form/test_customize_form.py +++ b/frappe/custom/doctype/customize_form/test_customize_form.py @@ -9,28 +9,28 @@ from frappe.core.doctype.doctype.doctype import InvalidFieldNameError test_dependencies = ["Custom Field", "Property Setter"] class TestCustomizeForm(unittest.TestCase): def insert_custom_field(self): - frappe.delete_doc_if_exists("Custom Field", "User-test_custom_field") + frappe.delete_doc_if_exists("Custom Field", "Event-test_custom_field") frappe.get_doc({ "doctype": "Custom Field", - "dt": "User", + "dt": "Event", "label": "Test Custom Field", "description": "A Custom Field for Testing", "fieldtype": "Select", "in_list_view": 1, "options": "\nCustom 1\nCustom 2\nCustom 3", "default": "Custom 3", - "insert_after": frappe.get_meta('User').fields[-1].fieldname + "insert_after": frappe.get_meta('Event').fields[-1].fieldname }).insert() def setUp(self): self.insert_custom_field() frappe.db.commit() - frappe.clear_cache(doctype="User") + frappe.clear_cache(doctype="Event") def tearDown(self): - frappe.delete_doc("Custom Field", "User-test_custom_field") + frappe.delete_doc("Custom Field", "Event-test_custom_field") frappe.db.commit() - frappe.clear_cache(doctype="User") + frappe.clear_cache(doctype="Event") def get_customize_form(self, doctype=None): d = frappe.get_doc("Customize Form") @@ -45,78 +45,67 @@ class TestCustomizeForm(unittest.TestCase): self.assertEqual(len(d.get("fields")), 0) d = self.get_customize_form("Event") - self.assertEqual(d.doc_type, "Event") - self.assertEqual(len(d.get("fields")), 27) + self.assertEquals(d.doc_type, "Event") + self.assertEquals(len(d.get("fields")), 30) - d = self.get_customize_form("User") - self.assertEqual(d.doc_type, "User") + d = self.get_customize_form("Event") + self.assertEquals(d.doc_type, "Event") self.assertEqual(len(d.get("fields")), len(frappe.get_doc("DocType", d.doc_type).fields) + 1) - self.assertEqual(d.get("fields")[-1].fieldname, "test_custom_field") - self.assertEqual(d.get("fields", {"fieldname": "location"})[0].in_list_view, 1) + self.assertEquals(d.get("fields")[-1].fieldname, "test_custom_field") + self.assertEquals(d.get("fields", {"fieldname": "event_type"})[0].in_list_view, 1) return d def test_save_customization_property(self): - d = self.get_customize_form("User") - self.assertEqual(frappe.db.get_value("Property Setter", - {"doc_type": "User", "property": "allow_copy"}, "value"), None) + d = self.get_customize_form("Event") + self.assertEquals(frappe.db.get_value("Property Setter", + {"doc_type": "Event", "property": "allow_copy"}, "value"), None) d.allow_copy = 1 d.run_method("save_customization") - self.assertEqual(frappe.db.get_value("Property Setter", - {"doc_type": "User", "property": "allow_copy"}, "value"), '1') + self.assertEquals(frappe.db.get_value("Property Setter", + {"doc_type": "Event", "property": "allow_copy"}, "value"), '1') d.allow_copy = 0 d.run_method("save_customization") - self.assertEqual(frappe.db.get_value("Property Setter", - {"doc_type": "User", "property": "allow_copy"}, "value"), None) + self.assertEquals(frappe.db.get_value("Property Setter", + {"doc_type": "Event", "property": "allow_copy"}, "value"), None) def test_save_customization_field_property(self): - d = self.get_customize_form("User") - self.assertEqual(frappe.db.get_value("Property Setter", - {"doc_type": "User", "property": "reqd", "field_name": "location"}, "value"), None) + d = self.get_customize_form("Event") + self.assertEquals(frappe.db.get_value("Property Setter", + {"doc_type": "Event", "property": "reqd", "field_name": "repeat_this_event"}, "value"), None) - location_field = d.get("fields", {"fieldname": "location"})[0] - location_field.reqd = 1 + repeat_this_event_field = d.get("fields", {"fieldname": "repeat_this_event"})[0] + repeat_this_event_field.reqd = 1 d.run_method("save_customization") - self.assertEqual(frappe.db.get_value("Property Setter", - {"doc_type": "User", "property": "reqd", "field_name": "location"}, "value"), '1') + self.assertEquals(frappe.db.get_value("Property Setter", + {"doc_type": "Event", "property": "reqd", "field_name": "repeat_this_event"}, "value"), '1') - location_field = d.get("fields", {"fieldname": "location"})[0] - location_field.reqd = 0 + repeat_this_event_field = d.get("fields", {"fieldname": "repeat_this_event"})[0] + repeat_this_event_field.reqd = 0 d.run_method("save_customization") - self.assertEqual(frappe.db.get_value("Property Setter", - {"doc_type": "User", "property": "reqd", "field_name": "location"}, "value"), None) - - # for not allowing to change mandatory property of standard fields - self.assertEqual(frappe.db.get_value("Property Setter", - {"doc_type": "User", "property": "reqd", "field_name": "email"}, "value"), None) - - email_field = d.get("fields", {"fieldname": "email"})[0] - email_field.reqd = 0 - d.run_method("save_customization") - - self.assertEqual(frappe.db.get_value("Property Setter", - {"doc_type": "User", "property": "reqd", "field_name": "email"}, "value"), None) + self.assertEquals(frappe.db.get_value("Property Setter", + {"doc_type": "Event", "property": "reqd", "field_name": "repeat_this_event"}, "value"), None) def test_save_customization_custom_field_property(self): - d = self.get_customize_form("User") - self.assertEqual(frappe.db.get_value("Custom Field", "User-test_custom_field", "reqd"), 0) + d = self.get_customize_form("Event") + self.assertEquals(frappe.db.get_value("Custom Field", "Event-test_custom_field", "reqd"), 0) custom_field = d.get("fields", {"fieldname": "test_custom_field"})[0] custom_field.reqd = 1 d.run_method("save_customization") - self.assertEqual(frappe.db.get_value("Custom Field", "User-test_custom_field", "reqd"), 1) + self.assertEquals(frappe.db.get_value("Custom Field", "Event-test_custom_field", "reqd"), 1) custom_field = d.get("fields", {"is_custom_field": True})[0] custom_field.reqd = 0 d.run_method("save_customization") - self.assertEqual(frappe.db.get_value("Custom Field", "User-test_custom_field", "reqd"), 0) + self.assertEquals(frappe.db.get_value("Custom Field", "Event-test_custom_field", "reqd"), 0) def test_save_customization_new_field(self): - d = self.get_customize_form("User") + d = self.get_customize_form("Event") last_fieldname = d.fields[-1].fieldname d.append("fields", { "label": "Test Add Custom Field Via Customize Form", @@ -124,19 +113,19 @@ class TestCustomizeForm(unittest.TestCase): "is_custom_field": 1 }) d.run_method("save_customization") - self.assertEqual(frappe.db.get_value("Custom Field", - "User-test_add_custom_field_via_customize_form", "fieldtype"), "Data") + self.assertEquals(frappe.db.get_value("Custom Field", + "Event-test_add_custom_field_via_customize_form", "fieldtype"), "Data") - self.assertEqual(frappe.db.get_value("Custom Field", - "User-test_add_custom_field_via_customize_form", 'insert_after'), last_fieldname) + self.assertEquals(frappe.db.get_value("Custom Field", + "Event-test_add_custom_field_via_customize_form", 'insert_after'), last_fieldname) - frappe.delete_doc("Custom Field", "User-test_add_custom_field_via_customize_form") - self.assertEqual(frappe.db.get_value("Custom Field", - "User-test_add_custom_field_via_customize_form"), None) + frappe.delete_doc("Custom Field", "Event-test_add_custom_field_via_customize_form") + self.assertEquals(frappe.db.get_value("Custom Field", + "Event-test_add_custom_field_via_customize_form"), None) def test_save_customization_remove_field(self): - d = self.get_customize_form("User") + d = self.get_customize_form("Event") custom_field = d.get("fields", {"fieldname": "test_custom_field"})[0] d.get("fields").remove(custom_field) d.run_method("save_customization") @@ -148,24 +137,24 @@ class TestCustomizeForm(unittest.TestCase): def test_reset_to_defaults(self): d = frappe.get_doc("Customize Form") - d.doc_type = "User" + d.doc_type = "Event" d.run_method('reset_to_defaults') - self.assertEqual(d.get("fields", {"fieldname": "location"})[0].in_list_view, 0) + self.assertEquals(d.get("fields", {"fieldname": "repeat_this_event"})[0].in_list_view, 0) frappe.local.test_objects["Property Setter"] = [] make_test_records_for_doctype("Property Setter") def test_set_allow_on_submit(self): - d = self.get_customize_form("User") - d.get("fields", {"fieldname": "first_name"})[0].allow_on_submit = 1 + d = self.get_customize_form("Event") + d.get("fields", {"fieldname": "subject"})[0].allow_on_submit = 1 d.get("fields", {"fieldname": "test_custom_field"})[0].allow_on_submit = 1 d.run_method("save_customization") - d = self.get_customize_form("User") + d = self.get_customize_form("Event") # don't allow for standard fields - self.assertEqual(d.get("fields", {"fieldname": "first_name"})[0].allow_on_submit or 0, 0) + self.assertEquals(d.get("fields", {"fieldname": "subject"})[0].allow_on_submit or 0, 0) # allow for custom field self.assertEqual(d.get("fields", {"fieldname": "test_custom_field"})[0].allow_on_submit, 1) @@ -193,4 +182,12 @@ class TestCustomizeForm(unittest.TestCase): # undo df.default = None - d.run_method("save_customization") \ No newline at end of file + d.run_method("save_customization") + + def test_core_doctype_customization(self): + d = self.get_customize_form('User') + e = self.get_customize_form('Custom Field') + + # core doctype is invalid, hence no attributes are set + self.assertEquals(d.get("fields"), []) + self.assertEquals(e.get("fields"), []) diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index 8c8a4bea32..a470072b19 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -12,6 +12,9 @@ display_fieldtypes = ('Section Break', 'Column Break', 'HTML', 'Button', 'Image' default_fields = ('doctype','name','owner','creation','modified','modified_by', 'parent','parentfield','parenttype','idx','docstatus') optional_fields = ("_user_tags", "_comments", "_assign", "_liked_by", "_seen") +core_doctypes_list = ('DocType', 'DocField', 'DocPerm', 'User', 'Role', 'Has Role', + 'Page', 'Module Def', 'Print Format', 'Report', 'Customize Form', + 'Customize Form Field', 'Property Setter', 'Custom Field', 'Custom Script') def copytables(srctype, src, srcfield, tartype, tar, tarfield, srcfields, tarfields=[]): if not tarfields: diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index 37e75489a4..a829386450 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -504,6 +504,9 @@ frappe.ui.form.Layout = Class.extend({ } else if(expression.substr(0,5)=='eval:') { try { out = eval(expression.substr(5)); + if(parent && parent.istable && expression.includes('is_submittable')) { + out = true; + } } catch(e) { frappe.throw(__('Invalid "depends_on" expression')); } diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 1d36bebdfa..29bdbd1b92 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -971,9 +971,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { items.push({ label: __('Customize'), - action: () => frappe.set_route('Form', 'Customize Form', { - doc_type: doctype - }), + action: () => { + if(this.meta && !this.meta.custom) { + frappe.set_route('Form', 'Customize Form', { + doc_type: doctype + }); + } + }, standard: true }); } diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index 5db9728cfe..b0d6975354 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -13,6 +13,10 @@ $.extend(frappe.model, { '_user_tags', '_comments', '_assign', '_liked_by', 'docstatus', 'parent', 'parenttype', 'parentfield', 'idx'], + core_doctypes_list: ['DocType', 'DocField', 'DocPerm', 'User', 'Role', 'Has Role', + 'Page', 'Module Def', 'Print Format', 'Report', 'Customize Form', + 'Customize Form Field', 'Property Setter', 'Custom Field', 'Custom Script'], + std_fields: [ {fieldname:'name', fieldtype:'Link', label:__('ID')}, {fieldname:'owner', fieldtype:'Link', label:__('Created By'), options: 'User'}, diff --git a/frappe/public/js/frappe/ui/toolbar/toolbar.js b/frappe/public/js/frappe/ui/toolbar/toolbar.js index 88c63cd9a0..d8225e43d3 100644 --- a/frappe/public/js/frappe/ui/toolbar/toolbar.js +++ b/frappe/public/js/frappe/ui/toolbar/toolbar.js @@ -109,7 +109,6 @@ frappe.ui.toolbar.Toolbar = Class.extend({ $("#input-help").on("keydown", function (e) { if(e.which == 13) { - var keywords = $(this).val(); $(this).val(""); } }); diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index 45d14c0595..8c43bf33ce 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -14,6 +14,11 @@ def get_pdf(html, options=None, output = None): html, options = prepare_options(html, options) fname = os.path.join("/tmp", "frappe-pdf-{0}.pdf".format(frappe.generate_hash())) + options.update({ + "disable-javascript": "", + "disable-local-file-access": "", + }) + try: pdfkit.from_string(html, fname, options=options or {}) if output: