Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

delete_doc.py 10 KiB

há 11 anos
há 11 anos
há 11 anos
há 10 anos
há 11 anos
há 11 anos
há 11 anos
há 11 anos
há 11 anos
há 11 anos
há 11 anos
[feature] Global Search (again) (#2710) * [start] global search frappe/erpnext#6674 * [fix] setup before running test * [start] global search frappe/erpnext#6674 * Display result as rudimentary list, rebuild old doctypes * Media view, child tables, delete document updates, searchable fields * More results UI * Code clean up * remove msgprint from document.py to resolve merge conflict * Modularization stage 1, get show more to work with it * Dedicated modal Search bar works, some clean up needed * Can't data-dismiss on links, Bootstrap issue, use hashchange * Accomodate missing field content syndrome * Search in boolean mode, make GS default in awesome bar, fix double modal bug and cleanup * Add in Meta * Add in customize form * Modularise Global Search * Search object * Commonify Search UI: Stage I * II: save list state, UI, default condensed view, refactor * Fix SQL bug, Refactor awesome bar, Fix unicode bug, add nav results * Refactor using separate search objects, some async issues * Fix async flow * Fix preceding more list bug * UI additions * another async fix, back link * Help: Stage I * Help: Stage II * Background jobs, fix route options bug * Fix GS syncing on install * Add GS options in awesome bar: test * Input now remembers search type state * More UI updates * Add description for GS results in awesome bar * Fix help modal bug * Fix: not commit during install * Test cases, some fixes * Update in_test flag in enqueue * Disable GS sync when not install_db * Add flag check * Disable field in child tables * Cleanups * Create table fix * Fix redis exception, remove commit enqueue, add gs in migrate * Fix tests * Single enqueue * cleanups * Fix tests * Fix event test * Fix duplication, search as first option * Add show name in global search * fix event tests and desk.less * Fix communication.json * [fixes] wip * [fix] tests * [minor] for tests * [minor] for tests * [minor] for tests * [minor] for tests
há 8 anos
há 11 anos
há 11 anos
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  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 comments
  203. frappe.db.sql("""delete from `tabCommunication`
  204. where
  205. communication_type = 'Comment'
  206. and reference_doctype=%s and reference_name=%s""", (doctype, name))
  207. # unlink communications
  208. frappe.db.sql("""update `tabCommunication`
  209. set reference_doctype=null, reference_name=null
  210. where
  211. communication_type = 'Communication'
  212. and reference_doctype=%s
  213. and reference_name=%s""", (doctype, name))
  214. # unlink secondary references
  215. frappe.db.sql("""update `tabCommunication`
  216. set link_doctype=null, link_name=null
  217. where link_doctype=%s and link_name=%s""", (doctype, name))
  218. # unlink feed
  219. frappe.db.sql("""update `tabCommunication`
  220. set timeline_doctype=null, timeline_name=null
  221. where timeline_doctype=%s and timeline_name=%s""", (doctype, name))
  222. # delete shares
  223. delete_doc("DocShare", frappe.db.sql_list("""select name from `tabDocShare`
  224. where share_doctype=%s and share_name=%s""", (doctype, name)),
  225. ignore_on_trash=True, force=True)
  226. # delete versions
  227. frappe.db.sql('delete from tabVersion where ref_doctype=%s and docname=%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)