Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

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