diff --git a/frappe/__init__.py b/frappe/__init__.py index 08c0f794b3..a8bf114b9b 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -12,6 +12,8 @@ Read the documentation: https://frappeframework.com/docs """ import os, warnings +STANDARD_USERS = ('Guest', 'Administrator') + _dev_server = os.environ.get('DEV_SERVER', False) if _dev_server: @@ -121,6 +123,7 @@ def set_user_lang(user, user_language=None): local.lang = get_user_lang(user) # local-globals + db = local("db") qb = local("qb") conf = local("conf") diff --git a/frappe/auth.py b/frappe/auth.py index 078a6bb165..a87edb6460 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -250,8 +250,7 @@ class LoginManager: if not self.user: return - from frappe.core.doctype.user.user import STANDARD_USERS - if self.user in STANDARD_USERS: + if self.user in frappe.STANDARD_USERS: return False reset_pwd_after_days = cint(frappe.db.get_single_value("System Settings", diff --git a/frappe/boot.py b/frappe/boot.py index e671d8b37d..d5d992343a 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -17,7 +17,7 @@ from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_p from frappe.model.base_document import get_controller from frappe.social.doctype.post.post import frequently_visited_links from frappe.core.doctype.navbar_settings.navbar_settings import get_navbar_settings, get_app_logo -from frappe.utils import get_time_zone +from frappe.utils import get_time_zone, add_user_info def get_bootinfo(): """build and return boot info""" @@ -222,17 +222,14 @@ def load_translations(bootinfo): bootinfo["__messages"] = messages def get_user_info(): - user_info = frappe.db.get_all('User', fields=['`name`', 'full_name as fullname', 'user_image as image', 'gender', - 'email', 'username', 'bio', 'location', 'interest', 'banner_image', 'allowed_in_mentions', 'user_type', 'time_zone'], - filters=dict(enabled=1)) + # get info for current user + user_info = frappe._dict() + add_user_info(frappe.session.user, user_info) - user_info_map = {d.name: d for d in user_info} + if frappe.session.user == 'Administrator' and user_info.Administrator.email: + user_info[user_info.Administrator.email] = user_info.Administrator - admin_data = user_info_map.get('Administrator') - if admin_data: - user_info_map[admin_data.email] = admin_data - - return user_info_map + return user_info def get_user(bootinfo): """get user info""" diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index ef7845d3b0..b674ea6891 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -19,7 +19,7 @@ from frappe.core.doctype.user_type.user_type import user_linked_with_permission_ from frappe.query_builder import DocType -STANDARD_USERS = ("Guest", "Administrator") +STANDARD_USERS = frappe.STANDARD_USERS class User(Document): __new_password = None diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index 0e644c3cf5..38b671d629 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -94,30 +94,78 @@ def get_docinfo(doc=None, doctype=None, name=None): automated_messages = filter(lambda x: x['communication_type'] == 'Automated Message', all_communications) communications_except_auto_messages = filter(lambda x: x['communication_type'] != 'Automated Message', all_communications) - frappe.response["docinfo"] = { + docinfo = frappe._dict(user_info = {}) + + add_comments(doc, docinfo) + + docinfo.update({ "attachments": get_attachments(doc.doctype, doc.name), - "attachment_logs": get_comments(doc.doctype, doc.name, 'attachment'), "communications": communications_except_auto_messages, "automated_messages": automated_messages, - 'comments': get_comments(doc.doctype, doc.name), 'total_comments': len(json.loads(doc.get('_comments') or '[]')), 'versions': get_versions(doc), "assignments": get_assignments(doc.doctype, doc.name), - "assignment_logs": get_comments(doc.doctype, doc.name, 'assignment'), "permissions": get_doc_permissions(doc), "shared": frappe.share.get_users(doc.doctype, doc.name), - "info_logs": get_comments(doc.doctype, doc.name, comment_type=['Info', 'Edit', 'Label']), - "share_logs": get_comments(doc.doctype, doc.name, 'share'), - "like_logs": get_comments(doc.doctype, doc.name, 'Like'), - "workflow_logs": get_comments(doc.doctype, doc.name, comment_type="Workflow"), "views": get_view_logs(doc.doctype, doc.name), "energy_point_logs": get_point_logs(doc.doctype, doc.name), "additional_timeline_content": get_additional_timeline_content(doc.doctype, doc.name), "milestones": get_milestones(doc.doctype, doc.name), "is_document_followed": is_document_followed(doc.doctype, doc.name, frappe.session.user), "tags": get_tags(doc.doctype, doc.name), - "document_email": get_document_email(doc.doctype, doc.name) - } + "document_email": get_document_email(doc.doctype, doc.name), + }) + + update_user_info(docinfo) + + frappe.response["docinfo"] = docinfo + +def add_comments(doc, docinfo): + # divide comments into separate lists + docinfo.comments = [] + docinfo.shared = [] + docinfo.assignment_logs = [] + docinfo.attachment_logs = [] + docinfo.info_logs = [] + docinfo.like_logs = [] + docinfo.workflow_logs = [] + + comments = frappe.get_all("Comment", + fields=["name", "creation", "content", "owner", "comment_type"], + filters={ + "reference_doctype": doc.doctype, + "reference_name": doc.name + } + ) + + for c in comments: + if c.comment_type == "Comment": + c.content = frappe.utils.markdown(c.content) + docinfo.comments.append(c) + + elif c.comment_type in ('Shared', 'Unshared'): + docinfo.shared.append(c) + + elif c.comment_type in ('Assignment Completed', 'Assigned'): + docinfo.assignment_logs.append(c) + + elif c.comment_type in ('Attachment', 'Attachment Removed'): + docinfo.attachment_logs.append(c) + + elif c.comment_type in ('Info', 'Edit', 'Label'): + docinfo.info_logs.append(c) + + elif c.comment_type == "Like": + docinfo.like_logs.append(c) + + elif c.comment_type == "Workflow": + docinfo.workflow_logs.append(c) + + frappe.utils.add_user_info(c.owner, docinfo.user_info) + + + return comments + def get_milestones(doctype, name): return frappe.db.get_all('Milestone', fields = ['creation', 'owner', 'track_field', 'value'], @@ -252,7 +300,7 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields= return communications def get_assignments(dt, dn): - cl = frappe.get_all("ToDo", + return frappe.get_all("ToDo", fields=['name', 'allocated_to as owner', 'description', 'status'], filters={ 'reference_type': dt, @@ -260,8 +308,6 @@ def get_assignments(dt, dn): 'status': ('!=', 'Cancelled'), }) - return cl - @frappe.whitelist() def get_badge_info(doctypes, filters): filters = json.loads(filters) @@ -319,3 +365,24 @@ def get_additional_timeline_content(doctype, docname): contents.extend(frappe.get_attr(method)(doctype, docname) or []) return contents + +def update_user_info(docinfo): + for d in docinfo.communications: + frappe.utils.add_user_info(d.sender, docinfo.user_info) + + for d in docinfo.shared: + frappe.utils.add_user_info(d.user, docinfo.user_info) + + for d in docinfo.assignments: + frappe.utils.add_user_info(d.owner, docinfo.user_info) + + for d in docinfo.views: + frappe.utils.add_user_info(d.owner, docinfo.user_info) + +@frappe.whitelist() +def get_user_info_for_viewers(users): + user_info = {} + for user in json.loads(users): + frappe.utils.add_user_info(user, user_info) + + return user_info diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index fb150e4bea..e81ed0767b 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -12,7 +12,7 @@ from io import StringIO from frappe.core.doctype.access_log.access_log import make_access_log from frappe.utils import cstr, format_duration from frappe.model.base_document import get_controller - +from frappe.utils import add_user_info @frappe.whitelist() @frappe.read_only() @@ -219,6 +219,8 @@ def compress(data, args=None): """separate keys and values""" from frappe.desk.query_report import add_total_row + user_info = {} + if not data: return data if args is None: args = {} @@ -230,13 +232,19 @@ def compress(data, args=None): new_row.append(row.get(key)) values.append(new_row) + # add user info for assignments (avatar) + if row._assign: + for user in json.loads(row._assign): + add_user_info(user, user_info) + if args.get("add_total_row"): meta = frappe.get_meta(args.doctype) values = add_total_row(values, keys, meta) return { "keys": keys, - "values": values + "values": values, + "user_info": user_info } @frappe.whitelist() diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 26a4658c36..631174b4db 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -646,8 +646,6 @@ class BaseDocument(object): value, comma_options)) def _validate_data_fields(self): - from frappe.core.doctype.user.user import STANDARD_USERS - # data_field options defined in frappe.model.data_field_options for data_field in self.meta.get_data_fields(): data = self.get(data_field.fieldname) @@ -658,7 +656,7 @@ class BaseDocument(object): continue if data_field_options == "Email": - if (self.owner in STANDARD_USERS) and (data in STANDARD_USERS): + if (self.owner in frappe.STANDARD_USERS) and (data in frappe.STANDARD_USERS): continue for email_address in frappe.utils.split_emails(data): frappe.utils.validate_email_address(email_address, throw=True) diff --git a/frappe/public/js/frappe/form/form_viewers.js b/frappe/public/js/frappe/form/form_viewers.js index 964576ef8a..ecf5eea504 100644 --- a/frappe/public/js/frappe/form/form_viewers.js +++ b/frappe/public/js/frappe/form/form_viewers.js @@ -27,19 +27,40 @@ frappe.ui.form.FormViewers.set_users = function(data, type) { const users = data.users || []; const new_users = users.filter(user => !past_users.includes(user)); - frappe.model.set_docinfo(doctype, docname, type, { - past: past_users.concat(new_users), - new: new_users, - current: users - }); - - if ( - cur_frm && - cur_frm.doc && - cur_frm.doc.doctype === doctype && - cur_frm.doc.name == docname && - cur_frm.viewers - ) { - cur_frm.viewers.refresh(true, type); + if (new_users.length===0) return; + + const set_and_refresh = () => { + const info = { + past: past_users.concat(new_users), + new: new_users, + current: users + }; + + frappe.model.set_docinfo(doctype, docname, type, info); + + if ( + cur_frm && + cur_frm.doc && + cur_frm.doc.doctype === doctype && + cur_frm.doc.name == docname && + cur_frm.viewers + ) { + cur_frm.viewers.refresh(true, type); + } + }; + + let unknown_users = []; + for (let user of users) { + if (!frappe.boot.user_info[user]) unknown_users.push(user); + } + + if (unknown_users.length===0) { + set_and_refresh(); + } else { + // load additional user info + frappe.xcall('frappe.desk.form.load.get_user_info_for_viewers', {users: unknown_users}).then((data) => { + Object.assign(frappe.boot.user_info, data); + set_and_refresh(); + }); } }; diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index aa1101c64e..8fa512bfc0 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -484,6 +484,11 @@ frappe.views.BaseList = class BaseList { prepare_data(r) { let data = r.message || {}; + + // extract user_info for assignments + Object.assign(frappe.boot.user_info, data.user_info); + delete data.user_info; + data = !Array.isArray(data) ? frappe.utils.dict(data.keys, data.values) : data; diff --git a/frappe/public/js/frappe/model/sync.js b/frappe/public/js/frappe/model/sync.js index 753046fa13..2cb7ca1a32 100644 --- a/frappe/public/js/frappe/model/sync.js +++ b/frappe/public/js/frappe/model/sync.js @@ -1,7 +1,7 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // MIT License. See license.txt -$.extend(frappe.model, { +Object.assign(frappe.model, { docinfo: {}, sync: function(r) { /* docs: @@ -33,22 +33,28 @@ $.extend(frappe.model, { } if(d.localname) { - frappe.model.new_names[d.localname] = d.name; - $(document).trigger('rename', [d.doctype, d.localname, d.name]); - delete locals[d.doctype][d.localname]; - - // update docinfo to new dict keys - if(i===0) { - frappe.model.docinfo[d.doctype][d.name] = frappe.model.docinfo[d.doctype][d.localname]; - frappe.model.docinfo[d.doctype][d.localname] = undefined; - } + frappe.model.rename_after_save(d, i); } } + } + frappe.model.sync_docinfo(r); + + }, + rename_after_save: (d, i) => { + frappe.model.new_names[d.localname] = d.name; + $(document).trigger('rename', [d.doctype, d.localname, d.name]); + delete locals[d.doctype][d.localname]; + // update docinfo to new dict keys + if(i===0) { + frappe.model.docinfo[d.doctype][d.name] = frappe.model.docinfo[d.doctype][d.localname]; + frappe.model.docinfo[d.doctype][d.localname] = undefined; } + }, + sync_docinfo: (r) => { // set docinfo (comments, assign, attachments) if(r.docinfo) { var doc; @@ -62,10 +68,14 @@ $.extend(frappe.model, { frappe.model.docinfo[doc.doctype] = {}; frappe.model.docinfo[doc.doctype][doc.name] = r.docinfo; } + + // copy values to frappe.boot.user_info + Object.assign(frappe.boot.user_info, r.docinfo.user_info); } return r.docs; }, + add_to_locals: function(doc) { if(!locals[doc.doctype]) locals[doc.doctype] = {}; @@ -100,6 +110,7 @@ $.extend(frappe.model, { } } }, + update_in_locals: function(doc) { // update values in the existing local doc instead of replacing let local_doc = locals[doc.doctype][doc.name]; diff --git a/frappe/public/js/frappe/utils/user.js b/frappe/public/js/frappe/utils/user.js index f22611b515..a5a7801cc1 100644 --- a/frappe/public/js/frappe/utils/user.js +++ b/frappe/public/js/frappe/utils/user.js @@ -2,14 +2,6 @@ frappe.user_info = function(uid) { if(!uid) uid = frappe.session.user; - if(uid.toLowerCase()==="bot") { - return { - fullname: __("Bot"), - image: "/assets/frappe/images/ui/bot.png", - abbr: "B" - }; - } - if(!(frappe.boot.user_info && frappe.boot.user_info[uid])) { var user_info = {fullname: uid || "Unknown"}; } else { @@ -22,29 +14,6 @@ frappe.user_info = function(uid) { return user_info; }; -frappe.ui.set_user_background = function(src, selector, style) { - if(!selector) selector = "#page-desktop"; - if(!style) style = "Fill Screen"; - if(src) { - if (window.cordova && src.indexOf("http") === -1) { - src = frappe.base_url + src; - } - var background = repl('background: url("%(src)s") center center;', {src: src}); - } else { - var background = "background-color: #4B4C9D;"; - } - - frappe.dom.set_style(repl('%(selector)s { \ - %(background)s \ - background-attachment: fixed; \ - %(style)s \ - }', { - selector:selector, - background:background, - style: style==="Fill Screen" ? "background-size: cover;" : "" - })); -}; - frappe.provide('frappe.user'); $.extend(frappe.user, { diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index ffff7032aa..54a6f0b7e8 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -56,7 +56,7 @@ def get_email_address(user=None): def get_formatted_email(user, mail=None): """get Email Address of user formatted as: `John Doe `""" fullname = get_fullname(user) - + method = get_hook_method('get_sender_details') if method: sender_name, mail = method() @@ -623,12 +623,11 @@ def get_installed_apps_info(): return out def get_site_info(): - from frappe.core.doctype.user.user import STANDARD_USERS from frappe.email.queue import get_emails_sent_this_month from frappe.utils.user import get_system_managers # only get system users - users = frappe.get_all('User', filters={'user_type': 'System User', 'name': ('not in', STANDARD_USERS)}, + users = frappe.get_all('User', filters={'user_type': 'System User', 'name': ('not in', frappe.STANDARD_USERS)}, fields=['name', 'enabled', 'last_login', 'last_active', 'language', 'time_zone']) system_managers = get_system_managers(only_name=True) for u in users: @@ -898,3 +897,14 @@ def dictify(arg): arg = frappe._dict(arg) return arg + +def add_user_info(user, user_info): + if user not in user_info: + info = frappe.db.get_value("User", + user, ["full_name", "user_image", "name", 'email'], as_dict=True) or frappe._dict() + user_info[user] = frappe._dict( + fullname = info.full_name or user, + image = info.user_image, + name = user, + email = info.email + ) diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py index 43580c1287..8ebb4b2937 100755 --- a/frappe/utils/scheduler.py +++ b/frappe/utils/scheduler.py @@ -17,7 +17,6 @@ import schedule # imports - module imports import frappe -from frappe.core.doctype.user.user import STANDARD_USERS from frappe.installer import update_site_config from frappe.utils import get_sites, now_datetime from frappe.utils.background_jobs import get_jobs diff --git a/frappe/utils/user.py b/frappe/utils/user.py index e5acb53daf..cbf38f6acb 100755 --- a/frappe/utils/user.py +++ b/frappe/utils/user.py @@ -230,7 +230,6 @@ def get_fullname_and_avatar(user): def get_system_managers(only_name=False): """returns all system manager's user details""" import email.utils - from frappe.core.doctype.user.user import STANDARD_USERS system_managers = frappe.db.sql("""SELECT DISTINCT `name`, `creation`, CONCAT_WS(' ', CASE WHEN `first_name`= '' THEN NULL ELSE `first_name` END, @@ -245,8 +244,8 @@ def get_system_managers(only_name=False): FROM `tabHas Role` AS ur WHERE ur.parent = p.name AND ur.role='System Manager') - ORDER BY `creation` DESC""".format(", ".join(["%s"]*len(STANDARD_USERS))), - STANDARD_USERS, as_dict=True) + ORDER BY `creation` DESC""".format(", ".join(["%s"]*len(frappe.STANDARD_USERS))), + frappe.STANDARD_USERS, as_dict=True) if only_name: return [p.name for p in system_managers]