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.
 
 
 
 
 
 

554 wiersze
18 KiB

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