feat(Communication): Move Timeline DocType and Name to Dynamic Links (#7467)

feat(Communication): Move Timeline DocType and Name to Dynamic Links
This commit is contained in:
Faris Ansari 2019-05-20 15:12:32 +05:30 committed by GitHub
commit cf8a53e433
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 407 additions and 227 deletions

View file

@ -6,7 +6,7 @@ from __future__ import unicode_literals
from frappe import _
from frappe.utils import get_fullname, now
from frappe.model.document import Document
from frappe.core.utils import get_parent_doc, set_timeline_doc
from frappe.core.utils import set_timeline_doc
import frappe
class ActivityLog(Document):

View file

@ -1,7 +1,7 @@
{
"allow_import": 1,
"creation": "2013-01-29 10:47:14",
"description": "Keep a track of all communications",
"description": "Keeps track of all communications",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
@ -41,14 +41,11 @@
"user",
"column_break_27",
"email_template",
"link_doctype",
"link_name",
"timeline_doctype",
"timeline_name",
"timeline_label",
"unread_notification_sent",
"seen",
"_user_tags",
"timeline_links_sections",
"timeline_links",
"email_inbox",
"message_id",
"uid",
@ -204,6 +201,7 @@
"label": "Date"
},
{
"default": "0",
"fieldname": "read_receipt",
"fieldtype": "Check",
"label": "Sent Read Receipt",
@ -220,6 +218,7 @@
"read_only": 1
},
{
"default": "0",
"fieldname": "read_by_recipient",
"fieldtype": "Check",
"label": "Read by Recipient",
@ -284,39 +283,6 @@
"fieldname": "column_break_27",
"fieldtype": "Column Break"
},
{
"fieldname": "link_doctype",
"fieldtype": "Link",
"label": "Link DocType",
"options": "DocType",
"read_only": 1
},
{
"fieldname": "link_name",
"fieldtype": "Dynamic Link",
"label": "Link Name",
"options": "link_doctype",
"read_only": 1
},
{
"fieldname": "timeline_doctype",
"fieldtype": "Link",
"label": "Timeline DocType",
"options": "DocType",
"read_only": 1
},
{
"fieldname": "timeline_name",
"fieldtype": "Dynamic Link",
"label": "Timeline Name",
"options": "timeline_doctype",
"read_only": 1
},
{
"fieldname": "timeline_label",
"fieldtype": "Data",
"label": "Timeline field Name"
},
{
"default": "0",
"fieldname": "unread_notification_sent",
@ -325,6 +291,7 @@
"read_only": 1
},
{
"default": "0",
"fieldname": "seen",
"fieldtype": "Check",
"label": "Seen",
@ -368,6 +335,7 @@
"options": "Open\nSpam\nTrash"
},
{
"default": "0",
"fieldname": "has_attachment",
"fieldtype": "Check",
"hidden": 1,
@ -398,11 +366,24 @@
"label": "Email Template",
"options": "Email Template",
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "timeline_links_sections",
"fieldtype": "Section Break",
"label": "Timeline Links"
},
{
"fieldname": "timeline_links",
"fieldtype": "Table",
"label": "Timeline Links",
"options": "Dynamic Link",
"permlevel": 2
}
],
"icon": "fa fa-comment",
"idx": 1,
"modified": "2019-05-04 15:36:35.818714",
"modified": "2019-05-20 14:14:01.514493",
"modified_by": "Administrator",
"module": "Core",
"name": "Communication",
@ -428,6 +409,18 @@
"role": "System Manager",
"share": 1
},
{
"delete": 1,
"email": 1,
"export": 1,
"permlevel": 2,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"delete": 1,
"email": 1,
@ -437,6 +430,7 @@
}
],
"search_fields": "subject",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "subject",
"track_changes": 1,

View file

