Merge branch 'hotfix'

This commit is contained in:
Sahil Khan 2019-05-21 14:07:05 +05:30
commit 1a06bc2c15
18 changed files with 719 additions and 501 deletions

View file

@ -23,7 +23,7 @@ if sys.version[0] == '2':
reload(sys)
sys.setdefaultencoding("utf-8")
__version__ = '11.1.28'
__version__ = '11.1.29'
__title__ = "Frappe Framework"
local = Local()
@ -187,15 +187,20 @@ def connect(site=None, db_name=None):
local.db = Database(user=db_name or local.conf.db_name)
set_user("Administrator")
def connect_read_only():
def connect_replica():
from frappe.database import Database
user = local.conf.db_name
password = local.conf.db_password
local.read_only_db = Database(local.conf.slave_host, local.conf.slave_db_name,
local.conf.slave_db_password)
if local.conf.different_credentials_for_replica:
user = local.conf.replica_db_name
password = local.conf.replica_db_password
local.replica_db = Database(host=local.conf.replica_host, user=user, password=password)
# swap db connections
local.master_db = local.db
local.db = local.read_only_db
local.primary_db = local.db
local.db = local.replica_db
def get_site_config(sites_path=None, site_path=None):
"""Returns `site_config.json` combined with `sites/common_site_config.json`.
@ -495,16 +500,17 @@ def whitelist(allow_guest=False, xss_safe=False):
def read_only():
def innfn(fn):
def wrapper_fn(*args, **kwargs):
if conf.use_slave_for_read_only:
connect_read_only()
if conf.read_from_replica:
connect_replica()
try:
retval = fn(*args, **get_newargs(fn, kwargs))
except:
raise
finally:
if local and hasattr(local, 'master_db'):
if local and hasattr(local, 'primary_db'):
local.db.close()
local.db = local.master_db
local.db = local.primary_db
return retval
return wrapper_fn

View file

@ -152,3 +152,11 @@ def filter_dynamic_link_doctypes(doctype, txt, searchfield, start, page_len, fil
valid_doctypes = [[doctype] for doctype in valid_doctypes]
return valid_doctypes
def set_link_title(doc):
if not doc.links:
return
for link in doc.links:
if not link.link_title:
linked_doc = frappe.get_doc(link.link_doctype, link.link_name)
link.link_title = linked_doc.get("title_field") or linked_doc.get("name")

View file

@ -15,6 +15,7 @@ from frappe.model.naming import make_autoname
from frappe.core.doctype.dynamic_link.dynamic_link import deduplicate_dynamic_links
from six import iteritems, string_types
from past.builtins import cmp
from frappe.contacts.address_and_contact import set_link_title
import functools
@ -39,6 +40,7 @@ class Address(Document):
def validate(self):
self.link_address()
self.validate_reference()
set_link_title(self)
deduplicate_dynamic_links(self)
def link_address(self):

View file

@ -10,6 +10,7 @@ from frappe.core.doctype.dynamic_link.dynamic_link import deduplicate_dynamic_li
from six import iteritems
from past.builtins import cmp
from frappe.model.naming import append_number_if_name_exists
from frappe.contacts.address_and_contact import set_link_title
import functools
@ -31,6 +32,7 @@ class Contact(Document):
if self.email_id:
self.email_id = self.email_id.strip()
self.set_user()
set_link_title(self)
if self.email_id and not self.image:
self.image = has_gravatar(self.email_id)

View file

@ -51,14 +51,14 @@ def run_background(prepared_report):
instance.status = "Completed"
instance.columns = json.dumps(result["columns"])
instance.report_end_time = frappe.utils.now()
instance.save()
instance.save(ignore_permissions=True)
except Exception:
frappe.log_error(frappe.get_traceback())
instance = frappe.get_doc("Prepared Report", prepared_report)
instance.status = "Error"
instance.error_message = frappe.get_traceback()
instance.save()
instance.save(ignore_permissions=True)
frappe.publish_realtime(
'report_generated',

View file

@ -34,9 +34,15 @@ class AutoRepeat(Document):
validate_template(self.message or "")
def before_submit(self):
start_date_copy = self.start_date
today_copy = add_days(today(), -1)
if start_date_copy <= today_copy:
start_date_copy = today_copy
if not self.next_schedule_date:
self.next_schedule_date = get_next_schedule_date(
self.start_date, self.frequency, self.repeat_on_day)
start_date_copy, self.frequency, self.repeat_on_day)
def on_submit(self):
self.update_auto_repeat_id()
@ -116,14 +122,15 @@ class AutoRepeat(Document):
days = 60 if self.frequency in ['Daily', 'Weekly'] else 365
end_date_copy = add_days(today_copy, days)
start_date_copy = get_next_schedule_date(start_date_copy, self.frequency, self.repeat_on_day)
while (getdate(start_date_copy) < getdate(end_date_copy)):
start_date_copy = get_next_schedule_date(start_date_copy, self.frequency, self.repeat_on_day)
row = {
"reference_document" : self.reference_document,
"frequency" : self.frequency,
"next_scheduled_date" : start_date_copy
}
schedule_details.append(row)
start_date_copy = get_next_schedule_date(start_date_copy, self.frequency, self.repeat_on_day)
return schedule_details

View file

@ -8,7 +8,7 @@ import unittest
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.desk.doctype.auto_repeat.auto_repeat import get_auto_repeat_entries, create_repeated_entries, disable_auto_repeat
from frappe.utils import today, add_days, getdate
from frappe.utils import today, add_days, getdate, add_months
def add_custom_fields():
@ -44,8 +44,8 @@ class TestAutoRepeat(unittest.TestCase):
self.assertEqual(todo.get('description'), new_todo.get('description'))
def test_monthly_auto_repeat(self):
start_date = '2018-01-01'
end_date = '2018-12-31'
start_date = today()
end_date = add_months(start_date, 12)
todo = frappe.get_doc(
dict(doctype='ToDo', description='test recurring todo', assigned_by='Administrator')).insert()
@ -103,7 +103,7 @@ def make_auto_repeat(**args):
'reference_document': args.reference_document or frappe.db.get_value('ToDo', {'docstatus': 1}, 'name'),
'frequency': args.frequency or 'Daily',
'start_date': args.start_date or add_days(today(), -1),
'end_date': args.end_date or add_days(today(), 1),
'end_date': args.end_date or add_days(today(), 2),
'submit_on_creation': args.submit_on_creation or 0,
'notify_by_email': args.notify or 0,
'recipients': args.recipients or "",

View file

@ -253,7 +253,7 @@ def get_report_list(module, is_standard="No"):
out.append({
"type": "report",
"doctype": r.ref_doctype,
"is_query_report": 1 if r.report_type in ("Query Report", "Script Report") else 0,
"is_query_report": 1 if r.report_type in ("Query Report", "Script Report", "Custom Report") else 0,
"label": _(r.name),
"name": r.name
})

View file

@ -2329,6 +2329,7 @@
},
"Suriname": {
"code": "sr",
"currency": "SRD",
"currency_fraction": "Cent",
"currency_fraction_units": 100,
"currency_symbol": "$",

View file

@ -1,317 +1,363 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-09-22 04:16:48.829658",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"editable_grid": 1,
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-09-22 04:16:48.829658",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"editable_grid": 1,
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "enabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Enabled",
"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": "ldap_server_url",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "LDAP Server Url",
"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": "organizational_unit",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Organizational Unit",
"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": "base_dn",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Base Distinguished Name (DN)",
"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": "password",
"fieldtype": "Password",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Password for Base DN",
"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_5",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 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": "ldap_search_string",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "LDAP Search String",
"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": "ldap_first_name_field",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "LDAP First Name Field",
"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": "ldap_email_field",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "LDAP Email Field",
"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": "ldap_username_field",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "LDAP Username Field",
"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_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "enabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Enabled",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "ldap_server_url",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "LDAP Server Url",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "organizational_unit",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Organizational Unit",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_dn",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Base Distinguished Name (DN)",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "password",
"fieldtype": "Password",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Password for Base DN",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "ldap_search_string",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "LDAP Search String",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "ldap_first_name_field",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "LDAP First Name Field",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "ldap_email_field",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "LDAP Email Field",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "ldap_username_field",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "LDAP Username Field",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "ldap_security",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "LDAP Security",
"length": 0,
"no_copy": 0,
@ -325,22 +371,28 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Off",
"description": "",
"fetch_if_empty": 0,
"fieldname": "ssl_tls_mode",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "SSL/TLS Mode",
"length": 0,
"no_copy": 0,
@ -355,21 +407,27 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "No",
"fetch_if_empty": 0,
"fieldname": "require_trusted_certificate",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Require Trusted Certificate",
"length": 0,
"no_copy": 0,
@ -384,53 +442,153 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "local_private_key_file",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Path to private Key File",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "local_server_certificate_file",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Path to Server Certificate",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "local_ca_certs_file",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Path to CA Certs File",
"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,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 1,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2019-01-30 11:02:41.011412",
"modified_by": "Administrator",
"module": "Integrations",
"name": "LDAP Settings",
"name_case": "",
"owner": "Administrator",
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 1,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2019-04-29 10:56:42.322696",
"modified_by": "Administrator",
"module": "Integrations",
"name": "LDAP Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 1,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
],
"quick_entry": 0,
"read_only": 1,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View file

@ -5,56 +5,90 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cstr
from frappe.model.document import Document
class LDAPSettings(Document):
def validate(self):
if not self.flags.ignore_mandatory:
self.validate_ldap_credentails()
if self.ldap_search_string.endswith("={0}"):
if self.enabled:
connect_to_ldap(server_url=self.ldap_server_url,
base_dn=self.base_dn,
password=self.get_password(raise_exception=False),
ssl_tls_mode=self.ssl_tls_mode,
trusted_cert=self.require_trusted_certificate,
private_key_file=self.local_private_key_file,
server_cert_file=self.local_server_certificate_file,
ca_certs_file=self.local_ca_certs_file)
else:
frappe.throw(_("LDAP Search String needs to end with a placeholder, eg sAMAccountName={0}"))
def validate_ldap_credentails(self):
try:
import ldap
conn = ldap.initialize(self.ldap_server_url)
try:
if self.ssl_tls_mode == 'StartTLS':
conn.set_option(ldap.OPT_X_TLS_DEMAND, True)
if self.require_trusted_certificate == 'Yes':
conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
conn.start_tls_s()
except:
frappe.throw(_("StartTLS is not supported"))
conn.simple_bind_s(self.base_dn, self.get_password(raise_exception=False))
except ImportError:
msg = """
<div>
{{_("Seems ldap is not installed on system.<br>Guidelines to install ldap dependancies and python package")}},
<a href="https://discuss.erpnext.com/t/frappe-v-7-1-beta-ldap-dependancies/15841" target="_blank">{{_("Click here")}}</a>,
</div>
"""
frappe.throw(msg, title=_("LDAP Not Installed"))
def get_ldap_client_settings():
#return the settings to be used on the client side.
result = {
"enabled": False
}
settings = frappe.get_doc("LDAP Settings")
except ldap.LDAPError:
conn.unbind_s()
frappe.throw(_("Incorrect UserId or Password"))
if settings and settings.enabled:
result["enabled"] = True
result["method"] = "frappe.integrations.doctype.ldap_settings.ldap_settings.login"
return result
def get_ldap_settings():
def connect_to_ldap(server_url,
base_dn,
password,
ssl_tls_mode,
trusted_cert,
private_key_file,
server_cert_file,
ca_certs_file):
try:
settings = frappe.get_doc("LDAP Settings")
import ldap3
import ssl
if trusted_cert == 'Yes':
tls_configuration = ldap3.Tls(validate=ssl.CERT_REQUIRED,
version=ssl.PROTOCOL_TLSv1)
else:
tls_configuration = ldap3.Tls(validate=ssl.CERT_NONE,
version=ssl.PROTOCOL_TLSv1)
if private_key_file:
tls_configuration.private_key_file = private_key_file
if server_cert_file:
tls_configuration.certificate_file = server_cert_file
if ca_certs_file:
tls_configuration.ca_certs_file = ca_certs_file
server = ldap3.Server(host=server_url,
tls=tls_configuration)
bind_type = ldap3.AUTO_BIND_TLS_BEFORE_BIND if ssl_tls_mode == "StartTLS" else True
conn = ldap3.Connection(server=server,
user=base_dn,
password=password,
auto_bind=bind_type,
read_only=True,
raise_exceptions=True)
return conn
except ImportError:
msg = _("Please Install the ldap3 library via pip to use ldap functionality.")
frappe.throw(msg, title=_("LDAP Not Installed"))
except ldap3.core.exceptions.LDAPInvalidCredentialsResult:
frappe.throw(_("Invalid Credentials"))
except Exception as ex:
frappe.throw(_(str(ex)))
settings.update({
"method": "frappe.integrations.doctype.ldap_settings.ldap_settings.login"
})
return settings
except Exception:
# this will return blank settings
return frappe._dict()
@frappe.whitelist(allow_guest=True)
def login():
#### LDAP LOGIN LOGIC #####
# LDAP LOGIN LOGIC
args = frappe.form_dict
user = authenticate_ldap_user(frappe.as_unicode(args.usr), frappe.as_unicode(args.pwd))
@ -64,64 +98,57 @@ def login():
# because of a GET request!
frappe.db.commit()
def authenticate_ldap_user(user=None, password=None):
dn = None
def authenticate_ldap_user(user=None,
password=None):
params = {}
settings = get_ldap_settings()
settings = frappe.get_doc("LDAP Settings")
if settings and settings.enabled:
conn = connect_to_ldap(server_url=settings.ldap_server_url,
base_dn=settings.base_dn,
password=settings.get_password(raise_exception=False),
ssl_tls_mode=settings.ssl_tls_mode,
trusted_cert=settings.require_trusted_certificate,
private_key_file=settings.local_private_key_file,
server_cert_file=settings.local_server_certificate_file,
ca_certs_file=settings.local_ca_certs_file)
try:
import ldap
except:
msg = """
<div>
{{_("Seems ldap is not installed on system.")}}<br>
<a href"https://discuss.erpnext.com/t/frappe-v-7-1-beta-ldap-dependancies/15841">{{_("Click here")}}</a>,
{{_("Guidelines to install ldap dependancies and python")}}
</div>
"""
frappe.throw(msg, title=_("LDAP Not Installed"))
user_filter = settings.ldap_search_string.format(user)
conn.search(search_base=settings.organizational_unit,
search_filter="({0})".format(user_filter),
attributes=[settings.ldap_email_field,
settings.ldap_username_field,
settings.ldap_first_name_field])
conn = ldap.initialize(settings.ldap_server_url)
try:
try:
# set TLS settings for secure connection
if settings.ssl_tls_mode == 'StartTLS':
conn.set_option(ldap.OPT_X_TLS_DEMAND, True)
if settings.require_trusted_certificate == 'Yes':
conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
conn.start_tls_s()
except:
frappe.throw(_("StartTLS is not supported"))
# simple_bind_s is synchronous binding to server, it takes two param DN and password
conn.simple_bind_s(settings.base_dn, settings.get_password(raise_exception=False))
#search for surnames beginning with a
#available options for how deep a search you want.
#LDAP_SCOPE_BASE, LDAP_SCOPE_ONELEVEL,LDAP_SCOPE_SUBTREE,
result = conn.search_s(settings.organizational_unit, ldap.SCOPE_SUBTREE,
settings.ldap_search_string.format(user))
for dn, r in result:
dn = cstr(dn)
params["email"] = cstr(r[settings.ldap_email_field][0])
params["username"] = cstr(r[settings.ldap_username_field][0])
params["first_name"] = cstr(r[settings.ldap_first_name_field][0])
if dn:
conn.simple_bind_s(dn, frappe.as_unicode(password))
if len(conn.entries) > 0 and conn.entries[0]:
user = conn.entries[0]
params["email"] = str(user[settings.ldap_email_field])
params["username"] = str(user[settings.ldap_username_field])
params["first_name"] = str(user[settings.ldap_first_name_field])
connect_to_ldap(server_url=settings.ldap_server_url,
base_dn=user.entry_dn,
password=frappe.as_unicode(password),
ssl_tls_mode=settings.ssl_tls_mode,
trusted_cert=settings.require_trusted_certificate,
private_key_file=settings.local_private_key_file,
server_cert_file=settings.local_server_certificate_file,
ca_certs_file=settings.local_ca_certs_file
)
return create_user(params)
else:
frappe.throw(_("Not a valid LDAP user"))
else:
frappe.throw(_("LDAP is not enabled."))
except ldap.LDAPError:
conn.unbind_s()
frappe.throw(_("Incorrect UserId or Password"))
def create_user(params):
if frappe.db.exists("User", params["email"]):
return frappe.get_doc("User", params["email"])
user = frappe.get_doc("User", params["email"])
user.first_name = params["first_name"]
user.username = params["username"]
user.save(ignore_permissions=True)
return user
else:
params.update({
@ -135,6 +162,5 @@ def create_user(params):
})
user = frappe.get_doc(params).insert(ignore_permissions=True)
frappe.db.commit()
return user

View file

@ -6,6 +6,7 @@ import re
def execute():
""" Create Contact for each User if not present """
frappe.reload_doc('contacts', 'doctype', 'contact')
frappe.reload_doc('core', 'doctype', 'dynamic_link')
users = frappe.get_all('User', filters={"name": ('not in', 'Administrator, Guest')}, fields=["*"])
for user in users:

View file

@ -167,13 +167,12 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({
}
if(!me.df.only_select) {
if(frappe.model.can_create(doctype)
&& me.df.fieldtype !== "Dynamic Link") {
if(frappe.model.can_create(doctype)) {
// new item
r.results.push({
label: "<span class='text-primary link-option'>"
+ "<i class='fa fa-plus' style='margin-right: 5px;'></i> "
+ __("Create a new {0}", [__(me.df.options)])
+ __("Create a new {0}", [__(me.get_options())])
+ "</span>",
value: "create_new__link_option",
action: me.new_doc

View file

@ -19,6 +19,7 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
let me = this;
this.page_length = 20;
this.start = 0;
let fields = [
{
@ -55,7 +56,12 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
},
{ fieldtype: "Section Break" },
{ fieldtype: "HTML", fieldname: "results_area" },
{ fieldtype: "Button", fieldname: "make_new", label: __("Make a new " + me.doctype) }
{ fieldtype: "Button", fieldname: "more_btn", label: __("More"),
click: function(){
me.start += 20;
me.get_results();
}
}
]);
let doctype_plural = !this.doctype.endsWith('y') ? this.doctype + 's'
@ -65,25 +71,30 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
title: __("Select {0}", [(this.doctype=='[Select]') ? __("value") : __(doctype_plural)]),
fields: fields,
primary_action_label: __("Get Items"),
secondary_action_label: __("Make {0}", [me.doctype]),
primary_action: function() {
me.action(me.get_checked_values(), me.args);
},
secondary_action: function(e) {
// If user wants to close the modal
if (e) {
frappe.route_options = {};
Object.keys(me.setters).forEach(function(setter) {
frappe.route_options[setter] = me.dialog.fields_dict[setter].get_value() || undefined;
});
frappe.new_doc(me.doctype, true);
}
}
});
this.$parent = $(this.dialog.body);
this.$wrapper = this.dialog.fields_dict.results_area.$wrapper.append(`<div class="results"
style="border: 1px solid #d1d8dd; border-radius: 3px; height: 300px; overflow: auto;"></div>`);
this.$results = this.$wrapper.find('.results');
this.$make_new_btn = this.dialog.fields_dict.make_new.$wrapper;
this.$placeholder = $(`<div class="multiselect-empty-state">
<span class="text-center" style="margin-top: -40px;">
<i class="fa fa-2x fa-tags text-extra-muted"></i>
<p class="text-extra-muted">No ${this.doctype} found</p>
<button class="btn btn-default btn-xs text-muted" data-fieldtype="Button"
data-fieldname="make_new" placeholder="" value="">Make a new ${this.doctype}</button>
</span>
</div>`);
this.$results = this.$wrapper.find('.results');
this.$results.append(this.make_list_row());
this.args = {};
@ -94,6 +105,7 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
bind_events: function() {
let me = this;
this.$results.on('click', '.list-item-container', function (e) {
if (!$(e.target).is(':checkbox') && !$(e.target).is('a')) {
$(this).find(':checkbox').trigger('click');
@ -119,14 +131,6 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
me.get_results();
}, 300));
});
this.$parent.on('click', '.btn[data-fieldname="make_new"]', (e) => {
frappe.route_options = {};
Object.keys(this.setters).forEach(function(setter) {
frappe.route_options[setter] = me.dialog.fields_dict[setter].get_value() || undefined;
});
frappe.new_doc(this.doctype, true);
});
},
get_checked_values: function() {
@ -170,23 +174,21 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
render_result_list: function(results, more = 0) {
var me = this;
this.$results.empty();
if(results.length === 0) {
this.$make_new_btn.addClass('hide');
this.$results.append(me.$placeholder);
return;
}
this.$make_new_btn.removeClass('hide');
this.$results.append(this.make_list_row());
var more_btn = me.dialog.fields_dict.more_btn.$wrapper;
if(results.length === 0) {
this.$results.empty();
more_btn.hide();
return;
} else {
more_btn.show();
}
results.forEach((result) => {
me.$results.append(me.make_list_row(result));
})
if (more) {
let message = __("Only {0} entries shown. Please filter for more specific results.", [this.page_length]);
me.$results.append($(`<div class="text-muted small" style="text-align: center;
margin: 10px;">${message}</div>`));
}
});
this.$results.animate({scrollTop: me.$results.prop('scrollHeight')}, 500);
},
get_results: function() {
@ -208,6 +210,7 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
txt: me.dialog.fields_dict["search_term"].get_value(),
filters: filters,
filter_fields: Object.keys(me.setters).concat([me.date_field]),
start: this.start,
page_length: this.page_length + 1,
query: this.get_query ? this.get_query().query : '',
as_dict: 1
@ -219,8 +222,8 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
args: args,
callback: function(r) {
let results = [], more = 0;
if(r.values.length) {
if(r.values.length > me.page_length){
if (r.values.length) {
if (r.values.length > me.page_length) {
r.values.pop();
more = 1;
}
@ -241,7 +244,9 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
});
// Preselect oldest entry
results[0].checked = 1
if (me.start < 1) {
results[0].checked = 1;
}
}
me.render_result_list(results, more);
}

View file

@ -31,15 +31,17 @@
{% var value = col.fieldname ? row[col.fieldname] : row[col.id]; %}
<td>
{{
col.formatter
? col.formatter(row._index, col._index, value, col, row, true)
: col.format
? col.format(value, row, col, data)
: col.docfield
? frappe.format(value, col.docfield)
: value
}}
<span {% if col._index == 0 %} style="padding-left: {%= cint(row.indent) * 2 %}em" {% endif %}>
{{
col.formatter
? col.formatter(row._index, col._index, value, col, row, true)
: col.format
? col.format(value, row, col, data)
: col.docfield
? frappe.format(value, col.docfield)
: value
}}
</span>
</td>
{% endif %}
{% endfor %}

View file

@ -999,9 +999,11 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
change: () => {
let doctype = d.get_value('doctype');
frappe.model.with_doctype(doctype, () => {
let fields = frappe.meta.get_docfields(doctype)
let options = frappe.meta.get_docfields(doctype)
.filter(frappe.model.is_value_type)
.map(df => ({ label: df.label, value: df.fieldname }));
d.set_df_property('field', 'options', fields);
d.set_df_property('field', 'options', options);
});
}

View file

@ -66,7 +66,7 @@ login.bind_events = function() {
}
});
{% if ldap_settings %}
{% if ldap_settings.enabled %}
$(".btn-ldap-login").on("click", function(){
var args = {};
args.cmd = "{{ ldap_settings.method }}";

View file

@ -8,7 +8,7 @@ from frappe.utils.oauth import get_oauth2_authorize_url, get_oauth_keys, login_v
import json
from frappe import _
from frappe.auth import LoginManager
from frappe.integrations.doctype.ldap_settings.ldap_settings import get_ldap_settings
from frappe.integrations.doctype.ldap_settings.ldap_settings import get_ldap_client_settings
from frappe.utils.password import get_decrypted_password
from frappe.utils.html_utils import get_icon_html
@ -38,8 +38,7 @@ def get_context(context):
"icon": icon
})
context["social_login"] = True
ldap_settings = get_ldap_settings()
ldap_settings = get_ldap_client_settings()
context["ldap_settings"] = ldap_settings
login_name_placeholder = [_("Email address")]