You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

294 line
10 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. import frappe
  5. import frappe.model.meta
  6. from frappe.model.dynamic_links import get_dynamic_link_map
  7. import frappe.defaults
  8. from frappe.utils.file_manager import remove_all
  9. from frappe.utils.password import delete_all_passwords_for
  10. from frappe import _
  11. from frappe.model.naming import revert_series_if_last
  12. from frappe.utils.global_search import delete_for_document
  13. from six import string_types
  14. def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False,
  15. ignore_permissions=False, flags=None, ignore_on_trash=False, ignore_missing=True):
  16. """
  17. Deletes a doc(dt, dn) and validates if it is not submitted and not linked in a live record
  18. """
  19. if not ignore_doctypes: ignore_doctypes = []
  20. # get from form
  21. if not doctype:
  22. doctype = frappe.form_dict.get('dt')
  23. name = frappe.form_dict.get('dn')
  24. names = name
  25. if isinstance(name, string_types):
  26. names = [name]
  27. for name in names or []:
  28. # already deleted..?
  29. if not frappe.db.exists(doctype, name):
  30. if not ignore_missing:
  31. raise frappe.DoesNotExistError
  32. else:
  33. return False
  34. # delete passwords
  35. delete_all_passwords_for(doctype, name)
  36. doc = None
  37. if doctype=="DocType":
  38. if for_reload:
  39. try:
  40. doc = frappe.get_doc(doctype, name)
  41. except frappe.DoesNotExistError:
  42. pass
  43. else:
  44. doc.run_method("before_reload")
  45. else:
  46. doc = frappe.get_doc(doctype, name)
  47. update_flags(doc, flags, ignore_permissions)
  48. check_permission_and_not_submitted(doc)
  49. frappe.db.sql("delete from `tabCustom Field` where dt = %s", name)
  50. frappe.db.sql("delete from `tabCustom Script` where dt = %s", name)
  51. frappe.db.sql("delete from `tabProperty Setter` where doc_type = %s", name)
  52. frappe.db.sql("delete from `tabReport` where ref_doctype=%s", name)
  53. frappe.db.sql("delete from `tabCustom DocPerm` where parent=%s", name)
  54. delete_from_table(doctype, name, ignore_doctypes, None)
  55. else:
  56. doc = frappe.get_doc(doctype, name)
  57. if not for_reload:
  58. update_flags(doc, flags, ignore_permissions)
  59. check_permission_and_not_submitted(doc)
  60. if not ignore_on_trash:
  61. doc.run_method("on_trash")
  62. doc.flags.in_delete = True
  63. doc.run_method('on_change')
  64. frappe.enqueue('frappe.model.delete_doc.delete_dynamic_links', doctype=doc.doctype, name=doc.name,
  65. async=False if frappe.flags.in_test else True)
  66. # check if links exist
  67. if not force:
  68. check_if_doc_is_linked(doc)
  69. check_if_doc_is_dynamically_linked(doc)
  70. update_naming_series(doc)
  71. delete_from_table(doctype, name, ignore_doctypes, doc)
  72. doc.run_method("after_delete")
  73. # delete attachments
  74. remove_all(doctype, name, from_delete=True)
  75. # delete global search entry
  76. delete_for_document(doc)
  77. if doc and not for_reload:
  78. add_to_deleted_document(doc)
  79. if not frappe.flags.in_patch:
  80. try:
  81. doc.notify_update()
  82. insert_feed(doc)
  83. except ImportError:
  84. pass
  85. # delete user_permissions
  86. frappe.defaults.clear_default(parenttype="User Permission", key=doctype, value=name)
  87. def add_to_deleted_document(doc):
  88. '''Add this document to Deleted Document table. Called after delete'''
  89. if doc.doctype != 'Deleted Document' and frappe.flags.in_install != 'frappe':
  90. frappe.get_doc(dict(
  91. doctype='Deleted Document',
  92. deleted_doctype=doc.doctype,
  93. deleted_name=doc.name,
  94. data=doc.as_json()
  95. )).db_insert()
  96. def update_naming_series(doc):
  97. if doc.meta.autoname:
  98. if doc.meta.autoname.startswith("naming_series:") \
  99. and getattr(doc, "naming_series", None):
  100. revert_series_if_last(doc.naming_series, doc.name)
  101. elif doc.meta.autoname.split(":")[0] not in ("Prompt", "field", "hash"):
  102. revert_series_if_last(doc.meta.autoname, doc.name)
  103. def delete_from_table(doctype, name, ignore_doctypes, doc):
  104. if doctype!="DocType" and doctype==name:
  105. frappe.db.sql("delete from `tabSingles` where doctype=%s", name)
  106. else:
  107. frappe.db.sql("delete from `tab%s` where name=%s" % (frappe.db.escape(doctype), "%s"), (name,))
  108. # get child tables
  109. if doc:
  110. tables = [d.options for d in doc.meta.get_table_fields()]
  111. else:
  112. def get_table_fields(field_doctype):
  113. return frappe.db.sql_list("""select options from `tab{}` where fieldtype='Table'
  114. and parent=%s""".format(field_doctype), doctype)
  115. tables = get_table_fields("DocField")
  116. if not frappe.flags.in_install=="frappe":
  117. tables += get_table_fields("Custom Field")
  118. # delete from child tables
  119. for t in list(set(tables)):
  120. if t not in ignore_doctypes:
  121. frappe.db.sql("delete from `tab%s` where parenttype=%s and parent = %s" % (t, '%s', '%s'), (doctype, name))
  122. def update_flags(doc, flags=None, ignore_permissions=False):
  123. if ignore_permissions:
  124. if not flags: flags = {}
  125. flags["ignore_permissions"] = ignore_permissions
  126. if flags:
  127. doc.flags.update(flags)
  128. def check_permission_and_not_submitted(doc):
  129. # permission
  130. if (not doc.flags.ignore_permissions
  131. and frappe.session.user!="Administrator"
  132. and (
  133. not doc.has_permission("delete")
  134. or (doc.doctype=="DocType" and not doc.custom))):
  135. frappe.msgprint(_("User not allowed to delete {0}: {1}")
  136. .format(doc.doctype, doc.name), raise_exception=frappe.PermissionError)
  137. # check if submitted
  138. if doc.docstatus == 1:
  139. frappe.msgprint(_("{0} {1}: Submitted Record cannot be deleted.").format(_(doc.doctype), doc.name),
  140. raise_exception=True)
  141. def check_if_doc_is_linked(doc, method="Delete"):
  142. """
  143. Raises excption if the given doc(dt, dn) is linked in another record.
  144. """
  145. from frappe.model.rename_doc import get_link_fields
  146. link_fields = get_link_fields(doc.doctype)
  147. link_fields = [[lf['parent'], lf['fieldname'], lf['issingle']] for lf in link_fields]
  148. for link_dt, link_field, issingle in link_fields:
  149. if not issingle:
  150. for item in frappe.db.get_values(link_dt, {link_field:doc.name},
  151. ["name", "parent", "parenttype", "docstatus"], as_dict=True):
  152. linked_doctype = item.parenttype if item.parent else link_dt
  153. if linked_doctype in ("Communication", "ToDo", "DocShare", "Email Unsubscribe", 'File', 'Version'):
  154. # don't check for communication and todo!
  155. continue
  156. if item and ((item.parent or item.name) != doc.name) \
  157. and ((method=="Delete" and item.docstatus<2) or (method=="Cancel" and item.docstatus==1)):
  158. # raise exception only if
  159. # linked to an non-cancelled doc when deleting
  160. # or linked to a submitted doc when cancelling
  161. frappe.throw(_('Cannot delete or cancel because {0} <a href="#Form/{0}/{1}">{1}</a> is linked with {2} <a href="#Form/{2}/{3}">{3}</a>')
  162. .format(doc.doctype, doc.name, linked_doctype,
  163. item.parent or item.name), frappe.LinkExistsError)
  164. def check_if_doc_is_dynamically_linked(doc, method="Delete"):
  165. '''Raise `frappe.LinkExistsError` if the document is dynamically linked'''
  166. for df in get_dynamic_link_map().get(doc.doctype, []):
  167. if df.parent in ("Communication", "ToDo", "DocShare", "Email Unsubscribe", 'File', 'Version'):
  168. # don't check for communication and todo!
  169. continue
  170. meta = frappe.get_meta(df.parent)
  171. if meta.issingle:
  172. # dynamic link in single doc
  173. refdoc = frappe.db.get_singles_dict(df.parent)
  174. if (refdoc.get(df.options)==doc.doctype
  175. and refdoc.get(df.fieldname)==doc.name
  176. and ((method=="Delete" and refdoc.docstatus < 2)
  177. or (method=="Cancel" and refdoc.docstatus==1))
  178. ):
  179. # raise exception only if
  180. # linked to an non-cancelled doc when deleting
  181. # or linked to a submitted doc when cancelling
  182. frappe.throw(_('Cannot delete or cancel because {0} <a href="#Form/{0}/{1}">{1}</a> is linked with {2} <a href="#Form/{2}/{3}">{3}</a>').format(doc.doctype,
  183. doc.name, df.parent, ""), frappe.LinkExistsError)
  184. else:
  185. # dynamic link in table
  186. df["table"] = ", parent, parenttype, idx" if meta.istable else ""
  187. for refdoc in frappe.db.sql("""select name, docstatus{table} from `tab{parent}` where
  188. {options}=%s and {fieldname}=%s""".format(**df), (doc.doctype, doc.name), as_dict=True):
  189. if ((method=="Delete" and refdoc.docstatus < 2) or (method=="Cancel" and refdoc.docstatus==1)):
  190. # raise exception only if
  191. # linked to an non-cancelled doc when deleting
  192. # or linked to a submitted doc when cancelling
  193. frappe.throw(_('Cannot delete or cancel because {0} <a href="#Form/{0}/{1}">{1}</a> is linked with {2} <a href="#Form/{2}/{3}">{3}</a> {4}')\
  194. .format(doc.doctype, doc.name, refdoc.parenttype if meta.istable else df.parent,
  195. refdoc.parent if meta.istable else refdoc.name,"Row: {0}".format(refdoc.idx) if meta.istable else ""), frappe.LinkExistsError)
  196. def delete_dynamic_links(doctype, name):
  197. delete_doc("ToDo", frappe.db.sql_list("""select name from `tabToDo`
  198. where reference_type=%s and reference_name=%s""", (doctype, name)),
  199. ignore_permissions=True, force=True)
  200. frappe.db.sql('''delete from `tabEmail Unsubscribe`
  201. where reference_doctype=%s and reference_name=%s''', (doctype, name))
  202. # delete shares
  203. delete_doc("DocShare", frappe.db.sql_list("""select name from `tabDocShare`
  204. where share_doctype=%s and share_name=%s""", (doctype, name)),
  205. ignore_on_trash=True, force=True)
  206. # delete versions
  207. frappe.db.sql('delete from tabVersion where ref_doctype=%s and docname=%s', (doctype, name))
  208. # delete comments
  209. frappe.db.sql("""delete from `tabCommunication`
  210. where
  211. communication_type = 'Comment'
  212. and reference_doctype=%s and reference_name=%s""", (doctype, name))
  213. # unlink communications
  214. frappe.db.sql("""update `tabCommunication`
  215. set reference_doctype=null, reference_name=null
  216. where
  217. communication_type = 'Communication'
  218. and reference_doctype=%s
  219. and reference_name=%s""", (doctype, name))
  220. # unlink secondary references
  221. frappe.db.sql("""update `tabCommunication`
  222. set link_doctype=null, link_name=null
  223. where link_doctype=%s and link_name=%s""", (doctype, name))
  224. # unlink feed
  225. frappe.db.sql("""update `tabCommunication`
  226. set timeline_doctype=null, timeline_name=null
  227. where timeline_doctype=%s and timeline_name=%s""", (doctype, name))
  228. def insert_feed(doc):
  229. from frappe.utils import get_fullname
  230. if frappe.flags.in_install or frappe.flags.in_import or getattr(doc, "no_feed_on_delete", False):
  231. return
  232. frappe.get_doc({
  233. "doctype": "Communication",
  234. "communication_type": "Comment",
  235. "comment_type": "Deleted",
  236. "reference_doctype": doc.doctype,
  237. "subject": "{0} {1}".format(_(doc.doctype), doc.name),
  238. "full_name": get_fullname(doc.owner)
  239. }).insert(ignore_permissions=True)