@ -8,11 +8,11 @@ from frappe.model.document import Document
from frappe.utils import validate_email_address, get_fullname, strip_html, cstr
from frappe.core.doctype.communication.email import (validate_email,
notify, _notify, update_parent_mins_to_first_response)
from frappe.core.utils import get_parent_doc, set_timeline_doc
from frappe.core.utils import get_parent_doc
from frappe.utils.bot import BotReply
from frappe.utils import parse_addr
from frappe.core.doctype.comment.comment import update_comment_in_doc
from email.utils import parseaddr
from collections import Counter
exclude_from_linked_with = True
@ -58,7 +58,10 @@ class Communication(Document):
self.set_sender_full_name()
validate_email(self)
set_timeline_doc(self)
if self.communication_medium == "Email":
self.set_timeline_links()
self.deduplicate_timeline_links()
def validate_reference(self):
if self.reference_doctype and self.reference_name:
@ -79,6 +82,7 @@ class Communication(Document):
circular_linking = True
break
doc = get_parent_doc(doc)
if circular_linking:
frappe.throw(_("Please make sure the Reference Communication Docs are not circularly linked."), frappe.CircularLinkingError)
@ -231,26 +235,66 @@ class Communication(Document):
if commit:
frappe.db.commit()
# Timeline Links
def set_timeline_links(self):
contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc])
for contact_name in contacts:
self.add_link('Contact', contact_name)
#link contact's dynamic links to communication
add_contact_links_to_communication(self, contact_name)
def deduplicate_timeline_links(self):
if self.timeline_links:
links, duplicate = [], False
for l in self.timeline_links:
t = (l.link_doctype, l.link_name)
if not t in links:
links.append(t)
else:
duplicate = True
if duplicate:
del self.timeline_links[:] # make it python 2 compatible as list.clear() is python 3 only
for l in links:
self.add_link(link_doctype=l[0], link_name=l[1])
def add_link(self, link_doctype, link_name, autosave=False):
self.append("timeline_links",
{
"link_doctype": link_doctype,
"link_name": link_name
}
)
if autosave:
self.save(ignore_permissions=True)
def get_links(self):
return self.timeline_links
def remove_link(self, link_doctype, link_name, autosave=False, ignore_permissions=True):
for l in self.timeline_links:
if l.link_doctype == link_doctype and l.link_name == link_name:
self.timeline_links.remove(l)
if autosave:
self.save(ignore_permissions=ignore_permissions)
def on_doctype_update():
"""Add indexes in `tabCommunication`"""
frappe.db.add_index("Communication", ["reference_doctype", "reference_name"])
frappe.db.add_index("Communication", ["timeline_doctype", "timeline_name"])
frappe.db.add_index("Communication", ["link_doctype", "link_name"])
frappe.db.add_index("Communication", ["status", "communication_type"])
def has_permission(doc, ptype, user):
if ptype=="read":
if (doc.reference_doctype == "Communication" and doc.reference_name == doc.name) \
or (doc.timeline_doctype == "Communication" and doc.timeline_name == doc.name):
return
if doc.reference_doctype == "Communication" and doc.reference_name == doc.name:
return
if doc.reference_doctype and doc.reference_name:
if frappe.has_permission(doc.reference_doctype, ptype="read", doc=doc.reference_name):
return True
if doc.timeline_doctype and doc.timeline_name:
if frappe.has_permission(doc.timeline_doctype, ptype="read", doc=doc.timeline_name):
return True
def get_permission_query_conditions_for_communication(user):
if not user: user = frappe.session.user
@ -270,3 +314,39 @@ def get_permission_query_conditions_for_communication(user):
email_accounts = [ '"%s"'%account.get("email_account") for account in accounts ]
return """tabCommunication.email_account in ({email_accounts})"""\
.format(email_accounts=','.join(email_accounts))
def get_contacts(email_strings):
email_addrs = []
for email_string in email_strings:
if email_string:
for email in email_string.split(","):
parsed_email = parseaddr(email)[1]
if parsed_email:
email_addrs.append(parsed_email)
contacts = []
for email in email_addrs:
contact_name = frappe.db.get_value('Contact', {'email_id': email})
if not contact_name:
contact = frappe.get_doc({
"doctype": "Contact",
"first_name": frappe.unscrub(email.split("@")[0]),
"email_id": email
}).insert(ignore_permissions=True)
contact_name = contact.name
contacts.append(contact_name)
return contacts
def add_contact_links_to_communication(communication, contact_name):
contact_links = frappe.get_list("Dynamic Link", filters={
"parenttype": "Contact",
"parent": contact_name
}, fields=["link_doctype", "link_name"])
if contact_links:
for contact_link in contact_links:
communication.add_link(contact_link.link_doctype, contact_link.link_name)

