您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

725 行
22 KiB

  1. # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
  2. # License: MIT. See LICENSE
  3. from types import NoneType
  4. from typing import TYPE_CHECKING
  5. import frappe
  6. from frappe import _, bold
  7. from frappe.model.document import Document
  8. from frappe.model.dynamic_links import get_dynamic_link_map
  9. from frappe.model.naming import validate_name
  10. from frappe.model.utils.user_settings import sync_user_settings, update_user_settings_data
  11. from frappe.query_builder import Field
  12. from frappe.utils.data import sbool
  13. from frappe.utils.password import rename_password
  14. from frappe.utils.scheduler import is_scheduler_inactive
  15. if TYPE_CHECKING:
  16. from frappe.model.meta import Meta
  17. @frappe.whitelist()
  18. def update_document_title(
  19. *,
  20. doctype: str,
  21. docname: str,
  22. title: str | None = None,
  23. name: str | None = None,
  24. merge: bool = False,
  25. enqueue: bool = False,
  26. **kwargs,
  27. ) -> str:
  28. """
  29. Update the name or title of a document. Returns `name` if document was renamed,
  30. `docname` if renaming operation was queued.
  31. :param doctype: DocType of the document
  32. :param docname: Name of the document
  33. :param title: New Title of the document
  34. :param name: New Name of the document
  35. :param merge: Merge the current Document with the existing one if exists
  36. :param enqueue: Enqueue the rename operation, title is updated in current process
  37. """
  38. # to maintain backwards API compatibility
  39. updated_title = kwargs.get("new_title") or title
  40. updated_name = kwargs.get("new_name") or name
  41. # TODO: omit this after runtime type checking (ref: https://github.com/frappe/frappe/pull/14927)
  42. for obj in [docname, updated_title, updated_name]:
  43. if not isinstance(obj, (str, NoneType)):
  44. frappe.throw(f"{obj=} must be of type str or None")
  45. # handle bad API usages
  46. merge = sbool(merge)
  47. enqueue = sbool(enqueue)
  48. doc = frappe.get_doc(doctype, docname)
  49. doc.check_permission(permtype="write")
  50. title_field = doc.meta.get_title_field()
  51. title_updated = (
  52. updated_title and (title_field != "name") and (updated_title != doc.get(title_field))
  53. )
  54. name_updated = updated_name and (updated_name != doc.name)
  55. if name_updated:
  56. if enqueue and not is_scheduler_inactive():
  57. current_name = doc.name
  58. # before_name hook may have DocType specific validations or transformations
  59. transformed_name = doc.run_method("before_rename", current_name, updated_name, merge)
  60. if isinstance(transformed_name, dict):
  61. transformed_name = transformed_name.get("new")
  62. transformed_name = transformed_name or updated_name
  63. # run rename validations before queueing
  64. # use savepoints to avoid partial renames / commits
  65. validate_rename(
  66. doctype=doctype,
  67. old=current_name,
  68. new=transformed_name,
  69. meta=doc.meta,
  70. merge=merge,
  71. save_point=True,
  72. )
  73. doc.queue_action("rename", name=transformed_name, merge=merge)
  74. else:
  75. doc.rename(updated_name, merge=merge)
  76. if title_updated:
  77. try:
  78. setattr(doc, title_field, updated_title)
  79. doc.save()
  80. frappe.msgprint(_("Saved"), alert=True, indicator="green")
  81. except Exception as e:
  82. if frappe.db.is_duplicate_entry(e):
  83. frappe.throw(
  84. _("{0} {1} already exists").format(doctype, frappe.bold(docname)),
  85. title=_("Duplicate Name"),
  86. exc=frappe.DuplicateEntryError,
  87. )
  88. raise
  89. return doc.name
  90. def rename_doc(
  91. doctype: str | None = None,
  92. old: str | None = None,
  93. new: str = None,
  94. force: bool = False,
  95. merge: bool = False,
  96. ignore_permissions: bool = False,
  97. ignore_if_exists: bool = False,
  98. show_alert: bool = True,
  99. rebuild_search: bool = True,
  100. doc: Document | None = None,
  101. validate: bool = True,
  102. ) -> str:
  103. """Rename a doc(dt, old) to doc(dt, new) and update all linked fields of type "Link".
  104. doc: Document object to be renamed.
  105. new: New name for the record. If None, and doctype is specified, new name may be automatically generated via before_rename hooks.
  106. doctype: DocType of the document. Not required if doc is passed.
  107. old: Current name of the document. Not required if doc is passed.
  108. force: Allow even if document is not allowed to be renamed.
  109. merge: Merge with existing document of new name.
  110. ignore_permissions: Ignore user permissions while renaming.
  111. ignore_if_exists: Don't raise exception if document with new name already exists. This will quietely overwrite the existing document.
  112. show_alert: Display alert if document is renamed successfully.
  113. rebuild_search: Rebuild linked doctype search after renaming.
  114. validate: Validate before renaming. If False, it is assumed that the caller has already validated.
  115. """
  116. old_usage_style = doctype and old and new
  117. new_usage_style = doc and new
  118. if not (new_usage_style or old_usage_style):
  119. raise TypeError(
  120. "{doctype, old, new} or {doc, new} are required arguments for frappe.model.rename_doc"
  121. )
  122. old = old or doc.name
  123. doctype = doctype or doc.doctype
  124. force = sbool(force)
  125. merge = sbool(merge)
  126. meta = frappe.get_meta(doctype)
  127. if validate:
  128. old_doc = doc or frappe.get_doc(doctype, old)
  129. out = old_doc.run_method("before_rename", old, new, merge) or {}
  130. new = (out.get("new") or new) if isinstance(out, dict) else (out or new)
  131. new = validate_rename(
  132. doctype=doctype,
  133. old=old,
  134. new=new,
  135. meta=meta,
  136. merge=merge,
  137. force=force,
  138. ignore_permissions=ignore_permissions,
  139. ignore_if_exists=ignore_if_exists,
  140. )
  141. if not merge:
  142. rename_parent_and_child(doctype, old, new, meta)
  143. else:
  144. update_assignments(old, new, doctype)
  145. # update link fields' values
  146. link_fields = get_link_fields(doctype)
  147. update_link_field_values(link_fields, old, new, doctype)
  148. rename_dynamic_links(doctype, old, new)
  149. # save the user settings in the db
  150. update_user_settings(old, new, link_fields)
  151. if doctype == "DocType":
  152. rename_doctype(doctype, old, new)
  153. update_customizations(old, new)
  154. update_attachments(doctype, old, new)
  155. rename_versions(doctype, old, new)
  156. rename_eps_records(doctype, old, new)
  157. # call after_rename
  158. new_doc = frappe.get_doc(doctype, new)
  159. # copy any flags if required
  160. new_doc._local = getattr(old_doc, "_local", None)
  161. new_doc.run_method("after_rename", old, new, merge)
  162. if not merge:
  163. rename_password(doctype, old, new)
  164. # update user_permissions
  165. DefaultValue = frappe.qb.DocType("DefaultValue")
  166. frappe.qb.update(DefaultValue).set(DefaultValue.defvalue, new).where(
  167. (DefaultValue.parenttype == "User Permission")
  168. & (DefaultValue.defkey == doctype)
  169. & (DefaultValue.defvalue == old)
  170. ).run()
  171. if merge:
  172. new_doc.add_comment("Edit", _("merged {0} into {1}").format(frappe.bold(old), frappe.bold(new)))
  173. else:
  174. new_doc.add_comment(
  175. "Edit", _("renamed from {0} to {1}").format(frappe.bold(old), frappe.bold(new))
  176. )
  177. if merge:
  178. frappe.delete_doc(doctype, old)
  179. new_doc.clear_cache()
  180. frappe.clear_cache()
  181. if rebuild_search:
  182. frappe.enqueue("frappe.utils.global_search.rebuild_for_doctype", doctype=doctype)
  183. if show_alert:
  184. frappe.msgprint(
  185. _("Document renamed from {0} to {1}").format(bold(old), bold(new)),
  186. alert=True,
  187. indicator="green",
  188. )
  189. return new
  190. def update_assignments(old: str, new: str, doctype: str) -> None:
  191. old_assignments = frappe.parse_json(frappe.db.get_value(doctype, old, "_assign")) or []
  192. new_assignments = frappe.parse_json(frappe.db.get_value(doctype, new, "_assign")) or []
  193. common_assignments = list(set(old_assignments).intersection(new_assignments))
  194. for user in common_assignments:
  195. # delete todos linked to old doc
  196. todos = frappe.get_all(
  197. "ToDo",
  198. {
  199. "owner": user,
  200. "reference_type": doctype,
  201. "reference_name": old,
  202. },
  203. ["name", "description"],
  204. )
  205. for todo in todos:
  206. frappe.delete_doc("ToDo", todo.name)
  207. unique_assignments = list(set(old_assignments + new_assignments))
  208. frappe.db.set_value(doctype, new, "_assign", frappe.as_json(unique_assignments, indent=0))
  209. def update_user_settings(old: str, new: str, link_fields: list[dict]) -> None:
  210. """
  211. Update the user settings of all the linked doctypes while renaming.
  212. """
  213. # store the user settings data from the redis to db
  214. sync_user_settings()
  215. if not link_fields:
  216. return
  217. # find the user settings for the linked doctypes
  218. linked_doctypes = {d.parent for d in link_fields if not d.issingle}
  219. UserSettings = frappe.qb.Table("__UserSettings")
  220. user_settings_details = (
  221. frappe.qb.from_(UserSettings)
  222. .select("user", "doctype", "data")
  223. .where(UserSettings.data.like(old) & UserSettings.doctype.isin(linked_doctypes))
  224. .run(as_dict=True)
  225. )
  226. # create the dict using the doctype name as key and values as list of the user settings
  227. from collections import defaultdict
  228. user_settings_dict = defaultdict(list)
  229. for user_setting in user_settings_details:
  230. user_settings_dict[user_setting.doctype].append(user_setting)
  231. # update the name in linked doctype whose user settings exists
  232. for fields in link_fields:
  233. user_settings = user_settings_dict.get(fields.parent)
  234. if user_settings:
  235. for user_setting in user_settings:
  236. update_user_settings_data(user_setting, "value", old, new, "docfield", fields.fieldname)
  237. else:
  238. continue
  239. def update_customizations(old: str, new: str) -> None:
  240. frappe.db.set_value("Custom DocPerm", {"parent": old}, "parent", new, update_modified=False)
  241. def update_attachments(doctype: str, old: str, new: str) -> None:
  242. if doctype != "DocType":
  243. File = frappe.qb.DocType("File")
  244. frappe.qb.update(File).set(File.attached_to_name, new).where(
  245. (File.attached_to_name == old) & (File.attached_to_doctype == doctype)
  246. ).run()
  247. def rename_versions(doctype: str, old: str, new: str) -> None:
  248. Version = frappe.qb.DocType("Version")
  249. frappe.qb.update(Version).set(Version.docname, new).where(
  250. (Version.docname == old) & (Version.ref_doctype == doctype)
  251. ).run()
  252. def rename_eps_records(doctype: str, old: str, new: str) -> None:
  253. EPL = frappe.qb.DocType("Energy Point Log")
  254. frappe.qb.update(EPL).set(EPL.reference_name, new).where(
  255. (EPL.reference_doctype == doctype) & (EPL.reference_name == old)
  256. ).run()
  257. def rename_parent_and_child(doctype: str, old: str, new: str, meta: "Meta") -> None:
  258. frappe.qb.update(doctype).set("name", new).where(Field("name") == old).run()
  259. update_autoname_field(doctype, new, meta)
  260. update_child_docs(old, new, meta)
  261. def update_autoname_field(doctype: str, new: str, meta: "Meta") -> None:
  262. # update the value of the autoname field on rename of the docname
  263. if meta.get("autoname"):
  264. field = meta.get("autoname").split(":")
  265. if field and field[0] == "field":
  266. frappe.qb.update(doctype).set(field[1], new).where(Field("name") == new).run()
  267. def validate_rename(
  268. doctype: str,
  269. old: str,
  270. new: str,
  271. meta: "Meta",
  272. merge: bool,
  273. force: bool = False,
  274. ignore_permissions: bool = False,
  275. ignore_if_exists: bool = False,
  276. save_point=False,
  277. ) -> str:
  278. # using for update so that it gets locked and someone else cannot edit it while this rename is going on!
  279. if save_point:
  280. _SAVE_POINT = f"validate_rename_{frappe.generate_hash(length=8)}"
  281. frappe.db.savepoint(_SAVE_POINT)
  282. exists = (
  283. frappe.qb.from_(doctype).where(Field("name") == new).for_update().select("name").run(pluck=True)
  284. )
  285. exists = exists[0] if exists else None
  286. if not frappe.db.exists(doctype, old):
  287. frappe.throw(_("Can't rename {0} to {1} because {0} doesn't exist.").format(old, new))
  288. if old == new:
  289. frappe.throw(_("No changes made because old and new name are the same.").format(old, new))
  290. if merge and not exists:
  291. frappe.throw(_("{0} {1} does not exist, select a new target to merge").format(doctype, new))
  292. if exists and exists != new:
  293. # for fixing case, accents
  294. exists = None
  295. if not merge and exists and not ignore_if_exists:
  296. frappe.throw(_("Another {0} with name {1} exists, select another name").format(doctype, new))
  297. if not (
  298. ignore_permissions or frappe.permissions.has_permission(doctype, "write", raise_exception=False)
  299. ):
  300. frappe.throw(_("You need write permission to rename"))
  301. if not (force or ignore_permissions) and not meta.allow_rename:
  302. frappe.throw(_("{0} not allowed to be renamed").format(_(doctype)))
  303. # validate naming like it's done in doc.py
  304. new = validate_name(doctype, new)
  305. if save_point:
  306. frappe.db.rollback(save_point=_SAVE_POINT)
  307. return new
  308. def rename_doctype(doctype: str, old: str, new: str) -> None:
  309. # change options for fieldtype Table, Table MultiSelect and Link
  310. fields_with_options = ("Link",) + frappe.model.table_fields
  311. for fieldtype in fields_with_options:
  312. update_options_for_fieldtype(fieldtype, old, new)
  313. # change options where select options are hardcoded i.e. listed
  314. select_fields = get_select_fields(old, new)
  315. update_link_field_values(select_fields, old, new, doctype)
  316. update_select_field_values(old, new)
  317. # change parenttype for fieldtype Table
  318. update_parenttype_values(old, new)
  319. def update_child_docs(old: str, new: str, meta: "Meta") -> None:
  320. # update "parent"
  321. for df in meta.get_table_fields():
  322. frappe.qb.update(df.options).set("parent", new).where(Field("parent") == old).run()
  323. def update_link_field_values(link_fields: list[dict], old: str, new: str, doctype: str) -> None:
  324. for field in link_fields:
  325. if field["issingle"]:
  326. try:
  327. single_doc = frappe.get_doc(field["parent"])
  328. if single_doc.get(field["fieldname"]) == old:
  329. single_doc.set(field["fieldname"], new)
  330. # update single docs using ORM rather then query
  331. # as single docs also sometimes sets defaults!
  332. single_doc.flags.ignore_mandatory = True
  333. single_doc.save(ignore_permissions=True)
  334. except ImportError:
  335. # fails in patches where the doctype has been renamed
  336. # or no longer exists
  337. pass
  338. else:
  339. parent = field["parent"]
  340. docfield = field["fieldname"]
  341. # Handles the case where one of the link fields belongs to
  342. # the DocType being renamed.
  343. # Here this field could have the current DocType as its value too.
  344. # In this case while updating link field value, the field's parent
  345. # or the current DocType table name hasn't been renamed yet,
  346. # so consider it's old name.
  347. if parent == new and doctype == "DocType":
  348. parent = old
  349. frappe.db.set_value(parent, {docfield: old}, docfield, new, update_modified=False)
  350. # update cached link_fields as per new
  351. if doctype == "DocType" and field["parent"] == old:
  352. field["parent"] = new
  353. def get_link_fields(doctype: str) -> list[dict]:
  354. # get link fields from tabDocField
  355. if not frappe.flags.link_fields:
  356. frappe.flags.link_fields = {}
  357. if doctype not in frappe.flags.link_fields:
  358. dt = frappe.qb.DocType("DocType")
  359. df = frappe.qb.DocType("DocField")
  360. cf = frappe.qb.DocType("Custom Field")
  361. ps = frappe.qb.DocType("Property Setter")
  362. st_issingle = frappe.qb.from_(dt).select(dt.issingle).where(dt.name == df.parent).as_("issingle")
  363. standard_fields = (
  364. frappe.qb.from_(df)
  365. .select(df.parent, df.fieldname, st_issingle)
  366. .where((df.options == doctype) & (df.fieldtype == "Link"))
  367. .run(as_dict=True)
  368. )
  369. cf_issingle = frappe.qb.from_(dt).select(dt.issingle).where(dt.name == cf.dt).as_("issingle")
  370. custom_fields = (
  371. frappe.qb.from_(cf)
  372. .select(cf.dt.as_("parent"), cf.fieldname, cf_issingle)
  373. .where((cf.options == doctype) & (cf.fieldtype == "Link"))
  374. .run(as_dict=True)
  375. )
  376. ps_issingle = (
  377. frappe.qb.from_(dt).select(dt.issingle).where(dt.name == ps.doc_type).as_("issingle")
  378. )
  379. property_setter_fields = (
  380. frappe.qb.from_(ps)
  381. .select(ps.doc_type.as_("parent"), ps.field_name.as_("fieldname"), ps_issingle)
  382. .where((ps.property == "options") & (ps.value == doctype) & (ps.field_name.notnull()))
  383. .run(as_dict=True)
  384. )
  385. frappe.flags.link_fields[doctype] = standard_fields + custom_fields + property_setter_fields
  386. return frappe.flags.link_fields[doctype]
  387. def update_options_for_fieldtype(fieldtype: str, old: str, new: str) -> None:
  388. CustomField = frappe.qb.DocType("Custom Field")
  389. PropertySetter = frappe.qb.DocType("Property Setter")
  390. if frappe.conf.developer_mode:
  391. for name in frappe.get_all("DocField", filters={"options": old}, pluck="parent"):
  392. doctype = frappe.get_doc("DocType", name)
  393. save = False
  394. for f in doctype.fields:
  395. if f.options == old:
  396. f.options = new
  397. save = True
  398. if save:
  399. doctype.save()
  400. else:
  401. DocField = frappe.qb.DocType("DocField")
  402. frappe.qb.update(DocField).set(DocField.options, new).where(
  403. (DocField.fieldtype == fieldtype) & (DocField.options == old)
  404. ).run()
  405. frappe.qb.update(CustomField).set(CustomField.options, new).where(
  406. (CustomField.fieldtype == fieldtype) & (CustomField.options == old)
  407. ).run()
  408. frappe.qb.update(PropertySetter).set(PropertySetter.value, new).where(
  409. (PropertySetter.property == "options") & (PropertySetter.value == old)
  410. ).run()
  411. def get_select_fields(old: str, new: str) -> list[dict]:
  412. """
  413. get select type fields where doctype's name is hardcoded as
  414. new line separated list
  415. """
  416. df = frappe.qb.DocType("DocField")
  417. dt = frappe.qb.DocType("DocType")
  418. cf = frappe.qb.DocType("Custom Field")
  419. ps = frappe.qb.DocType("Property Setter")
  420. # get link fields from tabDocField
  421. st_issingle = frappe.qb.from_(dt).select(dt.issingle).where(dt.name == df.parent).as_("issingle")
  422. standard_fields = (
  423. frappe.qb.from_(df)
  424. .select(df.parent, df.fieldname, st_issingle)
  425. .where((df.parent != new) & (df.fieldtype == "Select") & (df.options.like(f"%{old}%")))
  426. .run(as_dict=True)
  427. )
  428. # get link fields from tabCustom Field
  429. cf_issingle = frappe.qb.from_(dt).select(dt.issingle).where(dt.name == cf.dt).as_("issingle")
  430. custom_select_fields = (
  431. frappe.qb.from_(cf)
  432. .select(cf.dt.as_("parent"), cf.fieldname, cf_issingle)
  433. .where((cf.dt != new) & (cf.fieldtype == "Select") & (cf.options.like(f"%{old}%")))
  434. .run(as_dict=True)
  435. )
  436. # remove fields whose options have been changed using property setter
  437. ps_issingle = (
  438. frappe.qb.from_(dt).select(dt.issingle).where(dt.name == ps.doc_type).as_("issingle")
  439. )
  440. property_setter_select_fields = (
  441. frappe.qb.from_(ps)
  442. .select(ps.doc_type.as_("parent"), ps.field_name.as_("fieldname"), ps_issingle)
  443. .where(
  444. (ps.doc_type != new)
  445. & (ps.property == "options")
  446. & (ps.field_name.notnull())
  447. & (ps.value.like(f"%{old}%"))
  448. )
  449. .run(as_dict=True)
  450. )
  451. return standard_fields + custom_select_fields + property_setter_select_fields
  452. def update_select_field_values(old: str, new: str):
  453. from frappe.query_builder.functions import Replace
  454. DocField = frappe.qb.DocType("DocField")
  455. CustomField = frappe.qb.DocType("Custom Field")
  456. PropertySetter = frappe.qb.DocType("Property Setter")
  457. frappe.qb.update(DocField).set(DocField.options, Replace(DocField.options, old, new)).where(
  458. (DocField.fieldtype == "Select")
  459. & (DocField.parent != new)
  460. & (DocField.options.like(f"%\n{old}%") | DocField.options.like(f"%{old}\n%"))
  461. ).run()
  462. frappe.qb.update(CustomField).set(
  463. CustomField.options, Replace(CustomField.options, old, new)
  464. ).where(
  465. (CustomField.fieldtype == "Select")
  466. & (CustomField.dt != new)
  467. & (CustomField.options.like(f"%\n{old}%") | CustomField.options.like(f"%{old}\n%"))
  468. ).run()
  469. frappe.qb.update(PropertySetter).set(
  470. PropertySetter.value, Replace(PropertySetter.value, old, new)
  471. ).where(
  472. (PropertySetter.property == "options")
  473. & (PropertySetter.field_name.notnull())
  474. & (PropertySetter.doc_type != new)
  475. & (PropertySetter.value.like(f"%\n{old}%") | PropertySetter.value.like(f"%{old}\n%"))
  476. ).run()
  477. def update_parenttype_values(old: str, new: str):
  478. child_doctypes = frappe.get_all(
  479. "DocField",
  480. fields=["options", "fieldname"],
  481. filters={"parent": new, "fieldtype": ["in", frappe.model.table_fields]},
  482. )
  483. custom_child_doctypes = frappe.get_all(
  484. "Custom Field",
  485. fields=["options", "fieldname"],
  486. filters={"dt": new, "fieldtype": ["in", frappe.model.table_fields]},
  487. )
  488. child_doctypes += custom_child_doctypes
  489. fields = [d["fieldname"] for d in child_doctypes]
  490. property_setter_child_doctypes = frappe.get_all(
  491. "Property Setter",
  492. filters={"doc_type": new, "property": "options", "field_name": ("in", fields)},
  493. pluck="value",
  494. )
  495. child_doctypes = set(list(d["options"] for d in child_doctypes) + property_setter_child_doctypes)
  496. for doctype in child_doctypes:
  497. table = frappe.qb.DocType(doctype)
  498. frappe.qb.update(table).set(table.parenttype, new).where(table.parenttype == old).run()
  499. def rename_dynamic_links(doctype: str, old: str, new: str):
  500. Singles = frappe.qb.DocType("Singles")
  501. for df in get_dynamic_link_map().get(doctype, []):
  502. # dynamic link in single, just one value to check
  503. if frappe.get_meta(df.parent).issingle:
  504. refdoc = frappe.db.get_singles_dict(df.parent)
  505. if refdoc.get(df.options) == doctype and refdoc.get(df.fieldname) == old:
  506. frappe.qb.update(Singles).set(Singles.value, new).where(
  507. (Singles.field == df.fieldname) & (Singles.doctype == df.parent) & (Singles.value == old)
  508. ).run()
  509. else:
  510. # because the table hasn't been renamed yet!
  511. parent = df.parent if df.parent != new else old
  512. frappe.qb.update(parent).set(df.fieldname, new).where(
  513. (Field(df.options) == doctype) & (Field(df.fieldname) == old)
  514. ).run()
  515. def bulk_rename(
  516. doctype: str, rows: list[list] | None = None, via_console: bool = False
  517. ) -> list[str] | None:
  518. """Bulk rename documents
  519. :param doctype: DocType to be renamed
  520. :param rows: list of documents as `((oldname, newname, merge(optional)), ..)`"""
  521. if not rows:
  522. frappe.throw(_("Please select a valid csv file with data"))
  523. if not via_console:
  524. max_rows = 500
  525. if len(rows) > max_rows:
  526. frappe.throw(_("Maximum {0} rows allowed").format(max_rows))
  527. rename_log = []
  528. for row in rows:
  529. # if row has some content
  530. if len(row) > 1 and row[0] and row[1]:
  531. merge = len(row) > 2 and (row[2] == "1" or row[2].lower() == "true")
  532. try:
  533. if rename_doc(doctype, row[0], row[1], merge=merge, rebuild_search=False):
  534. msg = _("Successful: {0} to {1}").format(row[0], row[1])
  535. frappe.db.commit()
  536. else:
  537. msg = None
  538. except Exception as e:
  539. msg = _("** Failed: {0} to {1}: {2}").format(row[0], row[1], repr(e))
  540. frappe.db.rollback()
  541. if msg:
  542. if via_console:
  543. print(msg)
  544. else:
  545. rename_log.append(msg)
  546. frappe.enqueue("frappe.utils.global_search.rebuild_for_doctype", doctype=doctype)
  547. if not via_console:
  548. return rename_log
  549. def update_linked_doctypes(
  550. doctype: str, docname: str, linked_to: str, value: str, ignore_doctypes: list | None = None
  551. ) -> None:
  552. from frappe.model.utils.rename_doc import update_linked_doctypes
  553. show_deprecation_warning("update_linked_doctypes")
  554. return update_linked_doctypes(
  555. doctype=doctype,
  556. docname=docname,
  557. linked_to=linked_to,
  558. value=value,
  559. ignore_doctypes=ignore_doctypes,
  560. )
  561. def get_fetch_fields(
  562. doctype: str, linked_to: str, ignore_doctypes: list | None = None
  563. ) -> list[dict]:
  564. from frappe.model.utils.rename_doc import get_fetch_fields
  565. show_deprecation_warning("get_fetch_fields")
  566. return get_fetch_fields(doctype=doctype, linked_to=linked_to, ignore_doctypes=ignore_doctypes)
  567. def show_deprecation_warning(funct: str) -> None:
  568. from click import secho
  569. message = (
  570. f"Function frappe.model.rename_doc.{funct} has been deprecated and "
  571. "moved to the frappe.model.utils.rename_doc"
  572. )
  573. secho(message, fg="yellow")