@@ -119,7 +119,7 @@ def get_fullnames(): | |||||
ret = frappe.db.sql("""select name, | ret = frappe.db.sql("""select name, | ||||
concat(ifnull(first_name, ''), | concat(ifnull(first_name, ''), | ||||
if(ifnull(last_name, '')!='', ' ', ''), ifnull(last_name, '')) as fullname, | 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) | from tabUser where enabled=1 and user_type!="Website User" """, as_dict=1) | ||||
d = {} | d = {} | ||||
@@ -7,7 +7,8 @@ from frappe import _ | |||||
from frappe.website.render import clear_cache | from frappe.website.render import clear_cache | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
from frappe.model.db_schema import add_column | 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 | exclude_from_linked_with = True | ||||
@@ -38,6 +39,7 @@ class Comment(Document): | |||||
"""Send realtime updates""" | """Send realtime updates""" | ||||
if not self.comment_doctype: | if not self.comment_doctype: | ||||
return | return | ||||
if self.comment_doctype == 'Message': | if self.comment_doctype == 'Message': | ||||
if self.comment_docname == frappe.session.user: | if self.comment_docname == frappe.session.user: | ||||
message = self.as_dict() | message = self.as_dict() | ||||
@@ -50,6 +52,8 @@ class Comment(Document): | |||||
frappe.publish_realtime('new_comment', self.as_dict(), doctype= self.comment_doctype, | frappe.publish_realtime('new_comment', self.as_dict(), doctype= self.comment_doctype, | ||||
docname = self.comment_docname) | docname = self.comment_docname) | ||||
self.notify_mentions() | |||||
def validate(self): | def validate(self): | ||||
"""Raise exception for more than 50 comments.""" | """Raise exception for more than 50 comments.""" | ||||
if frappe.db.sql("""select count(*) from tabComment where comment_doctype=%s | 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) | 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(): | def on_doctype_update(): | ||||
"""Add index to `tabComment` `(comment_doctype, comment_name)`""" | """Add index to `tabComment` `(comment_doctype, comment_name)`""" | ||||
if not frappe.db.sql("""show index from `tabComment` | 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"): | if d.fieldtype not in ("Data", "Link", "Read Only"): | ||||
frappe.throw(_("Fieldtype {0} for {1} cannot be unique").format(d.fieldtype, d.label)) | 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"): | 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)) | frappe.throw(_("Fieldtype {0} for {1} cannot be indexed").format(d.fieldtype, d.label)) | ||||
@@ -24,6 +24,7 @@ | |||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -49,6 +50,7 @@ | |||||
"oldfieldtype": "Check", | "oldfieldtype": "Check", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -72,6 +74,7 @@ | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -97,6 +100,7 @@ | |||||
"options": "Email", | "options": "Email", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 1, | "reqd": 1, | ||||
@@ -121,6 +125,7 @@ | |||||
"oldfieldtype": "Data", | "oldfieldtype": "Data", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 1, | "reqd": 1, | ||||
@@ -145,6 +150,7 @@ | |||||
"oldfieldtype": "Data", | "oldfieldtype": "Data", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -169,6 +175,7 @@ | |||||
"oldfieldtype": "Data", | "oldfieldtype": "Data", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -176,6 +183,30 @@ | |||||
"set_only_once": 0, | "set_only_once": 0, | ||||
"unique": 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, | "allow_on_submit": 0, | ||||
"bold": 0, | "bold": 0, | ||||
@@ -194,6 +225,7 @@ | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -216,6 +248,7 @@ | |||||
"no_copy": 1, | "no_copy": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -238,6 +271,7 @@ | |||||
"oldfieldtype": "Column Break", | "oldfieldtype": "Column Break", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"print_width": "50%", | "print_width": "50%", | ||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
@@ -264,6 +298,7 @@ | |||||
"options": "Loading...", | "options": "Loading...", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -287,6 +322,7 @@ | |||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -310,6 +346,7 @@ | |||||
"no_copy": 1, | "no_copy": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -333,6 +370,7 @@ | |||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -358,6 +396,7 @@ | |||||
"options": "\nMale\nFemale\nOther", | "options": "\nMale\nFemale\nOther", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -382,6 +421,7 @@ | |||||
"oldfieldtype": "Date", | "oldfieldtype": "Date", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -404,6 +444,7 @@ | |||||
"no_copy": 1, | "no_copy": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -425,6 +466,7 @@ | |||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -447,6 +489,7 @@ | |||||
"no_copy": 1, | "no_copy": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -470,6 +513,7 @@ | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -493,6 +537,7 @@ | |||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -515,6 +560,7 @@ | |||||
"no_copy": 1, | "no_copy": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -539,6 +585,7 @@ | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -561,6 +608,7 @@ | |||||
"no_copy": 1, | "no_copy": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 1, | "print_hide": 1, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -585,6 +633,7 @@ | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -609,6 +658,7 @@ | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -631,6 +681,7 @@ | |||||
"no_copy": 1, | "no_copy": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -655,6 +706,7 @@ | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -678,6 +730,7 @@ | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -702,6 +755,7 @@ | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -726,6 +780,7 @@ | |||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 1, | "permlevel": 1, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -748,6 +803,7 @@ | |||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -771,6 +827,7 @@ | |||||
"options": "UserRole", | "options": "UserRole", | ||||
"permlevel": 1, | "permlevel": 1, | ||||
"print_hide": 1, | "print_hide": 1, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -796,6 +853,7 @@ | |||||
"permlevel": 1, | "permlevel": 1, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -819,6 +877,7 @@ | |||||
"permlevel": 1, | "permlevel": 1, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -843,6 +902,7 @@ | |||||
"permlevel": 1, | "permlevel": 1, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -867,6 +927,7 @@ | |||||
"oldfieldtype": "Column Break", | "oldfieldtype": "Column Break", | ||||
"permlevel": 1, | "permlevel": 1, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"print_width": "50%", | "print_width": "50%", | ||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
@@ -893,6 +954,7 @@ | |||||
"options": "DefaultValue", | "options": "DefaultValue", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -917,6 +979,7 @@ | |||||
"oldfieldtype": "Section Break", | "oldfieldtype": "Section Break", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -944,6 +1007,7 @@ | |||||
"options": "System User\nWebsite User", | "options": "System User\nWebsite User", | ||||
"permlevel": 1, | "permlevel": 1, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 1, | "reqd": 1, | ||||
@@ -967,6 +1031,7 @@ | |||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 1, | "permlevel": 1, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -990,6 +1055,7 @@ | |||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 1, | "permlevel": 1, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -1013,6 +1079,7 @@ | |||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 1, | "permlevel": 1, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -1035,6 +1102,7 @@ | |||||
"oldfieldtype": "Column Break", | "oldfieldtype": "Column Break", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"print_width": "50%", | "print_width": "50%", | ||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
@@ -1061,6 +1129,7 @@ | |||||
"oldfieldtype": "Read Only", | "oldfieldtype": "Read Only", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -1085,6 +1154,7 @@ | |||||
"oldfieldtype": "Read Only", | "oldfieldtype": "Read Only", | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -1108,6 +1178,7 @@ | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -1132,6 +1203,7 @@ | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -1155,6 +1227,7 @@ | |||||
"no_copy": 0, | "no_copy": 0, | ||||
"permlevel": 1, | "permlevel": 1, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -1177,6 +1250,7 @@ | |||||
"no_copy": 1, | "no_copy": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -1199,6 +1273,7 @@ | |||||
"no_copy": 1, | "no_copy": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -1221,6 +1296,7 @@ | |||||
"no_copy": 1, | "no_copy": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -1243,6 +1319,7 @@ | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | "read_only": 0, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -1265,6 +1342,7 @@ | |||||
"no_copy": 1, | "no_copy": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -1287,6 +1365,7 @@ | |||||
"no_copy": 1, | "no_copy": 1, | ||||
"permlevel": 0, | "permlevel": 0, | ||||
"print_hide": 0, | "print_hide": 0, | ||||
"print_hide_if_no_value": 0, | |||||
"read_only": 1, | "read_only": 1, | ||||
"report_hide": 0, | "report_hide": 0, | ||||
"reqd": 0, | "reqd": 0, | ||||
@@ -1305,7 +1384,8 @@ | |||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"max_attachments": 5, | "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", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "User", | "name": "User", | ||||
@@ -10,6 +10,7 @@ from frappe.desk.notifications import clear_notifications | |||||
from frappe.utils.user import get_system_managers | from frappe.utils.user import get_system_managers | ||||
import frappe.permissions | import frappe.permissions | ||||
import frappe.share | import frappe.share | ||||
import re | |||||
STANDARD_USERS = ("Guest", "Administrator") | STANDARD_USERS = ("Guest", "Administrator") | ||||
@@ -38,6 +39,8 @@ class User(Document): | |||||
self.update_gravatar() | self.update_gravatar() | ||||
self.ensure_unique_roles() | self.ensure_unique_roles() | ||||
self.remove_all_roles_for_guest() | self.remove_all_roles_for_guest() | ||||
self.validate_username() | |||||
if self.language == "Loading...": | if self.language == "Loading...": | ||||
self.language = None | self.language = None | ||||
@@ -296,6 +299,52 @@ class User(Document): | |||||
else: | else: | ||||
exists.append(d.role) | 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() | @frappe.whitelist() | ||||
def get_languages(): | def get_languages(): | ||||
from frappe.translate import get_lang_dict | 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"), | frappe.sendmail(recipients=get_system_managers(), subject=_("Administrator Logged In"), | ||||
message=message, bulk=True) | 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) |
@@ -109,3 +109,4 @@ frappe.patches.v6_9.rename_burmese_language | |||||
frappe.patches.v6_11.rename_field_in_email_account | frappe.patches.v6_11.rename_field_in_email_account | ||||
execute:frappe.create_folder(os.path.join(frappe.local.site_path, 'private', 'files')) | execute:frappe.create_folder(os.path.join(frappe.local.site_path, 'private', 'files')) | ||||
frappe.patches.v6_15.remove_property_setter_for_previous_field | frappe.patches.v6_15.remove_property_setter_for_previous_field | ||||
frappe.patches.v6_15.set_username |
@@ -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> | <div> | ||||
<textarea style="height: 80px" style="margin-top: 10px;" | <textarea style="height: 80px" style="margin-top: 10px;" | ||||
class="form-control"></textarea> | class="form-control"></textarea> | ||||
<input type="data" class="hidden mention-input"> | |||||
</div> | </div> | ||||
<div class="media"> | <div class="media"> | ||||
<span class="pull-left avatar avatar-medium"> | <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.list.on("click", ".toggle-blockquote", function() { | ||||
$(this).parent().siblings("blockquote").toggleClass("hidden"); | $(this).parent().siblings("blockquote").toggleClass("hidden"); | ||||
}); | }); | ||||
this.setup_mentions(); | |||||
}, | }, | ||||
refresh: function(scroll_to_end) { | refresh: function(scroll_to_end) { | ||||
var me = this; | var me = this; | ||||
@@ -66,6 +69,7 @@ frappe.ui.form.Comments = Class.extend({ | |||||
this.frm.sidebar.refresh_comments(); | this.frm.sidebar.refresh_comments(); | ||||
}, | }, | ||||
render_comment: function(c) { | render_comment: function(c) { | ||||
var me = this; | var me = this; | ||||
this.prepare_comment(c); | this.prepare_comment(c); | ||||
@@ -310,5 +314,155 @@ frappe.ui.form.Comments = Class.extend({ | |||||
}); | }); | ||||
return last_email; | 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; | |||||
} | |||||
} | |||||
}); | |||||
} | } | ||||
}); | }); |
@@ -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> |