View file

@ -71,12 +71,9 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
"message_id":get_message_id().strip(" <>"),
"read_receipt":read_receipt,
"has_attachment": 1 if attachments else 0
})
comm.insert(ignore_permissions=True)
}).insert(ignore_permissions=True)
if not doctype:
# if no reference given, then send it against the communication
comm.db_set(dict(reference_doctype='Communication', reference_name=comm.name))
comm.save(ignore_permissions=True)
if isinstance(attachments, string_types):
attachments = json.loads(attachments)
@ -557,5 +554,4 @@ def mark_email_as_seen(name=None):
frappe.response["type"] = 'binary'
frappe.response["filename"] = "imaginary_pixel.png"
frappe.response["filecontent"] = buffered_obj.getvalue()
frappe.response["filecontent"] = buffered_obj.getvalue()

View file

@ -44,28 +44,126 @@ class TestCommunication(unittest.TestCase):
self.assertFalse(frappe.utils.parse_addr(x)[0])
def test_circular_linking(self):
content = "This was created to test circular linking"
a = frappe.get_doc({
"doctype": "Communication",
"communication_type": "Communication",
"content": content,
}).insert()
"content": "This was created to test circular linking: Communication A",
}).insert(ignore_permissions=True)
b = frappe.get_doc({
"doctype": "Communication",
"communication_type": "Communication",
"content": content,
"content": "This was created to test circular linking: Communication B",
"reference_doctype": "Communication",
"reference_name": a.name
}).insert()
}).insert(ignore_permissions=True)
c = frappe.get_doc({
"doctype": "Communication",
"communication_type": "Communication",
"content": content,
"content": "This was created to test circular linking: Communication C",
"reference_doctype": "Communication",
"reference_name": b.name
}).insert()
}).insert(ignore_permissions=True)
a = frappe.get_doc("Communication", a.name)
a.reference_doctype = "Communication"
a.reference_name = c.name
self.assertRaises(frappe.CircularLinkingError, a.save)
def test_deduplication_timeline_links(self):
note = frappe.get_doc({
"doctype": "Note",
"title": "deduplication timeline links",
"content": "deduplication timeline links"
}).insert(ignore_permissions=True)
comm = frappe.get_doc({
"doctype": "Communication",
"communication_type": "Communication",
"content": "Deduplication of Links",
"communication_medium": "Email"
}).insert(ignore_permissions=True)
#adding same link twice
comm.add_link(link_doctype="Note", link_name=note.name, autosave=True)
comm.add_link(link_doctype="Note", link_name=note.name, autosave=True)
comm = frappe.get_doc("Communication", comm.name)
self.assertNotEqual(2, len(comm.timeline_links))
def test_contacts_attached(self):
contact_sender = frappe.get_doc({
"doctype": "Contact",
"first_name": frappe.generate_hash(length=10),
"email_id": "comm_sender@example.com"
}).insert(ignore_permissions=True)
contact_recipient = frappe.get_doc({
"doctype": "Contact",
"first_name": frappe.generate_hash(length=10),
"email_id": "comm_recipient@example.com"
}).insert(ignore_permissions=True)
contact_cc = frappe.get_doc({
"doctype": "Contact",
"first_name": frappe.generate_hash(length=10),
"email_id": "comm_cc@example.com"
}).insert(ignore_permissions=True)
comm = frappe.get_doc({
"doctype": "Communication",
"communication_medium": "Email",
"subject": "Contacts Attached Test",
"sender": "comm_sender@example.com",
"recipients": "comm_recipient@example.com",
"cc": "comm_cc@example.com"
}).insert(ignore_permissions=True)
comm = frappe.get_doc("Communication", comm.name)
contact_links = []
for timeline_link in comm.timeline_links:
contact_links.append(timeline_link.link_name)
self.assertIn(contact_sender.name, contact_links)
self.assertIn(contact_recipient.name, contact_links)
self.assertIn(contact_cc.name, contact_links)
def test_get_communication_data(self):
from frappe.desk.form.load import get_communication_data
note = frappe.get_doc({
"doctype": "Note",
"title": "get communication data",
"content": "get communication data"
}).insert(ignore_permissions=True)
comm_note_1 = frappe.get_doc({
"doctype": "Communication",
"communication_type": "Communication",
"content": "Test Get Communication Data 1",
"communication_medium": "Email"
}).insert(ignore_permissions=True)
comm_note_1.add_link(link_doctype="Note", link_name=note.name, autosave=True)
comm_note_2 = frappe.get_doc({
"doctype": "Communication",
"communication_type": "Communication",
"content": "Test Get Communication Data 2",
"communication_medium": "Email"
}).insert(ignore_permissions=True)
comm_note_2.add_link(link_doctype="Note", link_name=note.name, autosave=True)
comms = get_communication_data("Note", note.name, as_dict=True)
data = []
for comm in comms:
data.append(comm.name)
self.assertIn(comm_note_1.name, data)
self.assertIn(comm_note_2.name, data)

