@@ -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.12' | |||
__version__ = '7.2.15' | |||
__title__ = "Frappe Framework" | |||
local = Local() | |||
@@ -15,6 +15,7 @@ from frappe.sessions import Session, clear_sessions, delete_session | |||
from frappe.modules.patch_handler import check_session_stopped | |||
from frappe.translate import get_lang_code | |||
from frappe.utils.password import check_password | |||
from frappe.core.doctype.authentication_log.authentication_log import add_authentication_log | |||
from urllib import quote | |||
@@ -59,10 +60,6 @@ class HTTPRequest: | |||
# check status | |||
check_session_stopped() | |||
# run login triggers | |||
if frappe.form_dict.get('cmd')=='login': | |||
frappe.local.login_manager.run_trigger('on_session_creation') | |||
def validate_csrf_token(self): | |||
if frappe.local.request and frappe.local.request.method=="POST": | |||
if not frappe.local.session.data.csrf_token \ | |||
@@ -103,6 +100,9 @@ class LoginManager: | |||
if frappe.local.form_dict.get('cmd')=='login' or frappe.local.request.path=="/api/method/login": | |||
self.login() | |||
self.resume = False | |||
# run login triggers | |||
self.run_trigger('on_session_creation') | |||
else: | |||
try: | |||
self.resume = True | |||
@@ -183,7 +183,7 @@ class LoginManager: | |||
if not (user and pwd): | |||
user, pwd = frappe.form_dict.get('usr'), frappe.form_dict.get('pwd') | |||
if not (user and pwd): | |||
self.fail('Incomplete login details') | |||
self.fail('Incomplete login details', user=user) | |||
self.check_if_enabled(user) | |||
self.user = self.check_password(user, pwd) | |||
@@ -192,7 +192,7 @@ class LoginManager: | |||
"""raise exception if user not enabled""" | |||
if user=='Administrator': return | |||
if not cint(frappe.db.get_value('User', user, 'enabled')): | |||
self.fail('User disabled or missing') | |||
self.fail('User disabled or missing', user=user) | |||
def check_password(self, user, pwd): | |||
"""check password""" | |||
@@ -200,10 +200,12 @@ class LoginManager: | |||
# returns user in correct case | |||
return check_password(user, pwd) | |||
except frappe.AuthenticationError: | |||
self.fail('Incorrect password') | |||
self.fail('Incorrect password', user=user) | |||
def fail(self, message): | |||
def fail(self, message, user="NA"): | |||
frappe.local.response['message'] = message | |||
add_authentication_log(message, user, status="Failed") | |||
frappe.db.commit() | |||
raise frappe.AuthenticationError | |||
def run_trigger(self, event='on_login'): | |||
@@ -0,0 +1,8 @@ | |||
// Copyright (c) 2016, Frappe Technologies and contributors | |||
// For license information, please see license.txt | |||
frappe.ui.form.on('Authentication Log', { | |||
refresh: function(frm) { | |||
} | |||
}); |
@@ -0,0 +1,341 @@ | |||
{ | |||
"allow_copy": 0, | |||
"allow_import": 0, | |||
"allow_rename": 0, | |||
"beta": 0, | |||
"creation": "2017-01-23 16:56:25.875531", | |||
"custom": 0, | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "Setup", | |||
"editable_grid": 1, | |||
"engine": "InnoDB", | |||
"fields": [ | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "user_details", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "User Details", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "user", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "User", | |||
"length": 0, | |||
"no_copy": 0, | |||
"options": "", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "full_name", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"in_standard_filter": 0, | |||
"label": "Full Name", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "column_break_8", | |||
"fieldtype": "Column Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "date", | |||
"fieldtype": "Datetime", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Date", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "section_break_6", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "subject", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"in_standard_filter": 0, | |||
"label": "Subject", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "column_break_2", | |||
"fieldtype": "Column Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "operation", | |||
"fieldtype": "Select", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"in_standard_filter": 0, | |||
"label": "Operation", | |||
"length": 0, | |||
"no_copy": 0, | |||
"options": "\nLogin\nLogout", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "status", | |||
"fieldtype": "Select", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"in_standard_filter": 0, | |||
"label": "Status", | |||
"length": 0, | |||
"no_copy": 0, | |||
"options": "\nSuccess\nFailed", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
} | |||
], | |||
"hide_heading": 0, | |||
"hide_toolbar": 0, | |||
"idx": 0, | |||
"image_view": 0, | |||
"in_create": 1, | |||
"in_dialog": 0, | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"max_attachments": 0, | |||
"modified": "2017-01-24 14:51:13.726113", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Authentication Log", | |||
"name_case": "", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"amend": 0, | |||
"apply_user_permissions": 0, | |||
"cancel": 0, | |||
"create": 0, | |||
"delete": 0, | |||
"email": 1, | |||
"export": 1, | |||
"if_owner": 0, | |||
"import": 0, | |||
"is_custom": 0, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "All", | |||
"set_user_permissions": 0, | |||
"share": 1, | |||
"submit": 0, | |||
"write": 0 | |||
} | |||
], | |||
"quick_entry": 0, | |||
"read_only": 0, | |||
"read_only_onload": 0, | |||
"sort_field": "modified", | |||
"sort_order": "DESC", | |||
"title_field": "subject", | |||
"track_seen": 0 | |||
} |
@@ -0,0 +1,27 @@ | |||
# -*- coding: utf-8 -*- | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors | |||
# For license information, please see license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe.utils import get_fullname, now | |||
from frappe.model.document import Document | |||
class AuthenticationLog(Document): | |||
def before_insert(self): | |||
self.full_name = get_fullname(self.user) | |||
self.date = now() | |||
def add_authentication_log(subject, user, operation="Login", status="Success"): | |||
frappe.get_doc({ | |||
"doctype": "Authentication Log", | |||
"user": user, | |||
"status": status, | |||
"subject": subject, | |||
"operation": operation, | |||
}).insert(ignore_permissions=True) | |||
def clear_authentication_logs(): | |||
"""clear 100 day old authentication logs""" | |||
frappe.db.sql("""delete from `tabAuthentication Log` where | |||
creation<DATE_SUB(NOW(), INTERVAL 100 DAY)""") |
@@ -0,0 +1,8 @@ | |||
frappe.listview_settings['Authentication Log'] = { | |||
get_indicator: function(doc) { | |||
if(doc.operation == "Login" && doc.status == "Success") | |||
return [__(doc.status), "green"]; | |||
else if(doc.operation == "Login" && doc.status == "Failed") | |||
return [__(doc.status), "red"]; | |||
} | |||
}; |
@@ -0,0 +1,48 @@ | |||
# -*- coding: utf-8 -*- | |||
# Copyright (c) 2015, Frappe Technologies and Contributors | |||
# See license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
import unittest | |||
# test_records = frappe.get_test_records('Authentication Log') | |||
class TestAuthenticationLog(unittest.TestCase): | |||
def test_authentication_log(self): | |||
from frappe.auth import LoginManager, CookieManager | |||
# test user login log | |||
frappe.local.form_dict = { 'cmd': 'login' } | |||
frappe.form_dict = { | |||
'sid': 'Guest', | |||
'pwd': 'admin', | |||
'usr': 'Administrator' | |||
} | |||
frappe.local.cookie_manager = CookieManager() | |||
frappe.local.login_manager = LoginManager() | |||
auth_log = self.get_auth_log() | |||
self.assertEquals(auth_log.status, 'Success') | |||
# test user logout log | |||
frappe.local.login_manager.logout() | |||
auth_log = self.get_auth_log(operation='Logout') | |||
self.assertEquals(auth_log.status, 'Success') | |||
# test invalid login | |||
frappe.form_dict.update({ 'pwd': 'password' }) | |||
self.assertRaises(frappe.AuthenticationError, LoginManager) | |||
auth_log = self.get_auth_log() | |||
self.assertEquals(auth_log.status, 'Failed') | |||
def get_auth_log(self, operation='Login'): | |||
names = frappe.db.sql_list("""select name from `tabAuthentication Log` | |||
where user='Administrator' and operation='{operation}' order by | |||
creation desc""".format(operation=operation)) | |||
name = names[0] | |||
auth_log = frappe.get_doc('Authentication Log', name) | |||
return auth_log |
@@ -9,6 +9,7 @@ from frappe.model.document import Document | |||
from frappe.utils import get_fullname | |||
from frappe import _ | |||
from frappe.core.doctype.communication.comment import add_info_comment | |||
from frappe.core.doctype.authentication_log.authentication_log import add_authentication_log | |||
def update_feed(doc, method=None): | |||
"adds a new communication with comment_type='Updated'" | |||
@@ -53,19 +54,14 @@ def update_feed(doc, method=None): | |||
}).insert(ignore_permissions=True) | |||
def login_feed(login_manager): | |||
add_info_comment(**{ | |||
"subject": _("{0} logged in").format(get_fullname(login_manager.user)), | |||
"full_name": get_fullname(login_manager.user) | |||
}) | |||
if login_manager.user != "Guest": | |||
subject = _("{0} logged in").format(get_fullname(login_manager.user)) | |||
add_authentication_log(subject, login_manager.user) | |||
def logout_feed(user, reason): | |||
if not user: | |||
return | |||
add_info_comment(**{ | |||
"subject": _("{0} logged out: <b>{1}</b>").format(get_fullname(user), reason), | |||
"full_name": get_fullname(user), | |||
}) | |||
if user and user != "Guest": | |||
subject = _("{0} logged out: {1}").format(get_fullname(user), frappe.bold(reason)) | |||
add_authentication_log(subject, user, operation="Logout") | |||
def get_feed_match_conditions(user=None, force=True): | |||
if not user: user = frappe.session.user | |||
@@ -15,10 +15,7 @@ def get_roles_and_doctypes(): | |||
send_translations(frappe.get_lang_dict("doctype", "DocPerm")) | |||
return { | |||
"doctypes": [d[0] for d in frappe.db.sql("""select name from `tabDocType` dt where | |||
istable=0 and | |||
name not in ('DocType') and | |||
exists(select * from `tabDocField` where parent=dt.name) and | |||
exists(select * from `tabDocPerm` dp,`tabRole` role where dp.role = role.name and dp.parent=dt.name and not role.disabled)""")], | |||
istable=0 and name not in ('DocType')""")], | |||
"roles": [d[0] for d in frappe.db.sql("""select name from tabRole where | |||
name != 'Administrator' and disabled=0""")] | |||
} | |||
@@ -72,6 +72,14 @@ frappe.pages['activity'].on_page_load = function(wrapper) { | |||
}, 'fa fa-th') | |||
} | |||
this.page.add_menu_item(__('Authentication Log'), function() { | |||
frappe.route_options = { | |||
"user": user | |||
} | |||
frappe.set_route('Report', "Authentication Log"); | |||
}, 'fa fa-th') | |||
this.page.add_menu_item(__('Show Likes'), function() { | |||
frappe.route_options = { | |||
show_likes: true | |||
@@ -91,8 +91,8 @@ def get_context(context): | |||
'''Build recipients and send email alert''' | |||
context = get_context(doc) | |||
recipients = [] | |||
for recipient in self.recipients: | |||
recipients = [] | |||
if recipient.condition: | |||
if not eval(recipient.condition, context): | |||
continue | |||
@@ -108,6 +108,7 @@ def get_context(context): | |||
if not recipients: | |||
return | |||
recipients = list(set(recipients)) | |||
subject = self.subject | |||
context = {"doc": doc, "alert": self, "comments": None} | |||
@@ -188,7 +188,6 @@ class EMail: | |||
self.msg_root["Message-Id"] = '<' + message_id + '>' | |||
else: | |||
self.msg_root["Message-Id"] = get_message_id() | |||
self.msg_root["References"] = '<notification>' | |||
def set_in_reply_to(self, in_reply_to): | |||
"""Used to send the Message-Id of a received email back as In-Reply-To""" | |||
@@ -4,7 +4,7 @@ | |||
from __future__ import unicode_literals | |||
import frappe | |||
import HTMLParser | |||
import smtplib | |||
import smtplib, quopri | |||
from frappe import msgprint, throw, _ | |||
from frappe.email.smtp import SMTPServer, get_outgoing_email_account | |||
from frappe.email.email_body import get_email, get_formatted_html | |||
@@ -174,7 +174,7 @@ def get_unsubscribe_message(unsubscribe_message, expose_recipients): | |||
text = "\n<!--cc message-->" | |||
else: | |||
text = "" | |||
text += "\n\n{unsubscribe_message}: <!--unsubscribe url-->".format(unsubscribe_message=unsubscribe_message) | |||
text += "\n\n{unsubscribe_message}: <!--unsubscribe url-->\n".format(unsubscribe_message=unsubscribe_message) | |||
return frappe._dict({ | |||
"html": html, | |||
@@ -359,7 +359,7 @@ def prepare_message(email, recipient, recipients_list): | |||
if email.reference_doctype: # is missing the check for unsubscribe message but will not add as there will be no unsubscribe url | |||
unsubscribe_url = get_unsubcribed_url(email.reference_doctype, email.reference_name, recipient, | |||
email.unsubscribe_method, email.unsubscribe_params) | |||
message = message.replace("<!--unsubscribe url-->", unsubscribe_url) | |||
message = message.replace("<!--unsubscribe url-->", quopri.encodestring(unsubscribe_url)) | |||
if email.expose_recipients == "header": | |||
pass | |||
@@ -375,7 +375,7 @@ def prepare_message(email, recipient, recipients_list): | |||
email_sent_message = _("This email was sent to {0} and copied to {1}").format(email_sent_to,email_sent_cc) | |||
else: | |||
email_sent_message = _("This email was sent to {0}").format(email_sent_to) | |||
message = message.replace("<!--cc message-->", email_sent_message) | |||
message = message.replace("<!--cc message-->", quopri.encodestring(email_sent_message)) | |||
message = message.replace("<!--recipient-->", recipient) | |||
return message | |||
@@ -137,7 +137,8 @@ scheduler_events = { | |||
"frappe.utils.scheduler.restrict_scheduler_events_if_dormant", | |||
"frappe.limits.update_space_usage", | |||
"frappe.email.doctype.auto_email_report.auto_email_report.send_daily", | |||
"frappe.desk.page.backups.backups.delete_downloadable_backups" | |||
"frappe.desk.page.backups.backups.delete_downloadable_backups", | |||
"frappe.core.doctype.authentication_log.authentication_log.clear_authentication_logs" | |||
], | |||
"monthly": [ | |||
"frappe.email.doctype.auto_email_report.auto_email_report.send_monthly" | |||
@@ -304,7 +304,7 @@ | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"default": "1", | |||
"default": "0", | |||
"fieldname": "align_labels_left", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
@@ -333,7 +333,7 @@ | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"default": "1", | |||
"default": "0", | |||
"fieldname": "show_section_headings", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
@@ -362,7 +362,7 @@ | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"default": "1", | |||
"default": "0", | |||
"fieldname": "line_breaks", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
@@ -653,7 +653,7 @@ | |||
"issingle": 0, | |||
"istable": 0, | |||
"max_attachments": 0, | |||
"modified": "2016-11-07 05:23:46.983533", | |||
"modified": "2017-01-24 15:35:56.250107", | |||
"modified_by": "Administrator", | |||
"module": "Print", | |||
"name": "Print Format", | |||
@@ -52,12 +52,13 @@ frappe.ui.form.LinkedWith = Class.extend({ | |||
var me = this; | |||
var already_loaded = Object.keys(locals.DocType); | |||
var doctypes_to_load = []; | |||
$.each(Object.keys(me.frm.__linked_doctypes), function(i, v) { | |||
if (already_loaded.indexOf(v)===-1) { | |||
doctypes_to_load.push(v); | |||
} | |||
}); | |||
if (me.frm.__linked_doctypes) { | |||
$.each(Object.keys(me.frm.__linked_doctypes), function(i, v) { | |||
if (already_loaded.indexOf(v)===-1) { | |||
doctypes_to_load.push(v); | |||
} | |||
}); | |||
} | |||
// load all doctypes sequentially using with_doctype | |||
return $.when.apply($, $.map(doctypes_to_load, function(dt) { | |||
return frappe.model.with_doctype(dt, function() { | |||
@@ -51,7 +51,7 @@ frappe.breadcrumbs = { | |||
label = module_info ? module_info.label : breadcrumbs.module; | |||
if(module_info) { | |||
if(module_info && !module_info.blocked) { | |||
$(repl('<li><a href="#modules/%(module)s">%(label)s</a></li>', | |||
{ module: breadcrumbs.module, label: __(label) })) | |||
.appendTo($breadcrumbs); | |||
@@ -212,7 +212,7 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ | |||
set_route_filters: function(first_load) { | |||
var me = this; | |||
if(frappe.route_options) { | |||
if(frappe.route_options && !this.list_settings.filters) { | |||
this.set_filters_from_route_options(); | |||
return true; | |||
} else if(this.list_settings | |||
@@ -3,7 +3,7 @@ | |||
from __future__ import unicode_literals | |||
import unittest, frappe, re | |||
import unittest, frappe, re, email | |||
from frappe.test_runner import make_test_records | |||
make_test_records("User") | |||
@@ -87,6 +87,7 @@ class TestEmail(unittest.TestCase): | |||
self.assertTrue('This email was sent to test@example.com and copied to test1@example.com' in frappe.flags.sent_mail) | |||
def test_expose(self): | |||
from frappe.utils.verified_command import verify_request | |||
frappe.sendmail(recipients=['test@example.com'], | |||
cc=['test1@example.com'], | |||
sender="admin@example.com", | |||
@@ -103,9 +104,14 @@ class TestEmail(unittest.TestCase): | |||
where status='Sent'""", as_dict=1)[0].message | |||
self.assertTrue('<!--recipient-->' in message) | |||
frappe.local.flags.signed_query_string = re.search('(?<=/api/method/frappe.email.queue.unsubscribe\?).*(?=\n)', frappe.flags.sent_mail).group(0) | |||
from frappe.utils.verified_command import verify_request | |||
self.assertTrue(verify_request()) | |||
email_obj = email.message_from_string(frappe.flags.sent_mail) | |||
for part in email_obj.walk(): | |||
content = part.get_payload(decode=True) | |||
if content: | |||
frappe.local.flags.signed_query_string = re.search('(?<=/api/method/frappe.email.queue.unsubscribe\?).*(?=\n)', content).group(0) | |||
self.assertTrue(verify_request()) | |||
break | |||
def test_expired(self): | |||
self.test_email_queue() | |||
@@ -134,7 +134,7 @@ def has_gravatar(email): | |||
# since querying gravatar for every item will be slow | |||
return '' | |||
hexdigest = md5.md5(frappe.as_unicode(email)).hexdigest() | |||
hexdigest = md5.md5(frappe.as_unicode(email).encode('utf-8')).hexdigest() | |||
gravatar_url = "https://secure.gravatar.com/avatar/{hash}?d=404&s=200".format(hash=hexdigest) | |||
try: | |||
@@ -334,8 +334,7 @@ $.extend(frappe, { | |||
// Utility functions | |||
function valid_email(id) { | |||
return (id.toLowerCase().search("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")==-1) ? 0 : 1; | |||
return /"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"/.test(id.toLowerCase()); | |||
} | |||
var validate_email = valid_email; | |||
@@ -18,6 +18,5 @@ def get_context(context): | |||
context.javascript += "\n" + js | |||
if not frappe.conf.developer_mode: | |||
context["google_analytics_id"] = frappe.conf.get("google_analytics_id") \ | |||
or frappe.db.get_single_value("Website Settings", "google_analytics_id") | |||
context["google_analytics_id"] = (frappe.db.get_single_value("Website Settings", "google_analytics_id") | |||
or frappe.conf.get("google_analytics_id")) |