25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

589 lines
19 KiB

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