View file

@ -1,125 +1,47 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-01-13 04:55:18.835023",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"creation": "2017-01-13 04:55:18.835023",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"link_doctype",
"link_name",
"link_title"
],
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "link_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Link DocType",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "link_doctype",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Link DocType",
"options": "DocType",
"reqd": 1
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "link_name",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Link Name",
"length": 0,
"no_copy": 0,
"options": "link_doctype",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
"fieldname": "link_name",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Link Name",
"options": "link_doctype",
"reqd": 1
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "link_title",
"fieldtype": "Read Only",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Link Title",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"fieldname": "link_title",
"fieldtype": "Read Only",
"in_list_view": 1,
"label": "Link Title",
"read_only": 1
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-01-17 14:25:49.140730",
"modified_by": "Administrator",
"module": "Core",
"name": "Dynamic Link",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
],
"istable": 1,
"modified": "2019-05-16 19:54:31.400026",
"modified_by": "Administrator",
"module": "Core",
"name": "Dynamic Link",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View file

@ -43,10 +43,17 @@ class Event(Document):
def sync_communication(self):
if self.event_participants:
for participant in self.event_participants:
communication_name = frappe.db.get_value("Communication", dict(reference_doctype=self.doctype, reference_name=self.name, timeline_doctype=participant.reference_doctype, timeline_name=participant.reference_docname), "name")
if communication_name:
communication = frappe.get_doc("Communication", communication_name)
self.update_communication(participant, communication)
comms = frappe.get_list("Communication", filters=[
["Communication", "reference_doctype", "=", self.doctype],
["Communication", "reference_name", "=", self.name],
["Dynamic Link", "link_doctype", "=", participant.reference_doctype],
["Dynamic Link", "link_name", "=", participant.reference_docname]
], fields=["name"])
if comms:
for comm in comms:
communication = frappe.get_doc("Communication", comm.name)
self.update_communication(participant, communication)
else:
meta = frappe.get_meta(participant.reference_doctype)
if hasattr(meta, "allow_events_in_timeline") and meta.allow_events_in_timeline==1:
@ -62,12 +69,11 @@ class Event(Document):
communication.subject = self.subject
communication.content = self.description if self.description else self.subject
communication.communication_date = self.starts_on
communication.timeline_doctype = participant.reference_doctype
communication.timeline_name = participant.reference_docname
communication.reference_doctype = self.doctype
communication.reference_name = self.name
communication.communication_medium = communication_mapping[self.event_category] if self.event_category else ""
communication.status = "Linked"
communication.add_link(participant.reference_doctype, participant.reference_docname)
communication.save(ignore_permissions=True)
@frappe.whitelist()
@ -76,9 +82,18 @@ def delete_communication(event, reference_doctype, reference_docname):
if isinstance(event, string_types):
event = json.loads(event)
communication_name = frappe.db.get_value("Communication", dict(reference_doctype=event["doctype"], reference_name=event["name"], timeline_doctype=deleted_participant.reference_doctype, timeline_name=deleted_participant.reference_docname), "name")
if communication_name:
deletion = frappe.get_doc("Communication", communication_name).delete()
comms = frappe.get_list("Communication", filters=[
["Communication", "reference_doctype", "=", event.get("doctype")],
["Communication", "reference_name", "=", event.get("name")],
["Dynamic Link", "link_doctype", "=", deleted_participant.reference_doctype],
["Dynamic Link", "link_name", "=", deleted_participant.reference_docname]
], fields=["name"])
if comms:
deletion = []
for comm in comms:
delete = frappe.get_doc("Communication", comm.name).delete()
deletion.append(delete)
return deletion

