diff --git a/frappe/__init__.py b/frappe/__init__.py
index 75684d431e..cd662cd6cb 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -187,15 +187,20 @@ def connect(site=None, db_name=None):
local.db = get_db(user=db_name or local.conf.db_name)
set_user("Administrator")
-def connect_read_only():
+def connect_replica():
from frappe.database import get_db
+ user = local.conf.db_name
+ password = local.conf.db_password
- local.read_only_db = get_db(host=local.conf.slave_host, user=local.conf.slave_db_name,
- password=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 = get_db(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
diff --git a/frappe/commands/site.py b/frappe/commands/site.py
index 3798c07e91..ee8131c1dc 100755
--- a/frappe/commands/site.py
+++ b/frappe/commands/site.py
@@ -157,16 +157,17 @@ def _reinstall(site, admin_password=None, mariadb_root_username=None, mariadb_ro
admin_password=admin_password)
@click.command('install-app')
-@click.argument('app')
+@click.argument('apps', nargs=-1)
@pass_context
-def install_app(context, app):
- "Install a new app to site"
+def install_app(context, apps):
+ "Install a new app to site, supports multiple apps"
from frappe.installer import install_app as _install_app
for site in context.sites:
frappe.init(site=site)
frappe.connect()
try:
- _install_app(app, verbose=context.verbose)
+ for app in apps:
+ _install_app(app, verbose=context.verbose)
finally:
frappe.destroy()
diff --git a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py
index 0907476b30..f73858c8ab 100644
--- a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py
+++ b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
from six import iteritems
import frappe
-
+from frappe import _
field_map = {
"Contact": [ "first_name", "last_name", "phone", "mobile_no", "email_id", "is_primary_contact" ],
@@ -94,6 +94,9 @@ def get_reference_details(reference_doctype, doctype, reference_list, reference_
for d in records:
temp_records.append(d[1:])
+ if not reference_list:
+ frappe.throw(_("No records present in {0}".format(reference_doctype)))
+
reference_details[reference_list[0]][frappe.scrub(doctype)] = temp_records
return reference_details
diff --git a/frappe/core/doctype/activity_log/activity_log.py b/frappe/core/doctype/activity_log/activity_log.py
index 7badf737e4..8b7941c086 100644
--- a/frappe/core/doctype/activity_log/activity_log.py
+++ b/frappe/core/doctype/activity_log/activity_log.py
@@ -6,7 +6,7 @@ from __future__ import unicode_literals
from frappe import _
from frappe.utils import get_fullname, now
from frappe.model.document import Document
-from frappe.core.utils import get_parent_doc, set_timeline_doc
+from frappe.core.utils import set_timeline_doc
import frappe
class ActivityLog(Document):
diff --git a/frappe/core/doctype/comment/test_comment.py b/frappe/core/doctype/comment/test_comment.py
index 2f6583b7ba..2adc5eb899 100644
--- a/frappe/core/doctype/comment/test_comment.py
+++ b/frappe/core/doctype/comment/test_comment.py
@@ -48,10 +48,10 @@ class TestComment(unittest.TestCase):
add_comment('pleez vizits my site http://mysite.com', 'test@test.com', 'bad commentor',
'Blog Post', test_blog.name, test_blog.route)
- self.assertEqual(frappe.get_all('Comment', fields = ['*'], filters = dict(
+ self.assertEqual(len(frappe.get_all('Comment', fields = ['*'], filters = dict(
reference_doctype = test_blog.doctype,
reference_name = test_blog.name
- ))[0].published, 0)
+ ))), 0)
diff --git a/frappe/core/doctype/communication/communication.json b/frappe/core/doctype/communication/communication.json
index 3b845964f4..34d8d29bdb 100644
--- a/frappe/core/doctype/communication/communication.json
+++ b/frappe/core/doctype/communication/communication.json
@@ -1,7 +1,7 @@
{
"allow_import": 1,
"creation": "2013-01-29 10:47:14",
- "description": "Keep a track of all communications",
+ "description": "Keeps track of all communications",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
@@ -41,14 +41,11 @@
"user",
"column_break_27",
"email_template",
- "link_doctype",
- "link_name",
- "timeline_doctype",
- "timeline_name",
- "timeline_label",
"unread_notification_sent",
"seen",
"_user_tags",
+ "timeline_links_sections",
+ "timeline_links",
"email_inbox",
"message_id",
"uid",
@@ -204,6 +201,7 @@
"label": "Date"
},
{
+ "default": "0",
"fieldname": "read_receipt",
"fieldtype": "Check",
"label": "Sent Read Receipt",
@@ -220,6 +218,7 @@
"read_only": 1
},
{
+ "default": "0",
"fieldname": "read_by_recipient",
"fieldtype": "Check",
"label": "Read by Recipient",
@@ -284,39 +283,6 @@
"fieldname": "column_break_27",
"fieldtype": "Column Break"
},
- {
- "fieldname": "link_doctype",
- "fieldtype": "Link",
- "label": "Link DocType",
- "options": "DocType",
- "read_only": 1
- },
- {
- "fieldname": "link_name",
- "fieldtype": "Dynamic Link",
- "label": "Link Name",
- "options": "link_doctype",
- "read_only": 1
- },
- {
- "fieldname": "timeline_doctype",
- "fieldtype": "Link",
- "label": "Timeline DocType",
- "options": "DocType",
- "read_only": 1
- },
- {
- "fieldname": "timeline_name",
- "fieldtype": "Dynamic Link",
- "label": "Timeline Name",
- "options": "timeline_doctype",
- "read_only": 1
- },
- {
- "fieldname": "timeline_label",
- "fieldtype": "Data",
- "label": "Timeline field Name"
- },
{
"default": "0",
"fieldname": "unread_notification_sent",
@@ -325,6 +291,7 @@
"read_only": 1
},
{
+ "default": "0",
"fieldname": "seen",
"fieldtype": "Check",
"label": "Seen",
@@ -368,6 +335,7 @@
"options": "Open\nSpam\nTrash"
},
{
+ "default": "0",
"fieldname": "has_attachment",
"fieldtype": "Check",
"hidden": 1,
@@ -398,11 +366,24 @@
"label": "Email Template",
"options": "Email Template",
"read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "timeline_links_sections",
+ "fieldtype": "Section Break",
+ "label": "Timeline Links"
+ },
+ {
+ "fieldname": "timeline_links",
+ "fieldtype": "Table",
+ "label": "Timeline Links",
+ "options": "Communication Link",
+ "permlevel": 2
}
],
"icon": "fa fa-comment",
"idx": 1,
- "modified": "2019-05-04 15:36:35.818714",
+ "modified": "2019-05-21 09:48:24.892143",
"modified_by": "Administrator",
"module": "Core",
"name": "Communication",
@@ -428,6 +409,18 @@
"role": "System Manager",
"share": 1
},
+ {
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "permlevel": 2,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
{
"delete": 1,
"email": 1,
@@ -437,6 +430,7 @@
}
],
"search_fields": "subject",
+ "sort_field": "modified",
"sort_order": "DESC",
"title_field": "subject",
"track_changes": 1,
diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py
index 77ccefba71..7ed4aea4b5 100644
--- a/frappe/core/doctype/communication/communication.py
+++ b/frappe/core/doctype/communication/communication.py
@@ -8,11 +8,11 @@ from frappe.model.document import Document
from frappe.utils import validate_email_address, get_fullname, strip_html, cstr
from frappe.core.doctype.communication.email import (validate_email,
notify, _notify, update_parent_mins_to_first_response)
-from frappe.core.utils import get_parent_doc, set_timeline_doc
+from frappe.core.utils import get_parent_doc
from frappe.utils.bot import BotReply
from frappe.utils import parse_addr
from frappe.core.doctype.comment.comment import update_comment_in_doc
-
+from email.utils import parseaddr
from collections import Counter
exclude_from_linked_with = True
@@ -58,7 +58,10 @@ class Communication(Document):
self.set_sender_full_name()
validate_email(self)
- set_timeline_doc(self)
+
+ if self.communication_medium == "Email":
+ self.set_timeline_links()
+ self.deduplicate_timeline_links()
def validate_reference(self):
if self.reference_doctype and self.reference_name:
@@ -79,6 +82,7 @@ class Communication(Document):
circular_linking = True
break
doc = get_parent_doc(doc)
+
if circular_linking:
frappe.throw(_("Please make sure the Reference Communication Docs are not circularly linked."), frappe.CircularLinkingError)
@@ -231,26 +235,67 @@ class Communication(Document):
if commit:
frappe.db.commit()
+ # Timeline Links
+ def set_timeline_links(self):
+ contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc])
+ for contact_name in contacts:
+ self.add_link('Contact', contact_name)
+
+ #link contact's dynamic links to communication
+ add_contact_links_to_communication(self, contact_name)
+
+ def deduplicate_timeline_links(self):
+ if self.timeline_links:
+ links, duplicate = [], False
+
+ for l in self.timeline_links:
+ t = (l.link_doctype, l.link_name)
+ if not t in links:
+ links.append(t)
+ else:
+ duplicate = True
+
+ if duplicate:
+ del self.timeline_links[:] # make it python 2 compatible as list.clear() is python 3 only
+ for l in links:
+ self.add_link(link_doctype=l[0], link_name=l[1])
+
+ def add_link(self, link_doctype, link_name, autosave=False):
+ self.append("timeline_links",
+ {
+ "link_doctype": link_doctype,
+ "link_name": link_name
+ }
+ )
+
+ if autosave:
+ self.save(ignore_permissions=True)
+
+ def get_links(self):
+ return self.timeline_links
+
+ def remove_link(self, link_doctype, link_name, autosave=False, ignore_permissions=True):
+ for l in self.timeline_links:
+ if l.link_doctype == link_doctype and l.link_name == link_name:
+ self.timeline_links.remove(l)
+
+ if autosave:
+ self.save(ignore_permissions=ignore_permissions)
def on_doctype_update():
"""Add indexes in `tabCommunication`"""
frappe.db.add_index("Communication", ["reference_doctype", "reference_name"])
- frappe.db.add_index("Communication", ["timeline_doctype", "timeline_name"])
- frappe.db.add_index("Communication", ["link_doctype", "link_name"])
frappe.db.add_index("Communication", ["status", "communication_type"])
+ frappe.db.add_index("Communication Link", ["link_doctype", "link_name"])
def has_permission(doc, ptype, user):
if ptype=="read":
- if (doc.reference_doctype == "Communication" and doc.reference_name == doc.name) \
- or (doc.timeline_doctype == "Communication" and doc.timeline_name == doc.name):
- return
+ if doc.reference_doctype == "Communication" and doc.reference_name == doc.name:
+ return
if doc.reference_doctype and doc.reference_name:
if frappe.has_permission(doc.reference_doctype, ptype="read", doc=doc.reference_name):
return True
- if doc.timeline_doctype and doc.timeline_name:
- if frappe.has_permission(doc.timeline_doctype, ptype="read", doc=doc.timeline_name):
- return True
def get_permission_query_conditions_for_communication(user):
if not user: user = frappe.session.user
@@ -265,8 +310,44 @@ def get_permission_query_conditions_for_communication(user):
distinct=True, order_by="idx")
if not accounts:
- return """tabCommunication.communication_medium!='Email'"""
+ return """`tabCommunication`.communication_medium!='Email'"""
email_accounts = [ '"%s"'%account.get("email_account") for account in accounts ]
- return """tabCommunication.email_account in ({email_accounts})"""\
+ return """`tabCommunication`.email_account in ({email_accounts})"""\
.format(email_accounts=','.join(email_accounts))
+
+def get_contacts(email_strings):
+ email_addrs = []
+
+ for email_string in email_strings:
+ if email_string:
+ for email in email_string.split(","):
+ parsed_email = parseaddr(email)[1]
+ if parsed_email:
+ email_addrs.append(parsed_email)
+
+ contacts = []
+ for email in email_addrs:
+ contact_name = frappe.db.get_value('Contact', {'email_id': email})
+
+ if not contact_name:
+ contact = frappe.get_doc({
+ "doctype": "Contact",
+ "first_name": frappe.unscrub(email.split("@")[0]),
+ "email_id": email
+ }).insert(ignore_permissions=True)
+ contact_name = contact.name
+
+ contacts.append(contact_name)
+
+ return contacts
+
+def add_contact_links_to_communication(communication, contact_name):
+ contact_links = frappe.get_list("Dynamic Link", filters={
+ "parenttype": "Contact",
+ "parent": contact_name
+ }, fields=["link_doctype", "link_name"])
+
+ if contact_links:
+ for contact_link in contact_links:
+ communication.add_link(contact_link.link_doctype, contact_link.link_name)
\ No newline at end of file
diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py
index 0c68f8b118..36377a90f7 100755
--- a/frappe/core/doctype/communication/email.py
+++ b/frappe/core/doctype/communication/email.py
@@ -71,12 +71,9 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
"message_id":get_message_id().strip(" <>"),
"read_receipt":read_receipt,
"has_attachment": 1 if attachments else 0
- })
- comm.insert(ignore_permissions=True)
+ }).insert(ignore_permissions=True)
- if not doctype:
- # if no reference given, then send it against the communication
- comm.db_set(dict(reference_doctype='Communication', reference_name=comm.name))
+ comm.save(ignore_permissions=True)
if isinstance(attachments, string_types):
attachments = json.loads(attachments)
@@ -557,5 +554,4 @@ def mark_email_as_seen(name=None):
frappe.response["type"] = 'binary'
frappe.response["filename"] = "imaginary_pixel.png"
- frappe.response["filecontent"] = buffered_obj.getvalue()
-
+ frappe.response["filecontent"] = buffered_obj.getvalue()
\ No newline at end of file
diff --git a/frappe/core/doctype/communication/test_communication.py b/frappe/core/doctype/communication/test_communication.py
index 1941ff31cc..21756280a9 100644
--- a/frappe/core/doctype/communication/test_communication.py
+++ b/frappe/core/doctype/communication/test_communication.py
@@ -44,28 +44,130 @@ class TestCommunication(unittest.TestCase):
self.assertFalse(frappe.utils.parse_addr(x)[0])
def test_circular_linking(self):
- content = "This was created to test circular linking"
a = frappe.get_doc({
"doctype": "Communication",
"communication_type": "Communication",
- "content": content,
- }).insert()
+ "content": "This was created to test circular linking: Communication A",
+ }).insert(ignore_permissions=True)
+
b = frappe.get_doc({
"doctype": "Communication",
"communication_type": "Communication",
- "content": content,
+ "content": "This was created to test circular linking: Communication B",
"reference_doctype": "Communication",
"reference_name": a.name
- }).insert()
+ }).insert(ignore_permissions=True)
+
c = frappe.get_doc({
"doctype": "Communication",
"communication_type": "Communication",
- "content": content,
+ "content": "This was created to test circular linking: Communication C",
"reference_doctype": "Communication",
"reference_name": b.name
- }).insert()
+ }).insert(ignore_permissions=True)
+
a = frappe.get_doc("Communication", a.name)
a.reference_doctype = "Communication"
a.reference_name = c.name
+
self.assertRaises(frappe.CircularLinkingError, a.save)
+ def test_deduplication_timeline_links(self):
+ frappe.delete_doc_if_exists("Note", "deduplication timeline links")
+
+ note = frappe.get_doc({
+ "doctype": "Note",
+ "title": "deduplication timeline links",
+ "content": "deduplication timeline links"
+ }).insert(ignore_permissions=True)
+
+ comm = frappe.get_doc({
+ "doctype": "Communication",
+ "communication_type": "Communication",
+ "content": "Deduplication of Links",
+ "communication_medium": "Email"
+ }).insert(ignore_permissions=True)
+
+ #adding same link twice
+ comm.add_link(link_doctype="Note", link_name=note.name, autosave=True)
+ comm.add_link(link_doctype="Note", link_name=note.name, autosave=True)
+
+ comm = frappe.get_doc("Communication", comm.name)
+
+ self.assertNotEqual(2, len(comm.timeline_links))
+
+ def test_contacts_attached(self):
+ contact_sender = frappe.get_doc({
+ "doctype": "Contact",
+ "first_name": frappe.generate_hash(length=10),
+ "email_id": "comm_sender@example.com"
+ }).insert(ignore_permissions=True)
+
+ contact_recipient = frappe.get_doc({
+ "doctype": "Contact",
+ "first_name": frappe.generate_hash(length=10),
+ "email_id": "comm_recipient@example.com"
+ }).insert(ignore_permissions=True)
+
+ contact_cc = frappe.get_doc({
+ "doctype": "Contact",
+ "first_name": frappe.generate_hash(length=10),
+ "email_id": "comm_cc@example.com"
+ }).insert(ignore_permissions=True)
+
+ comm = frappe.get_doc({
+ "doctype": "Communication",
+ "communication_medium": "Email",
+ "subject": "Contacts Attached Test",
+ "sender": "comm_sender@example.com",
+ "recipients": "comm_recipient@example.com",
+ "cc": "comm_cc@example.com"
+ }).insert(ignore_permissions=True)
+
+ comm = frappe.get_doc("Communication", comm.name)
+
+ contact_links = []
+ for timeline_link in comm.timeline_links:
+ contact_links.append(timeline_link.link_name)
+
+ self.assertIn(contact_sender.name, contact_links)
+ self.assertIn(contact_recipient.name, contact_links)
+ self.assertIn(contact_cc.name, contact_links)
+
+ def test_get_communication_data(self):
+ from frappe.desk.form.load import get_communication_data
+
+ frappe.delete_doc_if_exists("Note", "get communication data")
+
+ note = frappe.get_doc({
+ "doctype": "Note",
+ "title": "get communication data",
+ "content": "get communication data"
+ }).insert(ignore_permissions=True)
+
+ comm_note_1 = frappe.get_doc({
+ "doctype": "Communication",
+ "communication_type": "Communication",
+ "content": "Test Get Communication Data 1",
+ "communication_medium": "Email"
+ }).insert(ignore_permissions=True)
+
+ comm_note_1.add_link(link_doctype="Note", link_name=note.name, autosave=True)
+
+ comm_note_2 = frappe.get_doc({
+ "doctype": "Communication",
+ "communication_type": "Communication",
+ "content": "Test Get Communication Data 2",
+ "communication_medium": "Email"
+ }).insert(ignore_permissions=True)
+
+ comm_note_2.add_link(link_doctype="Note", link_name=note.name, autosave=True)
+
+ comms = get_communication_data("Note", note.name, as_dict=True)
+
+ data = []
+ for comm in comms:
+ data.append(comm.name)
+
+ self.assertIn(comm_note_1.name, data)
+ self.assertIn(comm_note_2.name, data)
\ No newline at end of file
diff --git a/frappe/core/doctype/communication_link/__init__.py b/frappe/core/doctype/communication_link/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/core/doctype/communication_link/communication_link.json b/frappe/core/doctype/communication_link/communication_link.json
new file mode 100644
index 0000000000..1dd051bed2
--- /dev/null
+++ b/frappe/core/doctype/communication_link/communication_link.json
@@ -0,0 +1,47 @@
+{
+ "creation": "2019-05-21 09:47:23.043960",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "link_doctype",
+ "link_name",
+ "link_title"
+ ],
+ "fields": [
+ {
+ "fieldname": "link_doctype",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Link DocType",
+ "options": "DocType",
+ "reqd": 1
+ },
+ {
+ "fieldname": "link_name",
+ "fieldtype": "Dynamic Link",
+ "in_list_view": 1,
+ "label": "Link Name",
+ "options": "link_doctype",
+ "reqd": 1
+ },
+ {
+ "fieldname": "link_title",
+ "fieldtype": "Read Only",
+ "in_list_view": 1,
+ "label": "Link Title",
+ "read_only": 1
+ }
+ ],
+ "istable": 1,
+ "modified": "2019-05-21 09:47:23.043960",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Communication Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/communication_link/communication_link.py b/frappe/core/doctype/communication_link/communication_link.py
new file mode 100644
index 0000000000..0328f2b86a
--- /dev/null
+++ b/frappe/core/doctype/communication_link/communication_link.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class CommunicationLink(Document):
+ pass
diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json
index 599427f740..24a7a4c287 100644
--- a/frappe/core/doctype/doctype/doctype.json
+++ b/frappe/core/doctype/doctype/doctype.json
@@ -1,1889 +1,457 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 1,
- "autoname": "Prompt",
- "beta": 0,
- "creation": "2013-02-18 13:36:19",
- "custom": 0,
- "description": "DocType is a Table / Form in the application.",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 0,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "sb0",
- "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": "",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "permlevel": 0,
- "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": "module",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Module",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "module",
- "oldfieldtype": "Link",
- "options": "Module Def",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 1,
- "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,
- "depends_on": "eval:!doc.istable",
- "description": "Once submitted, submittable documents cannot be changed. They can only be Cancelled and Amended.",
- "fetch_if_empty": 0,
- "fieldname": "is_submittable",
- "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": "Is Submittable",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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,
- "description": "Child Tables are shown as a Grid in other DocTypes",
- "fetch_if_empty": 0,
- "fieldname": "istable",
- "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": 1,
- "label": "Is Child Table",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "istable",
- "oldfieldtype": "Check",
- "permlevel": 0,
- "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,
- "depends_on": "eval:!doc.istable",
- "description": "Single Types have only one record no tables associated. Values are stored in tabSingles",
- "fetch_if_empty": 0,
- "fieldname": "issingle",
- "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": 1,
- "label": "Is Single",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "issingle",
- "oldfieldtype": "Check",
- "permlevel": 0,
- "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": 1,
- "translatable": 0,
- "unique": 0
- },
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "1",
- "depends_on": "istable",
- "fetch_if_empty": 0,
- "fieldname": "editable_grid",
- "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": "Editable Grid",
- "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,
- "default": "1",
- "depends_on": "eval:!doc.istable && !doc.issingle",
- "description": "Open a dialog with mandatory fields to create a new record quickly",
- "fetch_if_empty": 0,
- "fieldname": "quick_entry",
- "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": "Quick Entry",
- "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": "cb01",
- "fieldtype": "Column 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,
- "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,
- "default": "1",
- "depends_on": "eval:!doc.istable",
- "description": "If enabled, changes to the document are tracked and shown in timeline",
- "fetch_if_empty": 0,
- "fieldname": "track_changes",
- "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": "Track Changes",
- "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,
- "depends_on": "eval:!doc.istable",
- "description": "If enabled, the document is marked as seen, the first time a user opens it",
- "fetch_if_empty": 0,
- "fieldname": "track_seen",
- "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": "Track Seen",
- "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,
- "default": "0",
- "depends_on": "eval:!doc.istable",
- "description": "If enabled, document views are tracked, this can happen multiple times",
- "fetch_if_empty": 0,
- "fieldname": "track_views",
- "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": "Track Views",
- "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": "custom",
- "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": "Custom?",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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": "beta",
- "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": "Beta",
- "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": "fields_section_break",
- "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": "Fields",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "permlevel": 0,
- "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": "fields",
- "fieldtype": "Table",
- "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": "Fields",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "fields",
- "oldfieldtype": "Table",
- "options": "DocField",
- "permlevel": 0,
- "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": "sb1",
- "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": "Naming",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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,
- "description": "Naming Options:\n
field:[fieldname] - By Fieldnaming_series: - By Naming Series (field called naming_series must be presentPrompt - Prompt user for a name[series] - Series by prefix (separated by a dot); for example PRE.##### \nformat:EXAMPLE-{MM}morewords{fieldname1}-{fieldname2}-{#####} - Replace all braced words (fieldnames, date words (DD, MM, YY), series) with their value. Outside braces, any characters can be used. ",
- "fetch_if_empty": 0,
- "fieldname": "autoname",
- "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": "Auto Name",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "autoname",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "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": "name_case",
- "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": "Name Case",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "name_case",
- "oldfieldtype": "Select",
- "options": "\nTitle Case\nUPPER CASE",
- "permlevel": 0,
- "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,
- "depends_on": "",
- "fetch_if_empty": 0,
- "fieldname": "column_break_15",
- "fieldtype": "Column 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,
- "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": "description",
- "fieldtype": "Small Text",
- "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": "Description",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "description",
- "oldfieldtype": "Text",
- "permlevel": 0,
- "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": 1,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "form_settings_section",
- "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": "Form Settings",
- "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,
- "description": "Must be of type \"Attach Image\"",
- "fetch_if_empty": 0,
- "fieldname": "image_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": "Image 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": 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,
- "depends_on": "eval:!doc.istable",
- "description": "Comments and Communications will be associated with this linked document",
- "fetch_if_empty": 0,
- "fieldname": "timeline_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": "Timeline 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": 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": "max_attachments",
- "fieldtype": "Int",
- "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": "Max Attachments",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "max_attachments",
- "oldfieldtype": "Int",
- "permlevel": 0,
- "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": "column_break_23",
- "fieldtype": "Column 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": "hide_toolbar",
- "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": "Hide Sidebar and Menu",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "hide_toolbar",
- "oldfieldtype": "Check",
- "permlevel": 0,
- "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,
- "description": "",
- "fetch_if_empty": 0,
- "fieldname": "allow_copy",
- "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": "Hide Copy",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "allow_copy",
- "oldfieldtype": "Check",
- "permlevel": 0,
- "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": "allow_rename",
- "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": "Allow Rename",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "allow_rename",
- "oldfieldtype": "Check",
- "permlevel": 0,
- "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,
- "description": "",
- "fetch_if_empty": 0,
- "fieldname": "allow_import",
- "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": "Allow Import (via Data Import Tool)",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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": "allow_events_in_timeline",
- "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": "Allow events in timeline",
- "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": 1,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "view_settings",
- "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": "View Settings",
- "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,
- "depends_on": "eval:!doc.istable",
- "description": "",
- "fetch_if_empty": 0,
- "fieldname": "title_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": "Title Field",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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,
- "depends_on": "eval:!doc.istable",
- "fetch_if_empty": 0,
- "fieldname": "search_fields",
- "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": "Search Fields",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "search_fields",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "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": "default_print_format",
- "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": "Default Print Format",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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,
- "default": "modified",
- "depends_on": "eval:!doc.istable",
- "description": "",
- "fetch_if_empty": 0,
- "fieldname": "sort_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": "Default Sort Field",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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,
- "default": "DESC",
- "depends_on": "eval:!doc.istable",
- "fetch_if_empty": 0,
- "fieldname": "sort_order",
- "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": "Default Sort Order",
- "length": 0,
- "no_copy": 0,
- "options": "ASC\nDESC",
- "permlevel": 0,
- "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": "column_break_29",
- "fieldtype": "Column 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,
- "description": "",
- "fetch_if_empty": 0,
- "fieldname": "document_type",
- "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": "Show in Module Section",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "document_type",
- "oldfieldtype": "Select",
- "options": "\nDocument\nSetup\nSystem\nOther",
- "permlevel": 0,
- "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": "icon",
- "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": "Icon",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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": "color",
- "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": "Color",
- "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": "show_name_in_global_search",
- "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": "Make \"name\" searchable in Global Search",
- "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,
- "depends_on": "eval:!doc.istable",
- "fetch_if_empty": 0,
- "fieldname": "sb2",
- "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": "Permission Rules",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "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,
- "depends_on": "",
- "fetch_if_empty": 0,
- "fieldname": "permissions",
- "fieldtype": "Table",
- "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": "Permissions",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "permissions",
- "oldfieldtype": "Table",
- "options": "DocPerm",
- "permlevel": 0,
- "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": "restrict_to_domain",
- "fieldtype": "Link",
- "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": "Restrict To Domain",
- "length": 0,
- "no_copy": 0,
- "options": "Domain",
- "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": "read_only",
- "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": "User Cannot Search",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "read_only",
- "oldfieldtype": "Check",
- "permlevel": 0,
- "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": "in_create",
- "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": "User Cannot Create",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "in_create",
- "oldfieldtype": "Check",
- "permlevel": 0,
- "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,
- "description": "",
- "fetch_if_empty": 0,
- "fieldname": "web_view",
- "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": "Web View",
- "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,
- "default": "0",
- "fetch_if_empty": 0,
- "fieldname": "has_web_view",
- "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": "Has Web View",
- "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,
- "default": "0",
- "depends_on": "has_web_view",
- "fetch_if_empty": 0,
- "fieldname": "allow_guest_to_view",
- "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": "Allow Guest to View",
- "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,
- "depends_on": "has_web_view",
- "fetch_if_empty": 0,
- "fieldname": "route",
- "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": "Route",
- "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,
- "default": "",
- "depends_on": "has_web_view",
- "fetch_if_empty": 0,
- "fieldname": "is_published_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": "Is Published 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": 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": 1,
- "columns": 0,
- "fetch_if_empty": 0,
- "fieldname": "advanced",
- "fieldtype": "Section Break",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Advanced",
- "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,
- "default": "InnoDB",
- "depends_on": "eval:!doc.issingle",
- "fetch_if_empty": 0,
- "fieldname": "engine",
- "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": "Database Engine",
- "length": 0,
- "no_copy": 0,
- "options": "InnoDB\nMyISAM",
- "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,
- "icon": "fa fa-bolt",
- "idx": 6,
- "image_field": "",
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "menu_index": 0,
- "modified": "2019-03-22 00:02:14.963400",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "DocType",
- "owner": "Administrator",
- "permissions": [
- {
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 1
- },
- {
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Administrator",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 1
- }
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "search_fields": "module",
- "show_name_in_global_search": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
- }
\ No newline at end of file
+ "allow_rename": 1,
+ "autoname": "Prompt",
+ "creation": "2013-02-18 13:36:19",
+ "description": "DocType is a Table / Form in the application.",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "engine": "InnoDB",
+ "field_order": [
+ "sb0",
+ "module",
+ "is_submittable",
+ "istable",
+ "issingle",
+ "editable_grid",
+ "quick_entry",
+ "cb01",
+ "track_changes",
+ "track_seen",
+ "track_views",
+ "custom",
+ "beta",
+ "fields_section_break",
+ "fields",
+ "sb1",
+ "autoname",
+ "name_case",
+ "column_break_15",
+ "description",
+ "form_settings_section",
+ "image_field",
+ "timeline_field",
+ "max_attachments",
+ "column_break_23",
+ "hide_toolbar",
+ "allow_copy",
+ "allow_rename",
+ "allow_import",
+ "allow_events_in_timeline",
+ "view_settings",
+ "title_field",
+ "search_fields",
+ "default_print_format",
+ "sort_field",
+ "sort_order",
+ "column_break_29",
+ "document_type",
+ "icon",
+ "color",
+ "show_preview_popup",
+ "show_name_in_global_search",
+ "sb2",
+ "permissions",
+ "restrict_to_domain",
+ "read_only",
+ "in_create",
+ "web_view",
+ "has_web_view",
+ "allow_guest_to_view",
+ "route",
+ "is_published_field",
+ "advanced",
+ "engine"
+ ],
+ "fields": [
+ {
+ "fieldname": "sb0",
+ "fieldtype": "Section Break",
+ "oldfieldtype": "Section Break"
+ },
+ {
+ "fieldname": "module",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Module",
+ "oldfieldname": "module",
+ "oldfieldtype": "Link",
+ "options": "Module Def",
+ "reqd": 1,
+ "search_index": 1
+ },
+ {
+ "depends_on": "eval:!doc.istable",
+ "description": "Once submitted, submittable documents cannot be changed. They can only be Cancelled and Amended.",
+ "fieldname": "is_submittable",
+ "fieldtype": "Check",
+ "label": "Is Submittable"
+ },
+ {
+ "description": "Child Tables are shown as a Grid in other DocTypes",
+ "fieldname": "istable",
+ "fieldtype": "Check",
+ "in_standard_filter": 1,
+ "label": "Is Child Table",
+ "oldfieldname": "istable",
+ "oldfieldtype": "Check"
+ },
+ {
+ "depends_on": "eval:!doc.istable",
+ "description": "Single Types have only one record no tables associated. Values are stored in tabSingles",
+ "fieldname": "issingle",
+ "fieldtype": "Check",
+ "in_standard_filter": 1,
+ "label": "Is Single",
+ "oldfieldname": "issingle",
+ "oldfieldtype": "Check",
+ "set_only_once": 1
+ },
+ {
+ "default": "1",
+ "depends_on": "istable",
+ "fieldname": "editable_grid",
+ "fieldtype": "Check",
+ "label": "Editable Grid"
+ },
+ {
+ "default": "1",
+ "depends_on": "eval:!doc.istable && !doc.issingle",
+ "description": "Open a dialog with mandatory fields to create a new record quickly",
+ "fieldname": "quick_entry",
+ "fieldtype": "Check",
+ "label": "Quick Entry"
+ },
+ {
+ "fieldname": "cb01",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "1",
+ "depends_on": "eval:!doc.istable",
+ "description": "If enabled, changes to the document are tracked and shown in timeline",
+ "fieldname": "track_changes",
+ "fieldtype": "Check",
+ "label": "Track Changes"
+ },
+ {
+ "depends_on": "eval:!doc.istable",
+ "description": "If enabled, the document is marked as seen, the first time a user opens it",
+ "fieldname": "track_seen",
+ "fieldtype": "Check",
+ "label": "Track Seen"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:!doc.istable",
+ "description": "If enabled, document views are tracked, this can happen multiple times",
+ "fieldname": "track_views",
+ "fieldtype": "Check",
+ "label": "Track Views"
+ },
+ {
+ "fieldname": "custom",
+ "fieldtype": "Check",
+ "label": "Custom?"
+ },
+ {
+ "fieldname": "beta",
+ "fieldtype": "Check",
+ "label": "Beta"
+ },
+ {
+ "fieldname": "fields_section_break",
+ "fieldtype": "Section Break",
+ "label": "Fields",
+ "oldfieldtype": "Section Break"
+ },
+ {
+ "fieldname": "fields",
+ "fieldtype": "Table",
+ "label": "Fields",
+ "oldfieldname": "fields",
+ "oldfieldtype": "Table",
+ "options": "DocField"
+ },
+ {
+ "fieldname": "sb1",
+ "fieldtype": "Section Break",
+ "label": "Naming"
+ },
+ {
+ "description": "Naming Options:\nfield:[fieldname] - By Fieldnaming_series: - By Naming Series (field called naming_series must be presentPrompt - Prompt user for a name[series] - Series by prefix (separated by a dot); for example PRE.##### \nformat:EXAMPLE-{MM}morewords{fieldname1}-{fieldname2}-{#####} - Replace all braced words (fieldnames, date words (DD, MM, YY), series) with their value. Outside braces, any characters can be used. ",
+ "fieldname": "autoname",
+ "fieldtype": "Data",
+ "label": "Auto Name",
+ "oldfieldname": "autoname",
+ "oldfieldtype": "Data"
+ },
+ {
+ "fieldname": "name_case",
+ "fieldtype": "Select",
+ "label": "Name Case",
+ "oldfieldname": "name_case",
+ "oldfieldtype": "Select",
+ "options": "\nTitle Case\nUPPER CASE"
+ },
+ {
+ "fieldname": "column_break_15",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "label": "Description",
+ "oldfieldname": "description",
+ "oldfieldtype": "Text"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "form_settings_section",
+ "fieldtype": "Section Break",
+ "label": "Form Settings"
+ },
+ {
+ "description": "Must be of type \"Attach Image\"",
+ "fieldname": "image_field",
+ "fieldtype": "Data",
+ "label": "Image Field"
+ },
+ {
+ "depends_on": "eval:!doc.istable",
+ "description": "Comments and Communications will be associated with this linked document",
+ "fieldname": "timeline_field",
+ "fieldtype": "Data",
+ "label": "Timeline Field"
+ },
+ {
+ "fieldname": "max_attachments",
+ "fieldtype": "Int",
+ "label": "Max Attachments",
+ "oldfieldname": "max_attachments",
+ "oldfieldtype": "Int"
+ },
+ {
+ "fieldname": "column_break_23",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "hide_toolbar",
+ "fieldtype": "Check",
+ "label": "Hide Sidebar and Menu",
+ "oldfieldname": "hide_toolbar",
+ "oldfieldtype": "Check"
+ },
+ {
+ "fieldname": "allow_copy",
+ "fieldtype": "Check",
+ "label": "Hide Copy",
+ "oldfieldname": "allow_copy",
+ "oldfieldtype": "Check"
+ },
+ {
+ "fieldname": "allow_rename",
+ "fieldtype": "Check",
+ "label": "Allow Rename",
+ "oldfieldname": "allow_rename",
+ "oldfieldtype": "Check"
+ },
+ {
+ "fieldname": "allow_import",
+ "fieldtype": "Check",
+ "label": "Allow Import (via Data Import Tool)"
+ },
+ {
+ "fieldname": "allow_events_in_timeline",
+ "fieldtype": "Check",
+ "label": "Allow events in timeline"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "view_settings",
+ "fieldtype": "Section Break",
+ "label": "View Settings"
+ },
+ {
+ "depends_on": "eval:!doc.istable",
+ "fieldname": "title_field",
+ "fieldtype": "Data",
+ "label": "Title Field"
+ },
+ {
+ "depends_on": "eval:!doc.istable",
+ "fieldname": "search_fields",
+ "fieldtype": "Data",
+ "label": "Search Fields",
+ "oldfieldname": "search_fields",
+ "oldfieldtype": "Data"
+ },
+ {
+ "fieldname": "default_print_format",
+ "fieldtype": "Data",
+ "label": "Default Print Format"
+ },
+ {
+ "default": "modified",
+ "depends_on": "eval:!doc.istable",
+ "fieldname": "sort_field",
+ "fieldtype": "Data",
+ "label": "Default Sort Field"
+ },
+ {
+ "default": "DESC",
+ "depends_on": "eval:!doc.istable",
+ "fieldname": "sort_order",
+ "fieldtype": "Select",
+ "label": "Default Sort Order",
+ "options": "ASC\nDESC"
+ },
+ {
+ "fieldname": "column_break_29",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "document_type",
+ "fieldtype": "Select",
+ "label": "Show in Module Section",
+ "oldfieldname": "document_type",
+ "oldfieldtype": "Select",
+ "options": "\nDocument\nSetup\nSystem\nOther"
+ },
+ {
+ "fieldname": "icon",
+ "fieldtype": "Data",
+ "label": "Icon"
+ },
+ {
+ "fieldname": "color",
+ "fieldtype": "Data",
+ "label": "Color"
+ },
+ {
+ "fieldname": "show_name_in_global_search",
+ "fieldtype": "Check",
+ "label": "Make \"name\" searchable in Global Search"
+ },
+ {
+ "depends_on": "eval:!doc.istable",
+ "fieldname": "sb2",
+ "fieldtype": "Section Break",
+ "label": "Permission Rules"
+ },
+ {
+ "fieldname": "permissions",
+ "fieldtype": "Table",
+ "label": "Permissions",
+ "oldfieldname": "permissions",
+ "oldfieldtype": "Table",
+ "options": "DocPerm"
+ },
+ {
+ "fieldname": "restrict_to_domain",
+ "fieldtype": "Link",
+ "label": "Restrict To Domain",
+ "options": "Domain"
+ },
+ {
+ "fieldname": "read_only",
+ "fieldtype": "Check",
+ "label": "User Cannot Search",
+ "oldfieldname": "read_only",
+ "oldfieldtype": "Check"
+ },
+ {
+ "fieldname": "in_create",
+ "fieldtype": "Check",
+ "label": "User Cannot Create",
+ "oldfieldname": "in_create",
+ "oldfieldtype": "Check"
+ },
+ {
+ "fieldname": "web_view",
+ "fieldtype": "Section Break",
+ "label": "Web View"
+ },
+ {
+ "default": "0",
+ "fieldname": "has_web_view",
+ "fieldtype": "Check",
+ "label": "Has Web View"
+ },
+ {
+ "default": "0",
+ "depends_on": "has_web_view",
+ "fieldname": "allow_guest_to_view",
+ "fieldtype": "Check",
+ "label": "Allow Guest to View"
+ },
+ {
+ "depends_on": "has_web_view",
+ "fieldname": "route",
+ "fieldtype": "Data",
+ "label": "Route"
+ },
+ {
+ "depends_on": "has_web_view",
+ "fieldname": "is_published_field",
+ "fieldtype": "Data",
+ "label": "Is Published Field"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "advanced",
+ "fieldtype": "Section Break",
+ "hidden": 1,
+ "label": "Advanced"
+ },
+ {
+ "default": "InnoDB",
+ "depends_on": "eval:!doc.issingle",
+ "fieldname": "engine",
+ "fieldtype": "Select",
+ "label": "Database Engine",
+ "options": "InnoDB\nMyISAM"
+ },
+ {
+ "default": "0",
+ "fieldname": "show_preview_popup",
+ "fieldtype": "Check",
+ "label": "Show Preview Popup"
+ }
+ ],
+ "icon": "fa fa-bolt",
+ "idx": 6,
+ "modified": "2019-05-16 14:58:33.405381",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "DocType",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Administrator",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "search_fields": "module",
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py
index 5db025f9fb..7efdba3237 100644
--- a/frappe/core/doctype/doctype/doctype.py
+++ b/frappe/core/doctype/doctype/doctype.py
@@ -616,7 +616,9 @@ def validate_fields(meta):
frappe.throw(_("Options 'Dynamic Link' type of field must point to another Link Field with options as 'DocType'"))
def check_illegal_default(d):
- if d.fieldtype == "Check" and d.default and d.default not in ('0', '1'):
+ if d.fieldtype == "Check" and not d.default:
+ d.default = '0'
+ if d.fieldtype == "Check" and d.default not in ('0', '1'):
frappe.throw(_("Default for 'Check' type of field must be either '0' or '1'"))
if d.fieldtype == "Select" and d.default and (d.default not in d.options.split("\n")):
frappe.throw(_("Default for {0} must be an option").format(d.fieldname))
diff --git a/frappe/core/doctype/dynamic_link/dynamic_link.json b/frappe/core/doctype/dynamic_link/dynamic_link.json
index 3689be6a3d..abc47df100 100644
--- a/frappe/core/doctype/dynamic_link/dynamic_link.json
+++ b/frappe/core/doctype/dynamic_link/dynamic_link.json
@@ -1,125 +1,47 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2017-01-13 04:55:18.835023",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "creation": "2017-01-13 04:55:18.835023",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "link_doctype",
+ "link_name",
+ "link_title"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "link_doctype",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Link DocType",
- "length": 0,
- "no_copy": 0,
- "options": "DocType",
- "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
- },
+ "fieldname": "link_doctype",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Link DocType",
+ "options": "DocType",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "link_name",
- "fieldtype": "Dynamic Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Link Name",
- "length": 0,
- "no_copy": 0,
- "options": "link_doctype",
- "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
- },
+ "fieldname": "link_name",
+ "fieldtype": "Dynamic Link",
+ "in_list_view": 1,
+ "label": "Link Name",
+ "options": "link_doctype",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "link_title",
- "fieldtype": "Read Only",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Link Title",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "link_title",
+ "fieldtype": "Read Only",
+ "in_list_view": 1,
+ "label": "Link Title",
+ "read_only": 1
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-01-17 14:25:49.140730",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "Dynamic Link",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "istable": 1,
+ "modified": "2019-05-16 19:54:31.400026",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Dynamic Link",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py
index 847f1a840b..29c069515f 100644
--- a/frappe/core/doctype/prepared_report/prepared_report.py
+++ b/frappe/core/doctype/prepared_report/prepared_report.py
@@ -54,14 +54,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',
diff --git a/frappe/database/mariadb/framework_mariadb.sql b/frappe/database/mariadb/framework_mariadb.sql
index 48c70afbbc..ad4cd7d5ef 100644
--- a/frappe/database/mariadb/framework_mariadb.sql
+++ b/frappe/database/mariadb/framework_mariadb.sql
@@ -37,6 +37,7 @@ CREATE TABLE `tabDocField` (
`unique` int(1) NOT NULL DEFAULT 0,
`no_copy` int(1) NOT NULL DEFAULT 0,
`allow_on_submit` int(1) NOT NULL DEFAULT 0,
+ `show_preview_popup` int(1) NOT NULL DEFAULT 0,
`trigger` varchar(255) DEFAULT NULL,
`collapsible_depends_on` text,
`depends_on` text,
diff --git a/frappe/database/postgres/framework_postgres.sql b/frappe/database/postgres/framework_postgres.sql
index 2ff024555d..756917ca97 100644
--- a/frappe/database/postgres/framework_postgres.sql
+++ b/frappe/database/postgres/framework_postgres.sql
@@ -37,6 +37,7 @@ CREATE TABLE "tabDocField" (
"unique" smallint NOT NULL DEFAULT 0,
"no_copy" smallint NOT NULL DEFAULT 0,
"allow_on_submit" smallint NOT NULL DEFAULT 0,
+ "show_preview_popup" smallint NOT NULL DEFAULT 0,
"trigger" varchar(255) DEFAULT NULL,
"collapsible_depends_on" text,
"depends_on" text,
diff --git a/frappe/database/postgres/schema.py b/frappe/database/postgres/schema.py
index 05b6b19a9a..b5129b60bb 100644
--- a/frappe/database/postgres/schema.py
+++ b/frappe/database/postgres/schema.py
@@ -40,7 +40,20 @@ class PostgresTable(DBTable):
query.append("ADD COLUMN `{}` {}".format(col.fieldname, col.get_definition()))
for col in self.change_type:
- query.append("ALTER COLUMN `{}` TYPE {}".format(col.fieldname, get_definition(col.fieldtype, precision=col.precision, length=col.length)))
+ using_clause = ""
+ if col.fieldtype in ("Datetime"):
+ # The USING option of SET DATA TYPE can actually specify any expression
+ # involving the old values of the row
+ # read more https://www.postgresql.org/docs/9.1/sql-altertable.html
+ using_clause = "USING {}::timestamp without time zone".format(col.fieldname)
+ elif col.fieldtype in ("Check"):
+ using_clause = "USING {}::smallint".format(col.fieldname)
+
+ query.append("ALTER COLUMN {0} TYPE {1} {2}".format(
+ col.fieldname,
+ get_definition(col.fieldtype, precision=col.precision, length=col.length),
+ using_clause)
+ )
for col in self.set_default:
if col.fieldname=="name":
@@ -93,4 +106,4 @@ class PostgresTable(DBTable):
fieldname, self.table_name)))
raise e
else:
- raise e
\ No newline at end of file
+ raise e
diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py
index 04f7455e2d..1ad57246a6 100644
--- a/frappe/desk/doctype/event/event.py
+++ b/frappe/desk/doctype/event/event.py
@@ -43,10 +43,17 @@ class Event(Document):
def sync_communication(self):
if self.event_participants:
for participant in self.event_participants:
- communication_name = frappe.db.get_value("Communication", dict(reference_doctype=self.doctype, reference_name=self.name, timeline_doctype=participant.reference_doctype, timeline_name=participant.reference_docname), "name")
- if communication_name:
- communication = frappe.get_doc("Communication", communication_name)
- self.update_communication(participant, communication)
+ comms = frappe.get_list("Communication", filters=[
+ ["Communication", "reference_doctype", "=", self.doctype],
+ ["Communication", "reference_name", "=", self.name],
+ ["Communication Link", "link_doctype", "=", participant.reference_doctype],
+ ["Communication Link", "link_name", "=", participant.reference_docname]
+ ], fields=["name"])
+
+ if comms:
+ for comm in comms:
+ communication = frappe.get_doc("Communication", comm.name)
+ self.update_communication(participant, communication)
else:
meta = frappe.get_meta(participant.reference_doctype)
if hasattr(meta, "allow_events_in_timeline") and meta.allow_events_in_timeline==1:
@@ -62,12 +69,11 @@ class Event(Document):
communication.subject = self.subject
communication.content = self.description if self.description else self.subject
communication.communication_date = self.starts_on
- communication.timeline_doctype = participant.reference_doctype
- communication.timeline_name = participant.reference_docname
communication.reference_doctype = self.doctype
communication.reference_name = self.name
communication.communication_medium = communication_mapping[self.event_category] if self.event_category else ""
communication.status = "Linked"
+ communication.add_link(participant.reference_doctype, participant.reference_docname)
communication.save(ignore_permissions=True)
@frappe.whitelist()
@@ -76,9 +82,18 @@ def delete_communication(event, reference_doctype, reference_docname):
if isinstance(event, string_types):
event = json.loads(event)
- communication_name = frappe.db.get_value("Communication", dict(reference_doctype=event["doctype"], reference_name=event["name"], timeline_doctype=deleted_participant.reference_doctype, timeline_name=deleted_participant.reference_docname), "name")
- if communication_name:
- deletion = frappe.get_doc("Communication", communication_name).delete()
+ comms = frappe.get_list("Communication", filters=[
+ ["Communication", "reference_doctype", "=", event.get("doctype")],
+ ["Communication", "reference_name", "=", event.get("name")],
+ ["Communication Link", "link_doctype", "=", deleted_participant.reference_doctype],
+ ["Communication Link", "link_name", "=", deleted_participant.reference_docname]
+ ], fields=["name"])
+
+ if comms:
+ deletion = []
+ for comm in comms:
+ delete = frappe.get_doc("Communication", comm.name).delete()
+ deletion.append(delete)
return deletion
diff --git a/frappe/desk/doctype/todo/todo.py b/frappe/desk/doctype/todo/todo.py
index 45c3874806..5d04f412c0 100644
--- a/frappe/desk/doctype/todo/todo.py
+++ b/frappe/desk/doctype/todo/todo.py
@@ -43,8 +43,11 @@ class ToDo(Document):
def on_trash(self):
# unlink todo from linked comments
- frappe.db.sql("""update `tabCommunication` set link_doctype=null, link_name=null
- where link_doctype=%(doctype)s and link_name=%(name)s""", {"doctype": self.doctype, "name": self.name})
+ frappe.db.sql("""
+ delete from `tabCommunication Link`
+ where link_doctype=%(doctype)s and link_name=%(name)s""", {
+ "doctype": self.doctype, "name": self.name
+ })
self.update_in_reference()
@@ -94,7 +97,7 @@ def get_permission_query_conditions(user):
if "System Manager" in frappe.get_roles(user):
return None
else:
- return """(tabToDo.owner = {user} or tabToDo.assigned_by = {user})"""\
+ return """(`tabToDo`.owner = {user} or `tabToDo`.assigned_by = {user})"""\
.format(user=frappe.db.escape(user))
def has_permission(doc, user):
@@ -108,4 +111,4 @@ def new_todo(description):
frappe.get_doc({
'doctype': 'ToDo',
'description': description
- }).insert()
\ No newline at end of file
+ }).insert()
diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py
index b45ee6d791..1538c07221 100644
--- a/frappe/desk/form/load.py
+++ b/frappe/desk/form/load.py
@@ -160,36 +160,59 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields=
group_by=None, as_dict=True):
'''Returns list of communications for a given document'''
if not fields:
- fields = '''`name`, `communication_type`,`communication_medium`, `comment_type`,
- `communication_date`, `content`, `sender`, `sender_full_name`, `cc`, `bcc`,
- `creation`, `subject`, `delivery_status`, `_liked_by`,
- `timeline_doctype`, `timeline_name`, `reference_doctype`, `reference_name`,
- `link_doctype`, `link_name`, `read_by_recipient`, `rating`, 'Communication' AS `doctype`'''
-
- conditions = '''communication_type = 'Communication'
- and (
- (reference_doctype=%(doctype)s and reference_name=%(name)s)
- or (
- (timeline_doctype=%(doctype)s and timeline_name=%(name)s)
- and (communication_type='Communication')
- )
- )'''
-
+ fields = '''
+ C.name, C.communication_type, C.communication_medium,
+ C.comment_type, C.communication_date, C.content,
+ C.sender, C.sender_full_name, C.cc, C.bcc,
+ C.creation, C.subject, C.delivery_status,
+ C._liked_by, C.reference_doctype, C.reference_name,
+ C.read_by_recipient, C.rating
+ '''
+ conditions = ''
if after:
# find after a particular date
- conditions+= ' and creation > {0}'.format(after)
+ conditions += '''
+ AND C.creation > {0}
+ '''.format(after)
if doctype=='User':
- conditions+= " and not (reference_doctype='User' and communication_type='Communication')"
+ conditions += '''
+ AND NOT (C.reference_doctype='User' AND C.communication_type='Communication')
+ '''
- communications = frappe.db.sql("""select {fields}
- from `tabCommunication`
- where {conditions} {group_by}
- order by creation desc LIMIT %(limit)s OFFSET %(start)s""".format(
- fields = fields, conditions=conditions, group_by=group_by or ""),
- { "doctype": doctype, "name": name, "start": frappe.utils.cint(start), "limit": limit },
- as_dict=as_dict)
+ # communications linked to reference_doctype
+ part1 = '''
+ SELECT {fields}
+ FROM `tabCommunication` as C
+ WHERE C.communication_type IN ('Communication', 'Feedback')
+ AND (C.reference_doctype = %(doctype)s AND C.reference_name = %(name)s)
+ {conditions}
+ '''.format(fields=fields, conditions=conditions)
+
+ # communications linked in Timeline Links
+ part2 = '''
+ SELECT {fields}
+ FROM `tabCommunication` as C
+ INNER JOIN `tabCommunication Link` ON C.name=`tabCommunication Link`.parent
+ WHERE C.communication_type IN ('Communication', 'Feedback')
+ AND `tabCommunication Link`.link_doctype = %(doctype)s AND `tabCommunication Link`.link_name = %(name)s
+ {conditions}
+ '''.format(fields=fields, conditions=conditions)
+
+ communications = frappe.db.sql('''
+ SELECT *
+ FROM (({part1}) UNION ({part2})) AS combined
+ {group_by}
+ ORDER BY combined.creation DESC
+ LIMIT %(limit)s
+ OFFSET %(start)s
+ '''.format(part1=part1, part2=part2, group_by=(group_by or '')), dict(
+ doctype=doctype,
+ name=name,
+ start=frappe.utils.cint(start),
+ limit=limit
+ ), as_dict=as_dict)
return communications
@@ -229,4 +252,4 @@ def get_view_logs(doctype, docname):
if view_logs:
logs = view_logs
- return logs
+ return logs
\ No newline at end of file
diff --git a/frappe/desk/moduleview.py b/frappe/desk/moduleview.py
index a9a460b124..ccde3aad40 100644
--- a/frappe/desk/moduleview.py
+++ b/frappe/desk/moduleview.py
@@ -383,7 +383,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
})
diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py
index 561cd680b6..0890e2ad7a 100644
--- a/frappe/desk/query_report.py
+++ b/frappe/desk/query_report.py
@@ -282,6 +282,10 @@ def export_query():
filters = json.loads(data["filters"])
if isinstance(data.get("report_name"), string_types):
report_name = data["report_name"]
+ frappe.permissions.can_export(
+ frappe.get_cached_value('Report', report_name, 'ref_doctype'),
+ raise_exception=True
+ )
if isinstance(data.get("file_format_type"), string_types):
file_format_type = data["file_format_type"]
diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py
index 2d23089f5a..6ef94883f7 100755
--- a/frappe/email/doctype/email_account/email_account.py
+++ b/frappe/email/doctype/email_account/email_account.py
@@ -387,7 +387,7 @@ class EmailAccount(Document):
communication._seen = json.dumps(users)
communication.flags.in_receive = True
- communication.insert(ignore_permissions = 1)
+ communication.insert(ignore_permissions=True)
# save attachments
communication._attachments = email.save_attachments_in_doc(communication)
diff --git a/frappe/email/doctype/email_account/test_email_account.py b/frappe/email/doctype/email_account/test_email_account.py
index f098a8b205..29b54d7f8b 100644
--- a/frappe/email/doctype/email_account/test_email_account.py
+++ b/frappe/email/doctype/email_account/test_email_account.py
@@ -26,7 +26,7 @@ class TestEmailAccount(unittest.TestCase):
email_account.db_set("enable_incoming", 0)
def test_incoming(self):
- frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'")
+ cleanup("test_sender@example.com")
with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-1.raw"), "r") as f:
test_mails = [f.read()]
@@ -52,7 +52,8 @@ class TestEmailAccount(unittest.TestCase):
"reference_name": comm.reference_name, "status":"Not Sent"}))
def test_incoming_with_attach(self):
- frappe.db.sql("DELETE FROM `tabCommunication` WHERE sender='test_sender@example.com'")
+ cleanup("test_sender@example.com")
+
existing_file = frappe.get_doc({'doctype': 'File', 'file_name': 'erpnext-conf-14.png'})
frappe.delete_doc("File", existing_file.name)
@@ -75,7 +76,7 @@ class TestEmailAccount(unittest.TestCase):
def test_incoming_attached_email_from_outlook_plain_text_only(self):
- frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'")
+ cleanup("test_sender@example.com")
with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-3.raw"), "r") as f:
test_mails = [f.read()]
@@ -88,7 +89,7 @@ class TestEmailAccount(unittest.TestCase):
self.assertTrue("This is an e-mail message sent automatically by Microsoft Outlook while" in comm.content)
def test_incoming_attached_email_from_outlook_layers(self):
- frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'")
+ cleanup("test_sender@example.com")
with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-4.raw"), "r") as f:
test_mails = [f.read()]
@@ -123,8 +124,7 @@ class TestEmailAccount(unittest.TestCase):
self.assertTrue("test-mail-002" in sent_mail.get("Subject"))
def test_threading(self):
- frappe.db.sql("""delete from tabCommunication
- where sender in ('test_sender@example.com', 'test@example.com')""")
+ cleanup(["in", ['test_sender@example.com', 'test@example.com']])
# send
sent_name = make(subject = "Test", content="test content",
@@ -149,8 +149,7 @@ class TestEmailAccount(unittest.TestCase):
self.assertEqual(comm.reference_name, sent.reference_name)
def test_threading_by_subject(self):
- frappe.db.sql("""delete from tabCommunication
- where sender in ('test_sender@example.com', 'test@example.com')""")
+ cleanup(["in", ['test_sender@example.com', 'test@example.com']])
with open(os.path.join(os.path.dirname(__file__), "test_mails", "reply-2.raw"), "r") as f:
test_mails = [f.read()]
@@ -170,7 +169,7 @@ class TestEmailAccount(unittest.TestCase):
self.assertEqual(comm_list[0].reference_name, comm_list[1].reference_name)
def test_threading_by_message_id(self):
- frappe.db.sql("""delete from tabCommunication""")
+ cleanup()
frappe.db.sql("""delete from `tabEmail Queue`""")
# reference document for testing
@@ -196,3 +195,13 @@ class TestEmailAccount(unittest.TestCase):
# check if threaded correctly
self.assertEqual(comm_list[0].reference_doctype, event.doctype)
self.assertEqual(comm_list[0].reference_name, event.name)
+
+def cleanup(sender=None):
+ filters = {}
+ if sender:
+ filters.update({"sender": sender})
+
+ names = frappe.get_list("Communication", filters=filters, fields=["name"])
+ for name in names:
+ frappe.delete_doc_if_exists("Communication", name.name)
+ frappe.delete_doc_if_exists("Communication Link", {"parent": name.name})
\ No newline at end of file
diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py
index 61906f585e..f53d7845c5 100644
--- a/frappe/email/doctype/notification/notification.py
+++ b/frappe/email/doctype/notification/notification.py
@@ -126,9 +126,14 @@ def get_context(context):
self.send_a_slack_msg(doc, context)
if self.set_property_after_alert:
- frappe.db.set_value(doc.doctype, doc.name, self.set_property_after_alert,
- self.property_value, update_modified = False)
- doc.set(self.set_property_after_alert, self.property_value)
+ allow_update = True
+ if doc.docstatus == 1 and not doc.meta.get_field(self.set_property_after_alert).allow_on_submit:
+ allow_update = False
+
+ if allow_update:
+ frappe.db.set_value(doc.doctype, doc.name, self.set_property_after_alert,
+ self.property_value, update_modified = False)
+ doc.set(self.set_property_after_alert, self.property_value)
def send_an_email(self, doc, context):
from email.utils import formataddr
diff --git a/frappe/geo/country_info.json b/frappe/geo/country_info.json
index bdd6414730..7a6ba56929 100644
--- a/frappe/geo/country_info.json
+++ b/frappe/geo/country_info.json
@@ -2330,6 +2330,7 @@
},
"Suriname": {
"code": "sr",
+ "currency": "SRD",
"currency_fraction": "Cent",
"currency_fraction_units": 100,
"currency_symbol": "$",
diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.json b/frappe/integrations/doctype/ldap_settings/ldap_settings.json
index 6eb44a2db8..aa43b2e9d0 100644
--- a/frappe/integrations/doctype/ldap_settings/ldap_settings.json
+++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.json
@@ -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
}
\ No newline at end of file
diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py
index e12a6fce05..e48619bfdd 100644
--- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py
+++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py
@@ -5,56 +5,93 @@
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.enabled:
+ return
+
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 = """
-
- {{_("Seems ldap is not installed on system.
Guidelines to install ldap dependancies and python package")}},
-
{{_("Click here")}} ,
-
- """
- 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 +101,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 = """
-
- {{_("Seems ldap is not installed on system.")}}
-
{{_("Click here")}} ,
- {{_("Guidelines to install ldap dependancies and python")}}
-
- """
- 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 +165,5 @@ def create_user(params):
})
user = frappe.get_doc(params).insert(ignore_permissions=True)
- frappe.db.commit()
return user
diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py
index 5f804e3c36..40e8616f12 100644
--- a/frappe/model/__init__.py
+++ b/frappe/model/__init__.py
@@ -76,27 +76,50 @@ def delete_fields(args_dict, delete=0):
args_dict = { dt: [field names] }
"""
import frappe.utils
- for dt in list(args_dict):
+ for dt in args_dict:
fields = args_dict[dt]
- if not fields: continue
+ if not fields:
+ continue
- frappe.db.sql("""\
+ frappe.db.sql("""
DELETE FROM `tabDocField`
- WHERE parent=%s AND fieldname IN (%s)
- """ % ('%s', ", ".join(['"' + f + '"' for f in fields])), dt)
+ WHERE parent='%s' AND fieldname IN (%s)
+ """ % (dt, ", ".join(["'{}'".format(f) for f in fields])))
- # Delete the data / column only if delete is specified
- if not delete: continue
+ # Delete the data/column only if delete is specified
+ if not delete:
+ continue
if frappe.db.get_value("DocType", dt, "issingle"):
- frappe.db.sql("""\
+ frappe.db.sql("""
DELETE FROM `tabSingles`
- WHERE doctype=%s AND field IN (%s)
- """ % ('%s', ", ".join(['"' + f + '"' for f in fields])), dt)
+ WHERE doctype='%s' AND field IN (%s)
+ """ % (dt, ", ".join(["'{}'".format(f) for f in fields])))
else:
- existing_fields = frappe.db.sql("desc `tab%s`" % dt)
+ existing_fields = frappe.db.multisql({
+ "mariadb": "DESC `tab%s`" % dt,
+ "postgres": """
+ SELECT
+ COLUMN_NAME
+ FROM
+ information_schema.COLUMNS
+ WHERE
+ TABLE_NAME = 'tab%s';
+ """ % dt,
+ })
existing_fields = existing_fields and [e[0] for e in existing_fields] or []
+ fields_need_to_delete = set(fields) & set(existing_fields)
+ if not fields_need_to_delete:
+ continue
+
+ if frappe.conf.db_type == 'mariadb':
+ # mariadb implicitly commits before DDL, make it explicit
+ frappe.db.commit()
+
query = "ALTER TABLE `tab%s` " % dt + \
- ", ".join(["DROP COLUMN `%s`" % f for f in fields if f in existing_fields])
- frappe.db.commit()
+ ", ".join(["DROP COLUMN `%s`" % f for f in fields_need_to_delete])
frappe.db.sql(query)
+
+ if frappe.conf.db_type == 'postgres':
+ # commit the results to db
+ frappe.db.commit()
diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py
index 8b03a21a2b..ae4bc07b1a 100644
--- a/frappe/model/base_document.py
+++ b/frappe/model/base_document.py
@@ -197,7 +197,7 @@ class BaseDocument(object):
return value
- def get_valid_dict(self, sanitize=True, convert_dates_to_str=False):
+ def get_valid_dict(self, sanitize=True, convert_dates_to_str=False, ignore_nulls = False):
d = frappe._dict()
for fieldname in self.meta.get_valid_columns():
d[fieldname] = self.get(fieldname)
@@ -234,6 +234,9 @@ class BaseDocument(object):
if convert_dates_to_str and isinstance(d[fieldname], (datetime.datetime, datetime.time, datetime.timedelta)):
d[fieldname] = str(d[fieldname])
+ if d[fieldname] == None and ignore_nulls:
+ del d[fieldname]
+
return d
def init_valid_columns(self):
@@ -306,7 +309,8 @@ class BaseDocument(object):
self.creation = self.modified = now()
self.created_by = self.modified_by = frappe.session.user
- d = self.get_valid_dict(convert_dates_to_str=True)
+ # if doctype is "DocType", don't insert null values as we don't know who is valid yet
+ d = self.get_valid_dict(convert_dates_to_str=True, ignore_nulls = self.doctype in ('DocType', 'DocField', 'DocPerm'))
columns = list(d)
try:
@@ -341,7 +345,7 @@ class BaseDocument(object):
self.db_insert()
return
- d = self.get_valid_dict(convert_dates_to_str=True)
+ d = self.get_valid_dict(convert_dates_to_str=True, ignore_nulls = self.doctype in ('DocType', 'DocField', 'DocPerm'))
# don't update name, as case might've been changed
name = d['name']
diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py
index 5dc74bff8d..cdf099e50e 100644
--- a/frappe/model/delete_doc.py
+++ b/frappe/model/delete_doc.py
@@ -278,9 +278,8 @@ def delete_dynamic_links(doctype, name):
delete_references('Document Follow', doctype, name, 'ref_doctype', 'ref_docname')
# unlink communications
+ clear_timeline_references(doctype, name)
clear_references('Communication', doctype, name)
- clear_references('Communication', doctype, name, 'link_doctype', 'link_name')
- clear_references('Communication', doctype, name, 'timeline_doctype', 'timeline_name')
clear_references('Activity Log', doctype, name)
clear_references('Activity Log', doctype, name, 'timeline_doctype', 'timeline_name')
@@ -301,6 +300,9 @@ def clear_references(doctype, reference_doctype, reference_name,
{1}=%s and {2}=%s'''.format(doctype, reference_doctype_field, reference_name_field), # nosec
(reference_doctype, reference_name))
+def clear_timeline_references(link_doctype, link_name):
+ frappe.db.sql("""delete from `tabCommunication Link`
+ where `tabCommunication Link`.link_doctype='{0}' and `tabCommunication Link`.link_name='{1}'""".format(link_doctype, link_name)) # nosec
def insert_feed(doc):
from frappe.utils import get_fullname
diff --git a/frappe/model/document.py b/frappe/model/document.py
index e8add2d619..3ed3118571 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -1195,18 +1195,10 @@ class Document(BaseDocument):
return
# update timeline doc in communication if it is different than current timeline doc
- frappe.db.sql("""update `tabCommunication`
- set timeline_doctype=%(timeline_doctype)s, timeline_name=%(timeline_name)s
- where
- reference_doctype=%(doctype)s and reference_name=%(name)s
- and (timeline_doctype is null or timeline_doctype != %(timeline_doctype)s
- or timeline_name is null or timeline_name != %(timeline_name)s)""",
- {
- "doctype": self.doctype,
- "name": self.name,
- "timeline_doctype": timeline_doctype,
- "timeline_name": timeline_name
- })
+ communication = frappe.get_doc("Communication", {"reference_doctype": self.doctype, "reference_name": self.name})
+ if communication.communication_medium == "Email":
+ # duplicate entries will be handled by deduplicate links in communication
+ communication.add_link(link_doctype=timeline_doctype, link_name=timeline_name, autosave=True)
def queue_action(self, action, **kwargs):
'''Run an action in background. If the action has an inner function,
diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py
index a373554696..12c57f2780 100644
--- a/frappe/model/rename_doc.py
+++ b/frappe/model/rename_doc.py
@@ -161,7 +161,7 @@ def validate_rename(doctype, new, meta, merge, force, ignore_permissions):
if (not merge) and exists:
frappe.msgprint(_("Another {0} with name {1} exists, select another name").format(doctype, new), raise_exception=1)
- if not (ignore_permissions or frappe.has_permission(doctype, "write")):
+ if not (ignore_permissions or frappe.permissions.has_permission(doctype, "write", raise_exception=False)):
frappe.msgprint(_("You need write permission to rename"), raise_exception=1)
if not (force or ignore_permissions) and not meta.allow_rename:
diff --git a/frappe/patches.txt b/frappe/patches.txt
index c0b2a8238f..82a33920f3 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -241,3 +241,6 @@ frappe.patches.v12_0.reset_home_settings
frappe.patches.v12_0.update_print_format_type
frappe.patches.v11_0.remove_doctype_user_permissions_for_page_and_report #2019-05-01
frappe.patches.v12_0.remove_feedback_rating
+execute:frappe.reload_doc('core', 'doctype', 'communication_link')
+execute:frappe.reload_doc('core', 'doctype', 'communication')
+frappe.patches.v12_0.move_timeline_links_to_dynamic_links
\ No newline at end of file
diff --git a/frappe/patches/v11_0/set_default_letter_head_source.py b/frappe/patches/v11_0/set_default_letter_head_source.py
index 069f4e3d2e..a43ea397e4 100644
--- a/frappe/patches/v11_0/set_default_letter_head_source.py
+++ b/frappe/patches/v11_0/set_default_letter_head_source.py
@@ -6,4 +6,4 @@ def execute():
frappe.reload_doctype('Letter Head')
# source of all existing letter heads must be HTML
- frappe.db.sql('update `tabLetter Head` set source = "HTML"')
\ No newline at end of file
+ frappe.db.sql("update `tabLetter Head` set source = 'HTML'")
diff --git a/frappe/patches/v12_0/init_desk_settings.py b/frappe/patches/v12_0/init_desk_settings.py
index 782ced8a26..31c6cf9207 100644
--- a/frappe/patches/v12_0/init_desk_settings.py
+++ b/frappe/patches/v12_0/init_desk_settings.py
@@ -8,4 +8,4 @@ from frappe.desk.moduleview import get_onboard_items
def execute():
"""Reset the initial customizations for desk, with modules, indices and links."""
frappe.reload_doc("core", "doctype", "user")
- frappe.db.sql("""update tabUser set home_settings = %s""", (''), debug=True)
+ frappe.db.sql("""update `tabUser` set home_settings = %s""", (''), debug=True)
diff --git a/frappe/patches/v12_0/move_timeline_links_to_dynamic_links.py b/frappe/patches/v12_0/move_timeline_links_to_dynamic_links.py
new file mode 100644
index 0000000000..8860c7c00d
--- /dev/null
+++ b/frappe/patches/v12_0/move_timeline_links_to_dynamic_links.py
@@ -0,0 +1,44 @@
+from __future__ import unicode_literals
+
+import frappe
+
+def execute():
+ communications = frappe.db.sql("""
+ SELECT
+ `tabCommunication`.name, `tabCommunication`.creation, `tabCommunication`.modified,
+ `tabCommunication`.modified_by,`tabCommunication`.timeline_doctype, `tabCommunication`.timeline_name,
+ `tabCommunication`.link_doctype, `tabCommunication`.link_name
+ FROM `tabCommunication`
+ WHERE `tabCommunication`.communication_medium='Email'
+ """, as_dict=True)
+
+ name = 1000000000
+ values = []
+
+ for count, communication in enumerate(communications):
+ counter = 1
+ if communication.timeline_doctype and communication.timeline_name:
+ name += 1
+ values.append("""({0}, "{1}", "timeline_links", "Communication", "{2}", "{3}", "{4}", "{5}", "{6}", "{7}")""".format(
+ counter, str(name), communication.name, communication.timeline_doctype,
+ communication.timeline_name, communication.creation, communication.modified, communication.modified_by
+ ))
+ counter += 1
+ if communication.link_doctype and communication.link_name:
+ name += 1
+ values.append("""({0}, "{1}", "timeline_links", "Communication", "{2}", "{3}", "{4}", "{5}", "{6}", "{7}")""".format(
+ counter, str(name), communication.name, communication.link_doctype,
+ communication.link_name, communication.creation, communication.modified, communication.modified_by
+ ))
+
+ if values and (count % 10000 == 0 or count == len(communications) - 1):
+ frappe.db.sql("""
+ INSERT INTO `tabCommunication Link`
+ (`idx`, `name`, `parentfield`, `parenttype`, `parent`, `link_doctype`, `link_name`, `creation`,
+ `modified`, `modified_by`)
+ VALUES {0}
+ """.format(", ".join([d for d in values])))
+
+ values = []
+
+ frappe.db.add_index("Communication Link", ["link_doctype", "link_name"])
\ No newline at end of file
diff --git a/frappe/patches/v12_0/setup_comments_from_communications.py b/frappe/patches/v12_0/setup_comments_from_communications.py
index 1a7a5aef84..b52304bc05 100644
--- a/frappe/patches/v12_0/setup_comments_from_communications.py
+++ b/frappe/patches/v12_0/setup_comments_from_communications.py
@@ -25,4 +25,4 @@ def execute():
new_comment.db_insert()
# clean up
- frappe.db.sql('delete from tabCommunication where communication_type = "Comment"')
\ No newline at end of file
+ frappe.db.sql("delete from `tabCommunication` where communication_type = 'Comment'")
diff --git a/frappe/patches/v12_0/update_print_format_type.py b/frappe/patches/v12_0/update_print_format_type.py
index 33035abc2a..577dc68d94 100644
--- a/frappe/patches/v12_0/update_print_format_type.py
+++ b/frappe/patches/v12_0/update_print_format_type.py
@@ -3,11 +3,11 @@ import frappe
def execute():
frappe.db.sql('''
UPDATE `tabPrint Format`
- SET `print_format_type` = "Jinja"
- WHERE `print_format_type` in ("Server", "Client")
+ SET `print_format_type` = 'Jinja'
+ WHERE `print_format_type` in ('Server', 'Client')
''')
frappe.db.sql('''
UPDATE `tabPrint Format`
- SET `print_format_type` = "JS"
- WHERE `print_format_type` = "Js"
+ SET `print_format_type` = 'JS'
+ WHERE `print_format_type` = 'Js'
''')
diff --git a/frappe/permissions.py b/frappe/permissions.py
index 3809bb3e9c..e5aa31d139 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -25,16 +25,18 @@ def print_has_permission_check_logs(func):
frappe.flags['has_permission_check_logs'] = []
result = func(*args, **kwargs)
self_perm_check = True if not kwargs.get('user') else kwargs.get('user') == frappe.session.user
+ raise_exception = False if kwargs.get('raise_exception') == False else True
+
# print only if access denied
# and if user is checking his own permission
- if not result and self_perm_check:
+ if not result and self_perm_check and raise_exception:
msgprint((' ').join(frappe.flags.get('has_permission_check_logs')))
frappe.flags.pop('has_permission_check_logs', None)
return result
return inner
@print_has_permission_check_logs
-def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None):
+def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None, raise_exception=True):
"""Returns True if user has permission `ptype` for given `doctype`.
If `doc` is passed, it also checks user, share and owner permissions.
diff --git a/frappe/public/build.json b/frappe/public/build.json
index cd8f04b2c9..a279519d72 100755
--- a/frappe/public/build.json
+++ b/frappe/public/build.json
@@ -137,6 +137,11 @@
"public/css/desk-rtl.css",
"public/css/report-rtl.css"
],
+ "css/printview.css": [
+ "public/css/bootstrap.css",
+ "public/css/font-awesome.css",
+ "public/less/print.less"
+ ],
"concat:js/libs.min.js": [
"public/js/lib/awesomplete/awesomplete.min.js",
"public/js/lib/Sortable.min.js",
diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js
index a7ed3f8a72..750ad7a64c 100644
--- a/frappe/public/js/frappe/form/controls/link.js
+++ b/frappe/public/js/frappe/form/controls/link.js
@@ -168,13 +168,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: ""
+ " "
- + __("Create a new {0}", [__(me.df.options)])
+ + __("Create a new {0}", [__(me.get_options())])
+ " ",
value: "create_new__link_option",
action: me.new_doc
diff --git a/frappe/public/js/frappe/form/controls/text_editor.js b/frappe/public/js/frappe/form/controls/text_editor.js
index ef6afde262..e616b9ef3e 100644
--- a/frappe/public/js/frappe/form/controls/text_editor.js
+++ b/frappe/public/js/frappe/form/controls/text_editor.js
@@ -57,7 +57,7 @@ Quill.register(DirectionStyle, true);
frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
make_wrapper() {
this._super();
- this.$wrapper.find(".like-disabled-input").addClass("ql-editor");
+ this.$wrapper.find(".like-disabled-input").addClass('text-editor-print');
},
make_input() {
@@ -198,7 +198,78 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
let $ul = $('').append($children);
$parent.replaceWith($ul);
});
- value = $value.html();
+ value = this.convertLists($value.html());
return value;
+ },
+
+ // hack
+ // https://github.com/quilljs/quill/issues/979
+ convertLists(richtext) {
+ const tempEl = window.document.createElement('div');
+ tempEl.setAttribute('style', 'display: none;');
+ tempEl.innerHTML = richtext;
+ const startLi = '::startli::';
+ const endLi = '::endli::';
+
+ ['ul','ol'].forEach((type) => {
+ const startTag = `::start${type}::`;
+ const endTag = `::end${type}::`;
+
+ // Grab each list, and work on it in turn
+ Array.from(tempEl.querySelectorAll(type)).forEach((outerListEl) => {
+ const listChildren = Array.from(outerListEl.children).filter((el) => el.tagName === 'LI');
+
+ let lastLiLevel = 0;
+ let currentLiLevel = 0;
+ let difference = 0;
+
+ // Now work through each li in this list
+ for (let i = 0; i < listChildren.length; i++) {
+ const currentLi = listChildren[i];
+ lastLiLevel = currentLiLevel;
+ currentLiLevel = this.getListLevel(currentLi);
+ difference = currentLiLevel - lastLiLevel;
+
+ // we only need to add tags if the level is changing
+ if (difference > 0) {
+ currentLi.before((startLi + startTag).repeat(difference));
+ } else if (difference < 0) {
+ currentLi.before((endTag + endLi).repeat(-difference));
+ }
+
+ if (i === listChildren.length - 1) {
+ // last li, account for the fact that it might not be at level 0
+ currentLi.after((endTag + endLi).repeat(currentLiLevel));
+ }
+ }
+ });
+ });
+
+ // Get the content in the element and replace the temporary tags with new ones
+ let newContent = tempEl.innerHTML;
+
+ newContent = newContent.replace(/::startul::/g, '');
+ newContent = newContent.replace(/::endul::/g, ' ');
+ newContent = newContent.replace(/::startol::/g, '');
+ newContent = newContent.replace(/::endol::/g, ' ');
+ newContent = newContent.replace(/::startli::/g, '');
+ newContent = newContent.replace(/::endli::/g, ' ');
+
+ // remove quill classes
+ newContent = newContent.replace(/data-list=.bullet./g, '');
+ newContent = newContent.replace(/class=.ql-indent-../g, '');
+
+ // ul/ol should not be inside another li
+ newContent = newContent.replace(/<\/li>/g, '');
+ newContent = newContent.replace(/<\/li>/g, '');
+ tempEl.remove();
+
+ return newContent;
+ },
+
+ getListLevel(el) {
+ const className = el.className || '0';
+ return +className.replace(/[^\d]/g, '');
}
+
});
diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js
index 250be73c0f..9a3fe3699b 100644
--- a/frappe/public/js/frappe/request.js
+++ b/frappe/public/js/frappe/request.js
@@ -197,11 +197,16 @@ frappe.request.call = function(opts) {
async: opts.async,
headers: Object.assign({
"X-Frappe-CSRF-Token": frappe.csrf_token,
- "Accept": "application/json"
+ "Accept": "application/json",
+ "X-Frappe-CMD": (opts.args && opts.args.cmd || '') || ''
}, opts.headers),
cache: false
};
+ if (opts.args && opts.args.doctype) {
+ ajax_args.headers["X-Frappe-Doctype"] = opts.args.doctype;
+ }
+
frappe.last_request = ajax_args.data;
return $.ajax(ajax_args)
diff --git a/frappe/public/js/frappe/roles_editor.js b/frappe/public/js/frappe/roles_editor.js
index 0e5a85be68..50a3db776c 100644
--- a/frappe/public/js/frappe/roles_editor.js
+++ b/frappe/public/js/frappe/roles_editor.js
@@ -183,7 +183,7 @@ frappe.RoleEditor = Class.extend({
%(set_user_permissions)s \
', perm));
}
-
+ me.perm_dialog.set_title(role);
me.perm_dialog.show();
}
});
@@ -191,7 +191,7 @@ frappe.RoleEditor = Class.extend({
},
make_perm_dialog: function() {
this.perm_dialog = new frappe.ui.Dialog({
- title:__('Role Permissions')
+ title: __('Role Permissions')
});
this.perm_dialog.$wrapper.find('.modal-dialog').css("width", "800px");
diff --git a/frappe/public/js/frappe/ui/link_preview.js b/frappe/public/js/frappe/ui/link_preview.js
index c0e2d162a6..962adcf344 100644
--- a/frappe/public/js/frappe/ui/link_preview.js
+++ b/frappe/public/js/frappe/ui/link_preview.js
@@ -28,7 +28,9 @@ frappe.ui.LinkPreview = class {
if (this.is_link) {
this.doctype = this.element.attr('data-doctype');
this.name = this.element.attr('data-name');
+ this.href = this.element.attr('href');
} else {
+ this.href = this.element.parents('.control-input-wrapper').find('.control-value a').attr('href');
// input
this.doctype = this.element.attr('data-target');
this.name = this.element.val();
@@ -36,7 +38,11 @@ frappe.ui.LinkPreview = class {
}
setup_popover_control(e) {
- if(!this.popover) {
+ //If control field value is changed, new popover has to be created
+ this.element.on('change',()=> {
+ this.new_popover = true;
+ });
+ if(!this.popover || this.new_popover) {
this.get_preview_fields().then(preview_fields => {
if(preview_fields.length) {
this.data_timeout = setTimeout(() => {
@@ -46,13 +52,21 @@ frappe.ui.LinkPreview = class {
});
} else {
this.popover_timeout = setTimeout(() => {
- this.popover.show();
+ if (this.element.is(':focus')) {
+ return;
+ }
+ this.show_popover(e);
}, 1000);
}
this.handle_popover_hide();
}
create_popover(e, preview_fields) {
+ this.new_popover = false;
+ if (this.element.is(':focus')) {
+ return;
+ }
+
this.get_preview_fields_value(preview_fields).then((preview_data)=> {
if(preview_data) {
if(this.popover_timeout) {
@@ -66,37 +80,37 @@ frappe.ui.LinkPreview = class {
} else {
this.init_preview_popover(preview_data);
}
-
- if(!this.is_link) {
- var left = e.pageX;
- this.element.popover('show');
- var width = $('.popover').width();
- $('.control-field-popover').css('left', (left-(width/2)) + 'px');
- } else {
- this.element.popover('show');
- }
+ this.show_popover(e);
}, 1000);
}
});
}
+ show_popover(e) {
+ if(!this.is_link) {
+ var left = e.pageX;
+ this.element.popover('show');
+ var width = $('.popover').width();
+ $('.control-field-popover').css('left', (left-(width/2)) + 'px');
+ } else {
+ this.element.popover('show');
+ }
+ }
+
handle_popover_hide() {
$(document.body).on('mouseout', this.LINK_CLASSES, () => {
- this.link_hovered = false;
+ // To allow popover to be hovered on
+ if (!$('.popover:hover').length) {
+ this.link_hovered = false;
+ }
if(this.data_timeout) {
clearTimeout(this.data_timeout);
}
if (this.popover_timeout) {
clearTimeout(this.popover_timeout);
}
- this.clear_all_popovers();
- });
-
- $(document.body).on('mousemove', () => {
- if (!this.link_hovered) {
- this.clear_all_popovers();
- }
+ if(!this.link_hovered) this.clear_all_popovers();
});
$(window).on('hashchange', () => {
@@ -113,7 +127,15 @@ frappe.ui.LinkPreview = class {
let dt = this.doctype;
let fields = [];
frappe.model.with_doctype(dt, () => {
- let meta_fields = frappe.get_meta(dt).fields;
+ let meta = frappe.get_meta(dt);
+ let meta_fields = meta.fields;
+
+ if (!meta.show_preview_popup) {
+ // no preview
+ resolve([]);
+ return;
+ }
+
meta_fields.filter((field) => {
// build list of fields to fetch
if(field.in_preview) {
@@ -171,41 +193,48 @@ frappe.ui.LinkPreview = class {
}
let image_html = '';
- let title_html = '';
- let content_html = ``;
+ let id_html = '';
+ let content_html = '';
+ let meta = frappe.get_meta(this.doctype);
+ let title = preview_data.title;
- if(preview_data['image']) {
- let image_url = encodeURI(preview_data['image']);
+ if(preview_data[meta.image_field]) {
+ let image_url = encodeURI(preview_data[meta.image_field]);
image_html += `
`;
}
- if(preview_data['title']) {
- title_html+= `${preview_data['title']} `;
+
+
+ if(title && title != preview_data.name) {
+ id_html+= `${preview_data.name} `;
+ }
+ if(!title) {
+ title = preview_data.name;
}
Object.keys(preview_data).forEach(key => {
- if(key!='image' && key!='name') {
+ if(key!=meta.image_field && key!='name' && key!=meta.title_field) {
let value = this.truncate_value(preview_data[key]);
let label = this.truncate_value(frappe.meta.get_label(this.doctype, key));
content_html += `
-
- ${label}
- ${value}
-
+
`;
}
});
- content_html+=`
`;
+
+ content_html = `${content_html}
`;
let popover_content =
`
@@ -218,8 +247,8 @@ frappe.ui.LinkPreview = class {
}
truncate_value(value) {
- if (value.length > 100) {
- value = value.slice(0,100) + '...';
+ if (value.length > 280) {
+ value = value.slice(0,280) + '...';
}
return value;
}
diff --git a/frappe/public/js/frappe/views/components/ModuleLinkItem.vue b/frappe/public/js/frappe/views/components/ModuleLinkItem.vue
index eccd0b0c33..946bc8c4ac 100644
--- a/frappe/public/js/frappe/views/components/ModuleLinkItem.vue
+++ b/frappe/public/js/frappe/views/components/ModuleLinkItem.vue
@@ -92,6 +92,7 @@ a:hover, a:focus {
.link-content {
flex: 1;
+ font-size: 1.1em;
}
.popover {
diff --git a/frappe/public/js/frappe/views/reports/print_grid.html b/frappe/public/js/frappe/views/reports/print_grid.html
index 12854e74f3..e99600c2e6 100644
--- a/frappe/public/js/frappe/views/reports/print_grid.html
+++ b/frappe/public/js/frappe/views/reports/print_grid.html
@@ -31,15 +31,17 @@
{% var value = col.fieldname ? row[col.fieldname] : row[col.id]; %}
- {{
- 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
- }}
+
+ {{
+ 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
+ }}
+
{% endif %}
{% endfor %}
diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js
index 485f26f952..5e7e79312f 100644
--- a/frappe/public/js/frappe/views/reports/query_report.js
+++ b/frappe/public/js/frappe/views/reports/query_report.js
@@ -970,6 +970,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
{
label: __('Export'),
action: () => this.export_report(),
+ condition: () => frappe.model.can_export(this.report_doc.ref_doctype),
standard: true
},
{
@@ -991,9 +992,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);
});
}
diff --git a/frappe/public/less/desk.less b/frappe/public/less/desk.less
index ad7a039dc6..48acfb14f7 100644
--- a/frappe/public/less/desk.less
+++ b/frappe/public/less/desk.less
@@ -2,6 +2,7 @@
@import "mixins.less";
@import "common.less";
@import "quill.less";
+@import "print.less";
.nav-pills a, .nav-pills a:hover {
border-bottom: none;
diff --git a/frappe/public/less/link_preview.less b/frappe/public/less/link_preview.less
index 2a3ccce5b3..9e8ed30e57 100644
--- a/frappe/public/less/link_preview.less
+++ b/frappe/public/less/link_preview.less
@@ -4,8 +4,7 @@
.popover-content {
padding: 0;
.preview-popover-header {
- background: #f7fafc;
- padding: 10px;
+ padding: 15px;
.preview-header {
display: inline-block;
@@ -34,19 +33,16 @@
}
.preview-table {
- margin: 10px;
+ padding: 15px;
+ padding-bottom: 5px;
max-width: 330px;
min-width: 200px;
- }
- .preview-field {
- td {
- padding: 3px;
- vertical-align: top;
- }
-
- .field-name {
- width: 39%;
+ .preview-field {
+ padding-bottom: 10px;
+ .preview-label {
+ padding-bottom: 4px;
+ }
}
}
}
diff --git a/frappe/public/less/print.less b/frappe/public/less/print.less
new file mode 100644
index 0000000000..203b1237c5
--- /dev/null
+++ b/frappe/public/less/print.less
@@ -0,0 +1,15 @@
+.text-editor-print {
+ ul li {
+ list-style-type: none;
+ padding-left: 1.5em;
+ }
+
+ ul li:before {
+ content: '\2022';
+ margin-left: -1.5em;
+ margin-right: 0.3em;
+ text-align: right;
+ white-space: nowrap;
+ width: 1.2em;
+ }
+}
diff --git a/frappe/public/scss/variables.scss b/frappe/public/scss/variables.scss
index ac9bf6cf03..6ee7cda884 100644
--- a/frappe/public/scss/variables.scss
+++ b/frappe/public/scss/variables.scss
@@ -1,20 +1,20 @@
-$gray-100: #fafbfc;
-$gray-150: #f5f7fa;
-$gray-200: #ebecf1;
-$gray-300: #d1d8dd;
-$gray-400: #ced4da;
-$gray-500: #adb5bd;
-$gray-600: #8d99a6;
-$gray-700: #495057;
-$gray-800: #36414c;
-$gray-900: #2e3338;
-$primary: #5e64ff;
+$gray-100: #fafbfc !default;
+$gray-150: #f5f7fa !default;
+$gray-200: #ebecf1 !default;
+$gray-300: #d1d8dd !default;
+$gray-400: #ced4da !default;
+$gray-500: #adb5bd !default;
+$gray-600: #8d99a6 !default;
+$gray-700: #495057 !default;
+$gray-800: #36414c !default;
+$gray-900: #2e3338 !default;
+$primary: #5e64ff !default;
-$black: #000;
+$black: #000 !default;
-$body-color: $gray-800;
-$text-muted: $gray-600;
-$border-color: $gray-200;
+$body-color: $gray-800 !default;
+$text-muted: $gray-600 !default;
+$border-color: $gray-200 !default;
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
diff --git a/frappe/social/doctype/energy_point_log/energy_point_log.py b/frappe/social/doctype/energy_point_log/energy_point_log.py
index 42e12c0374..c94c1bd371 100644
--- a/frappe/social/doctype/energy_point_log/energy_point_log.py
+++ b/frappe/social/doctype/energy_point_log/energy_point_log.py
@@ -31,36 +31,51 @@ class EnergyPointLog(Document):
frappe.publish_realtime('update_points', after_commit=True)
def get_alert_dict(doc):
- alert_dict = frappe._dict({
- 'message': '',
- 'indicator': 'green'
- })
+ alert_dict = frappe._dict()
owner_name = get_fullname(doc.owner)
doc_link = frappe.get_desk_link(doc.reference_doctype, doc.reference_name)
- points = frappe.bold(doc.points)
+ points = doc.points
+ bold_points = frappe.bold(doc.points)
if doc.type == 'Auto':
- alert_dict.message=_('You gained {} points').format(points)
+ if points == 1:
+ message = _('You gained {0} point')
+ else:
+ message = _('You gained {0} points')
+ alert_dict.message = message.format(bold_points)
+ alert_dict.indicator = 'green'
elif doc.type == 'Appreciation':
- alert_dict.message = _('{} appreciated your work on {} with {} points').format(
+ if points == 1:
+ message = _('{0} appreciated your work on {1} with {2} point')
+ else:
+ message = _('{0} appreciated your work on {1} with {2} points')
+ alert_dict.message = message.format(
owner_name,
doc_link,
- points
+ bold_points
)
+ alert_dict.indicator = 'green'
elif doc.type == 'Criticism':
- alert_dict.message = _('{} criticized your work on {} with {} points').format(
+ if points == 1:
+ message = _('{0} criticized your work on {1} with {2} point')
+ else:
+ message = _('{0} criticized your work on {1} with {2} points')
+
+ alert_dict.message = message.format(
owner_name,
doc_link,
- points
+ bold_points
)
alert_dict.indicator = 'red'
elif doc.type == 'Revert':
- alert_dict.message = _('{} reverted your points on {}').format(
+ if points == 1:
+ message = _('{0} reverted your point on {1}')
+ else:
+ message = _('{0} reverted your points on {1}')
+ alert_dict.message = message.format(
owner_name,
doc_link,
)
alert_dict.indicator = 'red'
- else:
- alert_dict = {}
return alert_dict
@@ -135,7 +150,7 @@ def get_user_energy_and_review_points(user=None, from_date=None, as_dict=True):
{conditions}
GROUP BY `user`
ORDER BY `energy_points` DESC
- """.format(conditions=conditions), values=values or (), as_dict=1)
+ """.format(conditions=conditions), values=tuple(values), as_dict=1)
if not as_dict:
return points_list
@@ -245,7 +260,7 @@ def send_summary(timespan):
def get_footer_message(timespan):
if timespan == 'Monthly':
- return _("Stats based on last month's performance (from {} to {})")
+ return _("Stats based on last month's performance (from {0} to {1})")
else:
- return _("Stats based on last week's performance (from {} to {})")
+ return _("Stats based on last week's performance (from {0} to {1})")
diff --git a/frappe/templates/includes/comments/comments.py b/frappe/templates/includes/comments/comments.py
index 52abb60b4d..cf2436da15 100644
--- a/frappe/templates/includes/comments/comments.py
+++ b/frappe/templates/includes/comments/comments.py
@@ -16,16 +16,18 @@ def add_comment(comment, comment_email, comment_by, reference_doctype, reference
frappe.msgprint(_('Comment Should be atleast 10 characters'))
return ''
+ blacklist = ['http://', 'https://', '@gmail.com']
+
+ if any([b in comment for b in blacklist]):
+ frappe.msgprint(_('Comments cannot have links or email addresses'))
+ return ''
+
comment = doc.add_comment(
text = comment,
comment_email = comment_email,
comment_by = comment_by)
- blacklist = ['http://', 'https://', '@gmail.com']
-
- if not any([b in comment.content for b in blacklist]):
- # probably not spam!
- comment.db_set('published', 1)
+ comment.db_set('published', 1)
# since comments are embedded in the page, clear the web cache
if route:
diff --git a/frappe/templates/includes/login/login.js b/frappe/templates/includes/login/login.js
index dd0f57eb4c..992051bc45 100644
--- a/frappe/templates/includes/login/login.js
+++ b/frappe/templates/includes/login/login.js
@@ -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 }}";
diff --git a/frappe/templates/print_formats/standard_macros.html b/frappe/templates/print_formats/standard_macros.html
index ea667704af..2d8035ac93 100644
--- a/frappe/templates/print_formats/standard_macros.html
+++ b/frappe/templates/print_formats/standard_macros.html
@@ -97,8 +97,10 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}"
{%- if df.fieldtype=="Code" %}
{{ doc.get(df.fieldname) }}
{% else -%}
+ {%- if df.fieldtype=="Text Editor" -%}{%- endif -%}
{{ doc.get_formatted(df.fieldname, parent_doc or doc, translated=df.translatable) }}
- {% endif -%}
+ {%- if df.fieldtype=="Text Editor" -%}
{%- endif -%}
+ {% endif -%}
{%- endif -%}
{%- endmacro -%}
diff --git a/frappe/utils/data.py b/frappe/utils/data.py
index 84ea04b593..bad5407b4b 100644
--- a/frappe/utils/data.py
+++ b/frappe/utils/data.py
@@ -108,6 +108,11 @@ def add_years(date, years):
def date_diff(string_ed_date, string_st_date):
return (getdate(string_ed_date) - getdate(string_st_date)).days
+def month_diff(string_ed_date, string_st_date):
+ ed_date = getdate(string_ed_date)
+ st_date = getdate(string_st_date)
+ return (ed_date.year - st_date.year) * 12 + ed_date.month - st_date.month + 1
+
def time_diff(string_ed_date, string_st_date):
return get_datetime(string_ed_date) - get_datetime(string_st_date)
diff --git a/frappe/www/login.html b/frappe/www/login.html
index 9d132cb718..d621ab0fbb 100644
--- a/frappe/www/login.html
+++ b/frappe/www/login.html
@@ -24,9 +24,9 @@
-
-
-
+
+
+
{{ _("Login") }}
diff --git a/frappe/www/login.py b/frappe/www/login.py
index dd51dabeab..f34664e1e2 100644
--- a/frappe/www/login.py
+++ b/frappe/www/login.py
@@ -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
@@ -39,7 +39,7 @@ def get_context(context):
})
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")]
diff --git a/frappe/www/printview.html b/frappe/www/printview.html
index 2953ff2c90..b7077b5a12 100644
--- a/frappe/www/printview.html
+++ b/frappe/www/printview.html
@@ -5,10 +5,7 @@
{{ title }}
-
-
+
{%- if has_rtl -%}
{%- endif -%}