The license.txt file has been replaced with LICENSE for quite a while now. INAL but it didn't seem accurate to say "hey, checkout license.txt although there's no such file". Apart from this, there were inconsistencies in the headers altogether...this change brings consistency.
298 lines
7.4 KiB
Python
298 lines
7.4 KiB
Python
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
# License: MIT. See LICENSE
|
|
|
|
import frappe
|
|
import frappe.utils
|
|
from frappe.utils import get_url_to_form
|
|
from frappe.model import log_types
|
|
from frappe import _
|
|
from itertools import groupby
|
|
|
|
@frappe.whitelist()
|
|
def update_follow(doctype, doc_name, following):
|
|
if following:
|
|
return follow_document(doctype, doc_name, frappe.session.user)
|
|
else:
|
|
return unfollow_document(doctype, doc_name, frappe.session.user)
|
|
|
|
|
|
@frappe.whitelist()
|
|
def follow_document(doctype, doc_name, user):
|
|
'''
|
|
param:
|
|
Doctype name
|
|
doc name
|
|
user email
|
|
|
|
condition:
|
|
avoided for some doctype
|
|
follow only if track changes are set to 1
|
|
'''
|
|
if (doctype in ("Communication", "ToDo", "Email Unsubscribe", "File", "Comment", "Email Account", "Email Domain")
|
|
or doctype in log_types):
|
|
return
|
|
|
|
if ((not frappe.get_meta(doctype).track_changes)
|
|
or user == "Administrator"):
|
|
return
|
|
|
|
if not frappe.db.get_value("User", user, "document_follow_notify", ignore=True, cache=True):
|
|
return
|
|
|
|
if not is_document_followed(doctype, doc_name, user):
|
|
doc = frappe.new_doc("Document Follow")
|
|
doc.update({
|
|
"ref_doctype": doctype,
|
|
"ref_docname": doc_name,
|
|
"user": user
|
|
})
|
|
doc.save()
|
|
return doc
|
|
|
|
@frappe.whitelist()
|
|
def unfollow_document(doctype, doc_name, user):
|
|
doc = frappe.get_all(
|
|
"Document Follow",
|
|
filters={
|
|
"ref_doctype": doctype,
|
|
"ref_docname": doc_name,
|
|
"user": user
|
|
},
|
|
fields=["name"],
|
|
limit=1
|
|
)
|
|
if doc:
|
|
frappe.delete_doc("Document Follow", doc[0].name)
|
|
return 1
|
|
return 0
|
|
|
|
def get_message(doc_name, doctype, frequency, user):
|
|
activity_list = get_version(doctype, doc_name, frequency, user) + get_comments(doctype, doc_name, frequency, user)
|
|
return sorted(activity_list, key=lambda k: k["time"], reverse=True)
|
|
|
|
def send_email_alert(receiver, docinfo, timeline):
|
|
if receiver:
|
|
frappe.sendmail(
|
|
subject=_("Document Follow Notification"),
|
|
recipients=[receiver],
|
|
template="document_follow",
|
|
args={
|
|
"docinfo": docinfo,
|
|
"timeline": timeline,
|
|
}
|
|
)
|
|
|
|
def send_document_follow_mails(frequency):
|
|
'''
|
|
param:
|
|
frequency for sanding mails
|
|
|
|
task:
|
|
set receiver according to frequency
|
|
group document list according to user
|
|
get changes, activity, comments on doctype
|
|
call method to send mail
|
|
'''
|
|
|
|
users = frappe.get_list("Document Follow",
|
|
fields=["*"])
|
|
|
|
sorted_users = sorted(users, key=lambda k: k['user'])
|
|
|
|
grouped_by_user = {}
|
|
for k, v in groupby(sorted_users, key=lambda k: k['user']):
|
|
grouped_by_user[k] = list(v)
|
|
|
|
for user in grouped_by_user:
|
|
user_frequency = frappe.db.get_value("User", user, "document_follow_frequency")
|
|
message = []
|
|
valid_document_follows = []
|
|
if user_frequency == frequency:
|
|
for d in grouped_by_user[user]:
|
|
content = get_message(d.ref_docname, d.ref_doctype, frequency, user)
|
|
if content:
|
|
message = message + content
|
|
valid_document_follows.append({
|
|
"reference_docname": d.ref_docname,
|
|
"reference_doctype": d.ref_doctype,
|
|
"reference_url": get_url_to_form(d.ref_doctype, d.ref_docname)
|
|
})
|
|
|
|
if message and frappe.db.get_value("User", user, "document_follow_notify", ignore=True):
|
|
send_email_alert(user, valid_document_follows, message)
|
|
|
|
|
|
def get_version(doctype, doc_name, frequency, user):
|
|
timeline = []
|
|
filters = get_filters("docname", doc_name, frequency, user)
|
|
version = frappe.get_all("Version",
|
|
filters=filters,
|
|
fields=["ref_doctype", "data", "modified", "modified", "modified_by"]
|
|
)
|
|
if version:
|
|
for v in version:
|
|
change = frappe.parse_json(v.data)
|
|
time = frappe.utils.format_datetime(v.modified, "hh:mm a")
|
|
timeline_items = []
|
|
if change.changed:
|
|
timeline_items = get_field_changed(change.changed, time, doctype, doc_name, v)
|
|
if change.row_changed:
|
|
timeline_items = get_row_changed(change.row_changed, time, doctype, doc_name, v)
|
|
if change.added:
|
|
timeline_items = get_added_row(change.added, time, doctype, doc_name, v)
|
|
|
|
timeline = timeline + timeline_items
|
|
|
|
return timeline
|
|
|
|
def get_comments(doctype, doc_name, frequency, user):
|
|
from html2text import html2text
|
|
|
|
timeline = []
|
|
filters = get_filters("reference_name", doc_name, frequency, user)
|
|
comments = frappe.get_all("Comment",
|
|
filters=filters,
|
|
fields=["content", "modified", "modified_by", "comment_type"]
|
|
)
|
|
for comment in comments:
|
|
if comment.comment_type == "Like":
|
|
by = ''' By : <b>{0}<b>'''.format(comment.modified_by)
|
|
elif comment.comment_type == "Comment":
|
|
by = '''Commented by : <b>{0}<b>'''.format(comment.modified_by)
|
|
else:
|
|
by = ''
|
|
|
|
time = frappe.utils.format_datetime(comment.modified, "hh:mm a")
|
|
timeline.append({
|
|
"time": comment.modified,
|
|
"data": {
|
|
"time": time,
|
|
"comment": html2text(str(comment.content)),
|
|
"by": by
|
|
},
|
|
"doctype": doctype,
|
|
"doc_name": doc_name,
|
|
"type": "comment"
|
|
})
|
|
return timeline
|
|
|
|
def is_document_followed(doctype, doc_name, user):
|
|
return frappe.db.exists(
|
|
"Document Follow",
|
|
{
|
|
"ref_doctype": doctype,
|
|
"ref_docname": doc_name,
|
|
"user": user
|
|
}
|
|
)
|
|
|
|
@frappe.whitelist()
|
|
def get_follow_users(doctype, doc_name):
|
|
return frappe.get_all(
|
|
"Document Follow",
|
|
filters={
|
|
"ref_doctype": doctype,
|
|
"ref_docname":doc_name
|
|
},
|
|
fields=["user"]
|
|
)
|
|
|
|
def get_row_changed(row_changed, time, doctype, doc_name, v):
|
|
from html2text import html2text
|
|
|
|
items = []
|
|
for d in row_changed:
|
|
d[2] = d[2] if d[2] else ' '
|
|
d[0] = d[0] if d[0] else ' '
|
|
d[3][0][1] = d[3][0][1] if d[3][0][1] else ' '
|
|
items.append({
|
|
"time": v.modified,
|
|
"data": {
|
|
"time": time,
|
|
"table_field": d[0],
|
|
"row": str(d[1]),
|
|
"field": d[3][0][0],
|
|
"from": html2text(str(d[3][0][1])),
|
|
"to": html2text(str(d[3][0][2]))
|
|
},
|
|
"doctype": doctype,
|
|
"doc_name": doc_name,
|
|
"type": "row changed",
|
|
"by": v.modified_by
|
|
})
|
|
return items
|
|
|
|
def get_added_row(added, time, doctype, doc_name, v):
|
|
items = []
|
|
for d in added:
|
|
items.append({
|
|
"time": v.modified,
|
|
"data": {
|
|
"to": d[0],
|
|
"time": time
|
|
},
|
|
"doctype": doctype,
|
|
"doc_name": doc_name,
|
|
"type": "row added",
|
|
"by": v.modified_by
|
|
})
|
|
return items
|
|
|
|
def get_field_changed(changed, time, doctype, doc_name, v):
|
|
from html2text import html2text
|
|
|
|
items = []
|
|
for d in changed:
|
|
d[1] = d[1] if d[1] else ' '
|
|
d[2] = d[2] if d[2] else ' '
|
|
d[0] = d[0] if d[0] else ' '
|
|
items.append({
|
|
"time": v.modified,
|
|
"data": {
|
|
"time": time,
|
|
"field": d[0],
|
|
"from": html2text(str(d[1])),
|
|
"to": html2text(str(d[2]))
|
|
},
|
|
"doctype": doctype,
|
|
"doc_name": doc_name,
|
|
"type": "field changed",
|
|
"by": v.modified_by
|
|
})
|
|
return items
|
|
|
|
def send_hourly_updates():
|
|
send_document_follow_mails("Hourly")
|
|
|
|
def send_daily_updates():
|
|
send_document_follow_mails("Daily")
|
|
|
|
def send_weekly_updates():
|
|
send_document_follow_mails("Weekly")
|
|
|
|
def get_filters(search_by, name, frequency, user):
|
|
filters = []
|
|
|
|
if frequency == "Weekly":
|
|
filters = [
|
|
[search_by, "=", name],
|
|
["modified", ">", frappe.utils.add_days(frappe.utils.nowdate(),-7)],
|
|
["modified", "<", frappe.utils.nowdate()],
|
|
["modified_by", "!=", user]
|
|
]
|
|
elif frequency == "Daily":
|
|
filters = [
|
|
[search_by, "=", name],
|
|
["modified", ">", frappe.utils.add_days(frappe.utils.nowdate(),-1)],
|
|
["modified", "<", frappe.utils.nowdate()],
|
|
["modified_by", "!=", user]
|
|
]
|
|
elif frequency == "Hourly":
|
|
filters = [
|
|
[search_by, "=", name],
|
|
["modified", ">", frappe.utils.add_to_date(frappe.utils.now_datetime(), hours=-1)],
|
|
["modified", "<", frappe.utils.now_datetime()],
|
|
["modified_by", "!=", user]
|
|
]
|
|
|
|
return filters
|