View file

@ -43,8 +43,11 @@ class ToDo(Document):
def on_trash(self):
# unlink todo from linked comments
frappe.db.sql("""update `tabCommunication` set link_doctype=null, link_name=null
where link_doctype=%(doctype)s and link_name=%(name)s""", {"doctype": self.doctype, "name": self.name})
frappe.db.sql("""
delete from `tabDynamic Link`
where link_doctype=%(doctype)s and link_name=%(name)s""", {
"doctype": self.doctype, "name": self.name
})
self.update_in_reference()

View file

@ -160,36 +160,50 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=
group_by=None, as_dict=True):
'''Returns list of communications for a given document'''
if not fields:
fields = '''`name`, `communication_type`,`communication_medium`, `comment_type`,
`communication_date`, `content`, `sender`, `sender_full_name`, `cc`, `bcc`,
`creation`, `subject`, `delivery_status`, `_liked_by`,
`timeline_doctype`, `timeline_name`, `reference_doctype`, `reference_name`,
`link_doctype`, `link_name`, `read_by_recipient`, `rating`, 'Communication' AS `doctype`'''
fields = '''
`tabCommunication`.name, `tabCommunication`.communication_type, `tabCommunication`.communication_medium,
`tabCommunication`.comment_type, `tabCommunication`.communication_date, `tabCommunication`.content,
`tabCommunication`.sender, `tabCommunication`.sender_full_name, `tabCommunication`.cc, `tabCommunication`.bcc,
`tabCommunication`.creation, `tabCommunication`.subject, `tabCommunication`.delivery_status,
`tabCommunication`._liked_by, `tabCommunication`.reference_doctype, `tabCommunication`.reference_name,
`tabCommunication`.read_by_recipient, `tabCommunication`.rating
'''
conditions = '''communication_type = 'Communication'
and (
(reference_doctype=%(doctype)s and reference_name=%(name)s)
conditions = '''
`tabCommunication`.communication_type in ('Communication', 'Feedback')
and (
(`tabCommunication`.reference_doctype=%(doctype)s and `tabCommunication`.reference_name=%(name)s)
or (
(timeline_doctype=%(doctype)s and timeline_name=%(name)s)
and (communication_type='Communication')
(`tabDynamic Link`.link_doctype=%(doctype)s and `tabDynamic Link`.link_name=%(name)s)
and (`tabCommunication`.communication_type='Communication')
)
)'''
)
'''
if after:
# find after a particular date
conditions+= ' and creation > {0}'.format(after)
conditions += '''
and `tabCommunication`.creation > {0}
'''.format(after)
if doctype=='User':
conditions+= " and not (reference_doctype='User' and communication_type='Communication')"
conditions += '''
and not (`tabCommunication`.reference_doctype='User' and `tabCommunication`.communication_type='Communication')
'''
communications = frappe.db.sql("""select {fields}
communications = frappe.db.sql('''
select distinct {fields}
from `tabCommunication`
inner join `tabDynamic Link`
on `tabCommunication`.name=`tabDynamic Link`.parent
where {conditions} {group_by}
order by creation desc LIMIT %(limit)s OFFSET %(start)s""".format(
fields = fields, conditions=conditions, group_by=group_by or ""),
{ "doctype": doctype, "name": name, "start": frappe.utils.cint(start), "limit": limit },
as_dict=as_dict)
order by `tabCommunication`.creation desc
limit %(limit)s offset %(start)s'''.format(fields = fields, conditions=conditions, group_by=group_by or ""),{
"doctype": doctype,
"name": name,
"start": frappe.utils.cint(start),
"limit": limit
}, as_dict=as_dict)
return communications
@ -229,4 +243,4 @@ def get_view_logs(doctype, docname):
if view_logs:
logs = view_logs
return logs
return logs

View file

@ -387,7 +387,7 @@ class EmailAccount(Document):
communication._seen = json.dumps(users)
communication.flags.in_receive = True
communication.insert(ignore_permissions = 1)
communication.insert(ignore_permissions=True)
# save attachments
communication._attachments = email.save_attachments_in_doc(communication)

View file

@ -26,7 +26,7 @@ class TestEmailAccount(unittest.TestCase):
email_account.db_set("enable_incoming", 0)
def test_incoming(self):
frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'")
cleanup("test_sender@example.com")
with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-1.raw"), "r") as f:
test_mails = [f.read()]
@ -52,7 +52,8 @@ class TestEmailAccount(unittest.TestCase):
"reference_name": comm.reference_name, "status":"Not Sent"}))
def test_incoming_with_attach(self):
frappe.db.sql("DELETE FROM `tabCommunication` WHERE sender='test_sender@example.com'")
cleanup("test_sender@example.com")
existing_file = frappe.get_doc({'doctype': 'File', 'file_name': 'erpnext-conf-14.png'})
frappe.delete_doc("File", existing_file.name)
@ -75,7 +76,7 @@ class TestEmailAccount(unittest.TestCase):
def test_incoming_attached_email_from_outlook_plain_text_only(self):
frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'")
cleanup("test_sender@example.com")
with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-3.raw"), "r") as f:
test_mails = [f.read()]
@ -88,7 +89,7 @@ class TestEmailAccount(unittest.TestCase):
self.assertTrue("This is an e-mail message sent automatically by Microsoft Outlook while" in comm.content)
def test_incoming_attached_email_from_outlook_layers(self):
frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'")
cleanup("test_sender@example.com")
with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-4.raw"), "r") as f:
test_mails = [f.read()]
@ -123,8 +124,7 @@ class TestEmailAccount(unittest.TestCase):
self.assertTrue("test-mail-002" in sent_mail.get("Subject"))
def test_threading(self):
frappe.db.sql("""delete from tabCommunication
where sender in ('test_sender@example.com', 'test@example.com')""")
cleanup(["in", ['test_sender@example.com', 'test@example.com']])
# send
sent_name = make(subject = "Test", content="test content",
@ -149,8 +149,7 @@ class TestEmailAccount(unittest.TestCase):
self.assertEqual(comm.reference_name, sent.reference_name)
def test_threading_by_subject(self):
frappe.db.sql("""delete from tabCommunication
where sender in ('test_sender@example.com', 'test@example.com')""")
cleanup(["in", ['test_sender@example.com', 'test@example.com']])
with open(os.path.join(os.path.dirname(__file__), "test_mails", "reply-2.raw"), "r") as f:
test_mails = [f.read()]
@ -170,7 +169,7 @@ class TestEmailAccount(unittest.TestCase):
self.assertEqual(comm_list[0].reference_name, comm_list[1].reference_name)
def test_threading_by_message_id(self):
frappe.db.sql("""delete from tabCommunication""")
cleanup()
frappe.db.sql("""delete from `tabEmail Queue`""")
# reference document for testing
@ -196,3 +195,13 @@ class TestEmailAccount(unittest.TestCase):
# check if threaded correctly
self.assertEqual(comm_list[0].reference_doctype, event.doctype)
self.assertEqual(comm_list[0].reference_name, event.name)
def cleanup(sender=None):
filters = {}
if sender:
filters.update({"sender": sender})
names = frappe.get_list("Communication", filters=filters, fields=["name"])
for name in names:
frappe.delete_doc_if_exists("Communication", name.name)
frappe.delete_doc_if_exists("Dynamic Link", {"parent": name.name})

View file

@ -278,9 +278,8 @@ def delete_dynamic_links(doctype, name):
delete_references('Document Follow', doctype, name, 'ref_doctype', 'ref_docname')
# unlink communications
clear_timeline_references(doctype, name)
clear_references('Communication', doctype, name)
clear_references('Communication', doctype, name, 'link_doctype', 'link_name')
clear_references('Communication', doctype, name, 'timeline_doctype', 'timeline_name')
clear_references('Activity Log', doctype, name)
clear_references('Activity Log', doctype, name, 'timeline_doctype', 'timeline_name')
@ -301,6 +300,9 @@ def clear_references(doctype, reference_doctype, reference_name,
{1}=%s and {2}=%s'''.format(doctype, reference_doctype_field, reference_name_field), # nosec
(reference_doctype, reference_name))
def clear_timeline_references(link_doctype, link_name):
frappe.db.sql("""delete from `tabDynamic Link`
where `tabDynamic Link`.link_doctype='{0}' and `tabDynamic Link`.link_name='{1}'""".format(link_doctype, link_name)) # nosec
def insert_feed(doc):
from frappe.utils import get_fullname

