[feature] mention users in comments. Fixes frappe/erpnext#3495
This commit is contained in:
parent
751e36a533
commit
389219da70
11 changed files with 351 additions and 8 deletions
|
|
@ -119,7 +119,7 @@ def get_fullnames():
|
|||
ret = frappe.db.sql("""select name,
|
||||
concat(ifnull(first_name, ''),
|
||||
if(ifnull(last_name, '')!='', ' ', ''), ifnull(last_name, '')) as fullname,
|
||||
user_image as image, gender, email
|
||||
user_image as image, gender, email, username
|
||||
from tabUser where enabled=1 and user_type!="Website User" """, as_dict=1)
|
||||
|
||||
d = {}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ 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
|
||||
from frappe.utils import get_fullname, get_link_to_form
|
||||
from frappe.core.doctype.user.user import extract_mentions
|
||||
|
||||
exclude_from_linked_with = True
|
||||
|
||||
|
|
@ -38,6 +39,7 @@ class Comment(Document):
|
|||
"""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()
|
||||
|
|
@ -50,6 +52,8 @@ class Comment(Document):
|
|||
frappe.publish_realtime('new_comment', self.as_dict(), doctype= self.comment_doctype,
|
||||
docname = self.comment_docname)
|
||||
|
||||
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
|
||||
|
|
@ -144,6 +148,33 @@ class Comment(Document):
|
|||
|
||||
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`
|
||||
|
|
|
|||
|
|
@ -296,12 +296,13 @@ def validate_fields(meta):
|
|||
if d.fieldtype not in ("Data", "Link", "Read Only"):
|
||||
frappe.throw(_("Fieldtype {0} for {1} cannot be unique").format(d.fieldtype, d.label))
|
||||
|
||||
has_non_unique_values = frappe.db.sql("""select `{fieldname}`, count(*)
|
||||
from `tab{doctype}` group by `{fieldname}` having count(*) > 1 limit 1""".format(
|
||||
doctype=d.parent, fieldname=d.fieldname))
|
||||
if not d.get("__islocal"):
|
||||
has_non_unique_values = frappe.db.sql("""select `{fieldname}`, count(*)
|
||||
from `tab{doctype}` group by `{fieldname}` having count(*) > 1 limit 1""".format(
|
||||
doctype=d.parent, fieldname=d.fieldname))
|
||||
|
||||
if has_non_unique_values and has_non_unique_values[0][0]:
|
||||
frappe.throw(_("Field '{0}' cannot be set as Unique as it has non-unique values").format(d.label))
|
||||
if has_non_unique_values and has_non_unique_values[0][0]:
|
||||
frappe.throw(_("Field '{0}' cannot be set as Unique as it has non-unique values").format(d.label))
|
||||
|
||||
if d.search_index and d.fieldtype in ("Text", "Long Text", "Small Text", "Code", "Text Editor"):
|
||||
frappe.throw(_("Fieldtype {0} for {1} cannot be indexed").format(d.fieldtype, d.label))
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -49,6 +50,7 @@
|
|||
"oldfieldtype": "Check",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -72,6 +74,7 @@
|
|||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -97,6 +100,7 @@
|
|||
"options": "Email",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
|
|
@ -121,6 +125,7 @@
|
|||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
|
|
@ -145,6 +150,7 @@
|
|||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -169,6 +175,7 @@
|
|||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -176,6 +183,30 @@
|
|||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "username",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Username",
|
||||
"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": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
|
|
@ -194,6 +225,7 @@
|
|||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -216,6 +248,7 @@
|
|||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -238,6 +271,7 @@
|
|||
"oldfieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "50%",
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
|
|
@ -264,6 +298,7 @@
|
|||
"options": "Loading...",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -287,6 +322,7 @@
|
|||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -310,6 +346,7 @@
|
|||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -333,6 +370,7 @@
|
|||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -358,6 +396,7 @@
|
|||
"options": "\nMale\nFemale\nOther",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -382,6 +421,7 @@
|
|||
"oldfieldtype": "Date",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -404,6 +444,7 @@
|
|||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -425,6 +466,7 @@
|
|||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -447,6 +489,7 @@
|
|||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -470,6 +513,7 @@
|
|||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -493,6 +537,7 @@
|
|||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -515,6 +560,7 @@
|
|||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -539,6 +585,7 @@
|
|||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -561,6 +608,7 @@
|
|||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -585,6 +633,7 @@
|
|||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -609,6 +658,7 @@
|
|||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -631,6 +681,7 @@
|
|||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -655,6 +706,7 @@
|
|||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -678,6 +730,7 @@
|
|||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -702,6 +755,7 @@
|
|||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -726,6 +780,7 @@
|
|||
"no_copy": 0,
|
||||
"permlevel": 1,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -748,6 +803,7 @@
|
|||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -771,6 +827,7 @@
|
|||
"options": "UserRole",
|
||||
"permlevel": 1,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -796,6 +853,7 @@
|
|||
"permlevel": 1,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -819,6 +877,7 @@
|
|||
"permlevel": 1,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -843,6 +902,7 @@
|
|||
"permlevel": 1,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -867,6 +927,7 @@
|
|||
"oldfieldtype": "Column Break",
|
||||
"permlevel": 1,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "50%",
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
|
|
@ -893,6 +954,7 @@
|
|||
"options": "DefaultValue",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -917,6 +979,7 @@
|
|||
"oldfieldtype": "Section Break",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -944,6 +1007,7 @@
|
|||
"options": "System User\nWebsite User",
|
||||
"permlevel": 1,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
|
|
@ -967,6 +1031,7 @@
|
|||
"no_copy": 0,
|
||||
"permlevel": 1,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -990,6 +1055,7 @@
|
|||
"no_copy": 0,
|
||||
"permlevel": 1,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -1013,6 +1079,7 @@
|
|||
"no_copy": 0,
|
||||
"permlevel": 1,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -1035,6 +1102,7 @@
|
|||
"oldfieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "50%",
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
|
|
@ -1061,6 +1129,7 @@
|
|||
"oldfieldtype": "Read Only",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -1085,6 +1154,7 @@
|
|||
"oldfieldtype": "Read Only",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -1108,6 +1178,7 @@
|
|||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -1132,6 +1203,7 @@
|
|||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -1155,6 +1227,7 @@
|
|||
"no_copy": 0,
|
||||
"permlevel": 1,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -1177,6 +1250,7 @@
|
|||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -1199,6 +1273,7 @@
|
|||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -1221,6 +1296,7 @@
|
|||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -1243,6 +1319,7 @@
|
|||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -1265,6 +1342,7 @@
|
|||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -1287,6 +1365,7 @@
|
|||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
|
|
@ -1305,7 +1384,8 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 5,
|
||||
"modified": "2015-11-16 06:29:59.828065",
|
||||
"menu_index": 0,
|
||||
"modified": "2015-12-23 02:45:19.261689",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from frappe.desk.notifications import clear_notifications
|
|||
from frappe.utils.user import get_system_managers
|
||||
import frappe.permissions
|
||||
import frappe.share
|
||||
import re
|
||||
|
||||
STANDARD_USERS = ("Guest", "Administrator")
|
||||
|
||||
|
|
@ -38,6 +39,8 @@ class User(Document):
|
|||
self.update_gravatar()
|
||||
self.ensure_unique_roles()
|
||||
self.remove_all_roles_for_guest()
|
||||
self.validate_username()
|
||||
|
||||
if self.language == "Loading...":
|
||||
self.language = None
|
||||
|
||||
|
|
@ -296,6 +299,52 @@ class User(Document):
|
|||
else:
|
||||
exists.append(d.role)
|
||||
|
||||
def validate_username(self):
|
||||
if not self.username and self.is_new():
|
||||
self.username = frappe.scrub(self.first_name)
|
||||
|
||||
if not self.username:
|
||||
return
|
||||
|
||||
# strip space and @
|
||||
self.username = self.username.strip(" @")
|
||||
|
||||
if self.username_exists():
|
||||
frappe.msgprint(_("Username {0} already exists"))
|
||||
self.suggest_username()
|
||||
raise frappe.DuplicateEntryError(self.username)
|
||||
|
||||
if not self.is_new():
|
||||
old_username = self.get_db_value("username")
|
||||
if old_username and self.username != old_username and "System Manager" not in frappe.get_roles():
|
||||
frappe.throw(_("Only a System Manager can change Username"))
|
||||
|
||||
# should be made up of characters, numbers and underscore only
|
||||
if not re.match(r"^[\w]+$", self.username):
|
||||
frappe.throw(_("Username should not contain any special characters other than letters, numbers and underscore"))
|
||||
|
||||
def suggest_username(self):
|
||||
def _check_suggestion(suggestion):
|
||||
if self.username != suggestion and not self.username_exists(suggestion):
|
||||
return suggestion
|
||||
|
||||
return None
|
||||
|
||||
# @firstname
|
||||
username = _check_suggestion(frappe.scrub(self.first_name))
|
||||
|
||||
if not username:
|
||||
# @firstname_last_name
|
||||
username = _check_suggestion(frappe.scrub("{0} {1}".format(self.first_name, self.last_name or "")))
|
||||
|
||||
if username:
|
||||
frappe.msgprint(_("Suggested Username: {0}").format(username))
|
||||
|
||||
return username
|
||||
|
||||
def username_exists(self, username=None):
|
||||
return frappe.db.get_value("User", {"username": username or self.username, "name": ("!=", self.name)})
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_languages():
|
||||
from frappe.translate import get_lang_dict
|
||||
|
|
@ -490,3 +539,7 @@ def notifify_admin_access_to_system_manager(login_manager=None):
|
|||
frappe.sendmail(recipients=get_system_managers(), subject=_("Administrator Logged In"),
|
||||
message=message, bulk=True)
|
||||
|
||||
def extract_mentions(txt):
|
||||
"""Find all instances of @username in the string.
|
||||
The mentions will be separated by non-word characters or may appear at the start of the string"""
|
||||
return re.findall(r'(?:[^\w]|^)@([\w]*)', txt)
|
||||
|
|
|
|||
|
|
@ -108,3 +108,4 @@ frappe.patches.v6_6.fix_file_url
|
|||
frappe.patches.v6_9.rename_burmese_language
|
||||
frappe.patches.v6_11.rename_field_in_email_account
|
||||
execute:frappe.create_folder(os.path.join(frappe.local.site_path, 'private', 'files'))
|
||||
frappe.patches.v6_15.set_username
|
||||
|
|
|
|||
0
frappe/patches/v6_15/__init__.py
Normal file
0
frappe/patches/v6_15/__init__.py
Normal file
15
frappe/patches/v6_15/set_username.py
Normal file
15
frappe/patches/v6_15/set_username.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype("User")
|
||||
|
||||
# give preference to System Users
|
||||
users = frappe.db.sql_list("""select name from `tabUser` order by if(user_type='System User', 0, 1)""")
|
||||
for name in users:
|
||||
user = frappe.get_doc("User", name)
|
||||
if user.username:
|
||||
continue
|
||||
|
||||
username = user.suggest_username()
|
||||
if username:
|
||||
user.db_set("username", username, update_modified=False)
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
<div>
|
||||
<textarea style="height: 80px" style="margin-top: 10px;"
|
||||
class="form-control"></textarea>
|
||||
<input type="data" class="hidden mention-input">
|
||||
</div>
|
||||
<div class="media">
|
||||
<span class="pull-left avatar avatar-medium">
|
||||
|
|
|
|||
|
|
@ -40,7 +40,10 @@ frappe.ui.form.Comments = Class.extend({
|
|||
this.list.on("click", ".toggle-blockquote", function() {
|
||||
$(this).parent().siblings("blockquote").toggleClass("hidden");
|
||||
});
|
||||
|
||||
this.setup_mentions();
|
||||
},
|
||||
|
||||
refresh: function(scroll_to_end) {
|
||||
var me = this;
|
||||
|
||||
|
|
@ -66,6 +69,7 @@ frappe.ui.form.Comments = Class.extend({
|
|||
this.frm.sidebar.refresh_comments();
|
||||
|
||||
},
|
||||
|
||||
render_comment: function(c) {
|
||||
var me = this;
|
||||
this.prepare_comment(c);
|
||||
|
|
@ -310,5 +314,155 @@ frappe.ui.form.Comments = Class.extend({
|
|||
});
|
||||
|
||||
return last_email;
|
||||
},
|
||||
|
||||
setup_mentions: function() {
|
||||
var me = this;
|
||||
|
||||
this.cursor_from = this.cursor_to = 0
|
||||
this.codes = $.ui.keyCode;
|
||||
this.up = $.Event("keydown", {"keyCode": this.codes.UP});
|
||||
this.down = $.Event("keydown", {"keyCode": this.codes.DOWN});
|
||||
this.enter = $.Event("keydown", {"keyCode": this.codes.ENTER});
|
||||
|
||||
this.setup_autocomplete_for_mentions();
|
||||
|
||||
this.setup_textarea_event();
|
||||
},
|
||||
|
||||
setup_autocomplete_for_mentions: function() {
|
||||
var me = this;
|
||||
|
||||
var username_user_map = {};
|
||||
for (var name in frappe.boot.user_info) {
|
||||
var _user = frappe.boot.user_info[name];
|
||||
username_user_map[_user.username] = _user;
|
||||
}
|
||||
|
||||
this.mention_input = this.wrapper.find(".mention-input");
|
||||
|
||||
this.mention_input.autocomplete({
|
||||
minLength: 0,
|
||||
autoFocus: true,
|
||||
source: Object.keys(username_user_map),
|
||||
select: function(event, ui) {
|
||||
var value = ui.item.value;
|
||||
var textarea_value = me.input.val();
|
||||
|
||||
var new_value = textarea_value.substring(0, me.cursor_from)
|
||||
+ value
|
||||
+ textarea_value.substring(me.cursor_to);
|
||||
|
||||
me.input.val(new_value);
|
||||
|
||||
var new_cursor_location = me.cursor_from + value.length;
|
||||
|
||||
// move cursor to right position
|
||||
if (me.input[0].setSelectionRange) {
|
||||
me.input.focus();
|
||||
me.input[0].setSelectionRange(new_cursor_location, new_cursor_location);
|
||||
|
||||
} else if (me.input[0].createTextRange) {
|
||||
var range = input[0].createTextRange();
|
||||
range.collapse(true);
|
||||
range.moveEnd('character', new_cursor_location);
|
||||
range.moveStart('character', new_cursor_location);
|
||||
range.select();
|
||||
|
||||
} else {
|
||||
me.input.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.mention_widget = this.mention_input.autocomplete("widget");
|
||||
|
||||
this.autocomplete_open = false;
|
||||
this.mention_input
|
||||
.on('autocompleteclose', function() {
|
||||
me.autocomplete_open = false;
|
||||
})
|
||||
.on('autocompleteopen', function() {
|
||||
me.autocomplete_open = true;
|
||||
});
|
||||
|
||||
// dirty hack to prevent backspace from navigating back to history
|
||||
$(document).on("keydown", function(e) {
|
||||
if (e.which===me.codes.BACKSPACE && me.autocomplete_open && document.activeElement==me.mention_widget.get(0)) {
|
||||
// me.input.focus();
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setup_textarea_event: function() {
|
||||
var me = this;
|
||||
|
||||
// binding this in keyup to get the value after it is set in textarea
|
||||
this.input.keyup(function(e) {
|
||||
if (e.which===16) {
|
||||
// don't trigger for shift
|
||||
return;
|
||||
|
||||
} else if ([me.codes.UP, me.codes.DOWN].indexOf(e.which)!==-1) {
|
||||
// focus on autocomplete if up and down arrows
|
||||
if (me.autocomplete_open) {
|
||||
me.mention_widget.focus();
|
||||
me.mention_widget.trigger(e.which===me.codes.UP ? me.up : me.down);
|
||||
}
|
||||
return;
|
||||
|
||||
} else if ([me.codes.ENTER, me.codes.ESCAPE, me.codes.TAB, me.codes.SPACE].indexOf(e.which)!==-1) {
|
||||
me.mention_input.autocomplete("close");
|
||||
return;
|
||||
|
||||
} else if (e.which !== 0 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
||||
if(!String.fromCharCode(e.which)) {
|
||||
// no point in parsing it if it is not a character key
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var value = $(this).val() || "";
|
||||
var i = e.target.selectionStart;
|
||||
var key = value[i-1];
|
||||
var substring = value.substring(0, i);
|
||||
var mention = substring.match(/(?=[^\w]|^)@([\w]*)$/);
|
||||
|
||||
if (mention && mention.length) {
|
||||
var mention = mention[0].slice(1);
|
||||
|
||||
// record location of cursor
|
||||
me.cursor_from = i - mention.length;
|
||||
me.cursor_to = i;
|
||||
|
||||
// render autocomplete at the bottom of the textbox and search for mention
|
||||
me.mention_input.autocomplete("option", "position", {
|
||||
of: me.input,
|
||||
my: "left top",
|
||||
at: "left bottom"
|
||||
});
|
||||
me.mention_input.autocomplete("search", mention);
|
||||
|
||||
} else {
|
||||
me.cursor_from = me.cursor_to = 0;
|
||||
me.mention_input.autocomplete("close");
|
||||
}
|
||||
});
|
||||
|
||||
// binding this in keydown to prevent default action
|
||||
this.input.keydown(function(e) {
|
||||
// enter, escape, tab
|
||||
if (me.autocomplete_open) {
|
||||
if ([me.codes.ENTER, me.codes.TAB].indexOf(e.which)!==-1) {
|
||||
// set focused value
|
||||
me.mention_widget.trigger(me.enter);
|
||||
|
||||
// prevent default
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
7
frappe/templates/emails/mentioned_in_comment.html
Normal file
7
frappe/templates/emails/mentioned_in_comment.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<p>
|
||||
{{ _("{0} mentioned you in a comment in {1}").format(sender_fullname, link) }}
|
||||
</p>
|
||||
<blockquote
|
||||
style="border-left: 3px solid #d1d8dd; padding: 7px 15px; margin-left: 0px;">
|
||||
{{ comment.comment | markdown }}
|
||||
</blockquote>
|
||||
Loading…
Add table
Reference in a new issue