[enhancement] Merge Comment and Feed into Communication 💥
This commit is contained in:
parent
2c95a9599e
commit
465318878e
76 changed files with 2512 additions and 2491 deletions
|
|
@ -25,7 +25,7 @@ def get_data():
|
|||
"label": _("Messages"),
|
||||
"name": "messages",
|
||||
"description": _("Chat messages and other notifications."),
|
||||
"data_doctype": "Comment"
|
||||
"data_doctype": "Communication"
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
Comments added on Forms, Blogs and other places
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
|
@ -1,338 +0,0 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "hash",
|
||||
"creation": "2012-08-08 10:40:11",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "comment",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Comment",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "comment",
|
||||
"oldfieldtype": "Text",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "comment_type",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Comment Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Email\nChat\nPhone\nSMS\nCreated\nSubmitted\nCancelled\nAssigned\nAssignment Completed\nComment\nWorkflow\nLabel\nAttachment\nAttachment Removed\nLike",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "comment_by",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Comment By",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "comment_by",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "comment_by_fullname",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Comment By Fullname",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "comment_by_fullname",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "comment_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Comment Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "comment_date",
|
||||
"oldfieldtype": "Date",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "comment_time",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Comment Time",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "comment_time",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "comment_doctype",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Comment Doctype",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "comment_doctype",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "comment_docname",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Comment Docname",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "comment_docname",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "post_topic",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Post Topic",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "post_topic",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "unsubscribed",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Unsubscribed",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference DocType",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"description": "Reference DocType and Reference Name are used to render a comment as a link (href) to a Doc.",
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "reference_doctype",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-comments",
|
||||
"idx": 1,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2015-12-26 06:29:43.314568",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Comment",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 1,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"title_field": "comment"
|
||||
}
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, json
|
||||
from frappe import _
|
||||
from frappe.website.render import clear_cache
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.db_schema import add_column
|
||||
from frappe.utils import get_fullname, get_link_to_form
|
||||
from frappe.core.doctype.user.user import extract_mentions
|
||||
|
||||
exclude_from_linked_with = True
|
||||
|
||||
class Comment(Document):
|
||||
"""Comments are added to Documents via forms or views like blogs etc."""
|
||||
no_feed_on_delete = True
|
||||
|
||||
def get_feed(self):
|
||||
"""Returns feed HTML from Comment."""
|
||||
if self.comment_doctype == "Message":
|
||||
return
|
||||
|
||||
if self.comment_type in ("Created", "Submitted", "Cancelled", "Label"):
|
||||
comment_type = "Label"
|
||||
elif self.comment_type in ("Comment", "Like"):
|
||||
comment_type = self.comment_type
|
||||
else:
|
||||
comment_type = "Info"
|
||||
|
||||
return {
|
||||
"subject": self.comment,
|
||||
"doctype": self.comment_doctype,
|
||||
"name": self.comment_docname,
|
||||
"feed_type": comment_type,
|
||||
"reference_doctype": self.reference_doctype,
|
||||
"reference_name": self.reference_name
|
||||
}
|
||||
|
||||
def after_insert(self):
|
||||
"""Send realtime updates"""
|
||||
if not self.comment_doctype:
|
||||
return
|
||||
|
||||
if self.comment_doctype == 'Message':
|
||||
if self.comment_docname == frappe.session.user:
|
||||
message = self.as_dict()
|
||||
message['broadcast'] = True
|
||||
frappe.publish_realtime('new_message', message, after_commit=True)
|
||||
else:
|
||||
# comment_docname contains the user who is addressed in the messages' page comment
|
||||
frappe.publish_realtime('new_message', self.as_dict(),
|
||||
user=self.comment_docname, after_commit=True)
|
||||
else:
|
||||
frappe.publish_realtime('new_comment', self.as_dict(),
|
||||
doctype= self.comment_doctype, docname = self.comment_docname,
|
||||
after_commit=True)
|
||||
|
||||
self.notify_mentions()
|
||||
|
||||
def validate(self):
|
||||
"""Raise exception for more than 50 comments."""
|
||||
if frappe.db.sql("""select count(*) from tabComment where comment_doctype=%s
|
||||
and comment_docname=%s""", (self.doctype, self.name))[0][0] >= 50:
|
||||
frappe.throw(_("Cannot add more than 50 comments"))
|
||||
|
||||
if not self.comment_by_fullname and self.comment_by:
|
||||
self.comment_by_fullname = get_fullname(self.comment_by)
|
||||
|
||||
def on_update(self):
|
||||
"""Updates `_comments` property in parent Document."""
|
||||
self.update_comment_in_doc()
|
||||
|
||||
def update_comment_in_doc(self):
|
||||
"""Updates `_comments` (JSON) property in parent Document.
|
||||
Creates a column `_comments` if property does not exist.
|
||||
|
||||
`_comments` format
|
||||
|
||||
{
|
||||
"comment": [String],
|
||||
"by": [user],
|
||||
"name": [Comment Document name]
|
||||
}"""
|
||||
if self.comment_doctype and self.comment_docname and self.comment and self.comment_type=="Comment":
|
||||
_comments = self.get_comments_from_parent()
|
||||
updated = False
|
||||
for c in _comments:
|
||||
if c.get("name")==self.name:
|
||||
c["comment"] = self.comment
|
||||
updated = True
|
||||
|
||||
if not updated:
|
||||
_comments.append({
|
||||
"comment": self.comment,
|
||||
"by": self.comment_by or self.owner,
|
||||
"name":self.name
|
||||
})
|
||||
self.update_comments_in_parent(_comments)
|
||||
|
||||
def get_comments_from_parent(self):
|
||||
try:
|
||||
_comments = frappe.db.get_value(self.comment_doctype,
|
||||
self.comment_docname, "_comments") or "[]"
|
||||
|
||||
return json.loads(_comments)
|
||||
|
||||
except Exception, e:
|
||||
|
||||
if e.args[0]==1054:
|
||||
if frappe.flags.in_test:
|
||||
return
|
||||
|
||||
add_column(self.comment_doctype, "_comments", "Text")
|
||||
|
||||
return self.get_comments_from_parent()
|
||||
|
||||
elif e.args[0]==1146:
|
||||
# no table
|
||||
pass
|
||||
|
||||
else:
|
||||
raise
|
||||
|
||||
def update_comments_in_parent(self, _comments):
|
||||
"""Updates `_comments` property in parent Document with given dict.
|
||||
|
||||
:param _comments: Dict of comments."""
|
||||
if not self.comment_doctype or frappe.db.get_value("DocType", self.comment_doctype, "issingle"):
|
||||
return
|
||||
|
||||
# use sql, so that we do not mess with the timestamp
|
||||
frappe.db.sql("""update `tab%s` set `_comments`=%s where name=%s""" % (self.comment_doctype,
|
||||
"%s", "%s"), (json.dumps(_comments), self.comment_docname))
|
||||
|
||||
comment_doc = frappe.get_doc(self.comment_doctype, self.comment_docname)
|
||||
if getattr(comment_doc, "get_route", None):
|
||||
clear_cache(comment_doc.get_route())
|
||||
|
||||
def on_trash(self):
|
||||
"""Removes from `_comments` in parent Document"""
|
||||
if self.comment_doctype == "Message":
|
||||
return
|
||||
|
||||
if (self.comment_type or "Comment") != "Comment":
|
||||
frappe.only_for("System Manager")
|
||||
|
||||
_comments = self.get_comments_from_parent()
|
||||
for c in _comments:
|
||||
if c.get("name")==self.name:
|
||||
_comments.remove(c)
|
||||
|
||||
self.update_comments_in_parent(_comments)
|
||||
|
||||
def notify_mentions(self):
|
||||
if self.comment_doctype and self.comment_docname and self.comment and self.comment_type=="Comment":
|
||||
mentions = extract_mentions(self.comment)
|
||||
|
||||
if not mentions:
|
||||
return
|
||||
|
||||
sender_fullname = get_fullname(frappe.session.user)
|
||||
parent_doc_label = "{0} {1}".format(_(self.comment_doctype), self.comment_docname)
|
||||
subject = _("{0} mentioned you in a comment in {1}").format(sender_fullname, parent_doc_label)
|
||||
message = frappe.get_template("templates/emails/mentioned_in_comment.html").render({
|
||||
"sender_fullname": sender_fullname,
|
||||
"comment": self,
|
||||
"link": get_link_to_form(self.comment_doctype, self.comment_docname, label=parent_doc_label)
|
||||
})
|
||||
|
||||
recipients = [frappe.db.get_value("User", {"enabled": 1, "username": username, "user_type": "System User"})
|
||||
for username in mentions]
|
||||
|
||||
frappe.sendmail(
|
||||
recipients=recipients,
|
||||
sender=frappe.session.user,
|
||||
subject=subject,
|
||||
message=message,
|
||||
bulk=True
|
||||
)
|
||||
|
||||
def on_doctype_update():
|
||||
"""Add index to `tabComment` `(comment_doctype, comment_name)`"""
|
||||
if not frappe.db.sql("""show index from `tabComment`
|
||||
where Key_name="comment_doctype_docname_index" """):
|
||||
frappe.db.commit()
|
||||
frappe.db.sql("""alter table `tabComment`
|
||||
add index comment_doctype_docname_index(comment_doctype, comment_docname)""")
|
||||
|
||||
if "_liked_by" not in frappe.db.get_table_columns("Comment"):
|
||||
add_column("Comment", "_liked_by", "Text")
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, unittest, json
|
||||
|
||||
# commented due to commits -- only run when comments are modified
|
||||
|
||||
# class TestComment(unittest.TestCase):
|
||||
# def setUp(self):
|
||||
# self.cleanup()
|
||||
# self.test_rec = frappe.get_doc({
|
||||
# "doctype":"Event",
|
||||
# "subject":"__Comment Test Event",
|
||||
# "event_type": "Private",
|
||||
# "starts_on": "2011-01-01 10:00:00",
|
||||
# "ends_on": "2011-01-01 10:00:00",
|
||||
# }).insert()
|
||||
#
|
||||
# def tearDown(self):
|
||||
# self.cleanup()
|
||||
#
|
||||
# def cleanup(self):
|
||||
# frappe.db.sql("""delete from tabEvent where subject='__Comment Test Event'""")
|
||||
# frappe.db.sql("""delete from tabComment where comment='__Test Comment'""")
|
||||
# frappe.db.commit()
|
||||
# if "_comments" in frappe.db.get_table_columns("Event"):
|
||||
# frappe.db.commit()
|
||||
# frappe.db.sql("""alter table `tabEvent` drop column `_comments`""")
|
||||
#
|
||||
# def test_add_comment(self):
|
||||
# self.comment = frappe.get_doc({
|
||||
# "doctype":"Comment",
|
||||
# "comment_doctype": self.test_rec.doctype,
|
||||
# "comment_docname": self.test_rec.name,
|
||||
# "comment": "__Test Comment"
|
||||
# }).insert()
|
||||
#
|
||||
# test_rec = frappe.get_doc(self.test_rec.doctype, self.test_rec.name)
|
||||
# _comments = json.loads(test_rec.get("_comments"))
|
||||
# self.assertTrue(_comments[0].get("comment")=="__Test Comment")
|
||||
#
|
||||
# def test_remove_comment(self):
|
||||
# self.test_add_comment()
|
||||
# frappe.delete_doc("Comment", self.comment.name)
|
||||
# test_rec = frappe.get_doc(self.test_rec.doctype, self.test_rec.name)
|
||||
# _comments = json.loads(test_rec.get("_comments"))
|
||||
# self.assertEqual(len(_comments), 0)
|
||||
#
|
||||
#
|
||||
# if __name__=="__main__":
|
||||
# unittest.main()
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
[
|
||||
{
|
||||
"doctype": "Comment",
|
||||
"name": "_Test Comment 1",
|
||||
"comment": "test comment"
|
||||
}
|
||||
]
|
||||
150
frappe/core/doctype/communication/comment.py
Normal file
150
frappe/core/doctype/communication/comment.py
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
import frappe
|
||||
from frappe import _
|
||||
import json
|
||||
from frappe.core.doctype.user.user import extract_mentions
|
||||
from frappe.utils import get_fullname, get_link_to_form
|
||||
from frappe.model.db_schema import add_column
|
||||
from frappe.website.render import clear_cache
|
||||
|
||||
def validate_comment(doc):
|
||||
"""Raise exception for more than 50 comments."""
|
||||
if not (doc.communication_type=='Comment' and doc.reference_doctype and doc.reference_name):
|
||||
return
|
||||
|
||||
comment_count = frappe.db.sql("""select count(*) from `tabCommunication`
|
||||
where
|
||||
communication_type='Comment'
|
||||
and reference_doctype=%(reference_doctype)s
|
||||
and reference_name=%(reference_name)s""",
|
||||
{"reference_doctype": doc.reference_doctype, "reference_name": doc.reference_name})[0][0]
|
||||
|
||||
if comment_count >= 50:
|
||||
frappe.throw(_("Cannot add more than 50 comments"))
|
||||
|
||||
def on_trash(doc):
|
||||
if doc.communication_type != "Comment":
|
||||
return
|
||||
|
||||
if doc.reference_doctype == "Message":
|
||||
return
|
||||
|
||||
if (doc.comment_type or "Comment") != "Comment":
|
||||
frappe.only_for("System Manager")
|
||||
|
||||
_comments = get_comments_from_parent(doc)
|
||||
for c in _comments:
|
||||
if c.get("name")==doc.name:
|
||||
_comments.remove(c)
|
||||
|
||||
update_comments_in_parent(doc, _comments)
|
||||
|
||||
def update_comment_in_doc(doc):
|
||||
"""Updates `_comments` (JSON) property in parent Document.
|
||||
Creates a column `_comments` if property does not exist.
|
||||
|
||||
`_comments` format
|
||||
|
||||
{
|
||||
"comment": [String],
|
||||
"by": [user],
|
||||
"name": [Comment Document name]
|
||||
}"""
|
||||
|
||||
if doc.communication_type != "Comment":
|
||||
return
|
||||
|
||||
if doc.reference_doctype and doc.reference_name and doc.content and doc.comment_type=="Comment":
|
||||
_comments = get_comments_from_parent(doc)
|
||||
updated = False
|
||||
for c in _comments:
|
||||
if c.get("name")==doc.name:
|
||||
c["comment"] = doc.content
|
||||
updated = True
|
||||
|
||||
if not updated:
|
||||
_comments.append({
|
||||
"comment": doc.content,
|
||||
"by": doc.sender or doc.owner,
|
||||
"name": doc.name
|
||||
})
|
||||
update_comments_in_parent(doc, _comments)
|
||||
|
||||
def notify_mentions(doc):
|
||||
if doc.communication_type != "Comment":
|
||||
return
|
||||
|
||||
if doc.reference_doctype and doc.reference_name and doc.content and doc.comment_type=="Comment":
|
||||
mentions = extract_mentions(doc.content)
|
||||
|
||||
if not mentions:
|
||||
return
|
||||
|
||||
sender_fullname = get_fullname(frappe.session.user)
|
||||
parent_doc_label = "{0} {1}".format(_(doc.reference_doctype), doc.reference_name)
|
||||
subject = _("{0} mentioned you in a comment in {1}").format(sender_fullname, parent_doc_label)
|
||||
message = frappe.get_template("templates/emails/mentioned_in_comment.html").render({
|
||||
"sender_fullname": sender_fullname,
|
||||
"comment": doc,
|
||||
"link": get_link_to_form(doc.reference_doctype, doc.reference_name, label=parent_doc_label)
|
||||
})
|
||||
|
||||
recipients = [frappe.db.get_value("User", {"enabled": 1, "username": username, "user_type": "System User"})
|
||||
for username in mentions]
|
||||
|
||||
frappe.sendmail(
|
||||
recipients=recipients,
|
||||
sender=frappe.session.user,
|
||||
subject=subject,
|
||||
message=message,
|
||||
bulk=True
|
||||
)
|
||||
|
||||
def get_comments_from_parent(doc):
|
||||
try:
|
||||
_comments = frappe.db.get_value(doc.reference_doctype, doc.reference_name, "_comments") or "[]"
|
||||
|
||||
return json.loads(_comments)
|
||||
|
||||
except Exception, e:
|
||||
|
||||
if e.args[0]==1054:
|
||||
if frappe.flags.in_test:
|
||||
return
|
||||
|
||||
add_column(doc.reference_doctype, "_comments", "Text")
|
||||
|
||||
return get_comments_from_parent(doc)
|
||||
|
||||
elif e.args[0]==1146:
|
||||
# no table
|
||||
pass
|
||||
|
||||
else:
|
||||
raise
|
||||
|
||||
def update_comments_in_parent(doc, _comments):
|
||||
"""Updates `_comments` property in parent Document with given dict.
|
||||
|
||||
:param _comments: Dict of comments."""
|
||||
if not doc.reference_doctype or frappe.db.get_value("DocType", doc.reference_doctype, "issingle"):
|
||||
return
|
||||
|
||||
# use sql, so that we do not mess with the timestamp
|
||||
frappe.db.sql("""update `tab%s` set `_comments`=%s where name=%s""" % (doc.reference_doctype,
|
||||
"%s", "%s"), (json.dumps(_comments), doc.reference_name))
|
||||
|
||||
reference_doc = frappe.get_doc(doc.reference_doctype, doc.reference_name)
|
||||
if getattr(reference_doc, "get_route", None):
|
||||
clear_cache(reference_doc.get_route())
|
||||
|
||||
def add_info_comment(**kwargs):
|
||||
kwargs.update({
|
||||
"doctype": "Communication",
|
||||
"communication_type": "Comment",
|
||||
"comment_type": "Info"
|
||||
})
|
||||
return frappe.get_doc(kwargs).insert(ignore_permissions=True)
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -3,22 +3,103 @@
|
|||
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
import frappe
|
||||
import json
|
||||
from email.utils import formataddr, parseaddr
|
||||
from frappe.utils import get_url, get_formatted_email, cint, validate_email_add, split_emails
|
||||
from frappe.utils.file_manager import get_file
|
||||
from frappe.email.bulk import check_bulk_limit
|
||||
import frappe.email.smtp
|
||||
from frappe import _
|
||||
from frappe.model.db_schema import add_column
|
||||
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import validate_email_add, get_fullname, strip_html
|
||||
from frappe.model.db_schema import add_column
|
||||
from frappe.core.doctype.communication.comment import validate_comment, notify_mentions, update_comment_in_doc
|
||||
from frappe.core.doctype.communication.email import validate_email, notify, _notify, update_parent_status
|
||||
|
||||
exclude_from_linked_with = True
|
||||
|
||||
class Communication(Document):
|
||||
no_feed_on_delete = True
|
||||
|
||||
"""Communication represents an external communication like Email."""
|
||||
|
||||
def validate(self):
|
||||
if self.reference_doctype and self.reference_name:
|
||||
if not self.reference_owner:
|
||||
self.reference_owner = frappe.db.get_value(self.reference_doctype, self.reference_name, "owner")
|
||||
|
||||
# prevent communication against a child table
|
||||
if frappe.get_meta(self.reference_doctype).istable:
|
||||
frappe.throw(_("Cannot create a {0} against a child document: {1}")
|
||||
.format(_(self.communication_type), _(self.reference_doctype)))
|
||||
|
||||
if not self.user:
|
||||
self.user = frappe.session.user
|
||||
|
||||
if not self.subject:
|
||||
self.subject = strip_html((self.content or "")[:141])
|
||||
|
||||
self.set_status()
|
||||
self.set_sender_full_name()
|
||||
validate_email(self)
|
||||
validate_comment(self)
|
||||
self.set_timeline_doc()
|
||||
|
||||
def after_insert(self):
|
||||
if not (self.reference_doctype and self.reference_name):
|
||||
return
|
||||
|
||||
if self.communication_type in ("Communication", "Comment"):
|
||||
# send new comment to listening clients
|
||||
frappe.publish_realtime('new_communication', self.as_dict(),
|
||||
doctype= self.reference_doctype, docname = self.reference_name,
|
||||
after_commit=True)
|
||||
|
||||
if self.communication_type == "Comment":
|
||||
notify_mentions(self)
|
||||
|
||||
elif self.communication_type in ("Chat", "Notification"):
|
||||
if self.reference_name == frappe.session.user:
|
||||
message = self.as_dict()
|
||||
message['broadcast'] = True
|
||||
frappe.publish_realtime('new_message', message, after_commit=True)
|
||||
else:
|
||||
# reference_name contains the user who is addressed in the messages' page comment
|
||||
frappe.publish_realtime('new_message', self.as_dict(),
|
||||
user=self.reference_name, after_commit=True)
|
||||
|
||||
def on_update(self):
|
||||
"""Update parent status as `Open` or `Replied`."""
|
||||
update_parent_status(self)
|
||||
update_comment_in_doc(self)
|
||||
|
||||
def on_trash(self):
|
||||
if (not self.flags.ignore_permissions
|
||||
and self.communication_type=="Comment" and self.comment_type != "Comment"):
|
||||
|
||||
# prevent deletion of auto-created comments if not ignore_permissions
|
||||
frappe.throw(_("Sorry! You cannot delete auto-generated comments"))
|
||||
|
||||
if self.communication_type in ("Communication", "Comment"):
|
||||
# send delete comment to listening clients
|
||||
frappe.publish_realtime('delete_communication', self.as_dict(),
|
||||
doctype= self.reference_doctype, docname = self.reference_name,
|
||||
after_commit=True)
|
||||
|
||||
def set_status(self):
|
||||
if not self.is_new():
|
||||
return
|
||||
|
||||
if self.reference_doctype and self.reference_name:
|
||||
self.status = "Linked"
|
||||
elif self.communication_type=="Communication":
|
||||
self.status = "Open"
|
||||
else:
|
||||
self.status = "Closed"
|
||||
|
||||
def set_sender_full_name(self):
|
||||
if not self.sender_full_name and self.sender:
|
||||
if self.sender == "Administrator":
|
||||
self.sender_full_name = self.sender
|
||||
self.sender = frappe.db.get_value("User", "Administrator", "email")
|
||||
else:
|
||||
validate_email_add(self.sender, throw=True)
|
||||
self.sender_full_name = get_fullname(self.sender)
|
||||
|
||||
def get_parent_doc(self):
|
||||
"""Returns document of `reference_doctype`, `reference_doctype`"""
|
||||
if not hasattr(self, "parent_doc"):
|
||||
|
|
@ -28,50 +109,25 @@ class Communication(Document):
|
|||
self.parent_doc = None
|
||||
return self.parent_doc
|
||||
|
||||
def validate(self):
|
||||
if self.get("__islocal"):
|
||||
if self.reference_doctype and self.reference_name:
|
||||
self.status = "Linked"
|
||||
else:
|
||||
self.status = "Open"
|
||||
|
||||
# validate recipients
|
||||
for email in split_emails(self.recipients):
|
||||
validate_email_add(email, throw=True)
|
||||
|
||||
# validate CC
|
||||
for email in split_emails(self.cc):
|
||||
validate_email_add(email, throw=True)
|
||||
|
||||
def after_insert(self):
|
||||
# send new comment to listening clients
|
||||
comment = self.as_dict()
|
||||
comment["comment"] = comment["content"]
|
||||
comment["comment_by"] = comment["sender"]
|
||||
comment["comment_type"] = comment["communication_medium"]
|
||||
|
||||
frappe.publish_realtime('new_comment', comment, doctype = self.reference_doctype,
|
||||
docname = self.reference_name, after_commit=True)
|
||||
|
||||
def on_update(self):
|
||||
"""Update parent status as `Open` or `Replied`."""
|
||||
self.update_parent()
|
||||
|
||||
def update_parent(self):
|
||||
"""Update status of parent document based on who is replying."""
|
||||
parent = self.get_parent_doc()
|
||||
if not parent:
|
||||
def set_timeline_doc(self):
|
||||
"""Set timeline_doctype and timeline_name"""
|
||||
parent_doc = self.get_parent_doc()
|
||||
if (self.timeline_doctype and self.timeline_name) or not parent_doc:
|
||||
return
|
||||
|
||||
status_field = parent.meta.get_field("status")
|
||||
timeline_field = parent_doc.meta.timeline_field
|
||||
if not timeline_field:
|
||||
return
|
||||
|
||||
if status_field and "Open" in (status_field.options or "").split("\n"):
|
||||
to_status = "Open" if self.sent_or_received=="Received" else "Replied"
|
||||
doctype = parent_doc.meta.get_link_doctype(timeline_field)
|
||||
name = parent_doc.get(timeline_field)
|
||||
|
||||
if to_status in status_field.options.splitlines():
|
||||
parent.db_set("status", to_status)
|
||||
if doctype and name:
|
||||
self.timeline_doctype = doctype
|
||||
self.timeline_name = name
|
||||
|
||||
parent.notify_update()
|
||||
else:
|
||||
return
|
||||
|
||||
def send(self, print_html=None, print_format=None, attachments=None,
|
||||
send_me_a_copy=False, recipients=None):
|
||||
|
|
@ -95,239 +151,12 @@ class Communication(Document):
|
|||
:param fetched_from_email_account: True when pulling email, the notification shouldn't go to the main recipient
|
||||
|
||||
"""
|
||||
recipients, cc = self.get_recipients_and_cc(recipients, cc,
|
||||
fetched_from_email_account=fetched_from_email_account)
|
||||
|
||||
self.emails_not_sent_to = set(self.all_email_addresses) - set(self.sent_email_addresses)
|
||||
|
||||
if frappe.flags.in_test:
|
||||
# for test cases, run synchronously
|
||||
self._notify(print_html=print_html, print_format=print_format, attachments=attachments,
|
||||
recipients=recipients, cc=cc)
|
||||
else:
|
||||
check_bulk_limit(list(set(self.sent_email_addresses)))
|
||||
|
||||
from frappe.tasks import sendmail
|
||||
sendmail.delay(frappe.local.site, self.name,
|
||||
print_html=print_html, print_format=print_format, attachments=attachments,
|
||||
recipients=recipients, cc=cc, lang=frappe.local.lang, session=frappe.local.session)
|
||||
notify(self, print_html, print_format, attachments, recipients, cc, fetched_from_email_account)
|
||||
|
||||
def _notify(self, print_html=None, print_format=None, attachments=None,
|
||||
recipients=None, cc=None):
|
||||
|
||||
self.prepare_to_notify(print_html, print_format, attachments)
|
||||
|
||||
frappe.sendmail(
|
||||
recipients=(recipients or []) + (cc or []),
|
||||
show_as_cc=(cc or []),
|
||||
expose_recipients=True,
|
||||
sender=self.sender,
|
||||
reply_to=self.incoming_email_account,
|
||||
subject=self.subject,
|
||||
content=self.content,
|
||||
reference_doctype=self.reference_doctype,
|
||||
reference_name=self.reference_name,
|
||||
attachments=self.attachments,
|
||||
message_id=self.name,
|
||||
unsubscribe_message=_("Leave this conversation"),
|
||||
bulk=True
|
||||
)
|
||||
|
||||
def get_recipients_and_cc(self, recipients, cc, fetched_from_email_account=False):
|
||||
self.all_email_addresses = []
|
||||
self.sent_email_addresses = []
|
||||
self.previous_email_sender = None
|
||||
|
||||
if not recipients:
|
||||
recipients = self.get_recipients(fetched_from_email_account=fetched_from_email_account)
|
||||
|
||||
if not cc:
|
||||
cc = self.get_cc(recipients, fetched_from_email_account=fetched_from_email_account)
|
||||
|
||||
if fetched_from_email_account:
|
||||
# email was already sent to the original recipient by the sender's email service
|
||||
original_recipients, recipients = recipients, []
|
||||
|
||||
# send email to the sender of the previous email in the thread which this email is a reply to
|
||||
if self.previous_email_sender:
|
||||
recipients.append(self.previous_email_sender)
|
||||
|
||||
# cc that was received in the email
|
||||
original_cc = split_emails(self.cc)
|
||||
|
||||
# don't cc to people who already received the mail from sender's email service
|
||||
cc = list(set(cc) - set(original_cc) - set(original_recipients))
|
||||
|
||||
return recipients, cc
|
||||
|
||||
def prepare_to_notify(self, print_html=None, print_format=None, attachments=None):
|
||||
"""Prepare to make multipart MIME Email
|
||||
|
||||
:param print_html: Send given value as HTML attachment.
|
||||
:param print_format: Attach print format of parent document."""
|
||||
|
||||
if print_format:
|
||||
self.content += self.get_attach_link(print_format)
|
||||
|
||||
self.set_incoming_outgoing_accounts()
|
||||
|
||||
if not self.sender or cint(self.outgoing_email_account.always_use_account_email_id_as_sender):
|
||||
self.sender = formataddr([frappe.session.data.full_name or "Notification", self.outgoing_email_account.email_id])
|
||||
|
||||
self.attachments = []
|
||||
|
||||
if print_html or print_format:
|
||||
self.attachments.append(frappe.attach_print(self.reference_doctype, self.reference_name,
|
||||
print_format=print_format, html=print_html))
|
||||
|
||||
if attachments:
|
||||
if isinstance(attachments, basestring):
|
||||
attachments = json.loads(attachments)
|
||||
|
||||
for a in attachments:
|
||||
if isinstance(a, basestring):
|
||||
# is it a filename?
|
||||
try:
|
||||
file = get_file(a)
|
||||
self.attachments.append({"fname": file[0], "fcontent": file[1]})
|
||||
except IOError:
|
||||
frappe.throw(_("Unable to find attachment {0}").format(a))
|
||||
else:
|
||||
self.attachments.append(a)
|
||||
|
||||
def set_incoming_outgoing_accounts(self):
|
||||
self.incoming_email_account = self.outgoing_email_account = None
|
||||
|
||||
if self.reference_doctype:
|
||||
self.incoming_email_account = frappe.db.get_value("Email Account",
|
||||
{"append_to": self.reference_doctype, "enable_incoming": 1}, "email_id")
|
||||
|
||||
self.outgoing_email_account = frappe.db.get_value("Email Account",
|
||||
{"append_to": self.reference_doctype, "enable_outgoing": 1},
|
||||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True)
|
||||
|
||||
if not self.incoming_email_account:
|
||||
self.incoming_email_account = frappe.db.get_value("Email Account",
|
||||
{"default_incoming": 1, "enable_incoming": 1}, "email_id")
|
||||
|
||||
if not self.outgoing_email_account:
|
||||
self.outgoing_email_account = frappe.db.get_value("Email Account",
|
||||
{"default_outgoing": 1, "enable_outgoing": 1},
|
||||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True) or frappe._dict()
|
||||
|
||||
def get_recipients(self, fetched_from_email_account=False):
|
||||
"""Build a list of email addresses for To"""
|
||||
# [EDGE CASE] self.recipients can be None when an email is sent as BCC
|
||||
recipients = split_emails(self.recipients)
|
||||
|
||||
if fetched_from_email_account and self.in_reply_to:
|
||||
# add sender of previous reply
|
||||
self.previous_email_sender = frappe.db.get_value("Communication", self.in_reply_to, "sender")
|
||||
recipients.append(self.previous_email_sender)
|
||||
|
||||
if recipients:
|
||||
# exclude email accounts
|
||||
exclude = [d[0] for d in
|
||||
frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)]
|
||||
exclude += [d[0] for d in
|
||||
frappe.db.get_all("Email Account", ["login_id"], {"enable_incoming": 1}, as_list=True)
|
||||
if d[0]]
|
||||
|
||||
recipients = self.filter_email_list(recipients, exclude)
|
||||
|
||||
return recipients
|
||||
|
||||
def get_cc(self, recipients=None, fetched_from_email_account=False):
|
||||
"""Build a list of email addresses for CC"""
|
||||
# get a copy of CC list
|
||||
cc = split_emails(self.cc)
|
||||
|
||||
if self.reference_doctype and self.reference_name:
|
||||
if fetched_from_email_account:
|
||||
# if it is a fetched email, add follows to CC
|
||||
cc.append(self.get_owner_email())
|
||||
cc += self.get_assignees()
|
||||
|
||||
if cc:
|
||||
# exclude email accounts, unfollows, recipients and unsubscribes
|
||||
exclude = [d[0] for d in
|
||||
frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)]
|
||||
exclude += [d[0] for d in
|
||||
frappe.db.get_all("Email Account", ["login_id"], {"enable_incoming": 1}, as_list=True)
|
||||
if d[0]]
|
||||
exclude += [d[0] for d in frappe.db.get_all("User", ["name"], {"thread_notify": 0}, as_list=True)]
|
||||
exclude += [(parseaddr(email)[1] or "").lower() for email in recipients]
|
||||
|
||||
if fetched_from_email_account:
|
||||
# exclude sender when pulling email
|
||||
exclude += [parseaddr(self.sender)[1]]
|
||||
|
||||
if self.reference_doctype and self.reference_name:
|
||||
exclude += [d[0] for d in frappe.db.get_all("Email Unsubscribe", ["email"],
|
||||
{"reference_doctype": self.reference_doctype, "reference_name": self.reference_name}, as_list=True)]
|
||||
|
||||
cc = self.filter_email_list(cc, exclude, is_cc=True)
|
||||
|
||||
if getattr(self, "send_me_a_copy", False) and self.sender not in cc:
|
||||
self.all_email_addresses.append((parseaddr(self.sender)[1] or "").lower())
|
||||
cc.append(self.sender)
|
||||
|
||||
return cc
|
||||
|
||||
def filter_email_list(self, email_list, exclude, is_cc=False):
|
||||
# temp variables
|
||||
filtered = []
|
||||
email_address_list = []
|
||||
|
||||
for email in list(set(email_list)):
|
||||
email_address = (parseaddr(email)[1] or "").lower()
|
||||
if not email_address:
|
||||
continue
|
||||
|
||||
# this will be used to eventually find email addresses that aren't sent to
|
||||
self.all_email_addresses.append(email_address)
|
||||
|
||||
if (email in exclude) or (email_address in exclude):
|
||||
continue
|
||||
|
||||
if is_cc:
|
||||
is_user_enabled = frappe.db.get_value("User", email_address, "enabled")
|
||||
if is_user_enabled==0:
|
||||
# don't send to disabled users
|
||||
continue
|
||||
|
||||
# make sure of case-insensitive uniqueness of email address
|
||||
if email_address not in email_address_list:
|
||||
# append the full email i.e. "Human <human@example.com>"
|
||||
filtered.append(email)
|
||||
email_address_list.append(email_address)
|
||||
|
||||
self.sent_email_addresses.extend(email_address_list)
|
||||
|
||||
return filtered
|
||||
|
||||
def get_owner_email(self):
|
||||
owner = self.get_parent_doc().owner
|
||||
return get_formatted_email(owner) or owner
|
||||
|
||||
def get_assignees(self):
|
||||
return [( get_formatted_email(d.owner) or d.owner ) for d in
|
||||
frappe.db.get_all("ToDo", filters={
|
||||
"reference_type": self.reference_doctype,
|
||||
"reference_name": self.reference_name,
|
||||
"status": "Open"
|
||||
}, fields=["owner"])
|
||||
]
|
||||
|
||||
def get_attach_link(self, print_format):
|
||||
"""Returns public link for the attachment via `templates/emails/print_link.html`."""
|
||||
return frappe.get_template("templates/emails/print_link.html").render({
|
||||
"url": get_url(),
|
||||
"doctype": self.reference_doctype,
|
||||
"name": self.reference_name,
|
||||
"print_format": print_format,
|
||||
"key": self.get_parent_doc().get_signature()
|
||||
})
|
||||
_notify(self, print_html, print_format, attachments, recipients, cc)
|
||||
|
||||
def on_doctype_update():
|
||||
"""Add index in `tabCommunication` for `(reference_doctype, reference_name)`"""
|
||||
|
|
@ -336,60 +165,8 @@ def on_doctype_update():
|
|||
if "_liked_by" not in frappe.db.get_table_columns("Communication"):
|
||||
add_column("Communication", "_liked_by", "Text")
|
||||
|
||||
@frappe.whitelist()
|
||||
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent",
|
||||
sender=None, recipients=None, communication_medium="Email", send_email=False,
|
||||
print_html=None, print_format=None, attachments='[]', ignore_doctype_permissions=False,
|
||||
send_me_a_copy=False, cc=None):
|
||||
"""Make a new communication.
|
||||
|
||||
:param doctype: Reference DocType.
|
||||
:param name: Reference Document name.
|
||||
:param content: Communication body.
|
||||
:param subject: Communication subject.
|
||||
:param sent_or_received: Sent or Received (default **Sent**).
|
||||
:param sender: Communcation sender (default current user).
|
||||
:param recipients: Communication recipients as list.
|
||||
:param communication_medium: Medium of communication (default **Email**).
|
||||
:param send_mail: Send via email (default **False**).
|
||||
:param print_html: HTML Print format to be sent as attachment.
|
||||
:param print_format: Print Format name of parent document to be sent as attachment.
|
||||
:param attachments: List of attachments as list of files or JSON string.
|
||||
:param send_me_a_copy: Send a copy to the sender (default **False**).
|
||||
"""
|
||||
|
||||
is_error_report = (doctype=="User" and name==frappe.session.user and subject=="Error Report")
|
||||
|
||||
if doctype and name and not is_error_report and not frappe.has_permission(doctype, "email", name) and not ignore_doctype_permissions:
|
||||
raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format(
|
||||
doctype=doctype, name=name))
|
||||
|
||||
if not sender:
|
||||
sender = get_formatted_email(frappe.session.user)
|
||||
|
||||
comm = frappe.get_doc({
|
||||
"doctype":"Communication",
|
||||
"subject": subject,
|
||||
"content": content,
|
||||
"sender": sender,
|
||||
"recipients": recipients,
|
||||
"cc": cc or None,
|
||||
"communication_medium": communication_medium,
|
||||
"sent_or_received": sent_or_received,
|
||||
"reference_doctype": doctype,
|
||||
"reference_name": name
|
||||
})
|
||||
comm.insert(ignore_permissions=True)
|
||||
|
||||
# needed for communication.notify which uses celery delay
|
||||
# if not committed, delayed task doesn't find the communication
|
||||
frappe.db.commit()
|
||||
|
||||
if send_email:
|
||||
comm.send_me_a_copy = send_me_a_copy
|
||||
comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy)
|
||||
|
||||
return {
|
||||
"name": comm.name,
|
||||
"emails_not_sent_to": ", ".join(comm.emails_not_sent_to) if hasattr(comm, "emails_not_sent_to") else None
|
||||
}
|
||||
def has_permission(doc, ptype, user):
|
||||
if ptype=="read" and doc.reference_doctype and doc.reference_name:
|
||||
if frappe.has_permission(doc.reference_doctype, ptype="read", doc=doc.reference_name):
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
frappe.listview_settings['Communication'] = {
|
||||
add_fields: ["sent_or_received", "recipients", "subject", "communication_medium"]
|
||||
add_fields: ["sent_or_received", "recipients", "subject", "communication_medium", "communication_type"],
|
||||
default_filters: [["Communication", "communication_type", "=", "Communication"]],
|
||||
filters: [["status", "=", "Open"]]
|
||||
};
|
||||
|
|
|
|||
348
frappe/core/doctype/communication/email.py
Normal file
348
frappe/core/doctype/communication/email.py
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
import frappe
|
||||
import json
|
||||
from email.utils import formataddr, parseaddr
|
||||
from frappe.utils import get_url, get_formatted_email, cint, validate_email_add, split_emails
|
||||
from frappe.utils.file_manager import get_file
|
||||
from frappe.email.bulk import check_bulk_limit
|
||||
import frappe.email.smtp
|
||||
from frappe import _
|
||||
|
||||
@frappe.whitelist()
|
||||
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent",
|
||||
sender=None, recipients=None, communication_medium="Email", send_email=False,
|
||||
print_html=None, print_format=None, attachments='[]', ignore_doctype_permissions=False,
|
||||
send_me_a_copy=False, cc=None):
|
||||
"""Make a new communication.
|
||||
|
||||
:param doctype: Reference DocType.
|
||||
:param name: Reference Document name.
|
||||
:param content: Communication body.
|
||||
:param subject: Communication subject.
|
||||
:param sent_or_received: Sent or Received (default **Sent**).
|
||||
:param sender: Communcation sender (default current user).
|
||||
:param recipients: Communication recipients as list.
|
||||
:param communication_medium: Medium of communication (default **Email**).
|
||||
:param send_mail: Send via email (default **False**).
|
||||
:param print_html: HTML Print format to be sent as attachment.
|
||||
:param print_format: Print Format name of parent document to be sent as attachment.
|
||||
:param attachments: List of attachments as list of files or JSON string.
|
||||
:param send_me_a_copy: Send a copy to the sender (default **False**).
|
||||
"""
|
||||
|
||||
is_error_report = (doctype=="User" and name==frappe.session.user and subject=="Error Report")
|
||||
|
||||
if doctype and name and not is_error_report and not frappe.has_permission(doctype, "email", name) and not ignore_doctype_permissions:
|
||||
raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format(
|
||||
doctype=doctype, name=name))
|
||||
|
||||
if not sender:
|
||||
sender = get_formatted_email(frappe.session.user)
|
||||
|
||||
comm = frappe.get_doc({
|
||||
"doctype":"Communication",
|
||||
"subject": subject,
|
||||
"content": content,
|
||||
"sender": sender,
|
||||
"recipients": recipients,
|
||||
"cc": cc or None,
|
||||
"communication_medium": communication_medium,
|
||||
"sent_or_received": sent_or_received,
|
||||
"reference_doctype": doctype,
|
||||
"reference_name": name
|
||||
})
|
||||
comm.insert(ignore_permissions=True)
|
||||
|
||||
# needed for communication.notify which uses celery delay
|
||||
# if not committed, delayed task doesn't find the communication
|
||||
frappe.db.commit()
|
||||
|
||||
if send_email:
|
||||
comm.send_me_a_copy = send_me_a_copy
|
||||
comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy)
|
||||
|
||||
return {
|
||||
"name": comm.name,
|
||||
"emails_not_sent_to": ", ".join(comm.emails_not_sent_to) if hasattr(comm, "emails_not_sent_to") else None
|
||||
}
|
||||
|
||||
def validate_email(doc):
|
||||
"""Validate Email Addresses of Recipients and CC"""
|
||||
if not (doc.communication_type=="Communication" and doc.communication_medium == "Email"):
|
||||
return
|
||||
|
||||
# validate recipients
|
||||
for email in split_emails(doc.recipients):
|
||||
validate_email_add(email, throw=True)
|
||||
|
||||
# validate CC
|
||||
for email in split_emails(doc.cc):
|
||||
validate_email_add(email, throw=True)
|
||||
|
||||
def notify(doc, print_html=None, print_format=None, attachments=None,
|
||||
recipients=None, cc=None, fetched_from_email_account=False):
|
||||
"""Calls a delayed celery task 'sendmail' that enqueus email in Bulk Email queue
|
||||
|
||||
:param print_html: Send given value as HTML attachment
|
||||
:param print_format: Attach print format of parent document
|
||||
:param attachments: A list of filenames that should be attached when sending this email
|
||||
:param recipients: Email recipients
|
||||
:param cc: Send email as CC to
|
||||
:param fetched_from_email_account: True when pulling email, the notification shouldn't go to the main recipient
|
||||
|
||||
"""
|
||||
recipients, cc = get_recipients_and_cc(doc, recipients, cc,
|
||||
fetched_from_email_account=fetched_from_email_account)
|
||||
|
||||
doc.emails_not_sent_to = set(doc.all_email_addresses) - set(doc.sent_email_addresses)
|
||||
|
||||
if frappe.flags.in_test:
|
||||
# for test cases, run synchronously
|
||||
doc._notify(print_html=print_html, print_format=print_format, attachments=attachments,
|
||||
recipients=recipients, cc=cc)
|
||||
else:
|
||||
check_bulk_limit(list(set(doc.sent_email_addresses)))
|
||||
|
||||
from frappe.tasks import sendmail
|
||||
sendmail.delay(frappe.local.site, doc.name,
|
||||
print_html=print_html, print_format=print_format, attachments=attachments,
|
||||
recipients=recipients, cc=cc, lang=frappe.local.lang, session=frappe.local.session)
|
||||
|
||||
def _notify(doc, print_html=None, print_format=None, attachments=None,
|
||||
recipients=None, cc=None):
|
||||
|
||||
prepare_to_notify(doc, print_html, print_format, attachments)
|
||||
|
||||
frappe.sendmail(
|
||||
recipients=(recipients or []) + (cc or []),
|
||||
show_as_cc=(cc or []),
|
||||
expose_recipients=True,
|
||||
sender=doc.sender,
|
||||
reply_to=doc.incoming_email_account,
|
||||
subject=doc.subject,
|
||||
content=doc.content,
|
||||
reference_doctype=doc.reference_doctype,
|
||||
reference_name=doc.reference_name,
|
||||
attachments=doc.attachments,
|
||||
message_id=doc.name,
|
||||
unsubscribe_message=_("Leave this conversation"),
|
||||
bulk=True
|
||||
)
|
||||
|
||||
def update_parent_status(doc):
|
||||
"""Update status of parent document based on who is replying."""
|
||||
if doc.communication_type != "Communication":
|
||||
return
|
||||
|
||||
parent = doc.get_parent_doc()
|
||||
if not parent:
|
||||
return
|
||||
|
||||
status_field = parent.meta.get_field("status")
|
||||
|
||||
if status_field and "Open" in (status_field.options or "").split("\n"):
|
||||
to_status = "Open" if doc.sent_or_received=="Received" else "Replied"
|
||||
|
||||
if to_status in status_field.options.splitlines():
|
||||
parent.db_set("status", to_status)
|
||||
|
||||
parent.notify_update()
|
||||
|
||||
def get_recipients_and_cc(doc, recipients, cc, fetched_from_email_account=False):
|
||||
doc.all_email_addresses = []
|
||||
doc.sent_email_addresses = []
|
||||
doc.previous_email_sender = None
|
||||
|
||||
if not recipients:
|
||||
recipients = get_recipients(doc, fetched_from_email_account=fetched_from_email_account)
|
||||
|
||||
if not cc:
|
||||
cc = get_cc(doc, recipients, fetched_from_email_account=fetched_from_email_account)
|
||||
|
||||
if fetched_from_email_account:
|
||||
# email was already sent to the original recipient by the sender's email service
|
||||
original_recipients, recipients = recipients, []
|
||||
|
||||
# send email to the sender of the previous email in the thread which this email is a reply to
|
||||
if doc.previous_email_sender:
|
||||
recipients.append(doc.previous_email_sender)
|
||||
|
||||
# cc that was received in the email
|
||||
original_cc = split_emails(doc.cc)
|
||||
|
||||
# don't cc to people who already received the mail from sender's email service
|
||||
cc = list(set(cc) - set(original_cc) - set(original_recipients))
|
||||
|
||||
return recipients, cc
|
||||
|
||||
def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None):
|
||||
"""Prepare to make multipart MIME Email
|
||||
|
||||
:param print_html: Send given value as HTML attachment.
|
||||
:param print_format: Attach print format of parent document."""
|
||||
|
||||
if print_format:
|
||||
doc.content += get_attach_link(doc, print_format)
|
||||
|
||||
set_incoming_outgoing_accounts(doc)
|
||||
|
||||
if not doc.sender or cint(doc.outgoing_email_account.always_use_account_email_id_as_sender):
|
||||
doc.sender = formataddr([frappe.session.data.full_name or "Notification", doc.outgoing_email_account.email_id])
|
||||
|
||||
doc.attachments = []
|
||||
|
||||
if print_html or print_format:
|
||||
doc.attachments.append(frappe.attach_print(doc.reference_doctype, doc.reference_name,
|
||||
print_format=print_format, html=print_html))
|
||||
|
||||
if attachments:
|
||||
if isinstance(attachments, basestring):
|
||||
attachments = json.loads(attachments)
|
||||
|
||||
for a in attachments:
|
||||
if isinstance(a, basestring):
|
||||
# is it a filename?
|
||||
try:
|
||||
file = get_file(a)
|
||||
doc.attachments.append({"fname": file[0], "fcontent": file[1]})
|
||||
except IOError:
|
||||
frappe.throw(_("Unable to find attachment {0}").format(a))
|
||||
else:
|
||||
doc.attachments.append(a)
|
||||
|
||||
def set_incoming_outgoing_accounts(doc):
|
||||
doc.incoming_email_account = doc.outgoing_email_account = None
|
||||
|
||||
if doc.reference_doctype:
|
||||
doc.incoming_email_account = frappe.db.get_value("Email Account",
|
||||
{"append_to": doc.reference_doctype, "enable_incoming": 1}, "email_id")
|
||||
|
||||
doc.outgoing_email_account = frappe.db.get_value("Email Account",
|
||||
{"append_to": doc.reference_doctype, "enable_outgoing": 1},
|
||||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True)
|
||||
|
||||
if not doc.incoming_email_account:
|
||||
doc.incoming_email_account = frappe.db.get_value("Email Account",
|
||||
{"default_incoming": 1, "enable_incoming": 1}, "email_id")
|
||||
|
||||
if not doc.outgoing_email_account:
|
||||
doc.outgoing_email_account = frappe.db.get_value("Email Account",
|
||||
{"default_outgoing": 1, "enable_outgoing": 1},
|
||||
["email_id", "always_use_account_email_id_as_sender"], as_dict=True) or frappe._dict()
|
||||
|
||||
def get_recipients(doc, fetched_from_email_account=False):
|
||||
"""Build a list of email addresses for To"""
|
||||
# [EDGE CASE] doc.recipients can be None when an email is sent as BCC
|
||||
recipients = split_emails(doc.recipients)
|
||||
|
||||
if fetched_from_email_account and doc.in_reply_to:
|
||||
# add sender of previous reply
|
||||
doc.previous_email_sender = frappe.db.get_value("Communication", doc.in_reply_to, "sender")
|
||||
recipients.append(doc.previous_email_sender)
|
||||
|
||||
if recipients:
|
||||
# exclude email accounts
|
||||
exclude = [d[0] for d in
|
||||
frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)]
|
||||
exclude += [d[0] for d in
|
||||
frappe.db.get_all("Email Account", ["login_id"], {"enable_incoming": 1}, as_list=True)
|
||||
if d[0]]
|
||||
|
||||
recipients = filter_email_list(doc, recipients, exclude)
|
||||
|
||||
return recipients
|
||||
|
||||
def get_cc(doc, recipients=None, fetched_from_email_account=False):
|
||||
"""Build a list of email addresses for CC"""
|
||||
# get a copy of CC list
|
||||
cc = split_emails(doc.cc)
|
||||
|
||||
if doc.reference_doctype and doc.reference_name:
|
||||
if fetched_from_email_account:
|
||||
# if it is a fetched email, add follows to CC
|
||||
cc.append(get_owner_email(doc))
|
||||
cc += get_assignees(doc)
|
||||
|
||||
if cc:
|
||||
# exclude email accounts, unfollows, recipients and unsubscribes
|
||||
exclude = [d[0] for d in
|
||||
frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)]
|
||||
exclude += [d[0] for d in
|
||||
frappe.db.get_all("Email Account", ["login_id"], {"enable_incoming": 1}, as_list=True)
|
||||
if d[0]]
|
||||
exclude += [d[0] for d in frappe.db.get_all("User", ["name"], {"thread_notify": 0}, as_list=True)]
|
||||
exclude += [(parseaddr(email)[1] or "").lower() for email in recipients]
|
||||
|
||||
if fetched_from_email_account:
|
||||
# exclude sender when pulling email
|
||||
exclude += [parseaddr(doc.sender)[1]]
|
||||
|
||||
if doc.reference_doctype and doc.reference_name:
|
||||
exclude += [d[0] for d in frappe.db.get_all("Email Unsubscribe", ["email"],
|
||||
{"reference_doctype": doc.reference_doctype, "reference_name": doc.reference_name}, as_list=True)]
|
||||
|
||||
cc = filter_email_list(doc, cc, exclude, is_cc=True)
|
||||
|
||||
if getattr(doc, "send_me_a_copy", False) and doc.sender not in cc:
|
||||
doc.all_email_addresses.append((parseaddr(doc.sender)[1] or "").lower())
|
||||
cc.append(doc.sender)
|
||||
|
||||
return cc
|
||||
|
||||
def filter_email_list(doc, email_list, exclude, is_cc=False):
|
||||
# temp variables
|
||||
filtered = []
|
||||
email_address_list = []
|
||||
|
||||
for email in list(set(email_list)):
|
||||
email_address = (parseaddr(email)[1] or "").lower()
|
||||
if not email_address:
|
||||
continue
|
||||
|
||||
# this will be used to eventually find email addresses that aren't sent to
|
||||
doc.all_email_addresses.append(email_address)
|
||||
|
||||
if (email in exclude) or (email_address in exclude):
|
||||
continue
|
||||
|
||||
if is_cc:
|
||||
is_user_enabled = frappe.db.get_value("User", email_address, "enabled")
|
||||
if is_user_enabled==0:
|
||||
# don't send to disabled users
|
||||
continue
|
||||
|
||||
# make sure of case-insensitive uniqueness of email address
|
||||
if email_address not in email_address_list:
|
||||
# append the full email i.e. "Human <human@example.com>"
|
||||
filtered.append(email)
|
||||
email_address_list.append(email_address)
|
||||
|
||||
doc.sent_email_addresses.extend(email_address_list)
|
||||
|
||||
return filtered
|
||||
|
||||
def get_owner_email(doc):
|
||||
owner = doc.get_parent_doc().owner
|
||||
return get_formatted_email(owner) or owner
|
||||
|
||||
def get_assignees(doc):
|
||||
return [( get_formatted_email(d.owner) or d.owner ) for d in
|
||||
frappe.db.get_all("ToDo", filters={
|
||||
"reference_type": doc.reference_doctype,
|
||||
"reference_name": doc.reference_name,
|
||||
"status": "Open"
|
||||
}, fields=["owner"])
|
||||
]
|
||||
|
||||
def get_attach_link(doc, print_format):
|
||||
"""Returns public link for the attachment via `templates/emails/print_link.html`."""
|
||||
return frappe.get_template("templates/emails/print_link.html").render({
|
||||
"url": get_url(),
|
||||
"doctype": doc.reference_doctype,
|
||||
"name": doc.reference_name,
|
||||
"print_format": print_format,
|
||||
"key": doc.get_parent_doc().get_signature()
|
||||
})
|
||||
87
frappe/core/doctype/communication/feed.py
Normal file
87
frappe/core/doctype/communication/feed.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import frappe.defaults
|
||||
import frappe.permissions
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import get_fullname
|
||||
from frappe import _
|
||||
from frappe.core.doctype.communication.comment import add_info_comment
|
||||
|
||||
def update_feed(doc, method=None):
|
||||
"adds a new communication with comment_type='Updated'"
|
||||
if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_import:
|
||||
return
|
||||
|
||||
if doc._action!="save":
|
||||
return
|
||||
|
||||
if doc.doctype == "Communication" or doc.meta.issingle:
|
||||
return
|
||||
|
||||
if hasattr(doc, "get_feed"):
|
||||
feed = doc.get_feed()
|
||||
|
||||
if feed:
|
||||
if isinstance(feed, basestring):
|
||||
feed = {"subject": feed}
|
||||
|
||||
feed = frappe._dict(feed)
|
||||
doctype = feed.doctype or doc.doctype
|
||||
name = feed.name or doc.name
|
||||
|
||||
# delete earlier feed
|
||||
frappe.db.sql("""delete from `tabCommunication`
|
||||
where
|
||||
reference_doctype=%s and reference_name=%s
|
||||
and communication_type='Comment'
|
||||
and comment_type='Updated'""", (doctype, name))
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "Communication",
|
||||
"communication_type": "Comment",
|
||||
"comment_type": "Updated",
|
||||
"reference_doctype": doctype,
|
||||
"reference_name": name,
|
||||
"subject": feed.subject,
|
||||
"full_name": get_fullname(doc.owner),
|
||||
"reference_owner": frappe.db.get_value(doctype, name, "owner"),
|
||||
"link_doctype": feed.link_doctype,
|
||||
"link_name": feed.link_name
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
def login_feed(login_manager):
|
||||
add_info_comment(**{
|
||||
"subject": _("{0} logged in").format(get_fullname(login_manager.user)),
|
||||
"full_name": get_fullname(login_manager.user)
|
||||
})
|
||||
|
||||
def get_feed_match_conditions(user=None, force=True):
|
||||
if not user: user = frappe.session.user
|
||||
|
||||
conditions = ['`tabCommunication`.owner="{user}" or `tabCommunication`.reference_owner="{user}"'.format(user=frappe.db.escape(user))]
|
||||
|
||||
user_permissions = frappe.defaults.get_user_permissions(user)
|
||||
can_read = frappe.get_user().get_can_read()
|
||||
|
||||
can_read_doctypes = ['"{}"'.format(doctype) for doctype in
|
||||
list(set(can_read) - set(user_permissions.keys()))]
|
||||
|
||||
if can_read_doctypes:
|
||||
conditions += ["""(tabCommunication.reference_doctype is null
|
||||
or tabCommunication.reference_doctype = ''
|
||||
or tabCommunication.reference_doctype in ({}))""".format(", ".join(can_read_doctypes))]
|
||||
|
||||
if user_permissions:
|
||||
can_read_docs = []
|
||||
for doctype, names in user_permissions.items():
|
||||
for n in names:
|
||||
can_read_docs.append('"{}|{}"'.format(doctype, n))
|
||||
|
||||
if can_read_docs:
|
||||
conditions.append("concat_ws('|', tabCommunication.reference_doctype, tabCommunication.reference_name) in ({})".format(
|
||||
", ".join(can_read_docs)))
|
||||
|
||||
return "(" + " or ".join(conditions) + ")"
|
||||
|
|
@ -383,6 +383,7 @@
|
|||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "column_break_15",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
|
|
@ -405,6 +406,7 @@
|
|||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "eval:!doc.istable",
|
||||
"description": "Show this field as title",
|
||||
"fieldname": "title_field",
|
||||
"fieldtype": "Data",
|
||||
|
|
@ -429,6 +431,33 @@
|
|||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "eval:!doc.istable",
|
||||
"description": "Comments and Communications will be associated with this linked document",
|
||||
"fieldname": "timeline_field",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Timeline Field",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "eval:!doc.istable",
|
||||
"fieldname": "search_fields",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
|
|
@ -455,6 +484,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "modified",
|
||||
"depends_on": "eval:!doc.istable",
|
||||
"description": "",
|
||||
"fieldname": "sort_field",
|
||||
"fieldtype": "Data",
|
||||
|
|
@ -480,6 +510,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "DESC",
|
||||
"depends_on": "eval:!doc.istable",
|
||||
"fieldname": "sort_order",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
|
|
@ -926,7 +957,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-01-19 12:04:52.332070",
|
||||
"modified": "2016-02-01 07:55:35.810722",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType",
|
||||
|
|
|
|||
|
|
@ -388,6 +388,19 @@ def validate_fields(meta):
|
|||
_validate_title_field_pattern(df.options)
|
||||
_validate_title_field_pattern(df.default)
|
||||
|
||||
def check_timeline_field(meta):
|
||||
if not meta.timeline_field:
|
||||
return
|
||||
|
||||
fieldname_list = [d.fieldname for d in fields]
|
||||
|
||||
if meta.timeline_field not in fieldname_list:
|
||||
frappe.throw(_("Timeline field must be a valid fieldname"), InvalidFieldNameError)
|
||||
|
||||
df = meta.get("fields", {"fieldname": meta.timeline_field})[0]
|
||||
if df.fieldtype not in ("Link", "Dynamic Link"):
|
||||
frappe.throw(_("Timeline field must be a Link or Dynamic Link"), InvalidFieldNameError)
|
||||
|
||||
fields = meta.get("fields")
|
||||
for d in fields:
|
||||
if not d.permlevel: d.permlevel = 0
|
||||
|
|
@ -407,6 +420,7 @@ def validate_fields(meta):
|
|||
check_fold(fields)
|
||||
check_search_fields(meta)
|
||||
check_title_field(meta)
|
||||
check_timeline_field(meta)
|
||||
|
||||
def validate_permissions_for_doctype(doctype, for_remove=False):
|
||||
"""Validates if permissions are set correctly."""
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import frappe
|
|||
from frappe.model.document import Document
|
||||
|
||||
class ErrorSnapshot(Document):
|
||||
no_feed_on_delete = True
|
||||
|
||||
def onload(self):
|
||||
if not self.parent_error_snapshot:
|
||||
self.db_set('seen', True, update_modified=False)
|
||||
|
|
|
|||
|
|
@ -225,8 +225,10 @@ class User(Document):
|
|||
and event_type='Private'""", (self.name,))
|
||||
|
||||
# delete messages
|
||||
frappe.db.sql("""delete from `tabComment` where comment_doctype='Message'
|
||||
and (comment_docname=%s or owner=%s)""", (self.name, self.name))
|
||||
frappe.db.sql("""delete from `tabCommunication`
|
||||
where communication_type in ('Chat', 'Notification')
|
||||
and reference_doctype='User'
|
||||
and (reference_name=%s or owner=%s)""", (self.name, self.name))
|
||||
|
||||
def before_rename(self, olddn, newdn, merge=False):
|
||||
frappe.clear_cache(user=olddn)
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ def get_notification_config():
|
|||
return {
|
||||
"for_doctype": {
|
||||
"Scheduler Log": {"seen": 0},
|
||||
"Communication": {"status": "Open"},
|
||||
"Communication": {"status": "Open", "communication_type": "Communication"},
|
||||
"ToDo": "frappe.core.notifications.get_things_todo",
|
||||
"Event": "frappe.core.notifications.get_todays_events",
|
||||
"Comment": "frappe.core.notifications.get_unread_messages",
|
||||
"Error Snapshot": {"seen": 0, "parent_error_snapshot": None},
|
||||
},
|
||||
"for_other": {
|
||||
"Likes": "frappe.core.notifications.get_unseen_likes"
|
||||
"Likes": "frappe.core.notifications.get_unseen_likes",
|
||||
"Messages": "frappe.core.notifications.get_unread_messages",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -39,17 +39,19 @@ def get_unread_messages():
|
|||
"returns unread (docstatus-0 messages for a user)"
|
||||
return frappe.db.sql("""\
|
||||
SELECT count(*)
|
||||
FROM `tabComment`
|
||||
WHERE comment_doctype IN ('My Company', 'Message')
|
||||
AND comment_docname = %s
|
||||
AND docstatus=0
|
||||
FROM `tabCommunication`
|
||||
WHERE communication_type in ('Chat', 'Notification')
|
||||
AND reference_doctype = 'User'
|
||||
AND reference_name = %s
|
||||
AND seen=0
|
||||
""", (frappe.session.user,))[0][0]
|
||||
|
||||
def get_unseen_likes():
|
||||
"""Returns count of unseen likes"""
|
||||
return frappe.db.sql("""select count(*) from `tabFeed`
|
||||
return frappe.db.sql("""select count(*) from `tabCommunication`
|
||||
where
|
||||
feed_type='Like'
|
||||
and owner is not null and owner!=%(user)s
|
||||
and doc_owner=%(user)s
|
||||
and seen=0""", {"user": frappe.session.user})[0][0]
|
||||
communication_type='Comment'
|
||||
and comment_type='Like'
|
||||
and owner is not null and owner!=%(user)s
|
||||
and reference_owner=%(user)s
|
||||
and seen=0""", {"user": frappe.session.user})[0][0]
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
Activity Feed.
|
||||
|
|
@ -1 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
|
|
@ -1,309 +0,0 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "hash",
|
||||
"creation": "2012-07-03 13:29:42",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "feed_type",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Feed Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nComment\nLogin\nLabel\nInfo\nLike",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "doc_type",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Doc Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "doc_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Doc Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "subject",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Subject",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "color",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Color",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "full_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Full Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "doc_owner",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Doc Owner",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "seen",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Seen",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"description": "Use this to provide alternative link to a feed record",
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference DocType",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Reference Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-rss",
|
||||
"idx": 1,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2015-12-30 02:48:03.860188",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Feed",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 1,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "All",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
}
|
||||
],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import frappe.defaults
|
||||
import frappe.permissions
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import get_fullname
|
||||
from frappe import _
|
||||
|
||||
exclude_from_linked_with = True
|
||||
|
||||
class Feed(Document):
|
||||
no_feed_on_delete = True
|
||||
|
||||
def validate(self):
|
||||
if not (self.reference_doctype and self.reference_name):
|
||||
# reset both if even one is missing
|
||||
self.reference_doctype = self.reference_name = None
|
||||
|
||||
|
||||
def on_doctype_update():
|
||||
if not frappe.db.sql("""show index from `tabFeed`
|
||||
where Key_name="feed_doctype_docname_index" """):
|
||||
frappe.db.commit()
|
||||
frappe.db.sql("""alter table `tabFeed`
|
||||
add index feed_doctype_docname_index(doc_type, doc_name)""")
|
||||
|
||||
def get_permission_query_conditions(user):
|
||||
if not user: user = frappe.session.user
|
||||
|
||||
use_user_permissions = frappe.permissions.apply_user_permissions("Feed", "read", user)
|
||||
if not use_user_permissions:
|
||||
return ""
|
||||
|
||||
conditions = ['`tabFeed`.owner="{user}" or `tabFeed`.doc_owner="{user}"'.format(user=frappe.db.escape(user))]
|
||||
|
||||
user_permissions = frappe.defaults.get_user_permissions(user)
|
||||
can_read = frappe.get_user().get_can_read()
|
||||
|
||||
can_read_doctypes = ['"{}"'.format(doctype) for doctype in
|
||||
list(set(can_read) - set(user_permissions.keys()))]
|
||||
|
||||
if not can_read_doctypes:
|
||||
conditions += ["tabFeed.doc_type in ({})".format(", ".join(can_read_doctypes))]
|
||||
|
||||
if user_permissions:
|
||||
can_read_docs = []
|
||||
for doctype, names in user_permissions.items():
|
||||
for n in names:
|
||||
can_read_docs.append('"{}|{}"'.format(doctype, n))
|
||||
|
||||
if can_read_docs:
|
||||
conditions.append("concat_ws('|', tabFeed.doc_type, tabFeed.doc_name) in ({})".format(
|
||||
", ".join(can_read_docs)))
|
||||
|
||||
return "(" + " or ".join(conditions) + ")"
|
||||
|
||||
def has_permission(doc, user):
|
||||
return frappe.has_permission(doc.doc_type, "read", doc.doc_name, user=user)
|
||||
|
||||
def update_feed(doc, method=None):
|
||||
"adds a new feed"
|
||||
if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_import:
|
||||
return
|
||||
|
||||
if doc.doctype == "Feed" or doc.meta.issingle:
|
||||
return
|
||||
|
||||
if hasattr(doc, "get_feed"):
|
||||
feed = doc.get_feed()
|
||||
|
||||
if feed:
|
||||
if isinstance(feed, basestring):
|
||||
feed = {"subject": feed}
|
||||
|
||||
feed = frappe._dict(feed)
|
||||
doctype = feed.doctype or doc.doctype
|
||||
name = feed.name or doc.name
|
||||
|
||||
# delete earlier feed
|
||||
frappe.db.sql("""delete from tabFeed
|
||||
where doc_type=%s and doc_name=%s
|
||||
and ifnull(feed_type,'')=''""", (doctype, name))
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "Feed",
|
||||
"feed_type": feed.feed_type or "",
|
||||
"doc_type": doctype,
|
||||
"doc_name": name,
|
||||
"subject": feed.subject,
|
||||
"full_name": get_fullname(doc.owner),
|
||||
"doc_owner": frappe.db.get_value(doctype, name, "owner"),
|
||||
"reference_doctype": feed.reference_doctype,
|
||||
"reference_name": feed.reference_name
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
def login_feed(login_manager):
|
||||
frappe.get_doc({
|
||||
"doctype": "Feed",
|
||||
"feed_type": "Login",
|
||||
"subject": _("{0} logged in").format(get_fullname(login_manager.user)),
|
||||
"full_name": get_fullname(login_manager.user)
|
||||
}).insert(ignore_permissions=True)
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
# test_records = frappe.get_test_records('Feed')
|
||||
|
||||
class TestFeed(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -14,41 +14,40 @@ exclude_from_linked_with = True
|
|||
|
||||
class ToDo(Document):
|
||||
def validate(self):
|
||||
self._assignment = None
|
||||
if self.is_new():
|
||||
self.add_assign_comment(frappe._("Assigned to {0}: {1}").format(get_fullname(self.owner), self.description), "Assigned")
|
||||
self._assignment = {
|
||||
"text": frappe._("Assigned to {0}: {1}").format(get_fullname(self.owner), self.description),
|
||||
"comment_type": "Assigned"
|
||||
}
|
||||
|
||||
else:
|
||||
# NOTE the previous value is only available in validate method
|
||||
if self.get_db_value("status") != self.status:
|
||||
self.add_assign_comment(frappe._("Assignment closed by {0}".format(get_fullname(frappe.session.user))),
|
||||
"Assignment Completed")
|
||||
self._assignment = {
|
||||
"text": frappe._("Assignment closed by {0}".format(get_fullname(frappe.session.user))),
|
||||
"comment_type": "Assignment Completed"
|
||||
}
|
||||
|
||||
def on_update(self):
|
||||
if self._assignment:
|
||||
self.add_assign_comment(**self._assignment)
|
||||
|
||||
self.update_in_reference()
|
||||
|
||||
def on_trash(self):
|
||||
# unlink assignment comment
|
||||
frappe.db.sql("""update `tabComment` set reference_doctype=null and reference_name=null
|
||||
where reference_doctype='ToDo' and reference_name=%s""", self.name)
|
||||
# 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})
|
||||
|
||||
self.update_in_reference()
|
||||
|
||||
def add_assign_comment(self, text, comment_type):
|
||||
if not self.reference_type and self.reference_name:
|
||||
if not (self.reference_type and self.reference_name):
|
||||
return
|
||||
|
||||
comment = frappe.get_doc({
|
||||
"doctype":"Comment",
|
||||
"comment_by": frappe.session.user,
|
||||
"comment_type": comment_type,
|
||||
"comment_doctype": self.reference_type,
|
||||
"comment_docname": self.reference_name,
|
||||
"comment": """{text}""".format(text=text),
|
||||
"reference_doctype": self.doctype,
|
||||
"reference_name": self.name
|
||||
})
|
||||
comment.flags.ignore_permissions = True
|
||||
comment.flags.ignore_links = True
|
||||
comment.insert()
|
||||
frappe.get_doc(self.reference_type, self.reference_name).add_comment(comment_type, text,
|
||||
link_doctype=self.doctype, link_name=self.name)
|
||||
|
||||
def update_in_reference(self):
|
||||
if not (self.reference_type and self.reference_name):
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import frappe.defaults
|
|||
import frappe.desk.form.meta
|
||||
from frappe.permissions import get_doc_permissions
|
||||
from frappe import _
|
||||
from frappe.core.doctype.communication.feed import get_feed_match_conditions
|
||||
|
||||
@frappe.whitelist()
|
||||
def getdoc(doctype, name, user=None):
|
||||
|
|
@ -85,7 +86,7 @@ def get_docinfo(doc=None, doctype=None, name=None):
|
|||
|
||||
frappe.response["docinfo"] = {
|
||||
"attachments": get_attachments(doc.doctype, doc.name),
|
||||
"comments": get_comments(doc.doctype, doc.name),
|
||||
"communications": _get_communications(doc.doctype, doc.name),
|
||||
"assignments": get_assignments(doc.doctype, doc.name),
|
||||
"permissions": get_doc_permissions(doc),
|
||||
"shared": frappe.share.get_users(doc.doctype, doc.name,
|
||||
|
|
@ -106,31 +107,46 @@ def get_attachments(dt, dn):
|
|||
return frappe.get_all("File", fields=["name", "file_name", "file_url", "is_private"],
|
||||
filters = {"attached_to_name": dn, "attached_to_doctype": dt})
|
||||
|
||||
def get_comments(dt, dn, limit=100):
|
||||
comments = frappe.db.sql("""select name, comment, comment_by, creation,
|
||||
reference_doctype, reference_name, comment_type, "Comment" as doctype, _liked_by
|
||||
from `tabComment`
|
||||
where comment_doctype=%s and comment_docname=%s
|
||||
order by creation desc limit %s""",
|
||||
(dt, dn, limit), as_dict=1)
|
||||
@frappe.whitelist()
|
||||
def get_communications(doctype, name, start=0, limit=20):
|
||||
doc = frappe.get_doc(doctype, name)
|
||||
if not doc.has_permission("read"):
|
||||
raise frappe.PermissionError
|
||||
|
||||
communications = frappe.db.sql("""select name,
|
||||
content as comment, sender as comment_by, creation,
|
||||
communication_medium as comment_type, subject, delivery_status, _liked_by,
|
||||
return _get_communications(doctype, name, start, limit)
|
||||
|
||||
|
||||
def _get_communications(doctype, name, start=0, limit=20):
|
||||
match_conditions = get_feed_match_conditions()
|
||||
communications = frappe.db.sql("""select name, communication_type,
|
||||
communication_medium, comment_type,
|
||||
content, sender, sender_full_name, creation, subject, delivery_status, _liked_by,
|
||||
timeline_doctype, timeline_name,
|
||||
reference_doctype, reference_name,
|
||||
"Communication" as doctype
|
||||
from tabCommunication
|
||||
where reference_doctype=%s and reference_name=%s
|
||||
order by creation desc limit %s""", (dt, dn, limit),
|
||||
where
|
||||
communication_type in ("Communication", "Comment")
|
||||
and (
|
||||
(reference_doctype=%(doctype)s and reference_name=%(name)s)
|
||||
or (timeline_doctype=%(doctype)s and timeline_name=%(name)s)
|
||||
)
|
||||
and (comment_type is null or comment_type != 'Update')
|
||||
{match_conditions}
|
||||
order by creation desc limit %(start)s, %(limit)s"""
|
||||
.format(match_conditions=("and " + match_conditions) if match_conditions else ""),
|
||||
{ "doctype": doctype, "name": name, "start": frappe.utils.cint(start), "limit": limit },
|
||||
as_dict=True)
|
||||
|
||||
for c in communications:
|
||||
c.attachments = json.dumps(frappe.get_all("File",
|
||||
fields=["file_url", "is_private"],
|
||||
filters={"attached_to_doctype": "Communication",
|
||||
"attached_to_name": c.name}
|
||||
))
|
||||
if c.communication_type=="Communication":
|
||||
c.attachments = json.dumps(frappe.get_all("File",
|
||||
fields=["file_url", "is_private"],
|
||||
filters={"attached_to_doctype": "Communication",
|
||||
"attached_to_name": c.name}
|
||||
))
|
||||
|
||||
return comments + communications
|
||||
return communications
|
||||
|
||||
def get_assignments(dt, dn):
|
||||
cl = frappe.db.sql("""select owner, description from `tabToDo`
|
||||
|
|
|
|||
|
|
@ -51,10 +51,10 @@ def add_comment(doc):
|
|||
"""allow any logged user to post a comment"""
|
||||
doc = frappe.get_doc(json.loads(doc))
|
||||
|
||||
if doc.doctype != "Comment":
|
||||
if not (doc.doctype=="Communication" and doc.communication_type=='Comment'):
|
||||
frappe.throw(_("This method can only be used to create a Comment"), frappe.PermissionError)
|
||||
|
||||
doc.insert(ignore_permissions = True)
|
||||
doc.insert(ignore_permissions=True)
|
||||
|
||||
return doc.as_dict()
|
||||
|
||||
|
|
|
|||
|
|
@ -64,40 +64,26 @@ def _toggle_like(doctype, name, add, user=None):
|
|||
def remove_like(doctype, name):
|
||||
"""Remove previous Like"""
|
||||
# remove Comment
|
||||
frappe.delete_doc("Comment", [c.name for c in frappe.get_all("Comment",
|
||||
frappe.delete_doc("Communication", [c.name for c in frappe.get_all("Communication",
|
||||
filters={
|
||||
"comment_doctype": doctype,
|
||||
"comment_docname": name,
|
||||
"comment_by": frappe.session.user,
|
||||
"comment_type": "Like"
|
||||
}
|
||||
)], ignore_permissions=True)
|
||||
|
||||
# remove Feed
|
||||
frappe.delete_doc("Feed", [c.name for c in frappe.get_all("Feed",
|
||||
filters={
|
||||
"doc_type": doctype,
|
||||
"doc_name": name,
|
||||
"communication_type": "Comment",
|
||||
"reference_doctype": doctype,
|
||||
"reference_name": name,
|
||||
"owner": frappe.session.user,
|
||||
"feed_type": "Like"
|
||||
"comment_type": "Like"
|
||||
}
|
||||
)], ignore_permissions=True)
|
||||
|
||||
def add_comment(doctype, name):
|
||||
doc = frappe.get_doc(doctype, name)
|
||||
|
||||
if doctype=="Comment":
|
||||
link = get_link_to_form(doc.comment_doctype, doc.comment_docname,
|
||||
"{0} {1}".format(_(doc.comment_doctype), doc.comment_docname))
|
||||
doc.add_comment("Like", _("Comment: {0} in {1}").format("<b>" + doc.comment + "</b>", link),
|
||||
reference_doctype=doc.comment_doctype, reference_name=doc.comment_docname)
|
||||
|
||||
elif doctype=="Communication":
|
||||
if doctype=="Communication":
|
||||
link = get_link_to_form(doc.reference_doctype, doc.reference_name,
|
||||
"{0} {1}".format(_(doc.reference_doctype), doc.reference_name))
|
||||
|
||||
doc.add_comment("Like", _("Communication: {0} in {1}").format("<b>" + doc.subject + "</b>", link),
|
||||
reference_doctype=doc.reference_doctype, reference_name=doc.reference_name)
|
||||
doc.add_comment("Like", _("{0}: {1} in {2}").format(_(doc.communication_type),
|
||||
"<b>" + doc.subject + "</b>", link),
|
||||
link_doctype=doc.reference_doctype, link_name=doc.reference_name)
|
||||
|
||||
else:
|
||||
doc.add_comment("Like", _("Liked"))
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ def get_notifications():
|
|||
"open_count_module": get_notifications_for_modules(config, notification_count),
|
||||
"open_count_other": get_notifications_for_other(config, notification_count),
|
||||
"new_messages": get_new_messages()
|
||||
# "likes": get_count_of_new_likes()
|
||||
}
|
||||
|
||||
def get_new_messages():
|
||||
|
|
@ -41,11 +40,12 @@ def get_new_messages():
|
|||
# no update for 30 mins, consider only the last 30 mins
|
||||
last_update = (now_datetime() - relativedelta(seconds=1800)).strftime(DATETIME_FORMAT)
|
||||
|
||||
return frappe.db.sql("""select comment_by_fullname, comment
|
||||
from tabComment
|
||||
where comment_doctype='Message'
|
||||
and comment_docname = %s
|
||||
and ifnull(creation, "2000-01-01") > %s
|
||||
return frappe.db.sql("""select sender_full_name, content
|
||||
from `tabCommunication`
|
||||
where communication_type in ('Chat', 'Notification')
|
||||
and reference_doctype='user'
|
||||
and reference_name = %s
|
||||
and creation > %s
|
||||
order by creation desc""", (frappe.session.user, last_update), as_dict=1)
|
||||
|
||||
def get_notifications_for_modules(config, notification_count):
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ frappe.pages['activity'].on_page_load = function(wrapper) {
|
|||
|
||||
this.page.set_title(__("Activity"));
|
||||
|
||||
frappe.model.with_doctype("Feed", function() {
|
||||
frappe.model.with_doctype("Communication", function() {
|
||||
me.page.list = new frappe.ui.Listing({
|
||||
hide_refresh: true,
|
||||
page: me.page,
|
||||
|
|
@ -28,7 +28,7 @@ frappe.pages['activity'].on_page_load = function(wrapper) {
|
|||
new frappe.activity.Feed(row, data);
|
||||
},
|
||||
show_filters: true,
|
||||
doctype: "Feed",
|
||||
doctype: "Communication",
|
||||
get_args: function() {
|
||||
if (frappe.route_options && frappe.route_options.show_likes) {
|
||||
delete frappe.route_options.show_likes;
|
||||
|
|
@ -52,19 +52,19 @@ frappe.pages['activity'].on_page_load = function(wrapper) {
|
|||
frappe.activity.render_plot(this.page);
|
||||
|
||||
this.page.main.on("click", ".activity-message", function() {
|
||||
var reference_doctype = $(this).attr("data-reference-doctype"),
|
||||
reference_name = $(this).attr("data-reference-name"),
|
||||
var link_doctype = $(this).attr("data-link-doctype"),
|
||||
link_name = $(this).attr("data-link-name"),
|
||||
doctype = $(this).attr("data-doctype"),
|
||||
docname = $(this).attr("data-docname");
|
||||
|
||||
if (doctype && docname) {
|
||||
frappe.set_route(["Form", reference_doctype || doctype, reference_name || docname]);
|
||||
|
||||
if (reference_doctype && reference_name) {
|
||||
if (link_doctype && link_name) {
|
||||
frappe.route_options = {
|
||||
scroll_to: { "doctype": doctype, "name": docname }
|
||||
}
|
||||
}
|
||||
|
||||
frappe.set_route(["Form", link_doctype || doctype, link_name || docname]);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -96,9 +96,23 @@ frappe.activity.Feed = Class.extend({
|
|||
data.add_class = "label-default";
|
||||
|
||||
data.link = "";
|
||||
if (data.doc_type && data.doc_name) {
|
||||
data.link = frappe.format(data.doc_name, {fieldtype: "Link", options: data.doc_type},
|
||||
{label: __(data.doc_type) + " " + __(data.doc_name)});
|
||||
if (data.link_doctype && data.link_name) {
|
||||
data.link = frappe.format(data.link_name, {fieldtype: "Link", options: data.link_doctype},
|
||||
{label: __(data.link_doctype) + " " + __(data.link_name)});
|
||||
|
||||
} else if (data.feed_type==="Comment" && data.comment_type==="Comment") {
|
||||
// hack for backward compatiblity
|
||||
data.link_doctype = data.reference_doctype;
|
||||
data.link_name = data.reference_name;
|
||||
data.reference_doctype = "Communication";
|
||||
data.reference_name = data.name;
|
||||
|
||||
data.link = frappe.format(data.link_name, {fieldtype: "Link", options: data.link_doctype},
|
||||
{label: __(data.link_doctype) + " " + __(data.link_name)});
|
||||
|
||||
} else if (data.reference_doctype && data.reference_name) {
|
||||
data.link = frappe.format(data.reference_name, {fieldtype: "Link", options: data.reference_doctype},
|
||||
{label: __(data.reference_doctype) + " " + __(data.reference_name)});
|
||||
}
|
||||
|
||||
$(row)
|
||||
|
|
@ -122,10 +136,10 @@ frappe.activity.Feed = Class.extend({
|
|||
"Comment": "label-danger",
|
||||
"Assignment": "label-warning",
|
||||
"Login": "label-default"
|
||||
}[data.feed_type] || "label-info"
|
||||
}[data.comment_type || data.communication_medium] || "label-info"
|
||||
|
||||
data.when = comment_when(data.creation);
|
||||
data.feed_type = __(data.feed_type);
|
||||
data.feed_type = data.comment_type || data.communication_medium;
|
||||
},
|
||||
add_date_separator: function(row, data) {
|
||||
var date = dateutil.str_to_obj(data.creation);
|
||||
|
|
|
|||
|
|
@ -4,25 +4,27 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import cint
|
||||
from frappe.desk.doctype.feed.feed import get_permission_query_conditions
|
||||
from frappe.core.doctype.communication.feed import get_feed_match_conditions
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_feed(limit_start, limit_page_length, show_likes=False):
|
||||
"""get feed"""
|
||||
# directly use the permission query condition function of feed
|
||||
match_conditions = get_permission_query_conditions(frappe.session.user)
|
||||
match_conditions = get_feed_match_conditions(frappe.session.user)
|
||||
|
||||
result = frappe.db.sql("""select name, feed_type, doc_type, doc_name, subject,
|
||||
owner, modified, creation, seen, reference_doctype, reference_name
|
||||
from `tabFeed`
|
||||
result = frappe.db.sql("""select name, owner, modified, creation, seen, comment_type,
|
||||
reference_doctype, reference_name, link_doctype, link_name, subject,
|
||||
communication_type, communication_medium, content
|
||||
from `tabCommunication`
|
||||
where
|
||||
((feed_type='Like' and (owner=%(user)s or doc_owner=%(user)s)) or feed_type!='Like')
|
||||
communication_type in ("Communication", "Comment")
|
||||
and (comment_type is null or comment_type != "Like"
|
||||
or (comment_type="Like" and (owner=%(user)s or reference_owner=%(user)s)))
|
||||
{match_conditions}
|
||||
{show_likes}
|
||||
order by creation desc
|
||||
limit %(limit_start)s, %(limit_page_length)s"""
|
||||
.format(match_conditions="and {0}".format(match_conditions) if match_conditions else "",
|
||||
show_likes="and feed_type='Like'" if show_likes else ""),
|
||||
show_likes="and comment_type='Like'" if show_likes else ""),
|
||||
{
|
||||
"user": frappe.session.user,
|
||||
"limit_start": cint(limit_start),
|
||||
|
|
@ -31,7 +33,8 @@ def get_feed(limit_start, limit_page_length, show_likes=False):
|
|||
|
||||
if show_likes:
|
||||
# mark likes as seen!
|
||||
frappe.db.sql("update `tabFeed` set seen=1 where feed_type='Like' and doc_owner=%s", frappe.session.user)
|
||||
frappe.db.sql("""update `tabCommunication` set seen=1
|
||||
where comment_type='Like' and reference_owner=%s""", frappe.session.user)
|
||||
frappe.local.flags.commit = True
|
||||
|
||||
return result
|
||||
|
|
@ -39,6 +42,10 @@ def get_feed(limit_start, limit_page_length, show_likes=False):
|
|||
@frappe.whitelist()
|
||||
def get_months_activity():
|
||||
return frappe.db.sql("""select date(creation), count(name)
|
||||
from `tabFeed` where date(creation) > subdate(curdate(), interval 1 month)
|
||||
from `tabCommunication`
|
||||
where
|
||||
communication_type in ("Communication", "Comment")
|
||||
and date(creation) > subdate(curdate(), interval 1 month)
|
||||
group by date(creation)
|
||||
order by creation asc""", as_list=1)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
<div class="col-xs-3 text-right activity-date"><span class="{%= date_class %}">
|
||||
{%= date_sep || "" %}</span></div>
|
||||
<div class="col-xs-9 activity-message"
|
||||
data-doctype="{%= doc_type %}"
|
||||
data-docname="{%= doc_name %}"
|
||||
data-reference-doctype="{{ reference_doctype }}"
|
||||
data-reference-name="{{ reference_name }}"
|
||||
data-doctype="{%= reference_doctype %}"
|
||||
data-docname="{%= reference_name %}"
|
||||
data-link-doctype="{{ link_doctype }}"
|
||||
data-link-name="{{ link_name }}"
|
||||
title="{%= by %} / {%= dateutil.str_to_user(creation) %}">
|
||||
<span class="avatar avatar-small">
|
||||
<div class="avatar-frame" style="background-image: url({{ imgsrc }});"></div>
|
||||
|
|
@ -16,19 +16,25 @@
|
|||
{%= __("Logged in") %}
|
||||
{% } else if (feed_type==="Label") { %}
|
||||
{%= __("{0} {1}", ["<strong>" + subject + "</strong>", link]) %}
|
||||
{% } else if (doc_type && feed_type==="Comment") { %}
|
||||
{%= __("Commented on {0}: {1}", [link, "<strong>" + subject + "</strong>"]) %}
|
||||
{% } else if (doc_type && !feed_type) { %}
|
||||
{% } else if (reference_doctype && feed_type==="Comment") { %}
|
||||
{%= __("Commented on {0}: {1}", [link, "<strong>" + content + "</strong>"]) %}
|
||||
{% } else if (reference_doctype && communication_type==="Communication") { %}
|
||||
{%= __("Communicated via {0} on {1}: {2}", [__(feed_type), link, "<strong>" + subject + "</strong>"]) %}
|
||||
{% } else if (reference_doctype && !feed_type) { %}
|
||||
{%= __("Updated {0}: {1}", [link, "<strong>" + subject + "</strong>"]) %}
|
||||
{% } else if (feed_type==="Like" && doc_type) { %}
|
||||
{% } else if (feed_type==="Like" && reference_doctype) { %}
|
||||
{%= by %} <i class="octicon octicon-heart"></i>
|
||||
{% if (in_list(["Comment", "Communication"], doc_type)) { %}
|
||||
{%= subject %}
|
||||
{% if (in_list(["Comment", "Communication"], reference_doctype)) { %}
|
||||
{%= content %}
|
||||
{% } else { %}
|
||||
{%= link %}
|
||||
{% } %}
|
||||
{% } else if (doc_type) { %}
|
||||
{%= __("{0}: {1}", [link, "<strong>" + subject + "</strong>"]) %}
|
||||
{% } else if (in_list(["Created", "Submitted", "Cancelled", "Deleted"], feed_type)) { %}
|
||||
{%= __("{0} {1}", ["<strong>" + __(feed_type) + "</strong>", feed_type==="Deleted" ? subject : link ]) %}
|
||||
{% } else if (feed_type==="Updated") { %}
|
||||
{%= __("Updated {0}: {1}", [link, "<strong>" + subject + "</strong>"]) %}
|
||||
{% } else if (reference_doctype && reference_name) { %}
|
||||
{%= __("{0}: {1}", [link, "<strong>" + content + "</strong>"]) %}
|
||||
{% } else { %}
|
||||
{%= subject %}
|
||||
{% } %}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ frappe.desk.pages.Messages = Class.extend({
|
|||
var me = this;
|
||||
frappe.realtime.on('new_message', function(comment) {
|
||||
if(comment.modified_by !== user) {
|
||||
frappe.utils.notify(__("Message from {0}", [comment.comment_by_fullname]), comment.comment);
|
||||
frappe.utils.notify(__("Message from {0}", [comment.sender_full_name]), comment.content);
|
||||
}
|
||||
if (frappe.get_route()[0] === 'messages' && comment.owner !== user) {
|
||||
var current_contact = $(cur_page.page).find('[data-contact]').data('contact');
|
||||
|
|
@ -166,7 +166,7 @@ frappe.desk.pages.Messages = Class.extend({
|
|||
hide_refresh: true,
|
||||
freeze: false,
|
||||
render_row: function(wrapper, data) {
|
||||
if(data.parenttype==="Assignment" || data.comment_type==="Shared") {
|
||||
if(data.communication_type==="Notification" || data.comment_type==="Shared") {
|
||||
data.is_system_message = 1;
|
||||
}
|
||||
var row = $(frappe.render_template("messages_row", {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.desk.notifications import delete_notification_count_for
|
||||
from frappe.core.doctype.user.user import STANDARD_USERS
|
||||
from frappe.utils.user import get_enabled_system_users
|
||||
from frappe.utils import cint, get_fullname
|
||||
from frappe.utils import cint
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_list(arg=None):
|
||||
|
|
@ -17,34 +18,36 @@ def get_list(arg=None):
|
|||
|
||||
# set all messages as read
|
||||
frappe.db.begin()
|
||||
frappe.db.sql("""UPDATE `tabComment`
|
||||
set docstatus = 1 where comment_doctype in ('My Company', 'Message')
|
||||
and comment_docname = %s
|
||||
""", frappe.session.user)
|
||||
frappe.db.sql("""UPDATE `tabCommunication` set seen = 1
|
||||
where
|
||||
communication_type in ('Chat', 'Notification')
|
||||
and reference_doctype = 'User'
|
||||
and reference_name = %s""", frappe.session.user)
|
||||
|
||||
delete_notification_count_for("Messages")
|
||||
|
||||
frappe.db.commit()
|
||||
frappe.local.flags.commit = True
|
||||
|
||||
if frappe.form_dict['contact'] == frappe.session['user']:
|
||||
# return messages
|
||||
return frappe.db.sql("""select * from `tabComment`
|
||||
where (owner=%(contact)s
|
||||
or comment_docname=%(user)s
|
||||
or (owner=comment_docname and ifnull(parenttype, "")!="Assignment")
|
||||
or owner=comment_docname)
|
||||
and comment_doctype ='Message'
|
||||
order by creation desc
|
||||
limit %(limit_start)s, %(limit_page_length)s""", frappe.local.form_dict, as_dict=1)
|
||||
return frappe.db.sql("""select * from `tabCommunication`
|
||||
where
|
||||
communication_type in ('Chat', 'Notification')
|
||||
and reference_doctype ='User'
|
||||
and (owner=%(contact)s
|
||||
or reference_name=%(user)s
|
||||
or owner=reference_name)
|
||||
order by creation desc
|
||||
limit %(limit_start)s, %(limit_page_length)s""", frappe.local.form_dict, as_dict=1)
|
||||
else:
|
||||
return frappe.db.sql("""select * from `tabComment`
|
||||
where ((owner=%(contact)s and comment_docname=%(user)s)
|
||||
or (owner=%(user)s and comment_docname=%(contact)s)
|
||||
or (owner=%(contact)s and comment_docname=%(contact)s))
|
||||
and comment_doctype ='Message'
|
||||
order by creation desc
|
||||
limit %(limit_start)s, %(limit_page_length)s""", frappe.local.form_dict, as_dict=1)
|
||||
|
||||
return frappe.db.sql("""select * from `tabCommunication`
|
||||
where
|
||||
communication_type in ('Chat', 'Notification')
|
||||
and reference_doctype ='User'
|
||||
and ((owner=%(contact)s and reference_name=%(user)s)
|
||||
or (owner=%(contact)s and reference_name=%(contact)s))
|
||||
order by creation desc
|
||||
limit %(limit_start)s, %(limit_page_length)s""", frappe.local.form_dict, as_dict=1)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_active_users():
|
||||
|
|
@ -71,15 +74,15 @@ def get_active_users():
|
|||
|
||||
@frappe.whitelist()
|
||||
def post(txt, contact, parenttype=None, notify=False, subject=None):
|
||||
import frappe
|
||||
"""post message"""
|
||||
|
||||
d = frappe.new_doc('Comment')
|
||||
d.parenttype = parenttype
|
||||
d.comment = txt
|
||||
d.comment_docname = contact
|
||||
d.comment_doctype = 'Message'
|
||||
d.comment_by_fullname = get_fullname(frappe.session.user)
|
||||
d = frappe.new_doc('Communication')
|
||||
d.communication_type = 'Notification' if parenttype else 'Chat'
|
||||
d.subject = subject
|
||||
d.content = txt
|
||||
d.reference_doctype = 'User'
|
||||
d.reference_name = contact
|
||||
d.sender = frappe.session.user
|
||||
d.insert(ignore_permissions=True)
|
||||
|
||||
delete_notification_count_for("Messages")
|
||||
|
|
@ -94,7 +97,7 @@ def post(txt, contact, parenttype=None, notify=False, subject=None):
|
|||
|
||||
@frappe.whitelist()
|
||||
def delete(arg=None):
|
||||
frappe.get_doc("Comment", frappe.form_dict['name']).delete()
|
||||
frappe.get_doc("Communication", frappe.form_dict['name']).delete()
|
||||
|
||||
def _notify(contact, txt, subject=None):
|
||||
from frappe.utils import get_fullname, get_url
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<div class="row message-row small">
|
||||
<div class="col-sm-9">
|
||||
<div class="media">
|
||||
{% if (data.owner==data.comment_docname
|
||||
&& data.parenttype!="Assignment") { %}
|
||||
{% if (data.owner==data.reference_name
|
||||
&& data.communication_type!="Notification") { %}
|
||||
<span class="pull-left" title="{{ __("Public") }}"><i class="octicon octicon-rss text-muted" style="margin-top: 3px;"></i></span>
|
||||
{% } else { %}
|
||||
<span class="pull-left" title="{{ __("Public") }}" style="width: 20px; height: 16px; display: inline-block;"></span>
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
</span>
|
||||
</div>
|
||||
<div class="media-body {{ data.is_system_message ? "text-muted" : "" }}">
|
||||
{%= data.comment %}
|
||||
{%= data.content %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
<span class="hidden-sm hidden-md hidden-lg">{%= frappe.user.full_name(data.owner) %}, </span>
|
||||
{%= comment_when(data.modified) %}
|
||||
</div>
|
||||
{% if (data.owner==user /* && !data.comment_type && data.parenttype!="Assignment" */ ) { %}
|
||||
{% if (data.owner==user /* && !data.comment_type && data.communication_type!="Notification" */ ) { %}
|
||||
<div>
|
||||
<a class="delete text-extra-muted" data-name="{%= data.name %}"
|
||||
onclick="frappe.desk.pages.messages.delete(this)">Delete</a>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from dateutil.relativedelta import relativedelta
|
|||
from datetime import datetime, timedelta
|
||||
from frappe.desk.form import assign_to
|
||||
from frappe.utils.user import get_system_managers
|
||||
from frappe.core.doctype.communication.email import set_incoming_outgoing_accounts
|
||||
|
||||
class SentEmailInInbox(Exception): pass
|
||||
|
||||
|
|
@ -333,7 +334,7 @@ class EmailAccount(Document):
|
|||
def send_auto_reply(self, communication, email):
|
||||
"""Send auto reply if set."""
|
||||
if self.enable_auto_reply:
|
||||
communication.set_incoming_outgoing_accounts()
|
||||
set_incoming_outgoing_accounts(communication)
|
||||
|
||||
frappe.sendmail(recipients = [email.from_email],
|
||||
sender = self.email_id,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import unittest, email
|
|||
|
||||
test_records = frappe.get_test_records('Email Account')
|
||||
|
||||
from frappe.core.doctype.communication.communication import make
|
||||
from frappe.core.doctype.communication.email import make
|
||||
from frappe.desk.form.load import get_attachments
|
||||
from frappe.utils.file_manager import delete_file_from_filesystem
|
||||
from frappe.email.doctype.email_account.email_account import notify_unreplied
|
||||
|
|
|
|||
|
|
@ -16,19 +16,21 @@ class TestEmailAlert(unittest.TestCase):
|
|||
frappe.set_user("Administrator")
|
||||
|
||||
def test_new_and_save(self):
|
||||
comment = frappe.new_doc("Comment")
|
||||
comment.comment = "test"
|
||||
comment.insert(ignore_permissions=True)
|
||||
communication = frappe.new_doc("Communication")
|
||||
communication.communication_type = 'Comment'
|
||||
communication.subject = "test"
|
||||
communication.content = "test"
|
||||
communication.insert(ignore_permissions=True)
|
||||
|
||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"reference_doctype": "Comment",
|
||||
"reference_name": comment.name, "status":"Not Sent"}))
|
||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"reference_doctype": "Communication",
|
||||
"reference_name": communication.name, "status":"Not Sent"}))
|
||||
frappe.db.sql("""delete from `tabBulk Email`""")
|
||||
|
||||
comment.description = "test"
|
||||
comment.save()
|
||||
communication.content = "test 2"
|
||||
communication.save()
|
||||
|
||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"reference_doctype": "Comment",
|
||||
"reference_name": comment.name, "status":"Not Sent"}))
|
||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"reference_doctype": "Communication",
|
||||
"reference_name": communication.name, "status":"Not Sent"}))
|
||||
|
||||
def test_condition(self):
|
||||
event = frappe.new_doc("Event")
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
{
|
||||
"doctype": "Email Alert",
|
||||
"subject":"_Test Email Alert 1",
|
||||
"document_type": "Comment",
|
||||
"document_type": "Communication",
|
||||
"event": "New",
|
||||
"attach_print": 0,
|
||||
"message": "New comment {{ doc.comment }} created",
|
||||
"message": "New comment {{ doc.content }} created",
|
||||
"condition": "doc.communication_type=='Comment'",
|
||||
"recipients": [
|
||||
{ "email_by_document_field": "owner" }
|
||||
]
|
||||
|
|
@ -13,10 +14,11 @@
|
|||
{
|
||||
"doctype": "Email Alert",
|
||||
"subject":"_Test Email Alert 2",
|
||||
"document_type": "Comment",
|
||||
"document_type": "Communication",
|
||||
"event": "Save",
|
||||
"attach_print": 0,
|
||||
"message": "New comment {{ doc.comment }} saved",
|
||||
"message": "New comment {{ doc.content }} saved",
|
||||
"condition": "doc.communication_type=='Comment'",
|
||||
"recipients": [
|
||||
{ "email_by_document_field": "owner" }
|
||||
]
|
||||
|
|
|
|||
|
|
@ -173,9 +173,9 @@ class FrappeClient(object):
|
|||
new_doc.insert()
|
||||
|
||||
if not meta.istable:
|
||||
if doctype != "Comment":
|
||||
self.migrate_doctype("Comment", {"comment_doctype": doctype, "comment_docname": doc["name"]},
|
||||
update={"comment_docname": new_doc.name}, verbose=0)
|
||||
if doctype != "Communication":
|
||||
self.migrate_doctype("Communication", {"reference_doctype": doctype, "reference_name": doc["name"]},
|
||||
update={"reference_name": new_doc.name}, verbose=0)
|
||||
|
||||
if doctype != "File":
|
||||
self.migrate_doctype("File", {"attached_to_doctype": doctype,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ calendars = ["Event"]
|
|||
# login
|
||||
|
||||
on_session_creation = [
|
||||
"frappe.desk.doctype.feed.feed.login_feed",
|
||||
"frappe.core.doctype.communication.feed.login_feed",
|
||||
"frappe.core.doctype.user.user.notifify_admin_access_to_system_manager"
|
||||
]
|
||||
|
||||
|
|
@ -70,16 +70,15 @@ permission_query_conditions = {
|
|||
"Event": "frappe.desk.doctype.event.event.get_permission_query_conditions",
|
||||
"ToDo": "frappe.desk.doctype.todo.todo.get_permission_query_conditions",
|
||||
"User": "frappe.core.doctype.user.user.get_permission_query_conditions",
|
||||
"Feed": "frappe.desk.doctype.feed.feed.get_permission_query_conditions",
|
||||
"Note": "frappe.desk.doctype.note.note.get_permission_query_conditions"
|
||||
"Note": "frappe.desk.doctype.note.note.get_permission_query_conditions",
|
||||
}
|
||||
|
||||
has_permission = {
|
||||
"Event": "frappe.desk.doctype.event.event.has_permission",
|
||||
"ToDo": "frappe.desk.doctype.todo.todo.has_permission",
|
||||
"User": "frappe.core.doctype.user.user.has_permission",
|
||||
"Feed": "frappe.desk.doctype.feed.feed.has_permission",
|
||||
"Note": "frappe.desk.doctype.note.note.has_permission"
|
||||
"Note": "frappe.desk.doctype.note.note.has_permission",
|
||||
"Communication": "frappe.core.doctype.communication.communication.has_permission"
|
||||
}
|
||||
|
||||
standard_queries = {
|
||||
|
|
@ -93,12 +92,11 @@ doc_events = {
|
|||
"on_update": [
|
||||
"frappe.desk.notifications.clear_doctype_notifications",
|
||||
"frappe.email.doctype.email_alert.email_alert.trigger_email_alerts",
|
||||
"frappe.desk.doctype.feed.feed.update_feed"
|
||||
"frappe.core.doctype.communication.feed.update_feed"
|
||||
],
|
||||
"after_rename": "frappe.desk.notifications.clear_doctype_notifications",
|
||||
"on_submit": [
|
||||
"frappe.email.doctype.email_alert.email_alert.trigger_email_alerts",
|
||||
"frappe.desk.doctype.feed.feed.update_feed"
|
||||
],
|
||||
"on_cancel": [
|
||||
"frappe.desk.notifications.clear_doctype_notifications",
|
||||
|
|
@ -141,12 +139,12 @@ get_translated_dict = {
|
|||
}
|
||||
|
||||
sounds = [
|
||||
{"name": "email", "src": "/assets/frappe/sounds/email.mp3"},
|
||||
{"name": "submit", "src": "/assets/frappe/sounds/submit.mp3"},
|
||||
{"name": "cancel", "src": "/assets/frappe/sounds/cancel.mp3"},
|
||||
{"name": "delete", "src": "/assets/frappe/sounds/delete.mp3"},
|
||||
{"name": "click", "src": "/assets/frappe/sounds/click.mp3"},
|
||||
{"name": "error", "src": "/assets/frappe/sounds/error.mp3"},
|
||||
{"name": "email", "src": "/assets/frappe/sounds/email.mp3", "volume": 0.1},
|
||||
{"name": "submit", "src": "/assets/frappe/sounds/submit.mp3", "volume": 0.1},
|
||||
{"name": "cancel", "src": "/assets/frappe/sounds/cancel.mp3", "volume": 0.1},
|
||||
{"name": "delete", "src": "/assets/frappe/sounds/delete.mp3", "volume": 0.05},
|
||||
{"name": "click", "src": "/assets/frappe/sounds/click.mp3", "volume": 0.05},
|
||||
{"name": "error", "src": "/assets/frappe/sounds/error.mp3", "volume": 0.1},
|
||||
# {"name": "alert", "src": "/assets/frappe/sounds/alert.mp3"},
|
||||
# {"name": "chime", "src": "/assets/frappe/sounds/chime.mp3"},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -429,8 +429,12 @@ class DatabaseQuery(object):
|
|||
) and not self.group_by)
|
||||
|
||||
if not group_function_without_group_by:
|
||||
args.order_by = "`tab{0}`.`{1}` {2}".format(self.doctype,
|
||||
meta.get("sort_field") or "modified", meta.get("sort_order") or "desc")
|
||||
sort_field = sort_order = None
|
||||
if meta.sort_field:
|
||||
sort_field = meta.sort_field
|
||||
sort_order = meta.sort_order
|
||||
|
||||
args.order_by = "`tab{0}`.`{1}` {2}".format(self.doctype, sort_field or "modified", sort_order or "desc")
|
||||
|
||||
# draft docs always on top
|
||||
if meta.is_submittable:
|
||||
|
|
@ -458,12 +462,21 @@ class DatabaseQuery(object):
|
|||
if "_comments" in r:
|
||||
comment_count = len(json.loads(r._comments or "[]"))
|
||||
else:
|
||||
comment_count = cint(frappe.db.get_value("Comment",
|
||||
filters={"comment_doctype": self.doctype, "comment_docname": r.name, "comment_type": "Comment"},
|
||||
comment_count = cint(frappe.db.get_value("Communication",
|
||||
filters={
|
||||
"communication_type": "Comment",
|
||||
"reference_doctype": self.doctype,
|
||||
"reference_name": r.name,
|
||||
"comment_type": "Comment"
|
||||
},
|
||||
fieldname="count(name)"))
|
||||
|
||||
communication_count = cint(frappe.db.get_value("Communication",
|
||||
filters={"reference_doctype": self.doctype, "reference_name": r.name},
|
||||
filters={
|
||||
"communication_type": "Communication",
|
||||
"reference_doctype": self.doctype,
|
||||
"reference_name": r.name
|
||||
},
|
||||
fieldname="count(name)"))
|
||||
|
||||
r._comment_count = comment_count + communication_count
|
||||
|
|
|
|||
|
|
@ -195,15 +195,22 @@ def delete_linked_todos(doc):
|
|||
|
||||
def delete_linked_comments(doc):
|
||||
"""Delete comments from the document"""
|
||||
delete_doc("Comment", frappe.db.sql_list("""select name from `tabComment`
|
||||
where comment_doctype=%s and comment_docname=%s""", (doc.doctype, doc.name)), ignore_on_trash=True,
|
||||
delete_doc("Communication", frappe.db.sql_list("""select name from `tabCommunication`
|
||||
where
|
||||
communication_type = 'Comment'
|
||||
and reference_doctype=%s
|
||||
and reference_name=%s""", (doc.doctype, doc.name)),
|
||||
ignore_on_trash=True,
|
||||
ignore_permissions=True)
|
||||
|
||||
def delete_linked_communications(doc):
|
||||
# make communications orphans
|
||||
frappe.db.sql("""update `tabCommunication`
|
||||
set reference_doctype=null, reference_name=null
|
||||
where reference_doctype=%s and reference_name=%s""", (doc.doctype, doc.name))
|
||||
where
|
||||
communication_type = 'Communication'
|
||||
and reference_doctype=%s
|
||||
and reference_name=%s""", (doc.doctype, doc.name))
|
||||
|
||||
def insert_feed(doc):
|
||||
from frappe.utils import get_fullname
|
||||
|
|
@ -212,11 +219,11 @@ def insert_feed(doc):
|
|||
return
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "Feed",
|
||||
"feed_type": "Label",
|
||||
"doc_type": doc.doctype,
|
||||
"doc_name": doc.name,
|
||||
"subject": _("Deleted"),
|
||||
"doctype": "Communication",
|
||||
"communication_type": "Comment",
|
||||
"comment_type": "Deleted",
|
||||
"reference_doctype": doc.doctype,
|
||||
"subject": "{0} {1}".format(_(doc.doctype), doc.name),
|
||||
"full_name": get_fullname(doc.owner)
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -770,19 +770,21 @@ class Document(BaseDocument):
|
|||
"""Returns Desk URL for this document. `/desk#Form/{doctype}/{name}`"""
|
||||
return "/desk#Form/{doctype}/{name}".format(doctype=self.doctype, name=self.name)
|
||||
|
||||
def add_comment(self, comment_type, text=None, comment_by=None, reference_doctype=None, reference_name=None):
|
||||
def add_comment(self, comment_type, text=None, comment_by=None, link_doctype=None, link_name=None):
|
||||
"""Add a comment to this document.
|
||||
|
||||
:param comment_type: e.g. `Comment`. See Comment for more info."""
|
||||
:param comment_type: e.g. `Comment`. See Communication for more info."""
|
||||
|
||||
comment = frappe.get_doc({
|
||||
"doctype":"Comment",
|
||||
"comment_by": comment_by or frappe.session.user,
|
||||
"doctype":"Communication",
|
||||
"communication_type": "Comment",
|
||||
"sender": comment_by or frappe.session.user,
|
||||
"comment_type": comment_type,
|
||||
"comment_doctype": self.doctype,
|
||||
"comment_docname": self.name,
|
||||
"comment": text or _(comment_type),
|
||||
"reference_doctype": reference_doctype,
|
||||
"reference_name": reference_name
|
||||
"reference_doctype": self.doctype,
|
||||
"reference_name": self.name,
|
||||
"content": text or _(comment_type),
|
||||
"link_doctype": link_doctype,
|
||||
"link_name": link_name
|
||||
}).insert(ignore_permissions=True)
|
||||
return comment
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,18 @@ class Meta(Document):
|
|||
def get_options(self, fieldname):
|
||||
return self.get_field(fieldname).options
|
||||
|
||||
def get_link_doctype(self, fieldname):
|
||||
df = self.get_field(fieldname)
|
||||
|
||||
if df.fieldtype == "Link":
|
||||
return df.options
|
||||
|
||||
elif df.fieldtype == "Dynamic Link":
|
||||
return self.get_options(df.options)
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_search_fields(self):
|
||||
search_fields = self.search_fields or "name"
|
||||
search_fields = [d.strip() for d in search_fields.split(",")]
|
||||
|
|
@ -167,16 +179,16 @@ class Meta(Document):
|
|||
if self.get("_idx"):
|
||||
newlist = []
|
||||
pending = self.get("fields")
|
||||
|
||||
|
||||
for fieldname in json.loads(self.get("_idx")):
|
||||
d = self.get("fields", {"fieldname": fieldname}, limit=1)
|
||||
if d:
|
||||
newlist.append(d[0])
|
||||
pending.remove(d[0])
|
||||
|
||||
|
||||
if pending:
|
||||
newlist += pending
|
||||
|
||||
|
||||
# renum
|
||||
idx = 1
|
||||
for d in newlist:
|
||||
|
|
@ -243,10 +255,10 @@ def get_field_currency(df, doc=None):
|
|||
|
||||
if not doc:
|
||||
return None
|
||||
|
||||
|
||||
if not getattr(frappe.local, "field_currency", None):
|
||||
frappe.local.field_currency = frappe._dict()
|
||||
|
||||
|
||||
if not frappe.local.field_currency.get((doc.doctype, doc.parent or doc.name), {}).get(df.fieldname):
|
||||
if ":" in cstr(df.get("options")):
|
||||
split_opts = df.get("options").split(":")
|
||||
|
|
@ -256,11 +268,11 @@ def get_field_currency(df, doc=None):
|
|||
currency = doc.get(df.get("options"))
|
||||
if not currency and doc.parent:
|
||||
currency = frappe.db.get_value(doc.parenttype, doc.parent, df.get("options"))
|
||||
|
||||
|
||||
if currency:
|
||||
frappe.local.field_currency.setdefault((doc.doctype, doc.parent or doc.name), frappe._dict())\
|
||||
.setdefault(df.fieldname, currency)
|
||||
|
||||
|
||||
return frappe.local.field_currency.get((doc.doctype, doc.parent or doc.name), {}).get(df.fieldname)
|
||||
|
||||
def get_field_precision(df, doc=None, currency=None):
|
||||
|
|
@ -355,4 +367,3 @@ def clear_cache(doctype=None):
|
|||
# clear all
|
||||
for name in groups:
|
||||
cache.delete_value(name)
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=F
|
|||
if doctype=='DocType':
|
||||
rename_doctype(doctype, old, new, force)
|
||||
|
||||
update_comments(doctype, old, new, force)
|
||||
update_attachments(doctype, old, new)
|
||||
|
||||
if merge:
|
||||
|
|
@ -118,14 +117,6 @@ def rename_doctype(doctype, old, new, force=False):
|
|||
# change parenttype for fieldtype Table
|
||||
update_parenttype_values(old, new)
|
||||
|
||||
# rename comments
|
||||
frappe.db.sql("""update tabComment set comment_doctype=%s where comment_doctype=%s""",
|
||||
(new, old))
|
||||
|
||||
def update_comments(doctype, old, new, force=False):
|
||||
frappe.db.sql("""update `tabComment` set comment_docname=%s
|
||||
where comment_doctype=%s and comment_docname=%s""", (new, doctype, old))
|
||||
|
||||
def update_child_docs(old, new, meta):
|
||||
# update "parent"
|
||||
for df in meta.get_table_fields():
|
||||
|
|
|
|||
|
|
@ -1,156 +0,0 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe, os
|
||||
import frappe.modules
|
||||
from frappe.utils import cstr
|
||||
from frappe.modules import export_doc, get_module_path, scrub
|
||||
|
||||
def listfolders(path, only_name=0):
|
||||
"""
|
||||
Returns the list of folders (with paths) in the given path,
|
||||
If only_name is set, it returns only the folder names
|
||||
"""
|
||||
|
||||
out = []
|
||||
for each in os.listdir(path):
|
||||
each = cstr(each)
|
||||
dirname = each.split(os.path.sep)[-1]
|
||||
fullpath = os.path.join(path, dirname)
|
||||
|
||||
if os.path.isdir(fullpath) and not dirname.startswith('.'):
|
||||
out.append(only_name and dirname or fullpath)
|
||||
return out
|
||||
|
||||
def switch_module(dt, dn, to, frm=None, export=None):
|
||||
"""
|
||||
Change the module of the given doctype, if export is true, then also export txt and copy
|
||||
code files from src
|
||||
"""
|
||||
frappe.db.sql("update `tab"+dt+"` set module=%s where name=%s", (to, dn))
|
||||
|
||||
if export:
|
||||
export_doc(dt, dn)
|
||||
|
||||
# copy code files
|
||||
if dt in ('DocType', 'Page', 'Report'):
|
||||
from_path = os.path.join(get_module_path(frm), scrub(dt), scrub(dn), scrub(dn))
|
||||
to_path = os.path.join(get_module_path(to), scrub(dt), scrub(dn), scrub(dn))
|
||||
|
||||
# make dire if exists
|
||||
os.system('mkdir -p %s' % os.path.join(get_module_path(to), scrub(dt), scrub(dn)))
|
||||
|
||||
for ext in ('py','js','html','css'):
|
||||
os.system('cp %s %s')
|
||||
|
||||
def commonify_doclist(doclist, with_comments=1):
|
||||
"""
|
||||
Makes a doclist more readable by extracting common properties.
|
||||
This is used for printing Documents in files
|
||||
"""
|
||||
from frappe.utils import get_common_dict, get_diff_dict
|
||||
|
||||
def make_common(doclist):
|
||||
c = {}
|
||||
if with_comments:
|
||||
c['##comment'] = 'These values are common in all dictionaries'
|
||||
for k in common_keys:
|
||||
c[k] = doclist[0][k]
|
||||
return c
|
||||
|
||||
def strip_common_and_idx(d):
|
||||
for k in common_keys:
|
||||
if k in d: del d[k]
|
||||
|
||||
if 'idx' in d: del d['idx']
|
||||
return d
|
||||
|
||||
def make_common_dicts(doclist):
|
||||
|
||||
common_dict = {} # one per doctype
|
||||
|
||||
# make common dicts for all records
|
||||
for d in doclist:
|
||||
if not d['doctype'] in common_dict:
|
||||
d1 = d.copy()
|
||||
if d1.has_key("name"):
|
||||
del d1['name']
|
||||
common_dict[d['doctype']] = d1
|
||||
else:
|
||||
common_dict[d['doctype']] = get_common_dict(common_dict[d['doctype']], d)
|
||||
return common_dict
|
||||
|
||||
common_keys = ['owner','docstatus','creation','modified','modified_by']
|
||||
common_dict = make_common_dicts(doclist)
|
||||
|
||||
# make docs
|
||||
final = []
|
||||
for d in doclist:
|
||||
f = strip_common_and_idx(get_diff_dict(common_dict[d['doctype']], d))
|
||||
f['doctype'] = d['doctype'] # keep doctype!
|
||||
|
||||
# strip name for child records (only an auto generated number!)
|
||||
if f['doctype'] != doclist[0]['doctype'] and f.has_key("name"):
|
||||
del f['name']
|
||||
|
||||
if with_comments:
|
||||
f['##comment'] = d['doctype'] + ('name' in f and (', ' + f['name']) or '')
|
||||
final.append(f)
|
||||
|
||||
# add commons
|
||||
commons = []
|
||||
for d in common_dict.values():
|
||||
d['name']='__common__'
|
||||
if with_comments:
|
||||
d['##comment'] = 'These values are common for all ' + d['doctype']
|
||||
commons.append(strip_common_and_idx(d))
|
||||
|
||||
common_values = make_common(doclist)
|
||||
return [common_values]+commons+final
|
||||
|
||||
def uncommonify_doclist(dl):
|
||||
"""
|
||||
Expands an commonified doclist
|
||||
"""
|
||||
# first one has common values
|
||||
common_values = dl[0]
|
||||
common_dict = frappe._dict()
|
||||
final = []
|
||||
idx_dict = {}
|
||||
|
||||
for d in dl[1:]:
|
||||
if 'name' in d and d['name']=='__common__':
|
||||
# common for a doctype -
|
||||
del d['name']
|
||||
common_dict[d['doctype']] = d
|
||||
else:
|
||||
dt = d['doctype']
|
||||
if not dt in idx_dict: idx_dict[dt] = 1;
|
||||
d1 = frappe._dict(common_values.copy())
|
||||
|
||||
# update from common and global
|
||||
d1.update(common_dict[dt])
|
||||
d1.update(d)
|
||||
|
||||
# idx by sequence
|
||||
d1['idx'] = idx_dict[dt]
|
||||
|
||||
# increment idx
|
||||
idx_dict[dt] += 1
|
||||
|
||||
final.append(d1)
|
||||
|
||||
return final
|
||||
|
||||
def pprint_doclist(doclist, with_comments = 1):
|
||||
from json import dumps
|
||||
return dumps(commonify_doclist(doclist, False), indent=1, sort_keys=True)
|
||||
|
||||
def peval_doclist(txt):
|
||||
from json import loads
|
||||
try:
|
||||
return uncommonify_doclist(loads(txt))
|
||||
except Exception, e:
|
||||
return uncommonify_doclist(eval(txt))
|
||||
|
|
@ -113,5 +113,5 @@ execute:frappe.create_folder(os.path.join(frappe.local.site_path, 'private', 'fi
|
|||
frappe.patches.v6_15.remove_property_setter_for_previous_field #2015-12-29
|
||||
frappe.patches.v6_15.set_username
|
||||
execute:frappe.permissions.reset_perms("Error Snapshot")
|
||||
frappe.patches.v6_19.comment_feed_communication
|
||||
frappe.patches.v6_16.feed_doc_owner
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("desk", "doctype", "feed")
|
||||
|
||||
for feed in frappe.db.sql("select name from `tabFeed` where doc_type='Comment'", as_dict=True):
|
||||
feed = frappe.get_doc("Feed", feed)
|
||||
comment = frappe.get_doc(feed.doc_type, feed.doc_name)
|
||||
new_feed = comment.get_feed()
|
||||
|
||||
if not new_feed:
|
||||
frappe.db.sql("delete from `tabFeed` where name=%s", feed.name)
|
||||
continue
|
||||
|
||||
feed.subject = new_feed["subject"]
|
||||
feed.doc_type = new_feed["doctype"]
|
||||
feed.doc_name = new_feed["name"]
|
||||
feed.feed_type = new_feed["feed_type"]
|
||||
feed.save()
|
||||
|
|
@ -2,15 +2,14 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype("Feed")
|
||||
frappe.reload_doctype("Communication")
|
||||
|
||||
frappe.db.sql("update `tabFeed` set seen=1")
|
||||
|
||||
for doctype, name in frappe.db.sql("""select distinct doc_type, doc_name from `tabFeed`
|
||||
for doctype, name in frappe.db.sql("""select distinct reference_doctype, reference_name
|
||||
from `tabCommunication`
|
||||
where
|
||||
(doc_type is not null and doc_type != '')
|
||||
and (doc_name is not null and doc_name != '')
|
||||
and doc_type != 'Feed'
|
||||
(reference_doctype is not null and reference_doctype != '')
|
||||
and (reference_name is not null and reference_name != '')
|
||||
and (reference_owner is null or reference_owner = '')
|
||||
for update"""):
|
||||
|
||||
owner = frappe.db.get_value(doctype, name, "owner")
|
||||
|
|
@ -18,12 +17,12 @@ def execute():
|
|||
if not owner:
|
||||
continue
|
||||
|
||||
frappe.db.sql("""update `tabFeed`
|
||||
set doc_owner=%(owner)s
|
||||
frappe.db.sql("""update `tabCommunication`
|
||||
set reference_owner=%(owner)s
|
||||
where
|
||||
doc_type=%(doctype)s
|
||||
and doc_name=%(name)s
|
||||
and (doc_owner is null or doc_owner = '')""".format(doctype=doctype), {
|
||||
reference_doctype=%(doctype)s
|
||||
and reference_name=%(name)s
|
||||
and (reference_owner is null or reference_owner = '')""".format(doctype=doctype), {
|
||||
"doctype": doctype,
|
||||
"name": name,
|
||||
"owner": owner
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ import frappe
|
|||
from frappe.model.db_schema import add_column
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype("Feed")
|
||||
|
||||
frappe.db.sql("""update `tabSingles` set field='_liked_by' where field='_starred_by'""")
|
||||
frappe.db.commit()
|
||||
|
||||
|
|
@ -13,6 +11,5 @@ def execute():
|
|||
if "_starred_by" in columns:
|
||||
frappe.db.sql_ddl("""alter table `{0}` change `_starred_by` `_liked_by` Text """.format(table))
|
||||
|
||||
for doctype in ("Comment", "Communication"):
|
||||
if not frappe.db.has_column(doctype, "_liked_by"):
|
||||
add_column(doctype, "_liked_by", "Text")
|
||||
if not frappe.db.has_column("Communication", "_liked_by"):
|
||||
add_column("Communication", "_liked_by", "Text")
|
||||
|
|
|
|||
0
frappe/patches/v6_19/__init__.py
Normal file
0
frappe/patches/v6_19/__init__.py
Normal file
305
frappe/patches/v6_19/comment_feed_communication.py
Normal file
305
frappe/patches/v6_19/comment_feed_communication.py
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.rename_doc import get_link_fields, dynamic_link_queries
|
||||
from frappe.permissions import reset_perms
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype("DocType")
|
||||
frappe.reload_doctype("Communication")
|
||||
reset_perms("Communication")
|
||||
|
||||
migrate_comments()
|
||||
frappe.delete_doc("DocType", "Comment")
|
||||
# frappe.db.sql_ddl("drop table `tabComment`")
|
||||
|
||||
migrate_feed()
|
||||
frappe.delete_doc("DocType", "Feed")
|
||||
# frappe.db.sql_ddl("drop table `tabFeed`")
|
||||
|
||||
update_timeline_doc_for("Blogger")
|
||||
|
||||
def migrate_comments():
|
||||
# comments
|
||||
frappe.db.sql("""insert ignore into `tabCommunication` (
|
||||
subject,
|
||||
content,
|
||||
sender,
|
||||
sender_full_name,
|
||||
comment_type,
|
||||
communication_date,
|
||||
reference_doctype,
|
||||
reference_name,
|
||||
link_doctype,
|
||||
link_name,
|
||||
|
||||
name,
|
||||
user,
|
||||
owner,
|
||||
creation,
|
||||
modified_by,
|
||||
modified,
|
||||
status,
|
||||
sent_or_received,
|
||||
communication_type,
|
||||
seen
|
||||
)
|
||||
select
|
||||
substring(comment, 1, 100) as subject,
|
||||
comment as content,
|
||||
comment_by as sender,
|
||||
comment_by_fullname as sender_full_name,
|
||||
comment_type,
|
||||
ifnull(timestamp(comment_date, comment_time), creation) as communication_date,
|
||||
comment_doctype as reference_doctype,
|
||||
comment_docname as reference_name,
|
||||
reference_doctype as link_doctype,
|
||||
reference_name as link_name,
|
||||
|
||||
name,
|
||||
owner as user,
|
||||
owner,
|
||||
creation,
|
||||
modified_by,
|
||||
modified,
|
||||
'Linked' as status,
|
||||
'Sent' as sent_or_received,
|
||||
'Comment' as communication_type,
|
||||
1 as seen
|
||||
from `tabComment` where comment_doctype is not null and comment_doctype not in ('Message', 'My Company')""")
|
||||
|
||||
# chat and assignment notifications
|
||||
frappe.db.sql("""insert ignore into `tabCommunication` (
|
||||
subject,
|
||||
content,
|
||||
sender,
|
||||
sender_full_name,
|
||||
comment_type,
|
||||
communication_date,
|
||||
reference_doctype,
|
||||
reference_name,
|
||||
link_doctype,
|
||||
link_name,
|
||||
|
||||
name,
|
||||
user,
|
||||
owner,
|
||||
creation,
|
||||
modified_by,
|
||||
modified,
|
||||
status,
|
||||
sent_or_received,
|
||||
communication_type,
|
||||
seen
|
||||
)
|
||||
select
|
||||
case
|
||||
when parenttype='Assignment' then %(assignment)s
|
||||
else substring(comment, 1, 100)
|
||||
end
|
||||
as subject,
|
||||
comment as content,
|
||||
comment_by as sender,
|
||||
comment_by_fullname as sender_full_name,
|
||||
comment_type,
|
||||
ifnull(timestamp(comment_date, comment_time), creation) as communication_date,
|
||||
'User' as reference_doctype,
|
||||
comment_docname as reference_name,
|
||||
reference_doctype as link_doctype,
|
||||
reference_name as link_name,
|
||||
|
||||
name,
|
||||
owner as user,
|
||||
owner,
|
||||
creation,
|
||||
modified_by,
|
||||
modified,
|
||||
'Linked' as status,
|
||||
'Sent' as sent_or_received,
|
||||
case
|
||||
when parenttype='Assignment' then 'Notification'
|
||||
else 'Chat'
|
||||
end
|
||||
as communication_type,
|
||||
1 as seen
|
||||
from `tabComment` where comment_doctype in ('Message', 'My Company')""", {"assignment": _("Assignment")})
|
||||
|
||||
def migrate_feed():
|
||||
# migrate delete feed
|
||||
for doctype in frappe.db.sql("""select distinct doc_type from `tabFeed` where subject=%(deleted)s""", {"deleted": _("Deleted")}):
|
||||
frappe.db.sql("""insert ignore into `tabCommunication` (
|
||||
subject,
|
||||
sender,
|
||||
sender_full_name,
|
||||
comment_type,
|
||||
communication_date,
|
||||
reference_doctype,
|
||||
|
||||
name,
|
||||
user,
|
||||
owner,
|
||||
creation,
|
||||
modified_by,
|
||||
modified,
|
||||
status,
|
||||
sent_or_received,
|
||||
communication_type,
|
||||
seen
|
||||
)
|
||||
select
|
||||
concat_ws(" ", %(_doctype)s, doc_name) as subject,
|
||||
owner as sender,
|
||||
full_name as sender_full_name,
|
||||
'Deleted' as comment_type,
|
||||
creation as communication_date,
|
||||
doc_type as reference_doctype,
|
||||
|
||||
name,
|
||||
owner as user,
|
||||
owner,
|
||||
creation,
|
||||
modified_by,
|
||||
modified,
|
||||
'Linked' as status,
|
||||
'Sent' as sent_or_received,
|
||||
'Comment' as communication_type,
|
||||
1 as seen
|
||||
from `tabFeed` where subject=%(deleted)s and doc_type=%(doctype)s""", {
|
||||
"deleted": _("Deleted"),
|
||||
"doctype": doctype,
|
||||
"_doctype": _(doctype)
|
||||
})
|
||||
|
||||
# migrate feed type login or empty
|
||||
frappe.db.sql("""insert ignore into `tabCommunication` (
|
||||
subject,
|
||||
sender,
|
||||
sender_full_name,
|
||||
comment_type,
|
||||
communication_date,
|
||||
reference_doctype,
|
||||
reference_name,
|
||||
reference_owner,
|
||||
link_doctype,
|
||||
link_name,
|
||||
|
||||
name,
|
||||
user,
|
||||
owner,
|
||||
creation,
|
||||
modified_by,
|
||||
modified,
|
||||
status,
|
||||
sent_or_received,
|
||||
communication_type,
|
||||
seen
|
||||
)
|
||||
select
|
||||
subject,
|
||||
owner as sender,
|
||||
full_name as sender_full_name,
|
||||
case
|
||||
when feed_type='Login' then 'Info'
|
||||
else 'Updated'
|
||||
end as comment_type,
|
||||
creation as communication_date,
|
||||
doc_type as reference_doctype,
|
||||
doc_name as reference_name,
|
||||
doc_owner as reference_owner,
|
||||
reference_doctype as link_doctype,
|
||||
reference_name as link_name,
|
||||
|
||||
name,
|
||||
owner as user,
|
||||
owner,
|
||||
creation,
|
||||
modified_by,
|
||||
modified,
|
||||
'Linked' as status,
|
||||
'Sent' as sent_or_received,
|
||||
'Comment' as communication_type,
|
||||
1 as seen
|
||||
from `tabFeed` where (feed_type in ('Login', '') or feed_type is null)""")
|
||||
|
||||
def update_timeline_doc_for(timeline_doctype):
|
||||
"""NOTE: This method may be used by other apps for patching. It also has COMMIT after each update."""
|
||||
|
||||
# find linked doctypes
|
||||
# link fields
|
||||
update_for_linked_docs(timeline_doctype)
|
||||
|
||||
# dynamic link fields
|
||||
update_for_dynamically_linked_docs(timeline_doctype)
|
||||
|
||||
def update_for_linked_docs(timeline_doctype):
|
||||
for df in get_link_fields(timeline_doctype):
|
||||
if df.issingle:
|
||||
continue
|
||||
|
||||
reference_doctype = df.parent
|
||||
|
||||
if not is_valid_timeline_doctype(reference_doctype, timeline_doctype):
|
||||
continue
|
||||
|
||||
for doc in frappe.get_all(reference_doctype, fields=["name", df.fieldname]):
|
||||
timeline_name = doc.get(df.fieldname)
|
||||
update_communication(timeline_doctype, timeline_name, reference_doctype, doc.name)
|
||||
|
||||
def update_for_dynamically_linked_docs(timeline_doctype):
|
||||
dynamic_link_fields = []
|
||||
for query in dynamic_link_queries:
|
||||
for df in frappe.db.sql(query, as_dict=True):
|
||||
dynamic_link_fields.append(df)
|
||||
|
||||
for df in dynamic_link_fields:
|
||||
reference_doctype = df.parent
|
||||
|
||||
if not is_valid_timeline_doctype(reference_doctype, timeline_doctype):
|
||||
continue
|
||||
|
||||
try:
|
||||
docs = frappe.get_all(reference_doctype, fields=["name", df.fieldname],
|
||||
filters={ df.options: timeline_doctype })
|
||||
except frappe.SQLError, e:
|
||||
if e.args and e.args[0]==1146:
|
||||
# single
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
|
||||
for doc in docs:
|
||||
timeline_name = doc.get(df.fieldname)
|
||||
update_communication(timeline_doctype, timeline_name, reference_doctype, doc.name)
|
||||
|
||||
def update_communication(timeline_doctype, timeline_name, reference_doctype, reference_name):
|
||||
if not timeline_name:
|
||||
return
|
||||
|
||||
frappe.db.sql("""update `tabCommunication` set timeline_doctype=%(timeline_doctype)s, timeline_name=%(timeline_name)s
|
||||
where (reference_doctype=%(reference_doctype)s and reference_name=%(reference_name)s)
|
||||
and (timeline_doctype is null or timeline_doctype='')
|
||||
and (timeline_name is null or timeline_name='')""", {
|
||||
"timeline_doctype": timeline_doctype,
|
||||
"timeline_name": timeline_name,
|
||||
"reference_doctype": reference_doctype,
|
||||
"reference_name": reference_name
|
||||
})
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
def is_valid_timeline_doctype(reference_doctype, timeline_doctype):
|
||||
# for reloading timeline_field
|
||||
frappe.reload_doctype(reference_doctype)
|
||||
|
||||
# make sure the timeline field's doctype is same as timeline doctype
|
||||
meta = frappe.get_meta(reference_doctype)
|
||||
if not meta.timeline_field:
|
||||
return False
|
||||
|
||||
doctype = meta.get_link_doctype(meta.timeline_field)
|
||||
if doctype != timeline_doctype:
|
||||
return False
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
|
@ -101,6 +101,7 @@
|
|||
"public/js/frappe/model/perm.js",
|
||||
"public/js/frappe/model/workflow.js",
|
||||
|
||||
"public/js/lib/md5.min.js",
|
||||
"public/js/frappe/misc/user.js",
|
||||
"public/js/frappe/misc/pretty_date.js",
|
||||
"public/js/frappe/misc/utils.js",
|
||||
|
|
|
|||
|
|
@ -97,6 +97,9 @@
|
|||
.timeline-item blockquote {
|
||||
font-size: inherit;
|
||||
}
|
||||
.timeline-item .btn-more {
|
||||
margin-left: 65px;
|
||||
}
|
||||
.timeline-items {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,8 +139,8 @@ frappe.ui.form.Attachments = Class.extend({
|
|||
return;
|
||||
}
|
||||
me.remove_fileid(fileid);
|
||||
me.frm.get_docinfo().comments.push(r.message);
|
||||
me.frm.comments.refresh();
|
||||
me.frm.get_docinfo().communications.push(r.message);
|
||||
me.frm.timeline.refresh();
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
|
|
@ -180,8 +180,8 @@ frappe.ui.form.Attachments = Class.extend({
|
|||
this.add_to_attachments(attachment);
|
||||
this.refresh();
|
||||
if(comment) {
|
||||
this.frm.get_docinfo().comments.push(comment);
|
||||
this.frm.comments.refresh();
|
||||
this.frm.get_docinfo().communications.push(comment);
|
||||
this.frm.timeline.refresh();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ frappe.ui.form.Footer = Class.extend({
|
|||
|
||||
},
|
||||
make_comments: function() {
|
||||
this.frm.comments = new frappe.ui.form.Comments({
|
||||
this.frm.timeline = new frappe.ui.form.Timeline({
|
||||
parent: this.wrapper.find(".form-comments"),
|
||||
frm: this.frm
|
||||
})
|
||||
|
|
@ -32,7 +32,7 @@ frappe.ui.form.Footer = Class.extend({
|
|||
this.parent.addClass("hide");
|
||||
} else {
|
||||
this.parent.removeClass("hide");
|
||||
this.frm.comments.refresh();
|
||||
this.frm.timeline.refresh();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
frappe.ui.form.Comments = Class.extend({
|
||||
frappe.ui.form.Timeline = Class.extend({
|
||||
init: function(opts) {
|
||||
$.extend(this, opts);
|
||||
this.make();
|
||||
|
|
@ -40,6 +40,34 @@ frappe.ui.form.Comments = Class.extend({
|
|||
this.setup_comment_like();
|
||||
|
||||
this.setup_mentions();
|
||||
|
||||
this.list.on("click", ".btn-more", function() {
|
||||
var communications = me.get_communications();
|
||||
frappe.call({
|
||||
btn: this,
|
||||
method: "frappe.desk.form.load.get_communications",
|
||||
args: {
|
||||
doctype: me.frm.doc.doctype,
|
||||
name: me.frm.doc.name,
|
||||
start: communications.length
|
||||
},
|
||||
callback: function(r) {
|
||||
if (!r.exc) {
|
||||
if (r.message) {
|
||||
var new_communications = r.message;
|
||||
var communications = me.get_communications().concat(new_communications);
|
||||
frappe.model.set_docinfo(me.frm.doc.doctype, me.frm.doc.name, "communications", communications);
|
||||
|
||||
} else {
|
||||
me.more = false;
|
||||
}
|
||||
|
||||
me.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
refresh: function(scroll_to_end) {
|
||||
|
|
@ -54,23 +82,42 @@ frappe.ui.form.Comments = Class.extend({
|
|||
this.wrapper.toggle(true);
|
||||
this.list.empty();
|
||||
|
||||
var comments = [{"comment": __("Created"), "comment_type": "Created",
|
||||
"comment_by": this.frm.doc.owner, "creation": this.frm.doc.creation}].concat(this.get_comments());
|
||||
// var communications = [].concat(this.get_communications());
|
||||
|
||||
$.each(comments.sort(function(a, b) { return a.creation > b.creation ? -1 : 1 }),
|
||||
var communications = this.get_communications();
|
||||
|
||||
$.each(communications.sort(function(a, b) { return a.creation > b.creation ? -1 : 1 }),
|
||||
function(i, c) {
|
||||
if(c.comment) me.render_comment(c);
|
||||
if(c.content) {
|
||||
c.frm = me.frm;
|
||||
me.render_timeline_item(c);
|
||||
}
|
||||
});
|
||||
|
||||
// more btn
|
||||
if (this.more===undefined && communications.length===20) {
|
||||
this.more = true;
|
||||
}
|
||||
|
||||
if (this.more) {
|
||||
var $more = $('<div class="timeline-item">\
|
||||
<button class="btn btn-default btn-xs btn-more">More</button>\
|
||||
</div>').appendTo(me.list);
|
||||
}
|
||||
|
||||
// created
|
||||
me.render_timeline_item({"content": __("Created"), "comment_type": "Created", "communication_type": "Comment",
|
||||
"sender": this.frm.doc.owner, "creation": this.frm.doc.creation, "frm": this.frm});
|
||||
|
||||
this.wrapper.find(".is-email").prop("checked", this.last_type==="Email").change();
|
||||
|
||||
this.frm.sidebar.refresh_comments();
|
||||
|
||||
},
|
||||
|
||||
render_comment: function(c) {
|
||||
render_timeline_item: function(c) {
|
||||
var me = this;
|
||||
this.prepare_comment(c);
|
||||
this.prepare_timeline_item(c);
|
||||
|
||||
var $timeline_item = $(frappe.render_template("timeline_item", {data:c}))
|
||||
.appendTo(me.list)
|
||||
|
|
@ -81,8 +128,8 @@ frappe.ui.form.Comments = Class.extend({
|
|||
});
|
||||
|
||||
|
||||
if(c.comment_type==="Email") {
|
||||
this.last_type = c.comment_type;
|
||||
if(c.communication_type=="Communication" && c.communication_medium==="Email") {
|
||||
this.last_type = c.communication_medium;
|
||||
this.add_reply_btn_event($timeline_item, c);
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +142,7 @@ frappe.ui.form.Comments = Class.extend({
|
|||
var last_email = null;
|
||||
|
||||
// find the email tor reply to
|
||||
me.get_comments().forEach(function(c) {
|
||||
me.get_communications().forEach(function(c) {
|
||||
if(c.name==name) {
|
||||
last_email = c;
|
||||
return false;
|
||||
|
|
@ -112,61 +159,61 @@ frappe.ui.form.Comments = Class.extend({
|
|||
});
|
||||
},
|
||||
|
||||
prepare_comment: function(c) {
|
||||
if((c.comment_type || "Comment") === "Comment" && frappe.model.can_delete("Comment")) {
|
||||
prepare_timeline_item: function(c) {
|
||||
if(c.communication_type=="Comment" && (c.comment_type || "Comment") === "Comment" && frappe.model.can_delete("Communication")) {
|
||||
c["delete"] = '<a class="close" href="#"><i class="octicon octicon-trashcan"></i></a>';
|
||||
} else {
|
||||
c["delete"] = "";
|
||||
}
|
||||
|
||||
if(!c.comment_by) c.comment_by = this.frm.doc.owner;
|
||||
if(!c.sender) c.sender = this.frm.doc.owner;
|
||||
|
||||
if(c.comment_by.indexOf("<")!==-1) {
|
||||
c.comment_by = c.comment_by.split("<")[1].split(">")[0];
|
||||
if(c.sender.indexOf("<")!==-1) {
|
||||
c.sender = c.sender.split("<")[1].split(">")[0];
|
||||
}
|
||||
|
||||
c.image = frappe.user_info(c.comment_by).image
|
||||
|| frappe.get_gravatar(c.comment_by);
|
||||
c.image = frappe.user_info(c.sender).image || frappe.get_gravatar(c.sender);
|
||||
c.comment_on = comment_when(c.creation);
|
||||
c.fullname = frappe.user.full_name(c.comment_by);
|
||||
c.fullname = c.sender_full_name || frappe.user.full_name(c.sender);
|
||||
|
||||
if(c.attachments && typeof c.attachments==="string")
|
||||
c.attachments = JSON.parse(c.attachments);
|
||||
|
||||
if(!c.comment_type)
|
||||
c.comment_type = "Comment"
|
||||
if(c.communication_type=="Comment" && !c.comment_type) {
|
||||
c.comment_type = "Comment";
|
||||
}
|
||||
|
||||
this.set_icon_and_color(c);
|
||||
|
||||
// label view
|
||||
if(c.comment_type==="Workflow" || c.comment_type==="Label") {
|
||||
c.comment_html = repl('<span class="label label-%(style)s">%(text)s</span>', {
|
||||
style: frappe.utils.guess_style(c.comment),
|
||||
text: __(c.comment)
|
||||
style: frappe.utils.guess_style(c.content),
|
||||
text: __(c.content)
|
||||
});
|
||||
} else {
|
||||
if(c.comment_type=="Email") {
|
||||
c.comment = c.comment.split("<!-- original-reply -->")[0];
|
||||
c.comment = frappe.utils.strip_original_content(c.comment);
|
||||
c.comment = frappe.dom.remove_script_and_style(c.comment);
|
||||
if(c.communication_type=="Communication" && c.communication_medium=="Email") {
|
||||
c.content = c.content.split("<!-- original-reply -->")[0];
|
||||
c.content = frappe.utils.strip_original_content(c.content);
|
||||
c.content = frappe.dom.remove_script_and_style(c.content);
|
||||
|
||||
c.original_comment = c.comment;
|
||||
c.comment = frappe.utils.toggle_blockquote(c.comment);
|
||||
c.original_content = c.content;
|
||||
c.content = frappe.utils.toggle_blockquote(c.content);
|
||||
}
|
||||
|
||||
if(!frappe.utils.is_html(c.comment)) {
|
||||
c.comment_html = frappe.markdown(__(c.comment));
|
||||
if(!frappe.utils.is_html(c.content)) {
|
||||
c.content_html = frappe.markdown(__(c.content));
|
||||
} else {
|
||||
c.comment_html = c.comment;
|
||||
c.comment_html = frappe.utils.strip_whitespace(c.comment_html);
|
||||
c.content_html = c.content;
|
||||
c.content_html = frappe.utils.strip_whitespace(c.content_html);
|
||||
}
|
||||
|
||||
// bold @mentions
|
||||
if(c.comment_type==="Comment") {
|
||||
c.comment_html = c.comment_html.replace(/(^|\W)(@\w+)/g, "$1<b>$2</b>");
|
||||
c.content_html = c.content_html.replace(/(^|\W)(@\w+)/g, "$1<b>$2</b>");
|
||||
}
|
||||
|
||||
if (in_list(["Comment", "Email"], c.comment_type)) {
|
||||
if (this.is_communication_or_comment(c)) {
|
||||
c.user_content = true;
|
||||
if (!$.isArray(c._liked_by)) {
|
||||
c._liked_by = JSON.parse(c._liked_by || "[]");
|
||||
|
|
@ -176,6 +223,11 @@ frappe.ui.form.Comments = Class.extend({
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
is_communication_or_comment: function(c) {
|
||||
return c.communication_type==="Communication" || (c.communication_type==="Comment" && c.comment_type==="Comment");
|
||||
},
|
||||
|
||||
set_icon_and_color: function(c) {
|
||||
c.icon = {
|
||||
"Email": "octicon octicon-mail",
|
||||
|
|
@ -195,7 +247,7 @@ frappe.ui.form.Comments = Class.extend({
|
|||
"Shared": "octicon octicon-eye",
|
||||
"Unshared": "octicon octicon-circle-slash",
|
||||
"Like": "octicon octicon-heart"
|
||||
}[c.comment_type]
|
||||
}[c.comment_type || c.communication_medium]
|
||||
|
||||
c.color = {
|
||||
"Email": "#3498db",
|
||||
|
|
@ -212,18 +264,18 @@ frappe.ui.form.Comments = Class.extend({
|
|||
"Label": "#2c3e50",
|
||||
"Attachment": "#7f8c8d",
|
||||
"Attachment Removed": "#eee"
|
||||
}[c.comment_type];
|
||||
}[c.comment_type || c.communication_medium];
|
||||
|
||||
c.icon_fg = {
|
||||
"Attachment Removed": "#333",
|
||||
}[c.comment_type]
|
||||
}[c.comment_type || c.communication_medium]
|
||||
|
||||
if(!c.icon_fg)
|
||||
c.icon_fg = "#fff";
|
||||
|
||||
},
|
||||
get_comments: function() {
|
||||
return this.frm.get_docinfo().comments;
|
||||
get_communications: function() {
|
||||
return this.frm.get_docinfo().communications;
|
||||
},
|
||||
add_comment: function(btn) {
|
||||
var txt = this.input.val();
|
||||
|
|
@ -238,12 +290,13 @@ frappe.ui.form.Comments = Class.extend({
|
|||
method: "frappe.desk.form.utils.add_comment",
|
||||
args: {
|
||||
doc:{
|
||||
doctype: "Comment",
|
||||
doctype: "Communication",
|
||||
communication_type: "Comment",
|
||||
comment_type: comment_type || "Comment",
|
||||
comment_doctype: this.frm.doctype,
|
||||
comment_docname: this.frm.docname,
|
||||
comment: comment,
|
||||
comment_by: user
|
||||
reference_doctype: this.frm.doctype,
|
||||
reference_name: this.frm.docname,
|
||||
content: comment,
|
||||
sender: user
|
||||
}
|
||||
},
|
||||
btn: btn,
|
||||
|
|
@ -254,7 +307,7 @@ frappe.ui.form.Comments = Class.extend({
|
|||
frappe.utils.play_sound("click");
|
||||
|
||||
var comment = r.message;
|
||||
var comments = me.get_comments();
|
||||
var comments = me.get_communications();
|
||||
var comment_exists = false;
|
||||
for (var i=0, l=comments.length; i<l; i++) {
|
||||
if (comments[i].name==comment.name) {
|
||||
|
|
@ -266,7 +319,7 @@ frappe.ui.form.Comments = Class.extend({
|
|||
return;
|
||||
}
|
||||
|
||||
me.frm.get_docinfo().comments = comments.concat([r.message]);
|
||||
me.frm.get_docinfo().communications = comments.concat([r.message]);
|
||||
me.refresh(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -279,15 +332,15 @@ frappe.ui.form.Comments = Class.extend({
|
|||
return frappe.call({
|
||||
method: "frappe.client.delete",
|
||||
args: {
|
||||
doctype: "Comment",
|
||||
doctype: "Communication",
|
||||
name: name
|
||||
},
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
frappe.utils.play_sound("delete");
|
||||
|
||||
me.frm.get_docinfo().comments =
|
||||
$.map(me.frm.get_docinfo().comments,
|
||||
me.frm.get_docinfo().communications =
|
||||
$.map(me.frm.get_docinfo().communications,
|
||||
function(v) {
|
||||
if(v.name==name) return null;
|
||||
else return v;
|
||||
|
|
@ -309,13 +362,13 @@ frappe.ui.form.Comments = Class.extend({
|
|||
|
||||
get_last_email: function(from_recipient) {
|
||||
var last_email = null,
|
||||
comments = this.frm.get_docinfo().comments,
|
||||
communications = this.frm.get_docinfo().communications,
|
||||
email = this.get_recipient();
|
||||
|
||||
$.each(comments.sort(function(a, b) { return a.creation > b.creation ? -1 : 1 }), function(i, c) {
|
||||
if(c.comment_type=="Email") {
|
||||
$.each(communications.sort(function(a, b) { return a.creation > b.creation ? -1 : 1 }), function(i, c) {
|
||||
if(c.communication_type=='Communication' && c.communication_medium=="Email") {
|
||||
if(from_recipient) {
|
||||
if(c.comment_by.indexOf(email)!==-1) {
|
||||
if(c.sender.indexOf(email)!==-1) {
|
||||
last_email = c;
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,13 +12,21 @@
|
|||
{%= data.delete %}
|
||||
</span>
|
||||
</div>
|
||||
{% if(data.doctype=="Communication" || data.comment_type=="Comment") { %}
|
||||
{% if(data.communication_type==="Communication" || (data.communication_type==="Comment" && data.comment_type==="Comment")) { %}
|
||||
<div class="comment-header small">
|
||||
<i class="{%= data.icon %} icon-fixed-width"></i>
|
||||
<span title="{%= data.comment_by %}">{%= data.fullname %}</span>
|
||||
<span>
|
||||
{% if (data.timeline_doctype===data.frm.doc.doctype && data.timeline_name===data.frm.doc.name) { %}
|
||||
–
|
||||
<a href="#Form/{%= data.reference_doctype %}/{%= data.reference_name %}" class="text-muted">
|
||||
<strong>{{ __(data.reference_doctype) }}</strong> {{ data.reference_name }}
|
||||
</a>
|
||||
{% } %}
|
||||
</span>
|
||||
<span class="text-muted" style="font-weight: normal;">
|
||||
– {%= data.comment_on %}</span>
|
||||
{% if(data.doctype=="Communication") { %}
|
||||
{% if(data.communication_type==="Communication") { %}
|
||||
{% if (frappe.model.can_read(\'Communication\')) { %}
|
||||
<a href="#Form/{%= data.doctype %}/{%= data.name %}"
|
||||
class="text-muted">
|
||||
|
|
@ -47,8 +55,10 @@
|
|||
</a>
|
||||
{% } %}
|
||||
|
||||
{% if (data.communication_medium === "Email") { %}
|
||||
<a class="text-muted reply-link pull-right"
|
||||
data-name="{%= data.name %}">{%= __("Reply") %}</a>
|
||||
{% } %}
|
||||
{% } %}
|
||||
<span class="comment-likes" data-liked-by=\'{{ JSON.stringify(data._liked_by) }}\'>
|
||||
<i class="octicon octicon-heart like-action
|
||||
|
|
@ -62,17 +72,23 @@
|
|||
</div>
|
||||
<div class="reply">
|
||||
<div>
|
||||
{%= data.comment_html %}
|
||||
{%= data.content_html %}
|
||||
</div>
|
||||
</div>
|
||||
{% } else if(in_list(["Assignment Completed", "Assigned", "Shared", "Unshared"], data.comment_type)) { %}
|
||||
<div class="small">
|
||||
<i class="{%= data.icon %} icon-fixed-width"></i>
|
||||
{% if(data.reference_doctype && data.reference_name) { %}
|
||||
<a href="#Form/{%= data.reference_doctype %}/{%= data.reference_name %}">
|
||||
{% if (data.timeline_doctype===data.frm.doc.doctype && data.timeline_name===data.frm.doc.name) { %}
|
||||
<a href="#Form/{%= data.reference_doctype %}/{%= data.reference_name %}">
|
||||
<strong>{{ __(data.reference_doctype) }}</strong> {{ data.reference_name }}
|
||||
</a>
|
||||
–
|
||||
{% } %}
|
||||
{%= data.comment %}
|
||||
{% if(data.reference_doctype && data.reference_name) { %}
|
||||
{% if(data.link_doctype && data.link_name) { %}
|
||||
<a href="#Form/{%= data.link_doctype %}/{%= data.link_name %}">
|
||||
{% } %}
|
||||
{%= data.content %}
|
||||
{% if(data.link_doctype && data.link_name) { %}
|
||||
</a>
|
||||
{% } %}
|
||||
<span class="text-muted" style="font-weight: normal;">
|
||||
|
|
@ -82,10 +98,23 @@
|
|||
<div class="small">
|
||||
<i class="{%= data.icon %} icon-fixed-width"></i>
|
||||
{% if (data.comment_type == "Like") { %}
|
||||
<span title="{%= data.comment_by %}">{%= __("Liked by {0}", [data.fullname]) %}</span>
|
||||
<span title="{%= data.comment_by %}">
|
||||
{% if (data.timeline_doctype===data.frm.doc.doctype && data.timeline_name===data.frm.doc.name) { %}
|
||||
<a href="#Form/{%= data.reference_doctype %}/{%= data.reference_name %}">
|
||||
<strong>{{ __(data.reference_doctype) }}</strong> {{ data.reference_name }}
|
||||
</a> –
|
||||
{% } %}
|
||||
{%= __("Liked by {0}", [data.fullname]) %}
|
||||
</span>
|
||||
{% } else { %}
|
||||
<span title="{%= data.comment_by %}">{%= data.fullname %}</span>
|
||||
{%= data.comment %}
|
||||
{%= data.content %}
|
||||
{% if (data.timeline_doctype===data.frm.doc.doctype && data.timeline_name===data.frm.doc.name) { %}
|
||||
–
|
||||
<a href="#Form/{%= data.reference_doctype %}/{%= data.reference_name %}">
|
||||
<strong>{{ __(data.reference_doctype) }}</strong> {{ data.reference_name }}
|
||||
</a>
|
||||
{% } %}
|
||||
{% } %}
|
||||
<span class="text-muted" style="font-weight: normal;">
|
||||
– {%= data.comment_on %}</span>
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
},
|
||||
|
||||
refresh_comments: function() {
|
||||
var comments = $.map(this.frm.comments.get_comments(), function(c) {
|
||||
return (c.comment_type==="Email" || c.comment_type==="Comment") ? c : null;
|
||||
var comments = $.map(this.frm.timeline.get_communications(), function(c) {
|
||||
return (c.communication_type==="Communication" || (c.communication_type=="Comment" && c.comment_type==="Comment")) ? c : null;
|
||||
});
|
||||
this.comments.find(".n-comments").html(comments.length);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ frappe.ui.form.States = Class.extend({
|
|||
|
||||
// success - add a comment
|
||||
var success = function() {
|
||||
me.frm.comments.insert_comment("Workflow", next_state);
|
||||
me.frm.timeline.insert_comment("Workflow", next_state);
|
||||
}
|
||||
if(new_docstatus==1 && me.frm.doc.docstatus==0) {
|
||||
me.frm.savesubmit(null, success, on_error);
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
|
|||
}
|
||||
|
||||
// apply default filters, if specified for a listing
|
||||
$.each((this.listview.default_filters || []), function(i, f) {
|
||||
$.each((this.listview.default_filters || this.listview.settings.default_filters || []), function(i, f) {
|
||||
args.filters.push(f);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ frappe.avatar = function(user, css_class, title) {
|
|||
|
||||
frappe.gravatars = {};
|
||||
frappe.get_gravatar = function(email_id) {
|
||||
frappe.require("/assets/frappe/js/lib/md5.min.js");
|
||||
if(!frappe.gravatars[email_id]) {
|
||||
frappe.gravatars[email_id] = "https://secure.gravatar.com/avatar/" + md5(email_id) + "?d=retro";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,29 +159,53 @@ $.extend(frappe.model, {
|
|||
}
|
||||
},
|
||||
|
||||
new_comment: function(comment) {
|
||||
var reference_doctype = comment.comment_doctype || comment.reference_doctype;
|
||||
var reference_name = comment.comment_docname || comment.reference_name;
|
||||
|
||||
if (frappe.model.docinfo[reference_doctype] && frappe.model.docinfo[reference_doctype][reference_name]) {
|
||||
var comments = frappe.model.docinfo[reference_doctype][reference_name].comments;
|
||||
var comment_exists = false;
|
||||
for (var i=0, l=comments.length; i<l; i++) {
|
||||
if (comments[i].name==comment.name) {
|
||||
comment_exists = true;
|
||||
new_communication: function(communication) {
|
||||
var docinfo = frappe.model.get_docinfo(communication.reference_doctype, communication.reference_name);
|
||||
if (docinfo && docinfo.communications) {
|
||||
var communications = docinfo.communications;
|
||||
var communication_exists = false;
|
||||
for (var i=0, l=communications.length; i<l; i++) {
|
||||
if (communications[i].name==communication.name) {
|
||||
communication_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!comment_exists) {
|
||||
frappe.model.docinfo[reference_doctype][reference_name].comments = comments.concat([comment]);
|
||||
if (!communication_exists) {
|
||||
docinfo.communications = communications.concat([communication]);
|
||||
}
|
||||
}
|
||||
if (cur_frm.doctype === reference_doctype && cur_frm.docname === reference_name) {
|
||||
cur_frm.comments && cur_frm.comments.refresh();
|
||||
|
||||
if (cur_frm.doctype === communication.reference_doctype && cur_frm.docname === communication.reference_name) {
|
||||
cur_frm.timeline && cur_frm.timeline.refresh();
|
||||
}
|
||||
},
|
||||
|
||||
delete_communication: function(communication) {
|
||||
var docinfo = frappe.model.get_docinfo(communication.reference_doctype, communication.reference_name);
|
||||
if (docinfo && docinfo.communications) {
|
||||
var communications = docinfo.communications;
|
||||
|
||||
var index = -1;
|
||||
for (var i=0, l=communications.length; i<l; i++) {
|
||||
if (communications[i].name==communication.name) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index !== -1) {
|
||||
// remove it from communications list
|
||||
docinfo.communications.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (cur_frm.doctype === communication.reference_doctype && cur_frm.docname === communication.reference_name) {
|
||||
cur_frm.timeline && cur_frm.timeline.refresh();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
get_shared: function(doctype, name) {
|
||||
return frappe.model.get_docinfo(doctype, name).shared;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -235,7 +235,6 @@ frappe.ui.Filter = Class.extend({
|
|||
}
|
||||
|
||||
var field_area = me.$w.find('.filter_field').empty().get(0);
|
||||
|
||||
var f = frappe.ui.form.make_control({
|
||||
df: df,
|
||||
parent: field_area,
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ frappe.ui.notifications.add_notification = function(doctype, notifications_map)
|
|||
// default notification config
|
||||
frappe.ui.notifications.config = {
|
||||
"ToDo": { label: __("To Do") },
|
||||
"Comment": { label: __("Messages"), route: "messages"},
|
||||
"Messages": { label: __("Messages"), route: "messages"},
|
||||
"Event": { label: __("Calendar"), route: "Calendar/Event" },
|
||||
"Likes": {
|
||||
label: __("Likes"),
|
||||
|
|
@ -94,12 +94,12 @@ frappe.ui.notifications.config = {
|
|||
};
|
||||
|
||||
if (frappe.get_route()[0]=="activity") {
|
||||
frappe.pages['activity'].on_page_show();
|
||||
frappe.pages['activity'].page.list.refresh();
|
||||
} else {
|
||||
frappe.set_route("activity");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
frappe.views.show_open_count_list = function(element) {
|
||||
|
|
|
|||
|
|
@ -110,10 +110,10 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
}
|
||||
}
|
||||
|
||||
var comments = this.frm.get_docinfo().comments;
|
||||
if (comments) {
|
||||
for ( var i=0, l=comments.length; i<l; i++ ) {
|
||||
cc.push( [comments[i].comment_by, 0] );
|
||||
var communications = this.frm.get_docinfo().communications;
|
||||
if (communications) {
|
||||
for ( var i=0, l=communications.length; i<l; i++ ) {
|
||||
cc.push( [communications[i].sender, 0] );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,21 +160,21 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
this.subject = this.subject || "";
|
||||
|
||||
if(!this.recipients && this.last_email) {
|
||||
this.recipients = this.last_email.comment_by;
|
||||
this.recipients = this.last_email.sender;
|
||||
}
|
||||
|
||||
if(!this.recipients) {
|
||||
this.recipients = this.frm && this.frm.comments.get_recipient();
|
||||
this.recipients = this.frm && this.frm.timeline.get_recipient();
|
||||
}
|
||||
|
||||
if(!this.subject && this.frm) {
|
||||
// get subject from last communication
|
||||
var last = this.frm.comments.get_last_email();
|
||||
var last = this.frm.timeline.get_last_email();
|
||||
|
||||
if(last) {
|
||||
this.subject = last.subject;
|
||||
if(!this.recipients) {
|
||||
this.recipients = last.comment_by;
|
||||
this.recipients = last.sender;
|
||||
}
|
||||
|
||||
// prepend "Re:"
|
||||
|
|
@ -420,7 +420,7 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
};
|
||||
|
||||
return frappe.call({
|
||||
method:"frappe.core.doctype.communication.communication.make",
|
||||
method:"frappe.core.doctype.communication.email.make",
|
||||
args: {
|
||||
recipients: form_values.recipients,
|
||||
cc: form_values.cc,
|
||||
|
|
@ -453,7 +453,7 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
delete frappe.last_edited_communication[cur_frm.doctype][cur_frm.docname];
|
||||
}
|
||||
// clear input
|
||||
cur_frm.comments.input.val("");
|
||||
cur_frm.timeline.input.val("");
|
||||
cur_frm.reload_doc();
|
||||
}
|
||||
} else {
|
||||
|
|
@ -469,7 +469,7 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
last_email = this.last_email;
|
||||
|
||||
if(!last_email) {
|
||||
last_email = this.frm && this.frm.comments.get_last_email(true);
|
||||
last_email = this.frm && this.frm.timeline.get_last_email(true);
|
||||
}
|
||||
|
||||
if(!frappe.utils.is_html(signature)) {
|
||||
|
|
@ -489,13 +489,13 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
+ (signature ? ("<br>" + signature) : "");
|
||||
|
||||
if(last_email) {
|
||||
var last_email_content = last_email.original_comment || last_email.comment;
|
||||
var last_email_content = last_email.original_comment || last_email.content;
|
||||
|
||||
fields.content.set_input(reply
|
||||
+ "<br><!-- original-reply --><br>"
|
||||
+ '<blockquote>' +
|
||||
'<p>' + __("On {0}, {1} wrote:",
|
||||
[frappe.datetime.global_date_format(last_email.creation) , last_email.comment_by]) + '</p>' +
|
||||
[frappe.datetime.global_date_format(last_email.creation) , last_email.sender]) + '</p>' +
|
||||
last_email_content +
|
||||
'<blockquote>');
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -25,8 +25,12 @@ frappe.views.FormFactory = frappe.views.Factory.extend({
|
|||
frappe.ui.form.close_grid_form();
|
||||
});
|
||||
|
||||
frappe.realtime.on("new_comment", function(data) {
|
||||
frappe.model.new_comment(data);
|
||||
frappe.realtime.on("new_communication", function(data) {
|
||||
frappe.model.new_communication(data);
|
||||
});
|
||||
|
||||
frappe.realtime.on("delete_communication", function(data) {
|
||||
frappe.model.delete_communication(data);
|
||||
});
|
||||
|
||||
frappe.realtime.on("doc_viewers", function(data) {
|
||||
|
|
|
|||
|
|
@ -815,7 +815,7 @@ _f.Frm.prototype.reload_docinfo = function(callback) {
|
|||
callback: function(r) {
|
||||
// docinfo will be synced
|
||||
if(callback) callback(r.docinfo);
|
||||
me.comments.refresh();
|
||||
me.timeline.refresh();
|
||||
me.assign_to.refresh();
|
||||
me.attachments.refresh();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,6 +127,10 @@
|
|||
blockquote {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.btn-more {
|
||||
margin-left: 65px;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-items {
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@
|
|||
</p>
|
||||
<blockquote
|
||||
style="border-left: 3px solid #d1d8dd; padding: 7px 15px; margin-left: 0px;">
|
||||
{{ comment.comment | markdown }}
|
||||
{{ comment.content | markdown }}
|
||||
</blockquote>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
<div class="blog-comment-row">
|
||||
<div class="inline-block" style="vertical-align: top">
|
||||
<div class="avatar avatar-medium" style="margin-top: 11px;">
|
||||
<img itemprop="thumbnailUrl" src="{{ frappe.get_gravatar(comment.comment_by) }}" />
|
||||
<img itemprop="thumbnailUrl" src="{{ frappe.get_gravatar(comment.sender) }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-block" style="width: calc(100% - 100px)">
|
||||
<h4 itemprop="name">{{ comment.comment_by_fullname }}
|
||||
<h4 itemprop="name">{{ comment.sender_full_name }}
|
||||
<br>
|
||||
<small class="text-muted">
|
||||
<span itemprop="commentTime">{{ comment.creation|global_date_format }}</span>
|
||||
</small>
|
||||
</h4>
|
||||
<div itemprop="commentText">{{ comment.comment|markdown }}</div>
|
||||
<div itemprop="commentText">{{ comment.content|markdown }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -81,8 +81,8 @@
|
|||
comment_by_fullname: $("[name='comment_by_fullname']").val(),
|
||||
comment_by: $("[name='comment_by']").val(),
|
||||
comment: $("[name='comment']").val(),
|
||||
comment_doctype: "{{ comment_doctype or doctype }}",
|
||||
comment_docname: "{{ comment_docname or name }}",
|
||||
reference_doctype: "{{ reference_doctype or doctype }}",
|
||||
reference_name: "{{ reference_name or name }}",
|
||||
comment_type: "Comment",
|
||||
page_name: "{{ pathname }}",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,48 +15,46 @@ def add_comment(args=None):
|
|||
'comment': '',
|
||||
'comment_by': '',
|
||||
'comment_by_fullname': '',
|
||||
'comment_doctype': '',
|
||||
'comment_docname': '',
|
||||
'reference_doctype': '',
|
||||
'reference_name': '',
|
||||
'page_name': '',
|
||||
}
|
||||
"""
|
||||
|
||||
if not args:
|
||||
args = frappe.local.form_dict
|
||||
args['doctype'] = "Comment"
|
||||
|
||||
page_name = args.get("page_name")
|
||||
if "page_name" in args:
|
||||
del args["page_name"]
|
||||
if "cmd" in args:
|
||||
del args["cmd"]
|
||||
|
||||
comment = frappe.get_doc(args)
|
||||
comment.comment_type = "Comment"
|
||||
doc = frappe.get_doc(args["reference_doctype"], args["reference_name"])
|
||||
comment = doc.add_comment("Comment", args["comment"], comment_by=args["comment_by"])
|
||||
comment.flags.ignore_permissions = True
|
||||
comment.insert()
|
||||
comment.sender_full_name = args["comment_by_fullname"]
|
||||
comment.save()
|
||||
|
||||
# since comments are embedded in the page, clear the web cache
|
||||
clear_cache(page_name)
|
||||
|
||||
# notify commentors
|
||||
commentors = [d[0] for d in frappe.db.sql("""select comment_by from tabComment where
|
||||
comment_doctype=%s and comment_docname=%s and
|
||||
unsubscribed=0""", (comment.comment_doctype, comment.comment_docname))]
|
||||
commentors = [d[0] for d in frappe.db.sql("""select sender from `tabCommunication`
|
||||
where
|
||||
communication_type = 'Comment' and comment_type = 'Comment'
|
||||
and reference_doctype=%s
|
||||
and reference_name=%s""", (comment.reference_doctype, comment.reference_name))]
|
||||
|
||||
owner = frappe.db.get_value(comment.comment_doctype, comment.comment_docname, "owner")
|
||||
owner = frappe.db.get_value(doc.doctype, doc.name, "owner")
|
||||
recipients = list(set(commentors if owner=="Administrator" else (commentors + [owner])))
|
||||
|
||||
message = _("{0} by {1}").format(markdown2.markdown(args.get("comment")), comment.comment_by_fullname)
|
||||
message = _("{0} by {1}").format(markdown2.markdown(args.get("comment")), comment.sender_full_name)
|
||||
message += "<p><a href='{0}/{1}' style='font-size: 80%'>{2}</a></p>".format(frappe.utils.get_request_site_address(),
|
||||
page_name, _("View it in your browser"))
|
||||
|
||||
from frappe.email.bulk import send
|
||||
|
||||
send(recipients=recipients,
|
||||
subject = _("New comment on {0} {1}").format(comment.comment_doctype, comment.comment_docname),
|
||||
subject = _("New comment on {0} {1}").format(doc.doctype, doc.name),
|
||||
message = message,
|
||||
reference_doctype=comment.comment_doctype, reference_name=comment.comment_docname)
|
||||
reference_doctype=doc.doctype, reference_name=doc.name)
|
||||
|
||||
template = frappe.get_template("templates/includes/comments/comment.html")
|
||||
|
||||
|
|
|
|||
|
|
@ -141,8 +141,12 @@ def get_blog_list(doctype, txt=None, filters=None, limit_start=0, limit_page_len
|
|||
t1.published_on as creation,
|
||||
ifnull(t1.blog_intro, t1.content) as content,
|
||||
t2.full_name, t2.avatar, t1.blogger,
|
||||
(select count(name) from `tabComment` where
|
||||
comment_doctype='Blog Post' and comment_docname=t1.name and comment_type="Comment") as comments
|
||||
(select count(name) from `tabCommunication`
|
||||
where
|
||||
communication_type='Comment'
|
||||
and comment_type='Comment'
|
||||
and reference_doctype='Blog Post'
|
||||
and reference_name=t1.name) as comments
|
||||
from `tabBlog Post` t1, `tabBlogger` t2
|
||||
where ifnull(t1.published,0)=1
|
||||
and t1.blogger = t2.name
|
||||
|
|
|
|||
|
|
@ -103,8 +103,8 @@ class WebForm(WebsiteGenerator):
|
|||
context.doc = frappe.get_doc(self.doc_type, frappe.form_dict.name)
|
||||
context.title = context.doc.get(context.doc.meta.get_title_field())
|
||||
|
||||
context.comment_doctype = context.doc.doctype
|
||||
context.comment_docname = context.doc.name
|
||||
context.reference_doctype = context.doc.doctype
|
||||
context.reference_name = context.doc.name
|
||||
|
||||
if self.allow_comments and frappe.form_dict.name:
|
||||
context.comment_list = get_comment_list(context.doc.doctype, context.doc.name)
|
||||
|
|
|
|||
|
|
@ -26,10 +26,14 @@ def can_cache(no_cache=False):
|
|||
|
||||
def get_comment_list(doctype, name):
|
||||
return frappe.db.sql("""select
|
||||
comment, comment_by_fullname, creation, comment_by
|
||||
from `tabComment` where comment_doctype=%s
|
||||
and ifnull(comment_type, "Comment")="Comment"
|
||||
and comment_docname=%s order by creation""", (doctype, name), as_dict=1) or []
|
||||
content, sender_full_name, creation, sender
|
||||
from `tabCommunication`
|
||||
where
|
||||
communication_type='Comment'
|
||||
and reference_doctype=%s
|
||||
and reference_name=%s
|
||||
and (comment_type is null or comment_type='Comment')
|
||||
order by creation""", (doctype, name), as_dict=1) or []
|
||||
|
||||
def get_home_page():
|
||||
if frappe.local.flags.home_page:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue