diff --git a/frappe/__init__.py b/frappe/__init__.py
index 507f0b7283..256683aea2 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -13,7 +13,7 @@ import os, sys, importlib, inspect, json
from .exceptions import *
from .utils.jinja import get_jenv, get_template, render_template
-__version__ = '7.2.9'
+__version__ = '7.2.11'
__title__ = "Frappe Framework"
local = Local()
diff --git a/frappe/auth.py b/frappe/auth.py
index fcaa9da0da..24c442980a 100644
--- a/frappe/auth.py
+++ b/frappe/auth.py
@@ -255,7 +255,7 @@ class LoginManager:
self.run_trigger('on_logout')
if user == frappe.session.user:
- delete_session(frappe.session.sid)
+ delete_session(frappe.session.sid, user=user, reason="User Manually Logged Out")
self.clear_cookies()
else:
clear_sessions(user)
diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py
index 7c74e41834..67fef989ad 100644
--- a/frappe/commands/utils.py
+++ b/frappe/commands/utils.py
@@ -52,15 +52,16 @@ def clear_website_cache(context):
frappe.destroy()
@click.command('destroy-all-sessions')
+@click.option('--reason')
@pass_context
-def destroy_all_sessions(context):
+def destroy_all_sessions(context, reason=None):
"Clear sessions of all users (logs them out)"
import frappe.sessions
for site in context.sites:
try:
frappe.init(site=site)
frappe.connect()
- frappe.sessions.clear_all_sessions()
+ frappe.sessions.clear_all_sessions(reason)
frappe.db.commit()
finally:
frappe.destroy()
diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py
index eb4bf2a9c2..2fcaa42b52 100755
--- a/frappe/core/doctype/communication/email.py
+++ b/frappe/core/doctype/communication/email.py
@@ -334,12 +334,13 @@ def add_attachments(name, attachments):
# loop through attachments
for a in attachments:
- attach = frappe.db.get_value("File", {"name":a},
- ["file_name", "file_url", "is_private"], as_dict=1)
+ if isinstance(a, basestring):
+ attach = frappe.db.get_value("File", {"name":a},
+ ["file_name", "file_url", "is_private"], as_dict=1)
- # save attachments to new doc
- save_url(attach.file_url, attach.file_name, "Communication", name,
- "Home/Attachments", attach.is_private)
+ # save attachments to new doc
+ save_url(attach.file_url, attach.file_name, "Communication", name,
+ "Home/Attachments", attach.is_private)
def filter_email_list(doc, email_list, exclude, is_cc=False):
# temp variables
diff --git a/frappe/core/doctype/communication/feed.py b/frappe/core/doctype/communication/feed.py
index b9054f7218..b49bd1163e 100644
--- a/frappe/core/doctype/communication/feed.py
+++ b/frappe/core/doctype/communication/feed.py
@@ -58,6 +58,15 @@ def login_feed(login_manager):
"full_name": get_fullname(login_manager.user)
})
+def logout_feed(user, reason):
+ if not user:
+ return
+
+ add_info_comment(**{
+ "subject": _("{0} logged out: {1}").format(get_fullname(user), reason),
+ "full_name": get_fullname(user),
+ })
+
def get_feed_match_conditions(user=None, force=True):
if not user: user = frappe.session.user
diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js
index 05de1d2e06..b9ef83c1b3 100644
--- a/frappe/core/doctype/user/user.js
+++ b/frappe/core/doctype/user/user.js
@@ -88,8 +88,8 @@ frappe.ui.form.on('User', {
}
}
- if (frappe.route_titles["unsaved"]===1){
- delete frappe.route_titles["unsaved"];
+ if (frappe.route_flags.unsaved===1){
+ delete frappe.route_flags.unsaved;
for ( var i=0;i ' + __("On {0}, {1} wrote:",
- [frappe.datetime.global_date_format(last_email.creation) , last_email.sender]) + '
"
+ '' +
'
'); } else { @@ -598,8 +598,6 @@ frappe.views.CommunicationComposer = Class.extend({ frappe.call({ method:'frappe.email.get_contact_list', args: { - 'fieldname': "email_id", - 'doctype': "Contact", 'txt': extractLast(term) || '%' }, quiet: true, diff --git a/frappe/public/js/frappe/views/reports/reportview.js b/frappe/public/js/frappe/views/reports/reportview.js index bd07b52b60..a5699aa4fc 100644 --- a/frappe/public/js/frappe/views/reports/reportview.js +++ b/frappe/public/js/frappe/views/reports/reportview.js @@ -574,7 +574,7 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ var me = this; this.page.add_inner_button(__('Show Totals'), function() { - me.add_totals_row = 1 - me.add_totals_row; + me.add_totals_row = 1 - (me.add_totals_row ? me.add_totals_row : 0); me.render_list(); }); }, diff --git a/frappe/public/js/legacy/form.js b/frappe/public/js/legacy/form.js index 3f86e401ff..54fe4f830a 100644 --- a/frappe/public/js/legacy/form.js +++ b/frappe/public/js/legacy/form.js @@ -147,7 +147,7 @@ _f.Frm.prototype.setup_drag_drop = function() { callback: function(attachment, r) { me.attachments.attachment_uploaded(attachment, r); }, - + confirm_is_private: true }); }); @@ -348,7 +348,7 @@ _f.Frm.prototype.refresh_header = function(is_a_different_doc) { this.dashboard.refresh(); if(this.meta.is_submittable && - frappe.perm.get_perm(this.docname, this.doc).submit && + this.perm[0] && this.perm[0].submit && ! this.is_dirty() && ! this.is_new() && this.doc.docstatus===0) { diff --git a/frappe/sessions.py b/frappe/sessions.py index 56c075ee17..cdfa07b806 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -61,7 +61,7 @@ def clear_sessions(user=None, keep_current=False, device=None): :param device: delete sessions of this device (default: desktop) ''' for sid in get_sessions_to_clear(user, keep_current, device): - delete_session(sid) + delete_session(sid, reason="Logged In From Another Session") def get_sessions_to_clear(user=None, keep_current=False, device=None): '''Returns sessions of the current user. Called at login / logout @@ -85,23 +85,30 @@ def get_sessions_to_clear(user=None, keep_current=False, device=None): if keep_current: condition = ' and sid != "{0}"'.format(frappe.db.escape(frappe.session.sid)) - return frappe.db.sql_list("""select sid from tabSessions where user=%s and device=%s {condition} order by lastupdate desc limit {limit}, 100""".format(condition=condition, limit=limit), (user, device)) -def delete_session(sid=None, user=None): +def delete_session(sid=None, user=None, reason="Session Expired"): + from frappe.core.doctype.communication.feed import logout_feed + frappe.cache().hdel("session", sid) frappe.cache().hdel("last_db_session_update", sid) + if sid and not user: + user_details = frappe.db.sql("""select user from tabSessions where sid=%s""", sid, as_dict=True) + if user_details: user = user_details[0].get("user") + + logout_feed(user, reason) frappe.db.sql("""delete from tabSessions where sid=%s""", sid) frappe.db.commit() -def clear_all_sessions(): +def clear_all_sessions(reason=None): """This effectively logs out all users""" frappe.only_for("Administrator") + if not reason: reason = "Deleted All Active Session" for sid in frappe.db.sql_list("select sid from `tabSessions`"): - delete_session(sid) + delete_session(sid, reason=reason) def get_expired_sessions(): '''Returns list of expired sessions''' @@ -113,11 +120,10 @@ def get_expired_sessions(): return expired - def clear_expired_sessions(): """This function is meant to be called from scheduler""" for sid in get_expired_sessions(): - delete_session(sid) + delete_session(sid, reason="Session Expired") def get(): """get session boot info""" @@ -335,7 +341,7 @@ class Session: return (cint(parts[0]) * 3600) + (cint(parts[1]) * 60) + cint(parts[2]) def delete_session(self): - delete_session(self.sid) + delete_session(self.sid, reason="Session Expired") def start_as_guest(self): """all guests share the same 'Guest' session"""