View file

@ -241,3 +241,4 @@ frappe.patches.v12_0.reset_home_settings
frappe.patches.v12_0.update_print_format_type
frappe.patches.v11_0.remove_doctype_user_permissions_for_page_and_report #2019-05-01
frappe.patches.v12_0.remove_feedback_rating
frappe.patches.v12_0.move_timeline_links_to_dynamic_links

View file

@ -0,0 +1,46 @@
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc('core', 'doctype', 'communication')
communications = frappe.db.sql("""
SELECT
`tabCommunication`.name, `tabCommunication`.creation, `tabCommunication`.modified,
`tabCommunication`.modified_by,`tabCommunication`.timeline_doctype, `tabCommunication`.timeline_name,
`tabCommunication`.link_doctype, `tabCommunication`.link_name
FROM `tabCommunication`
WHERE `tabCommunication`.communication_medium='Email'
""", as_dict=True)
for count, communication in enumerate(communications):
counter = 1
if communication.timeline_doctype and communication.timeline_name:
values = [
counter, frappe.generate_hash(length=10), 'timeline_links', 'Communication', communication.name,
communication.timeline_doctype, communication.timeline_name, communication.creation, communication.modified,
communication.modified_by
]
execute_query(values)
counter += 1
if communication.link_doctype and communication.link_name:
values = [
counter, frappe.generate_hash(length=10), 'timeline_links', 'Communication', communication.name,
communication.link_doctype, communication.link_name, communication.creation, communication.modified,
communication.modified_by
]
execute_query(values)
def execute_query(values):
try:
frappe.db.sql("""
INSERT INTO `tabDynamic Link`
(`idx`, `name`, `parentfield`, `parenttype`, `parent`, `link_doctype`, `link_name`, `creation`,
`modified`, `modified_by`)
VALUES ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9})
""".format(values[0], values[1], values[2], values[3], values[4], values[5], values[6], values[7], values[8], values[9]))
except Exception as e:
values[1] = frappe.generate_hash(length=10)
execute_query(values)