diff --git a/frappe/__init__.py b/frappe/__init__.py index f59a4c0be2..cdfe372715 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1340,4 +1340,18 @@ def safe_eval(code, eval_globals=None, eval_locals=None): eval_globals.update(whitelisted_globals) - return eval(code, eval_globals, eval_locals) \ No newline at end of file + return eval(code, eval_globals, eval_locals) + +def get_active_domains(): + """ get the domains set in the Domain Settings as active domain """ + + active_domains = cache().hget("domains", "active_domains") or None + if active_domains is None: + domains = get_all("Has Domain", filters={ "parent": "Domain Settings" }, + fields=["domain"], distinct=True) + + active_domains = [row.get("domain") for row in domains] + active_domains.append("") + cache().hset("domains", "active_domains", active_domains) + + return active_domains \ No newline at end of file diff --git a/frappe/boot.py b/frappe/boot.py index 7f6edf8322..9f2db7bc69 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -37,6 +37,8 @@ def get_bootinfo(): bootinfo.module_list = [] load_desktop_icons(bootinfo) bootinfo.letter_heads = get_letter_heads() + bootinfo.active_domains = frappe.get_active_domains() + bootinfo.all_domains = [d.get("name") for d in frappe.get_all("Domain")] bootinfo.module_app = frappe.local.module_app bootinfo.single_types = frappe.db.sql_list("""select name from tabDocType diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 14851244ea..acd825f275 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -15,6 +15,7 @@ "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -44,6 +45,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -75,6 +77,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -106,6 +109,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -138,6 +142,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -169,6 +174,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -196,6 +202,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -227,6 +234,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -258,6 +266,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -286,6 +295,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -315,6 +325,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -346,6 +357,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -374,6 +386,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -403,6 +416,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -434,6 +448,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -462,6 +477,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -493,6 +509,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -524,6 +541,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -552,6 +570,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -582,6 +601,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -611,6 +631,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -641,6 +662,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -672,6 +694,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -702,6 +725,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -733,6 +757,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -761,6 +786,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -793,6 +819,38 @@ "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "restrict_to_domain", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Restrict To Domain", + "length": 0, + "no_copy": 0, + "options": "Domain", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -821,6 +879,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -850,6 +909,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -880,6 +940,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -910,6 +971,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -940,6 +1002,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -970,6 +1033,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -999,6 +1063,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1028,6 +1093,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1060,6 +1126,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1088,6 +1155,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1116,6 +1184,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1144,6 +1213,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1173,6 +1243,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1203,6 +1274,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1231,6 +1303,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1261,6 +1334,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1289,6 +1363,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1318,6 +1393,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1349,6 +1425,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1380,6 +1457,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1408,6 +1486,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1438,6 +1517,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1468,6 +1548,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1497,6 +1578,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1527,6 +1609,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1558,6 +1641,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1586,6 +1670,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1616,6 +1701,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1647,6 +1733,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -1676,6 +1763,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1715,13 +1803,12 @@ "idx": 6, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-03-03 15:55:30.792653", - "modified_by": "Administrator", + "modified": "2017-05-03 16:15:40.198072", + "modified_by": "makarand@erpnext.com", "module": "Core", "name": "DocType", "owner": "Administrator", diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index c06bde9690..678e52451a 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -28,4 +28,4 @@ class TestDocType(unittest.TestCase): frappe.delete_doc("DocType", name) doc = self.new_doctype(name).insert() - doc.delete() + doc.delete() \ No newline at end of file diff --git a/frappe/core/doctype/domain/__init__.py b/frappe/core/doctype/domain/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/domain/domain.js b/frappe/core/doctype/domain/domain.js new file mode 100644 index 0000000000..397ed4b19c --- /dev/null +++ b/frappe/core/doctype/domain/domain.js @@ -0,0 +1,8 @@ +// Copyright (c) 2017, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Domain', { + refresh: function(frm) { + + } +}); diff --git a/frappe/core/doctype/domain/domain.json b/frappe/core/doctype/domain/domain.json new file mode 100644 index 0000000000..b0c4e89f14 --- /dev/null +++ b/frappe/core/doctype/domain/domain.json @@ -0,0 +1,95 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:domain", + "beta": 0, + "creation": "2017-05-03 15:07:39.752820", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "domain", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Domain", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-05-09 11:39:03.877720", + "modified_by": "Administrator", + "module": "Core", + "name": "Domain", + "name_case": "", + "owner": "makarand@erpnext.com", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 1, + "read_only_onload": 0, + "search_fields": "domain", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "domain", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/core/doctype/domain/domain.py b/frappe/core/doctype/domain/domain.py new file mode 100644 index 0000000000..d8a571537c --- /dev/null +++ b/frappe/core/doctype/domain/domain.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class Domain(Document): + pass diff --git a/frappe/core/doctype/domain/test_domain.py b/frappe/core/doctype/domain/test_domain.py new file mode 100644 index 0000000000..8e0bc65c54 --- /dev/null +++ b/frappe/core/doctype/domain/test_domain.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestDomain(unittest.TestCase): + pass diff --git a/frappe/core/doctype/domain_settings/__init__.py b/frappe/core/doctype/domain_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/domain_settings/domain_settings.js b/frappe/core/doctype/domain_settings/domain_settings.js new file mode 100644 index 0000000000..39cd8d5dd9 --- /dev/null +++ b/frappe/core/doctype/domain_settings/domain_settings.js @@ -0,0 +1,57 @@ +// Copyright (c) 2017, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Domain Settings', { + onload: function(frm) { + let domains = $('
') + .appendTo(frm.fields_dict.domains_html.wrapper); + + if(!frm.domain_editor) { + frm.domain_editor = new frappe.DomainsEditor(domains, frm); + } + + frm.domain_editor.show(); + }, + + validate: function(frm) { + if(frm.domain_editor) { + frm.domain_editor.set_items_in_table(); + } + }, +}); + +frappe.DomainsEditor = frappe.CheckboxEditor.extend({ + init: function(wrapper, frm) { + var opts = {}; + $.extend(opts, { + wrapper: wrapper, + frm: frm, + field_mapper: { + child_table_field: "active_domains", + item_field: "domain", + cdt: "Has Domain" + }, + attribute: 'data-domain', + checkbox_selector: false, + get_items: this.get_all_domains, + editor_template: this.get_template() + }); + + this._super(opts); + }, + + get_template: function() { + return ` +
+ + {{__(item)}} +
+ `; + }, + + get_all_domains: function() { + // return all the domains available in the system + this.items = frappe.boot.all_domains; + this.render_items(); + }, +}); \ No newline at end of file diff --git a/frappe/core/doctype/domain_settings/domain_settings.json b/frappe/core/doctype/domain_settings/domain_settings.json new file mode 100644 index 0000000000..9d65159099 --- /dev/null +++ b/frappe/core/doctype/domain_settings/domain_settings.json @@ -0,0 +1,153 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-05-03 16:28:11.295095", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "domains", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Domains", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "domains_html", + "fieldtype": "HTML", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Domains", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "active_domains", + "fieldtype": "Table", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Active Domains", + "length": 0, + "no_copy": 0, + "options": "Has Domain", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2017-05-12 17:01:18.615000", + "modified_by": "Administrator", + "module": "Core", + "name": "Domain Settings", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/core/doctype/domain_settings/domain_settings.py b/frappe/core/doctype/domain_settings/domain_settings.py new file mode 100644 index 0000000000..7ef3654567 --- /dev/null +++ b/frappe/core/doctype/domain_settings/domain_settings.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class DomainSettings(Document): + def on_update(self): + cache = frappe.cache() + cache.delete_key("domains", "active_domains") \ No newline at end of file diff --git a/frappe/core/doctype/has_domain/__init__.py b/frappe/core/doctype/has_domain/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/has_domain/has_domain.json b/frappe/core/doctype/has_domain/has_domain.json new file mode 100644 index 0000000000..bfc1764138 --- /dev/null +++ b/frappe/core/doctype/has_domain/has_domain.json @@ -0,0 +1,72 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-05-03 15:20:22.326623", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "domain", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Domain", + "length": 0, + "no_copy": 0, + "options": "Domain", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-05-04 11:05:54.750351", + "modified_by": "Administrator", + "module": "Core", + "name": "Has Domain", + "name_case": "", + "owner": "makarand@erpnext.com", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/core/doctype/has_domain/has_domain.py b/frappe/core/doctype/has_domain/has_domain.py new file mode 100644 index 0000000000..6381996035 --- /dev/null +++ b/frappe/core/doctype/has_domain/has_domain.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class HasDomain(Document): + pass diff --git a/frappe/core/doctype/page/page.json b/frappe/core/doctype/page/page.json index df8fe91906..59c94ec35d 100644 --- a/frappe/core/doctype/page/page.json +++ b/frappe/core/doctype/page/page.json @@ -14,6 +14,7 @@ "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -43,6 +44,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -72,6 +74,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -102,6 +105,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -130,6 +134,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -158,6 +163,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -185,6 +191,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -216,6 +223,38 @@ "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "restrict_to_domain", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Restrict To Domain", + "length": 0, + "no_copy": 0, + "options": "Domain", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -247,6 +286,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -274,6 +314,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -317,7 +358,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-04-12 16:39:15.179130", + "modified": "2017-05-03 17:24:10.162110", "modified_by": "Administrator", "module": "Core", "name": "Page", diff --git a/frappe/core/doctype/role/role.json b/frappe/core/doctype/role/role.json index d665707efd..104ee7d53c 100644 --- a/frappe/core/doctype/role/role.json +++ b/frappe/core/doctype/role/role.json @@ -9,9 +9,11 @@ "custom": 0, "docstatus": 0, "doctype": "DocType", + "document_type": "Document", "editable_grid": 0, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -42,6 +44,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -72,6 +75,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -100,6 +104,37 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "restrict_to_domain", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Restrict To Domain", + "length": 0, + "no_copy": 0, + "options": "Domain", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "has_web_view": 0, @@ -113,7 +148,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-04-18 01:50:32.995937", + "modified": "2017-05-04 11:03:41.533058", "modified_by": "Administrator", "module": "Core", "name": "Role", diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index a2e05029cb..73ea87d0a7 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -490,8 +490,17 @@ def get_timezones(): @frappe.whitelist() def get_all_roles(arg=None): """return all roles""" - return [r[0] for r in frappe.db.sql("""select name from tabRole - where name not in ('Administrator', 'Guest', 'All') and not disabled order by name""")] + active_domains = frappe.get_active_domains() + + roles = frappe.get_all("Role", filters={ + "name": ("not in", "Administrator,Guest,All"), + "disabled": 0 + }, or_filters={ + "ifnull(restrict_to_domain, '')": "", + "restrict_to_domain": ("in", active_domains) + }, order_by="name") + + return [ role.get("name") for role in roles ] @frappe.whitelist() def get_roles(arg=None): diff --git a/frappe/core/page/permission_manager/permission_manager.py b/frappe/core/page/permission_manager/permission_manager.py index 279c85a8d3..3f787a42fc 100644 --- a/frappe/core/page/permission_manager/permission_manager.py +++ b/frappe/core/page/permission_manager/permission_manager.py @@ -14,11 +14,28 @@ from frappe import _ def get_roles_and_doctypes(): frappe.only_for("System Manager") send_translations(frappe.get_lang_dict("doctype", "DocPerm")) + + active_domains = frappe.get_active_domains() + + doctypes = frappe.get_all("DocType", filters={ + "istable": 0, + "name": ("not in", "DocType"), + }, or_filters={ + "ifnull(restrict_to_domain, '')": "", + "restrict_to_domain": ("in", active_domains) + }, fields=["name"]) + + roles = frappe.get_all("Role", filters={ + "name": ("not in", "Administrator"), + "disabled": 0, + }, or_filters={ + "ifnull(restrict_to_domain, '')": "", + "restrict_to_domain": ("in", active_domains) + }, fields=["name"]) + return { - "doctypes": [d[0] for d in frappe.db.sql("""select name from `tabDocType` dt where - istable=0 and name not in ('DocType')""")], - "roles": [d[0] for d in frappe.db.sql("""select name from tabRole where - name != 'Administrator' and disabled=0""")] + "doctypes": [d.get("name") for d in doctypes], + "roles": [d.get("name") for d in roles] } @frappe.whitelist() diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js index 6515805a85..77b489241b 100644 --- a/frappe/custom/doctype/customize_form/customize_form.js +++ b/frappe/custom/doctype/customize_form/customize_form.js @@ -15,7 +15,8 @@ frappe.ui.form.on("Customize Form", { ['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'] + Customize Form Field'], + ['DocType', 'restrict_to_domain', 'in', frappe.boot.active_domains] ] }; }); diff --git a/frappe/desk/doctype/desktop_icon/desktop_icon.py b/frappe/desk/doctype/desktop_icon/desktop_icon.py index e53266a38f..2a9e38fab9 100644 --- a/frappe/desk/doctype/desktop_icon/desktop_icon.py +++ b/frappe/desk/doctype/desktop_icon/desktop_icon.py @@ -32,27 +32,40 @@ def get_desktop_icons(user=None): fields = ['module_name', 'hidden', 'label', 'link', 'type', 'icon', 'color', '_doctype', '_report', 'idx', 'force_show', 'reverse', 'custom', 'standard', 'blocked'] + active_domains = frappe.get_active_domains() + + blocked_doctypes = frappe.get_all("DocType", filters={ + "ifnull(restrict_to_domain, '')": ("not in", ",".join(active_domains)) + }, fields=["name"]) + + blocked_doctypes = [ d.get("name") for d in blocked_doctypes ] + standard_icons = frappe.db.get_all('Desktop Icon', fields=fields, filters={'standard': 1}) standard_map = {} for icon in standard_icons: + if icon._doctype in blocked_doctypes: + icon.blocked = 1 standard_map[icon.module_name] = icon user_icons = frappe.db.get_all('Desktop Icon', fields=fields, filters={'standard': 0, 'owner': user}) - # update hidden property for icon in user_icons: standard_icon = standard_map.get(icon.module_name, None) + if icon._doctype in blocked_doctypes: + icon.blocked = 1 + # override properties from standard icon if standard_icon: for key in ('route', 'label', 'color', 'icon', 'link'): if standard_icon.get(key): icon[key] = standard_icon.get(key) + if standard_icon.blocked: icon.hidden = 1 diff --git a/frappe/desk/moduleview.py b/frappe/desk/moduleview.py index 7a15b14597..d365f3d511 100644 --- a/frappe/desk/moduleview.py +++ b/frappe/desk/moduleview.py @@ -55,6 +55,27 @@ def build_config_from_file(module): except ImportError: pass + return filter_by_restrict_to_domain(data) + +def filter_by_restrict_to_domain(data): + """ filter Pages and DocType depending on the Active Module(s) """ + mapper = { + "page": "Page", + "doctype": "DocType" + } + active_domains = frappe.get_active_domains() + + for d in data: + _items = [] + for item in d.get("items", []): + doctype = mapper.get(item.get("type")) + + doctype_domain = frappe.db.get_value(doctype, item.get("name"), "restrict_to_domain") or '' + if not doctype_domain or (doctype_domain in active_domains): + _items.append(item) + + d.update({ "items": _items }) + return data def build_standard_config(module, doctype_info): @@ -95,11 +116,16 @@ def add_custom_doctypes(data, doctype_info): def get_doctype_info(module): """Returns list of non child DocTypes for given module.""" - doctype_info = frappe.db.sql("""select "doctype" as type, name, description, - ifnull(document_type, "") as document_type, custom as custom, - issingle as issingle - from `tabDocType` where module=%s and istable=0 - order by custom asc, document_type desc, name asc""", module, as_dict=True) + active_domains = frappe.get_active_domains() + + doctype_info = frappe.get_all("DocType", filters={ + "module": module, + "istable": 0 + }, or_filters={ + "ifnull(restrict_to_domain, '')": "", + "restrict_to_domain": ("in", active_domains) + }, fields=["'doctype' as type", "name", "description", "ifnull(document_type, '') as document_type", + "custom", "issingle"], order_by="custom asc, document_type desc, name asc") for d in doctype_info: d.description = _(d.description or "") diff --git a/frappe/public/build.json b/frappe/public/build.json index bb2216727d..f7a399e611 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -90,6 +90,7 @@ "public/js/frappe/socketio_client.js", "public/js/frappe/router.js", "public/js/frappe/defaults.js", + "public/js/frappe/checkbox_editor.js", "public/js/frappe/roles_editor.js", "public/js/lib/microtemplate.js", diff --git a/frappe/public/js/frappe/checkbox_editor.js b/frappe/public/js/frappe/checkbox_editor.js new file mode 100644 index 0000000000..45891cb882 --- /dev/null +++ b/frappe/public/js/frappe/checkbox_editor.js @@ -0,0 +1,140 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +// opts: +// frm +// wrapper +// get_items +// add_btn_label +// remove_btn_label +// field_mapper: +// cdt +// child_table_field +// item_field +// attribute + +frappe.CheckboxEditor = Class.extend({ + init: function(opts) { + $.extend(this, opts); + + this.doctype = this.field_mapper.cdt; + this.fieldname = this.field_mapper.child_table_field; + this.item_fieldname = this.field_mapper.item_field; + + $(this.wrapper).html('
' + __("Loading") + '...
'); + + if(this.get_items) { + this.get_items(); + } + }, + render_items: function(callback) { + let me = this; + $(this.wrapper).empty(); + + if(this.checkbox_selector) { + let toolbar = $('

\ +

').appendTo($(this.wrapper)); + + toolbar.find(".btn-add") + .html(__(this.add_btn_label)) + .on("click", function() { + $(me.wrapper).find('input[type="checkbox"]').each(function(i, check) { + if(!$(check).is(":checked")) { + check.checked = true; + } + }); + }); + + toolbar.find(".btn-remove") + .html(__(this.remove_btn_label)) + .on("click", function() { + $(me.wrapper).find('input[type="checkbox"]').each(function(i, check) { + if($(check).is(":checked")) { + check.checked = false; + } + }); + }); + } + + $.each(this.items, function(i, item) { + $(me.wrapper).append(frappe.render(me.editor_template, {'item': item})); + }); + + $(this.wrapper).find('input[type="checkbox"]').change(function() { + if(me.fieldname && me.doctype && me.item_field) { + me.set_items_in_table(); + me.frm.dirty(); + } + }); + + callback && callback() + }, + show: function() { + let me = this; + + // uncheck all items + $(this.wrapper).find('input[type="checkbox"]') + .each(function(i, checkbox) { checkbox.checked = false; }); + + // set user items as checked + $.each((me.frm.doc[this.fieldname] || []), function(i, row) { + let selector = repl('[%(attribute)s="%(value)s"] input[type="checkbox"]', { + attribute: me.attribute, + value: row[me.item_fieldname] + }); + + let checkbox = $(me.wrapper) + .find(selector).get(0); + if(checkbox) checkbox.checked = true; + }); + }, + + get_selected_unselected_items: function() { + let checked_items = []; + let unchecked_items = []; + let selector = repl('[%(attribute)s]', { attribute: this.attribute }); + let me = this; + + $(this.wrapper).find(selector).each(function() { + if($(this).find('input[type="checkbox"]:checked').length) { + checked_items.push($(this).attr(me.attribute)); + } else { + unchecked_items.push($(this).attr(me.attribute)); + } + }); + + return { + checked_items: checked_items, + unchecked_items: unchecked_items + } + }, + + set_items_in_table: function() { + let opts = this.get_selected_unselected_items(); + let existing_items_map = {}; + let existing_items_list = []; + let me = this; + + $.each(me.frm.doc[this.fieldname] || [], function(i, row) { + existing_items_map[row[me.item_fieldname]] = row.name; + existing_items_list.push(row[me.item_fieldname]); + }); + + // remove unchecked items + $.each(opts.unchecked_items, function(i, item) { + if(existing_items_list.indexOf(item)!=-1) { + frappe.model.clear_doc(me.doctype, existing_items_map[item]); + } + }); + + // add new items that are checked + $.each(opts.checked_items, function(i, item) { + if(existing_items_list.indexOf(item)==-1) { + let row = frappe.model.add_child(me.frm.doc, me.doctype, me.fieldname); + row[me.item_fieldname] = item; + } + }); + + refresh_field(this.fieldname); + } +}); \ No newline at end of file diff --git a/frappe/tests/test_domainification.py b/frappe/tests/test_domainification.py new file mode 100644 index 0000000000..58cac79f46 --- /dev/null +++ b/frappe/tests/test_domainification.py @@ -0,0 +1,136 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt +from __future__ import unicode_literals + +import unittest, frappe +from frappe.core.page.permission_manager.permission_manager import get_roles_and_doctypes +from frappe.desk.doctype.desktop_icon.desktop_icon import (get_desktop_icons, add_user_icon, + clear_desktop_icons_cache) + +class TestDomainification(unittest.TestCase): + def setUp(self): + # create test domain + self.new_domain("_Test Domain 1") + self.new_domain("_Test Domain 2") + + self.remove_from_active_domains(remove_all=True) + self.add_active_domain("_Test Domain 1") + + def tearDown(self): + frappe.db.sql("delete from tabRole where name='_Test Role'") + frappe.db.sql("delete from tabDomain where name in ('_Test Domain 1', '_Test Domain 2')") + frappe.delete_doc('DocType', 'Test Domainification') + + def add_active_domain(self, domain): + """ add domain in active domain """ + + if not domain: + return + + domain_settings = frappe.get_doc("Domain Settings", "Domain Settings") + domain_settings.append("active_domains", { "domain": domain }) + domain_settings.save() + + def remove_from_active_domains(self, domain=None, remove_all=False): + """ remove domain from domain settings """ + if not domain: + return + + domain_settings = frappe.get_doc("Domain Settings", "Domain Settings") + + if remove_all: + domain_settings.set("active_domains", []) + else: + to_remove = [] + [ to_remove.append(row) for row in domain_settings.active_domains if row.domain == domain ] + [ domain_settings.remove(row) for row in to_remove ] + + domain_settings.save() + + def new_domain(self, domain): + # create new domain + frappe.get_doc({ + "doctype": "Domain", + "domain": domain + }).insert() + + def new_doctype(self, name): + return frappe.get_doc({ + "doctype": "DocType", + "module": "Core", + "custom": 1, + "fields": [{"label": "Some Field", "fieldname": "some_fieldname", "fieldtype": "Data"}], + "permissions": [{"role": "System Manager", "read": 1}], + "name": name + }) + + def test_active_domains(self): + self.assertTrue("_Test Domain 1" in frappe.get_active_domains()) + self.assertFalse("_Test Domain 2" in frappe.get_active_domains()) + + self.add_active_domain("_Test Domain 2") + self.assertTrue("_Test Domain 2" in frappe.get_active_domains()) + + self.remove_from_active_domains("_Test Domain 1") + self.assertTrue("_Test Domain 1" not in frappe.get_active_domains()) + + def test_doctype_and_role_domainification(self): + """ + test if doctype is hidden if the doctype's restrict to domain is not included + in active domains + """ + + test_doctype = self.new_doctype("Test Domainification") + test_doctype.insert() + + test_role = frappe.get_doc({ + "doctype": "Role", + "role_name": "_Test Role" + }).insert() + + # doctype should be hidden in desktop icon, role permissions + results = get_roles_and_doctypes() + self.assertTrue("Test Domainification" in results.get("doctypes")) + self.assertTrue("_Test Role" in results.get("roles")) + + self.add_active_domain("_Test Domain 2") + test_doctype.restrict_to_domain = "_Test Domain 2" + test_doctype.save() + + test_role.restrict_to_domain = "_Test Domain 2" + test_role.save() + + results = get_roles_and_doctypes() + self.assertTrue("Test Domainification" in results.get("doctypes")) + self.assertTrue("_Test Role" in results.get("roles")) + + self.remove_from_active_domains("_Test Domain 2") + results = get_roles_and_doctypes() + + self.assertTrue("Test Domainification" not in results.get("doctypes")) + self.assertTrue("_Test Role" not in results.get("roles")) + + def test_desktop_icon_for_domainification(self): + """ desktop icon should be hidden if doctype's restrict to domain is not in active domains """ + + test_doctype = self.new_doctype("Test Domainification") + test_doctype.restrict_to_domain = "_Test Domain 2" + test_doctype.insert() + + self.add_active_domain("_Test Domain 2") + add_user_icon('Test Domainification') + + icons = get_desktop_icons() + + doctypes = [icon.get("_doctype") for icon in icons if icon.get("_doctype") == "Test Domainification" \ + and icon.get("blocked") == 0] + self.assertTrue("Test Domainification" in doctypes) + + # doctype should be hidden from the desk + self.remove_from_active_domains("_Test Domain 2") + clear_desktop_icons_cache() # clear cache to fetch the desktop icon according to new active domains + icons = get_desktop_icons() + + doctypes = [icon.get("_doctype") for icon in icons if icon.get("_doctype") == "Test Domainification" \ + and icon.get("blocked") == 0] + self.assertFalse("Test Domainification" in doctypes)