diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py index 151a9b935f..d2022215c7 100644 --- a/frappe/desk/form/meta.py +++ b/frappe/desk/form/meta.py @@ -45,19 +45,6 @@ def get_meta(doctype, cached=True) -> "FormMeta": # In prod don't use cached meta when explicitly requesting from DB. meta = FormMeta(doctype, cached=frappe.conf.developer_mode) - if meta.name not in meta.special_doctypes: - meta = mask_protected_fields(meta) - - return meta - - -def mask_protected_fields(meta): - for df in meta.fields: - if df.get("mask") and not meta.has_permlevel_access_to( - fieldname=df.fieldname, df=df, permission_type="mask" - ): - # store orignal fieldtype and change fieldtype to Data - df.mask_readonly = 1 return meta @@ -89,6 +76,9 @@ class FormMeta(Meta): for k in ASSET_KEYS: d[k] = __dict.get(k) + # add masked fields (per-user, per-meta) + d["masked_fields"] = [df.fieldname for df in self.get_masked_fields()] + return d def add_code(self): diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 11dc18b7df..b95ebfa5b9 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -88,11 +88,6 @@ def get_meta(doctype: "str | DocType", cached: bool = True) -> "_Meta": meta = Meta(doctype) - if meta.name not in meta.special_doctypes: - from frappe.desk.form.meta import mask_protected_fields - - meta = mask_protected_fields(meta) - key = f"doctype_meta::{meta.name}" frappe.client_cache.set_value(key, meta) return meta @@ -200,7 +195,26 @@ class Meta(Document): return self._dynamic_link_fields def get_masked_fields(self): - return self.get("fields", {"mask_readonly": 1}) + import copy + + if frappe.session.user == "Administrator": + return [] + cache_key = f"masked_fields::{self.name}::{frappe.session.user}" + masked_fields = frappe.cache.get_value(cache_key) + + if masked_fields is None: + masked_fields = [] + for df in self.fields: + if df.get("mask") and not self.has_permlevel_access_to( + fieldname=df.fieldname, df=df, permission_type="mask" + ): + # work on a copy instead of original df + df_copy = copy.deepcopy(df) + df_copy.mask_readonly = 1 + masked_fields.append(df_copy) + frappe.cache.set_value(cache_key, masked_fields) + + return masked_fields @cached_property def _dynamic_link_fields(self): diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index c3effffc2a..8eab942aa1 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1174,11 +1174,11 @@ frappe.ui.form.Form = class FrappeForm { } mark_mask_fields_readonly() { - this.fields.forEach((field) => { - if (field.df.mask && field.df.mask_readonly) { - this.set_df_property(field.df.fieldname, "disabled", "1"); - this.set_df_property(field.df.fieldname, "fieldtype", "Data"); - } + const masked_fields = this.meta.masked_fields || []; + + masked_fields.forEach((fieldname) => { + this.set_df_property(fieldname, "read_only", 1); + this.set_df_property(fieldname, "fieldtype", "Data"); }); } diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 8a5b60f174..da916be9eb 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -417,7 +417,13 @@ frappe.form.get_formatter = function (fieldtype) { }; frappe.format = function (value, df, options, doc) { - if (!df || df?.mask_readonly) df = { fieldtype: "Data" }; + let mask_readonly = false; + if (df.parent) { + const mask_fields = frappe.get_meta(df.parent)?.masked_fields; + mask_readonly = mask_fields?.includes(df.fieldname); + } + + if (!df || mask_readonly) df = { fieldtype: "Data" }; if (df.fieldname == "_user_tags") df = { ...df, fieldtype: "Tag" }; var fieldtype = df.fieldtype || "Data"; diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index cb5445691a..fc50b9bfcd 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -245,12 +245,6 @@ frappe.ui.form.Layout = class Layout { } init_field(df, parent, render = false) { - if (df.mask && df.mask_readonly) { - if (df.fieldtype !== "Data") { - df.read_only = 1; - df.fieldtype = "Data"; - } - } const fieldobj = frappe.ui.form.make_control({ df: df, doctype: this.doctype, diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 16d75604ec..343f44b8f8 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -922,7 +922,10 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { _value = _value * out_of_ratings; } - let filterable = df?.mask_readonly ? "no-underline" : " filterable"; + let masked_fields = frappe.get_meta(this.doctype).masked_fields || []; + let is_masked = masked_fields.includes(df.fieldname); + + let filterable = is_masked ? "no-underline" : " filterable"; if (df.fieldtype === "Image") { html = df.options diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 75859d0dab..8f5b881e1c 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1090,22 +1090,14 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } update_masked_fields_in_columns(columns) { - const meta_fields = frappe.get_meta(this.report_doc?.ref_doctype).fields; + const masked_fields = frappe.get_meta(this.report_doc?.ref_doctype).masked_fields; - const masked_field_map = Object.fromEntries( - meta_fields - .filter((field) => field.mask && field.mask_readonly) - .map((field) => [field.fieldname, field]) - ); - - // return updated columns with masked field metadata applied return columns.map((col) => { - const masked_field = masked_field_map[col.fieldname]; - if (masked_field) { + if (masked_fields.includes(col.fieldname)) { return { ...col, fieldtype: "Data", - options: masked_field.options, + options: [], }; } return col;