fix: timeline docs to dynamic links

This commit is contained in:
Himanshu Warekar 2019-05-14 12:01:14 +05:30
parent 8381e0048a
commit 5d09fb26a9
9 changed files with 106 additions and 73 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

@ -8,7 +8,6 @@ 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
from frappe.utils.bot import BotReply
from frappe.utils import parse_addr
from frappe.core.doctype.comment.comment import update_comment_in_doc
@ -57,6 +56,8 @@ class Communication(Document):
self.set_status()
self.set_sender_full_name()
self.validate_dynamic_links()
self.deduplicate_dynamic_links()
validate_email(self)
def validate_reference(self):
@ -72,12 +73,14 @@ class Communication(Document):
# Prevent circular linking of Communication DocTypes
if self.reference_doctype == "Communication":
circular_linking = False
doc = get_parent_doc(self)
while doc.reference_doctype == "Communication":
if get_parent_doc(doc).name==self.name:
circular_linking = True
break
doc = get_parent_doc(doc)
doc = get_parent_doc(self.reference_doctype, self.reference_name)
if doc:
while doc.reference_doctype == "Communication":
if doc:
if doc.reference_name == self.name:
circular_linking = True
break
doc = get_parent_doc(doc.reference_doctype, doc.reference_name)
if circular_linking:
frappe.throw(_("Please make sure the Reference Communication Docs are not circularly linked."), frappe.CircularLinkingError)
@ -247,32 +250,22 @@ class Communication(Document):
for l in links:
self.add_link(link_doctype=l[0], link_name=l[1])
def validate_circular_links(self):
def validate_dynamic_links(self):
circular_linking = False
for dynamic_link in self.dynamic_links:
# Prevent circular linking of Communication DocTypes
# Prevent circular linking of Timeline DocTypes
if dynamic_link.link_doctype == "Communication":
circular_linking = False
circular_level_1 = get_timeline_parent_doc(dynamic_link.link_doctype, dynamic_link.link_name)
doc = get_parent_doc(dynamic_link.link_doctype, dynamic_link.link_name)
if doc:
while doc.reference_doctype == "Communication":
if doc:
if doc.reference_name == self.name:
circular_linking = True
break
# Level 1
if circular_level_1:
for link in circular_level_1.dynamic_links:
if link.link_doctype == "Communication":
circular_level_2 = get_timeline_parent_doc(link.link_doctype, link.link_name)
# Level 2
if circular_level_2:
for ref_link in circular_level_2.dynamic_links:
if ref_link.link_doctype == "Communication":
circular_level_3 = get_timeline_parent_doc(ref_link.link_doctype, ref_link.link_name)
# Level 3
if circular_level_3:
if circular_level_3.name == self.name:
circular_linking = True
break
if circular_linking:
frappe.throw(_("Please make sure the Timeline Communication Docs are not circularly linked."), frappe.CircularLinkingError)
if circular_linking:
frappe.throw(_("Please make sure the Timeline Communication Docs are not circularly linked."), frappe.CircularLinkingError)
def add_link(self, link_doctype, link_name, autosave=False):
self.append("dynamic_links",
@ -330,8 +323,8 @@ def get_permission_query_conditions_for_communication(user):
return """tabCommunication.email_account in ({email_accounts})"""\
.format(email_accounts=','.join(email_accounts))
def get_timeline_parent_doc(link_doctype, link_name):
def get_parent_doc(link_doctype, link_name):
"""Returns document of `link_doctype`, `link_name`"""
if link_doctype and link_name:
parent_doc = frappe.get_doc(link_doctype, link_name)
return parent_doc if parent_doc else None
return parent_doc if parent_doc else None

View file

@ -72,21 +72,21 @@ 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.reference_doctype = 'Communication'
comm.reference_name = comm.name
# contacts = get_contacts([sender, recipients, cc, bcc])
# for contact_name in contacts:
# comm.add_link('Contact', contact_name)
contacts = get_contacts([sender, recipients, cc, bcc])
for contact_name in contacts:
comm.add_link('Contact', contact_name)
# #link contact's dynamic links to communication
# add_contact_links_to_communication(comm, contact_name)
#link contact's dynamic links to communication
add_contact_links_to_communication(comm, contact_name)
# comm.save(ignore_permissions=True)
comm.save(ignore_permissions=True)
if isinstance(attachments, string_types):
attachments = json.loads(attachments)

View file

@ -44,28 +44,30 @@ 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,
"content": "This was created to test circular linking: Communication A",
}).insert()
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()
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()
a = frappe.get_doc("Communication", a.name)
a.reference_doctype = "Communication"
a.reference_name = c.name
self.assertRaises(frappe.CircularLinkingError, a.save)
self.assertRaises(frappe.CircularLinkingError, a.save)

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", comms.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

@ -182,6 +182,11 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=
)
'''
if not group_by:
group_by = '''
group by `tabCommunication`.name
'''
if after:
# find after a particular date
conditions += '''
@ -210,7 +215,7 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=
"start": frappe.utils.cint(start),
"limit": limit
}, as_dict=as_dict, debug=True)
print(communications)
return communications
def get_assignments(dt, dn):

View file

@ -386,15 +386,15 @@ class EmailAccount(Document):
users = list(set([ user.get("parent") for user in users ]))
communication._seen = json.dumps(users)
# contacts = get_contacts([sender, recipients, cc, bcc])
# for contact_name in contacts:
# comm.add_link('Contact', contact_name)
contacts = get_contacts([email.from_email, email.mail.get("To"), email.mail.get("CC"), email.from_email])
for contact_name in contacts:
communication.add_link('Contact', contact_name)
# #link contact's dynamic links to communication
# add_contact_links_to_communication(comm, contact_name)
#link contact's dynamic links to communication
add_contact_links_to_communication(communication, contact_name)
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

@ -279,7 +279,7 @@ def delete_dynamic_links(doctype, name):
# unlink communications
clear_references('Communication', doctype, name)
clear_references('Communication', doctype, name, 'link_doctype', 'link_name')
clear_references('Communication', doctype, name, 'timeline_doctype', 'timeline_name')
clear_timeline_references(doctype, name)
clear_references('Activity Log', doctype, name)
clear_references('Activity Log', doctype, name, 'timeline_doctype', 'timeline_name')
@ -300,6 +300,15 @@ 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):
comms = frappe.get_list("Communication", filters=[
["Dynamic Link", "link_doctype", "=", link_doctype],
["Dynamic Link", "link_name", "=", link_name]
], fields=["name"])
for comm in comms:
doc = frappe.get_doc("Communication", comm.name)
doc.remove_link(link_doctype=link_doctype, link_name=link_name, autosave=True)
def insert_feed(doc):
from frappe.utils import get_fullname