Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

delete_doc.py 9.9 KiB

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