feat: allow syncing new fields in Doctype Layout
This commit is contained in:
parent
fbee80f734
commit
c2e44754d1
4 changed files with 152 additions and 19 deletions
|
|
@ -102,6 +102,20 @@ class CustomField(Document):
|
|||
|
||||
# delete property setter entries
|
||||
frappe.db.delete("Property Setter", {"doc_type": self.dt, "field_name": self.fieldname})
|
||||
|
||||
# update doctype layouts
|
||||
doctype_layouts = frappe.get_all(
|
||||
"DocType Layout", filters={"document_type": self.dt}, pluck="name"
|
||||
)
|
||||
|
||||
for layout in doctype_layouts:
|
||||
layout_doc = frappe.get_doc("DocType Layout", layout)
|
||||
for field in layout_doc.fields:
|
||||
if field.fieldname == self.fieldname:
|
||||
layout_doc.remove(field)
|
||||
layout_doc.save()
|
||||
break
|
||||
|
||||
frappe.clear_cache(doctype=self.dt)
|
||||
|
||||
def validate_insert_after(self, meta):
|
||||
|
|
|
|||
|
|
@ -2,31 +2,88 @@
|
|||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("DocType Layout", {
|
||||
refresh: function (frm) {
|
||||
frm.trigger("document_type");
|
||||
frm.events.set_button(frm);
|
||||
onload_post_render(frm) {
|
||||
// disallow users from manually adding/deleting rows; this doctype should only
|
||||
// be used for managing layout, and docfields and custom fields should be used
|
||||
// to manage other field metadata (hidden, etc.)
|
||||
frm.set_df_property("fields", "cannot_add_rows", true);
|
||||
frm.set_df_property("fields", "cannot_delete_rows", true);
|
||||
},
|
||||
|
||||
refresh(frm) {
|
||||
frm.events.add_buttons(frm);
|
||||
},
|
||||
|
||||
document_type(frm) {
|
||||
frm.set_fields_as_options("fields", frm.doc.document_type, null, [], "fieldname").then(
|
||||
() => {
|
||||
// child table empty? then show all fields as default
|
||||
if (frm.doc.document_type) {
|
||||
if (!(frm.doc.fields || []).length) {
|
||||
for (let f of frappe.get_doc("DocType", frm.doc.document_type).fields) {
|
||||
frm.add_child("fields", { fieldname: f.fieldname, label: f.label });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
if (frm.doc.document_type) {
|
||||
frm.set_value("fields", []);
|
||||
frm.events.sync_fields(frm, false);
|
||||
}
|
||||
},
|
||||
|
||||
set_button(frm) {
|
||||
add_buttons(frm) {
|
||||
if (!frm.is_new()) {
|
||||
frm.add_custom_button(__("Go to {0} List", [frm.doc.name]), () => {
|
||||
window.open(`/app/${frappe.router.slug(frm.doc.name)}`);
|
||||
});
|
||||
|
||||
frm.add_custom_button(__("Sync {0} Fields", [frm.doc.name]), async () => {
|
||||
await frm.events.sync_fields(frm, true);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async sync_fields(frm, notify) {
|
||||
frappe.dom.freeze("Fetching fields...");
|
||||
const response = await frm.call({ doc: frm.doc, method: "sync_fields" });
|
||||
frm.refresh_field("fields");
|
||||
frappe.dom.unfreeze();
|
||||
|
||||
if (!response.message) {
|
||||
frappe.msgprint(__("No changes to sync"));
|
||||
return;
|
||||
}
|
||||
|
||||
frm.dirty();
|
||||
if (notify) {
|
||||
const addedFields = response.message.added;
|
||||
const removedFields = response.message.removed;
|
||||
|
||||
const getChangedMessage = (fields) => {
|
||||
let changes = "";
|
||||
for (const field of fields) {
|
||||
if (field.label) {
|
||||
changes += `<li>Row #${field.idx}: ${field.fieldname.bold()} (${
|
||||
field.label
|
||||
})</li>`;
|
||||
} else {
|
||||
changes += `<li>Row #${field.idx}: ${field.fieldname.bold()}</li>`;
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
};
|
||||
|
||||
let message = "";
|
||||
|
||||
if (addedFields.length) {
|
||||
message += `The following fields have been added:<br><br><ul>${getChangedMessage(
|
||||
addedFields
|
||||
)}</ul>`;
|
||||
}
|
||||
|
||||
if (removedFields.length) {
|
||||
message += `The following fields have been removed:<br><br><ul>${getChangedMessage(
|
||||
removedFields
|
||||
)}</ul>`;
|
||||
}
|
||||
|
||||
if (message) {
|
||||
frappe.msgprint({
|
||||
message: __(message),
|
||||
indicator: "green",
|
||||
title: __("Synced Fields"),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "Prompt",
|
||||
"autoname": "prompt",
|
||||
"creation": "2020-11-16 17:05:35.306846",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
|
|
@ -19,7 +19,8 @@
|
|||
"in_list_view": 1,
|
||||
"label": "Document Type",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "fields",
|
||||
|
|
@ -42,10 +43,11 @@
|
|||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-10 15:01:04.352184",
|
||||
"modified": "2022-09-01 03:22:33.973058",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "DocType Layout",
|
||||
"naming_rule": "Set by user",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
|
@ -68,5 +70,6 @@
|
|||
"route": "doctype-layout",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -1,11 +1,70 @@
|
|||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# License: MIT. See LICENSE
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import frappe
|
||||
from frappe.desk.utils import slug
|
||||
from frappe.model.document import Document
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.core.doctype.docfield.docfield import DocField
|
||||
|
||||
|
||||
class DocTypeLayout(Document):
|
||||
def validate(self):
|
||||
if not self.route:
|
||||
self.route = slug(self.name)
|
||||
|
||||
@frappe.whitelist()
|
||||
def sync_fields(self):
|
||||
layout_fieldnames = {field.fieldname for field in self.fields}
|
||||
doctype_fields = frappe.get_meta(self.document_type).fields
|
||||
doctype_fieldnames = {field.fieldname for field in doctype_fields}
|
||||
|
||||
added_fields = list(doctype_fieldnames - layout_fieldnames)
|
||||
removed_fields = list(layout_fieldnames - doctype_fieldnames)
|
||||
|
||||
if not (added_fields or removed_fields):
|
||||
return
|
||||
|
||||
added = self.add_fields(added_fields, doctype_fields)
|
||||
removed = self.remove_fields(removed_fields)
|
||||
|
||||
for index, field in enumerate(self.fields):
|
||||
field.idx = index + 1
|
||||
|
||||
return {"added": added, "removed": removed}
|
||||
|
||||
def add_fields(self, added_fields: list[str], doctype_fields: list["DocField"]) -> list[dict]:
|
||||
added = []
|
||||
for field in added_fields:
|
||||
field_details = next((f for f in doctype_fields if f.fieldname == field), None)
|
||||
if not field_details:
|
||||
continue
|
||||
|
||||
# remove 'doctype' data from the DocField to allow adding it to the layout
|
||||
row = self.append("fields", field_details.as_dict(no_default_fields=True))
|
||||
if field_details.insert_after:
|
||||
insert_after = next(
|
||||
(f for f in self.fields if f.fieldname == field_details.insert_after),
|
||||
None,
|
||||
)
|
||||
|
||||
# initialize new row to just after the insert_after field
|
||||
self.fields.insert(insert_after.idx, row)
|
||||
self.fields.pop()
|
||||
|
||||
added.append({"idx": insert_after.idx + 1, "fieldname": row.fieldname, "label": row.label})
|
||||
else:
|
||||
added.append(row.as_dict())
|
||||
return added
|
||||
|
||||
def remove_fields(self, removed_fields: list[str]) -> list[dict]:
|
||||
removed = []
|
||||
for field in removed_fields:
|
||||
field_details = next((f for f in self.fields if f.fieldname == field), None)
|
||||
if field_details:
|
||||
self.remove(field_details)
|
||||
removed.append(field_details.as_dict())
|
||||
return removed
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue