diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 282481888e..23bbc8af24 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -39,7 +39,7 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype='Data') => { let selector = `.form-control[data-fieldname="${fieldname}"]`; if (fieldtype === 'Text Editor') { - selector = `[data-fieldname="${fieldname}"] .ql-editor`; + selector = `[data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]`; } if (fieldtype === 'Code') { selector = `[data-fieldname="${fieldname}"] .ace_text-input`; diff --git a/frappe/__init__.py b/frappe/__init__.py index 0242843cee..cd662cd6cb 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -16,7 +16,6 @@ from faker import Faker # public from .exceptions import * from .utils.jinja import (get_jenv, get_template, render_template, get_email_from_template, get_jloader) -from .utils.error import get_frame_locals # Hamless for Python 3 # For Python 2 set default encoding to utf-8 @@ -188,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`. @@ -274,7 +278,7 @@ def errprint(msg): if not request or (not "cmd" in local.form_dict) or conf.developer_mode: print(msg) - error_log.append({"exc": msg, "locals": get_frame_locals()}) + error_log.append({"exc": msg}) def log(msg): """Add to `debug_log`. @@ -496,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 @@ -1329,14 +1334,15 @@ def format(*args, **kwargs): import frappe.utils.formatters return frappe.utils.formatters.format_value(*args, **kwargs) -def get_print(doctype=None, name=None, print_format=None, style=None, html=None, as_pdf=False, doc=None, output = None, no_letterhead = 0): +def get_print(doctype=None, name=None, print_format=None, style=None, html=None, as_pdf=False, doc=None, output = None, no_letterhead = 0, password=None): """Get Print Format for given document. :param doctype: DocType of document. :param name: Name of document. :param print_format: Print Format name. Default 'Standard', :param style: Print Format style. - :param as_pdf: Return as PDF. Default False.""" + :param as_pdf: Return as PDF. Default False. + :param password: Password to encrypt the pdf with. Default None""" from frappe.website.render import build_page from frappe.utils.pdf import get_pdf @@ -1347,15 +1353,19 @@ def get_print(doctype=None, name=None, print_format=None, style=None, html=None, local.form_dict.doc = doc local.form_dict.no_letterhead = no_letterhead + options = None + if password: + options = {'password': password} + if not html: html = build_page("printview") if as_pdf: - return get_pdf(html, output = output) + return get_pdf(html, output = output, options = options) else: return html -def attach_print(doctype, name, file_name=None, print_format=None, style=None, html=None, doc=None, lang=None, print_letterhead=True): +def attach_print(doctype, name, file_name=None, print_format=None, style=None, html=None, doc=None, lang=None, print_letterhead=True, password=None): from frappe.utils import scrub_urls if not file_name: file_name = name @@ -1374,12 +1384,12 @@ def attach_print(doctype, name, file_name=None, print_format=None, style=None, h if int(print_settings.send_print_as_pdf or 0): out = { "fname": file_name + ".pdf", - "fcontent": get_print(doctype, name, print_format=print_format, style=style, html=html, as_pdf=True, doc=doc, no_letterhead=no_letterhead) + "fcontent": get_print(doctype, name, print_format=print_format, style=style, html=html, as_pdf=True, doc=doc, no_letterhead=no_letterhead, password=password) } else: out = { "fname": file_name + ".html", - "fcontent": scrub_urls(get_print(doctype, name, print_format=print_format, style=style, html=html, doc=doc, no_letterhead=no_letterhead)).encode("utf-8") + "fcontent": scrub_urls(get_print(doctype, name, print_format=print_format, style=style, html=html, doc=doc, no_letterhead=no_letterhead, password=password)).encode("utf-8") } local.flags.ignore_print_permissions = False diff --git a/frappe/auth.py b/frappe/auth.py index 37a79bd4a6..3a58330e11 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -8,7 +8,7 @@ from frappe import _ import frappe import frappe.database import frappe.utils -from frappe.utils import cint, flt, get_datetime, datetime +from frappe.utils import cint, flt, get_datetime, datetime, date_diff, today import frappe.utils.user from frappe import conf from frappe.sessions import Session, clear_sessions, delete_session @@ -124,6 +124,12 @@ class LoginManager: frappe.clear_cache(user = frappe.form_dict.get('usr')) user, pwd = get_cached_user_pass() self.authenticate(user=user, pwd=pwd) + if self.force_user_to_reset_password(): + doc = frappe.get_doc("User", self.user) + frappe.local.response["redirect_to"] = doc.reset_password(send_email=False, password_expired=True) + frappe.local.response["message"] = "Password Reset" + return False + if should_run_2fa(self.user): authenticate_for_2factor(self.user) if not confirm_otp_token(self): @@ -209,6 +215,22 @@ class LoginManager: self.check_if_enabled(user) self.user = self.check_password(user, pwd) + def force_user_to_reset_password(self): + if not self.user: + return + + reset_pwd_after_days = cint(frappe.db.get_single_value("System Settings", + "force_user_to_reset_password")) + + if reset_pwd_after_days: + last_password_reset_date = frappe.db.get_value("User", + self.user, "last_password_reset_date") or today() + + last_pwd_reset_days = date_diff(today(), last_password_reset_date) + + if last_pwd_reset_days > reset_pwd_after_days: + return True + def check_if_enabled(self, user): """raise exception if user not enabled""" doc = frappe.get_doc("System Settings") diff --git a/frappe/boot.py b/frappe/boot.py index 0583b31181..4acb7ee3c1 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -16,7 +16,6 @@ from frappe.desk.form.load import get_meta_bundle from frappe.utils.change_log import get_versions from frappe.translate import get_lang_dict from frappe.email.inbox import get_email_accounts -from frappe.core.doctype.feedback_trigger.feedback_trigger import get_enabled_feedback_trigger from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_points from frappe.social.doctype.post.post import frequently_visited_links @@ -77,7 +76,6 @@ def get_bootinfo(): bootinfo.calendars = sorted(frappe.get_hooks("calendars")) bootinfo.treeviews = frappe.get_hooks("treeviews") or [] bootinfo.lang_dict = get_lang_dict() - bootinfo.feedback_triggers = get_enabled_feedback_trigger() bootinfo.gsuite_enabled = get_gsuite_status() bootinfo.success_action = get_success_action() bootinfo.update(get_email_accounts(user=frappe.session.user)) diff --git a/frappe/client.py b/frappe/client.py index fb2d47925b..3f7491ade5 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -371,4 +371,5 @@ def check_parent_permission(parent, child_doctype): if frappe.permissions.has_permission(parent): return # Either parent not passed or the user doesn't have permission on parent doctype of child table! - raise frappe.PermissionError \ No newline at end of file + raise frappe.PermissionError + diff --git a/frappe/commands/__init__.py b/frappe/commands/__init__.py index 993f1f7d11..84e5f4937f 100644 --- a/frappe/commands/__init__.py +++ b/frappe/commands/__init__.py @@ -72,12 +72,11 @@ def call_command(cmd, context): def get_commands(): # prevent circular imports - from .docs import commands as doc_commands from .scheduler import commands as scheduler_commands from .site import commands as site_commands from .translate import commands as translate_commands from .utils import commands as utils_commands - return list(set(doc_commands + scheduler_commands + site_commands + translate_commands + utils_commands)) + return list(set(scheduler_commands + site_commands + translate_commands + utils_commands)) commands = get_commands() diff --git a/frappe/commands/docs.py b/frappe/commands/docs.py deleted file mode 100644 index 6c73739478..0000000000 --- a/frappe/commands/docs.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import unicode_literals, absolute_import -import click -import os, shutil -import frappe -from frappe.commands import pass_context - -@click.command('build-docs') -@pass_context -@click.argument('app') -@click.option('--docs-version', default='current') -@click.option('--target', default=None) -@click.option('--local', default=False, is_flag=True, help='Run app locally') -@click.option('--watch', default=False, is_flag=True, help='Watch for changes and rewrite') -def build_docs(context, app, docs_version="current", target=None, local=False, watch=False): - "Setup docs in target folder of target app" - from frappe.utils import watch as start_watch - from frappe.utils.setup_docs import add_breadcrumbs_tag - - for site in context.sites: - _build_docs_once(site, app, docs_version, target, local) - - if watch: - def trigger_make(source_path, event_type): - if "/docs/user/" in source_path: - # user file - target_path = frappe.get_app_path(target, 'www', 'docs', 'user', - os.path.relpath(source_path, start=frappe.get_app_path(app, 'docs', 'user'))) - shutil.copy(source_path, target_path) - add_breadcrumbs_tag(target_path) - - if source_path.endswith('/docs/index.md'): - target_path = frappe.get_app_path(target, 'www', 'docs', 'index.md') - shutil.copy(source_path, target_path) - - apps_path = frappe.get_app_path(app) - start_watch(apps_path, handler=trigger_make) - -def _build_docs_once(site, app, docs_version, target, local, only_content_updated=False): - from frappe.utils.setup_docs import setup_docs - - try: - - frappe.init(site=site) - frappe.connect() - make = setup_docs(app, target) - - if not only_content_updated: - make.build(docs_version) - - #make.make_docs(target, local) - - finally: - frappe.destroy() - -commands = [ - build_docs, -] 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/commands/utils.py b/frappe/commands/utils.py index ab8597832d..219a99070b 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -625,19 +625,30 @@ def setup_help(context): print_in_app_help_deprecation() @click.command('rebuild-global-search') +@click.option('--static-pages', is_flag=True, default=False, help='Rebuild global search for static pages') @pass_context -def rebuild_global_search(context): +def rebuild_global_search(context, static_pages=False): '''Setup help table in the current site (called after migrate)''' - from frappe.utils.global_search import (get_doctypes_with_global_search, rebuild_for_doctype) + from frappe.utils.global_search import (get_doctypes_with_global_search, rebuild_for_doctype, + get_routes_to_index, add_route_to_global_search, sync_global_search) for site in context.sites: try: frappe.init(site) frappe.connect() - doctypes = get_doctypes_with_global_search() - for i, doctype in enumerate(doctypes): - rebuild_for_doctype(doctype) - update_progress_bar('Rebuilding Global Search', i, len(doctypes)) + + if static_pages: + routes = get_routes_to_index() + for i, route in enumerate(routes): + add_route_to_global_search(route) + frappe.local.request = None + update_progress_bar('Rebuilding Global Search', i, len(routes)) + sync_global_search() + else: + doctypes = get_doctypes_with_global_search() + for i, doctype in enumerate(doctypes): + rebuild_for_doctype(doctype) + update_progress_bar('Rebuilding Global Search', i, len(doctypes)) finally: frappe.destroy() diff --git a/frappe/config/__init__.py b/frappe/config/__init__.py index 1fe06f9094..b2c1f41d78 100644 --- a/frappe/config/__init__.py +++ b/frappe/config/__init__.py @@ -78,15 +78,22 @@ def get_modules_from_app(app): return active_modules_list def get_all_empty_tables_by_module(): - results = frappe.db.sql(""" - SELECT - name, module - FROM information_schema.tables as i - JOIN tabDocType as d - ON i.table_name = CONCAT('tab', d.name) - WHERE table_rows = 0; - - """) + results = frappe.db.multisql({ + 'mariadb': ''' + SELECT `name`, `module` + FROM information_schema.tables AS i + JOIN `tabDocType` AS d + ON i.table_name = CONCAT('tab', d.name) + WHERE `table_rows` = 0; + ''', + 'postgres': ''' + SELECT "name", "module" + FROM "pg_stat_all_tables" AS i + JOIN "tabDocType" AS d + ON i.relname = CONCAT('tab', d.name) + WHERE n_tup_ins = 0; + ''' + }) empty_tables_by_module = {} @@ -95,7 +102,6 @@ def get_all_empty_tables_by_module(): empty_tables_by_module[module].append(doctype) else: empty_tables_by_module[module] = [doctype] - return empty_tables_by_module def is_domain(module): diff --git a/frappe/contacts/address_and_contact.py b/frappe/contacts/address_and_contact.py index 38d292e9b4..6523b0b1e5 100644 --- a/frappe/contacts/address_and_contact.py +++ b/frappe/contacts/address_and_contact.py @@ -152,3 +152,11 @@ def filter_dynamic_link_doctypes(doctype, txt, searchfield, start, page_len, fil valid_doctypes = [[doctype] for doctype in valid_doctypes] return valid_doctypes + +def set_link_title(doc): + if not doc.links: + return + for link in doc.links: + if not link.link_title: + linked_doc = frappe.get_doc(link.link_doctype, link.link_name) + link.link_title = linked_doc.get("title_field") or linked_doc.get("name") diff --git a/frappe/contacts/doctype/address/address.py b/frappe/contacts/doctype/address/address.py index c705f972d3..57ed4e937f 100644 --- a/frappe/contacts/doctype/address/address.py +++ b/frappe/contacts/doctype/address/address.py @@ -15,6 +15,7 @@ from frappe.model.naming import make_autoname from frappe.core.doctype.dynamic_link.dynamic_link import deduplicate_dynamic_links from six import iteritems, string_types from past.builtins import cmp +from frappe.contacts.address_and_contact import set_link_title import functools @@ -39,6 +40,7 @@ class Address(Document): def validate(self): self.link_address() self.validate_reference() + set_link_title(self) deduplicate_dynamic_links(self) def link_address(self): diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index 987fad3829..0e0d9aeabc 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -10,6 +10,7 @@ from frappe.core.doctype.dynamic_link.dynamic_link import deduplicate_dynamic_li from six import iteritems from past.builtins import cmp from frappe.model.naming import append_number_if_name_exists +from frappe.contacts.address_and_contact import set_link_title import functools @@ -31,6 +32,7 @@ class Contact(Document): if self.email_id: self.email_id = self.email_id.strip() self.set_user() + set_link_title(self) if self.email_id and not self.image: self.image = has_gravatar(self.email_id) 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/activity_log/feed.py b/frappe/core/doctype/activity_log/feed.py index e09436196e..8892038681 100644 --- a/frappe/core/doctype/activity_log/feed.py +++ b/frappe/core/doctype/activity_log/feed.py @@ -67,7 +67,7 @@ def get_feed_match_conditions(user=None, doctype='Comment'): user_permissions = frappe.permissions.get_user_permissions(user) can_read = frappe.get_user().get_can_read() - can_read_doctypes = ['"{}"'.format(doctype) for doctype in + can_read_doctypes = ["'{}'".format(doctype) for doctype in list(set(can_read) - set(list(user_permissions)))] if can_read_doctypes: 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.js b/frappe/core/doctype/communication/communication.js index 8e35c388a5..924c29bee2 100644 --- a/frappe/core/doctype/communication/communication.js +++ b/frappe/core/doctype/communication/communication.js @@ -31,13 +31,6 @@ frappe.ui.form.on("Communication", { } } - if(frm.doc.communication_type == "Feedback") { - frm.add_custom_button(__("Resend"), function() { - var feedback = new frappe.utils.Feedback(); - feedback.resend_feedback_request(frm.doc); - }); - } - if(frm.doc.status==="Open") { frm.add_custom_button(__("Close"), function() { frm.set_value("status", "Closed"); @@ -54,7 +47,7 @@ frappe.ui.form.on("Communication", { frm.trigger('show_relink_dialog'); }); - if(frm.doc.communication_type=="Communication" + if(frm.doc.communication_type=="Communication" && frm.doc.communication_medium == "Email" && frm.doc.sent_or_received == "Received") { @@ -90,7 +83,7 @@ frappe.ui.form.on("Communication", { } } - if(frm.doc.communication_type=="Communication" + if(frm.doc.communication_type=="Communication" && frm.doc.communication_medium == "Phone" && frm.doc.sent_or_received == "Received"){ @@ -185,7 +178,7 @@ frappe.ui.form.on("Communication", { forward_mail: function(frm) { var args = frm.events.get_mail_args(frm) - $.extend(args, { + $.extend(args, { forward: true, subject: __("Fw: {0}", [frm.doc.subject]), }) diff --git a/frappe/core/doctype/communication/communication.json b/frappe/core/doctype/communication/communication.json index ea0e429e0f..34d8d29bdb 100644 --- a/frappe/core/doctype/communication/communication.json +++ b/frappe/core/doctype/communication/communication.json @@ -1,1728 +1,438 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2013-01-29 10:47:14", - "custom": 0, - "description": "Keep a track of all communications", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, - "engine": "InnoDB", + "allow_import": 1, + "creation": "2013-01-29 10:47:14", + "description": "Keeps track of all communications", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "subject", + "section_break_10", + "communication_medium", + "sender", + "column_break_4", + "recipients", + "cc", + "bcc", + "phone_no", + "delivery_status", + "section_break_8", + "content", + "status_section", + "text_content", + "communication_type", + "comment_type", + "column_break_5", + "status", + "sent_or_received", + "additional_info", + "communication_date", + "read_receipt", + "column_break_14", + "sender_full_name", + "read_by_recipient", + "read_by_recipient_on", + "reference_section", + "reference_doctype", + "reference_name", + "reference_owner", + "email_account", + "in_reply_to", + "user", + "column_break_27", + "email_template", + "unread_notification_sent", + "seen", + "_user_tags", + "timeline_links_sections", + "timeline_links", + "email_inbox", + "message_id", + "uid", + "email_status", + "has_attachment", + "feedback_section", + "rating", + "feedback_request" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "subject", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Subject", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "subject", + "fieldtype": "Small Text", + "in_global_search": 1, + "label": "Subject", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "", - "columns": 0, - "fieldname": "section_break_10", - "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": "To and CC", - "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 - }, + "collapsible": 1, + "fieldname": "section_break_10", + "fieldtype": "Section Break", + "label": "To and CC" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "depends_on": "eval:doc.communication_type===\"Communication\"", - "fieldname": "communication_medium", - "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": "Type", - "length": 0, - "no_copy": 0, - "options": "\nEmail\nChat\nPhone\nSMS\nEvent\nMeeting\nVisit\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 - }, + "depends_on": "eval:doc.communication_type===\"Communication\"", + "fieldname": "communication_medium", + "fieldtype": "Select", + "label": "Type", + "options": "\nEmail\nChat\nPhone\nSMS\nEvent\nMeeting\nVisit\nOther" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.communication_medium===\"Email\"", - "fieldname": "sender", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "From", - "length": 255, - "no_copy": 0, - "options": "Email", - "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 - }, + "depends_on": "eval:doc.communication_medium===\"Email\"", + "fieldname": "sender", + "fieldtype": "Data", + "in_global_search": 1, + "label": "From", + "length": 255, + "options": "Email" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "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 - }, + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "recipients", - "fieldtype": "Code", - "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": "To", - "length": 0, - "no_copy": 0, - "options": "Email", - "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 - }, + "fieldname": "recipients", + "fieldtype": "Code", + "label": "To", + "options": "Email" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.communication_medium===\"Email\"", - "fieldname": "cc", - "fieldtype": "Code", - "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": "CC", - "length": 0, - "no_copy": 0, - "options": "Email", - "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 - }, + "depends_on": "eval:doc.communication_medium===\"Email\"", + "fieldname": "cc", + "fieldtype": "Code", + "label": "CC", + "options": "Email" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.communication_medium===\"Email\"", - "fieldname": "bcc", - "fieldtype": "Code", - "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": "BCC", - "length": 0, - "no_copy": 0, - "options": "Email", - "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 - }, + "depends_on": "eval:doc.communication_medium===\"Email\"", + "fieldname": "bcc", + "fieldtype": "Code", + "label": "BCC", + "options": "Email" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:in_list([\"Phone\",\"SMS\"],doc.communication_medium)", - "fieldname": "phone_no", - "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": "Phone No.", - "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 - }, + "depends_on": "eval:in_list([\"Phone\",\"SMS\"],doc.communication_medium)", + "fieldname": "phone_no", + "fieldtype": "Data", + "label": "Phone No." + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Integrations can use this field to set email delivery status", - "fieldname": "delivery_status", - "fieldtype": "Select", - "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": "Delivery Status", - "length": 0, - "no_copy": 0, - "options": "\nSent\nBounced\nOpened\nMarked As Spam\nRejected\nDelayed\nSoft-Bounced\nClicked\nRecipient Unsubscribed\nError\nExpired\nSending\nRead", - "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 - }, + "description": "Integrations can use this field to set email delivery status", + "fieldname": "delivery_status", + "fieldtype": "Select", + "hidden": 1, + "label": "Delivery Status", + "options": "\nSent\nBounced\nOpened\nMarked As Spam\nRejected\nDelayed\nSoft-Bounced\nClicked\nRecipient Unsubscribed\nError\nExpired\nSending\nRead" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_8", - "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 - }, + "fieldname": "section_break_8", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "content", - "fieldtype": "Text Editor", - "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": "Message", - "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, + "fieldname": "content", + "fieldtype": "Text Editor", + "label": "Message", "width": "400" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "status_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": "Status", - "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 - }, + "collapsible": 1, + "fieldname": "status_section", + "fieldtype": "Section Break", + "label": "Status" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "text_content", - "fieldtype": "Code", - "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": "Text Content", - "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 - }, + "fieldname": "text_content", + "fieldtype": "Code", + "hidden": 1, + "label": "Text Content" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Communication", - "fieldname": "communication_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": "Communication Type", - "length": 0, - "no_copy": 0, - "options": "Communication\nComment\nChat\nBot\nNotification\nFeedback", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Communication", + "fieldname": "communication_type", + "fieldtype": "Select", + "label": "Communication Type", + "options": "Communication\nComment\nChat\nBot\nNotification\nFeedback", + "read_only": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "comment_type", - "fieldtype": "Select", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Comment Type", - "length": 0, - "no_copy": 0, - "options": "\nComment\nLike\nInfo\nLabel\nWorkflow\nCreated\nSubmitted\nCancelled\nUpdated\nDeleted\nAssigned\nAssignment Completed\nAttachment\nAttachment Removed\nShared\nUnshared\nBot\nRelinked", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "comment_type", + "fieldtype": "Select", + "hidden": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Comment Type", + "options": "\nComment\nLike\nInfo\nLabel\nWorkflow\nCreated\nSubmitted\nCancelled\nUpdated\nDeleted\nAssigned\nAssignment Completed\nAttachment\nAttachment Removed\nShared\nUnshared\nBot\nRelinked", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_5", - "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 - }, + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.communication_type===\"Communication\"", - "fieldname": "status", - "fieldtype": "Select", - "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": "Status", - "length": 0, - "no_copy": 0, - "options": "Open\nReplied\nClosed\nLinked", - "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 - }, + "depends_on": "eval:doc.communication_type===\"Communication\"", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "Open\nReplied\nClosed\nLinked", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.communication_type===\"Communication\"", - "fieldname": "sent_or_received", - "fieldtype": "Select", - "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": "Sent or Received", - "length": 0, - "no_copy": 0, - "options": "Sent\nReceived", - "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": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.communication_type===\"Communication\"", + "fieldname": "sent_or_received", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Sent or Received", + "options": "Sent\nReceived", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "additional_info", - "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": "More Information", - "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 - }, + "collapsible": 1, + "fieldname": "additional_info", + "fieldtype": "Section Break", + "label": "More Information" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Now", - "fieldname": "communication_date", - "fieldtype": "Datetime", - "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": "Date", - "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 - }, + "default": "Now", + "fieldname": "communication_date", + "fieldtype": "Datetime", + "label": "Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "read_receipt", - "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": "Sent Read Receipt", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "read_receipt", + "fieldtype": "Check", + "label": "Sent Read Receipt", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_14", - "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 - }, + "fieldname": "column_break_14", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sender_full_name", - "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": "From Full Name", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "sender_full_name", + "fieldtype": "Data", + "label": "From Full Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "read_by_recipient", - "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": "Read by Recipient", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "read_by_recipient", + "fieldtype": "Check", + "label": "Read by Recipient", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "read_by_recipient_on", - "fieldtype": "Datetime", - "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": "Read by Recipient On", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "read_by_recipient_on", + "fieldtype": "Datetime", + "label": "Read by Recipient On", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "reference_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": "Reference", - "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 - }, + "collapsible": 1, + "fieldname": "reference_section", + "fieldtype": "Section Break", + "label": "Reference" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_doctype", - "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": "Reference 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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "reference_doctype", + "fieldtype": "Link", + "label": "Reference DocType", + "options": "DocType" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_name", - "fieldtype": "Dynamic 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": "Reference Name", - "length": 0, - "no_copy": 0, - "options": "reference_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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "label": "Reference Name", + "options": "reference_doctype" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "reference_name.owner", - "fieldname": "reference_owner", - "fieldtype": "Read Only", - "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": "Reference Owner", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "reference_name.owner", + "fieldname": "reference_owner", + "fieldtype": "Read Only", + "label": "Reference Owner", + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.communication_medium===\"Email\"", - "fieldname": "email_account", - "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": "Email Account", - "length": 0, - "no_copy": 0, - "options": "Email Account", - "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, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.communication_medium===\"Email\"", + "fieldname": "email_account", + "fieldtype": "Link", + "label": "Email Account", + "options": "Email Account", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "in_reply_to", - "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": "In Reply To", - "length": 0, - "no_copy": 0, - "options": "Communication", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "in_reply_to", + "fieldtype": "Link", + "label": "In Reply To", + "options": "Communication", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "__user", - "fieldname": "user", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "User", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "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, - "translatable": 0, - "unique": 0 - }, + "default": "__user", + "fieldname": "user", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "User", + "options": "User", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_27", - "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 - }, + "fieldname": "column_break_27", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "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_global_search": 0, - "in_list_view": 0, - "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": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "unread_notification_sent", + "fieldtype": "Check", + "label": "Unread Notification Sent", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "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_global_search": 0, - "in_list_view": 0, - "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": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "seen", + "fieldtype": "Check", + "label": "Seen", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "timeline_doctype", - "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": "Timeline DocType", - "length": 0, - "no_copy": 0, - "options": "DocType", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "_user_tags", + "fieldtype": "Data", + "hidden": 1, + "label": "User Tags", + "no_copy": 1, + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "timeline_name", - "fieldtype": "Dynamic 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": "Timeline Name", - "length": 0, - "no_copy": 0, - "options": "timeline_doctype", - "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, - "translatable": 0, - "unique": 0 - }, + "collapsible": 1, + "fieldname": "email_inbox", + "fieldtype": "Section Break", + "label": "Email Inbox", + "permlevel": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "timeline_label", - "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 Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "message_id", + "fieldtype": "Data", + "ignore_xss_filter": 1, + "label": "Message ID", + "length": 995, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fieldname": "unread_notification_sent", - "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": "Unread Notification Sent", - "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, - "translatable": 0, - "unique": 0 - }, + "collapsible": 1, + "fieldname": "uid", + "fieldtype": "Int", + "hidden": 1, + "label": "UID", + "no_copy": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "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": "Seen", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "email_status", + "fieldtype": "Select", + "label": "Email Status", + "options": "Open\nSpam\nTrash" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "_user_tags", - "fieldtype": "Data", - "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": "User Tags", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "print_hide": 1, - "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 - }, + "default": "0", + "fieldname": "has_attachment", + "fieldtype": "Check", + "hidden": 1, + "label": "Has Attachment" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "email_inbox", - "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": "Email Inbox", - "length": 0, - "no_copy": 0, - "permlevel": 1, - "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 - }, + "collapsible": 1, + "depends_on": "eval: doc.rating > 0", + "fieldname": "feedback_section", + "fieldtype": "Section Break", + "label": "Feedback" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "message_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Message ID", - "length": 995, - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "rating", + "fieldtype": "Int", + "label": "Rating", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "uid", - "fieldtype": "Int", - "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": "UID", - "length": 0, - "no_copy": 1, - "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 - }, + "fieldname": "feedback_request", + "fieldtype": "Data", + "label": "Feedback Request", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "email_status", - "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": "Email Status", - "length": 0, - "no_copy": 0, - "options": "Open\nSpam\nTrash", - "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 - }, + "fieldname": "email_template", + "fieldtype": "Link", + "label": "Email Template", + "options": "Email Template", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "has_attachment", - "fieldtype": "Check", - "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": "Has Attachment", - "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 - }, + "collapsible": 1, + "fieldname": "timeline_links_sections", + "fieldtype": "Section Break", + "label": "Timeline Links" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "depends_on": "eval: doc.rating > 0", - "fieldname": "feedback_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": "Feedback", - "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, - "fieldname": "rating", - "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": "Rating", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "feedback_request", - "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": "Feedback Request", - "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, - "translatable": 0, - "unique": 0 + "fieldname": "timeline_links", + "fieldtype": "Table", + "label": "Timeline Links", + "options": "Communication Link", + "permlevel": 2 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-comment", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-09-24 13:53:52.389244", - "modified_by": "chdecultot@dokos.io", - "module": "Core", - "name": "Communication", - "owner": "Administrator", + ], + "icon": "fa fa-comment", + "idx": 1, + "modified": "2019-05-21 09:48:24.892143", + "modified_by": "Administrator", + "module": "Core", + "name": "Communication", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - }, + "create": 1, + "delete": 1, + "email": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1 + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - }, + "delete": 1, + "email": 1, + "export": 1, + "permlevel": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1 + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 1, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "All", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "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, + "if_owner": 1, + "read": 1, + "role": "All" } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "subject", - "show_name_in_global_search": 0, - "sort_order": "DESC", - "title_field": "subject", - "track_changes": 1, - "track_seen": 1, - "track_views": 0 + ], + "search_fields": "subject", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "subject", + "track_changes": 1, + "track_seen": 1 } \ No newline at end of file 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 0048668ee2..36377a90f7 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -22,7 +22,7 @@ from frappe.utils.background_jobs import enqueue def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False, print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, bcc=None, - flags=None, read_receipt=None, print_letterhead=True): + flags=None, read_receipt=None, print_letterhead=True, email_template=None): """Make a new communication. :param doctype: Reference DocType. @@ -38,6 +38,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = :param print_format: Print Format name of parent document to be sent as attachment. :param attachments: List of attachments as list of files or JSON string. :param send_me_a_copy: Send a copy to the sender (default **False**). + :param email_template: Template which is used to compose mail . """ is_error_report = (doctype=="User" and name==frappe.session.user and subject=="Error Report") @@ -66,15 +67,13 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "sent_or_received": sent_or_received, "reference_doctype": doctype, "reference_name": name, + "email_template": email_template, "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) @@ -555,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/feedback_request/__init__.py b/frappe/core/doctype/communication_link/__init__.py similarity index 100% rename from frappe/core/doctype/feedback_request/__init__.py rename to frappe/core/doctype/communication_link/__init__.py 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/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index b295a9be66..c78495898e 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -30,6 +30,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "", "length": 0, @@ -62,6 +63,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 1, + "in_preview": 0, "in_standard_filter": 0, "label": "Label", "length": 0, @@ -99,13 +101,14 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 1, + "in_preview": 0, "in_standard_filter": 0, "label": "Type", "length": 0, "no_copy": 0, "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -134,6 +137,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 1, + "in_preview": 0, "in_standard_filter": 0, "label": "Name", "length": 0, @@ -168,6 +172,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 1, + "in_preview": 0, "in_standard_filter": 0, "label": "Mandatory", "length": 0, @@ -206,6 +211,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Precision", "length": 0, @@ -240,6 +246,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Length", "length": 0, @@ -273,6 +280,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Index", "length": 0, @@ -309,6 +317,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "In List View", "length": 0, @@ -343,6 +352,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "In Standard Filter", "length": 0, @@ -377,6 +387,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "In Global Search", "length": 0, @@ -394,6 +405,40 @@ "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_preview", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "label": "In Preview", + "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, @@ -410,6 +455,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Allow in Quick Entry", "length": 0, @@ -443,6 +489,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Bold", "length": 0, @@ -478,6 +525,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Translatable", "length": 0, @@ -512,6 +560,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Collapsible", "length": 255, @@ -546,6 +595,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Collapsible Depends On", "length": 0, @@ -580,6 +630,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "length": 0, "no_copy": 0, @@ -612,6 +663,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 1, + "in_preview": 0, "in_standard_filter": 0, "label": "Options", "length": 0, @@ -646,6 +698,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Default", "length": 0, @@ -680,6 +733,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Fetch From", "length": 0, @@ -714,6 +768,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Fetch If Empty", "length": 0, @@ -747,6 +802,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Permissions", "length": 0, @@ -779,6 +835,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Display Depends On", "length": 255, @@ -814,6 +871,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Hidden", "length": 0, @@ -850,6 +908,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Read Only", "length": 0, @@ -884,6 +943,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Unique", "length": 0, @@ -918,6 +978,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Set Only Once", "length": 0, @@ -951,6 +1012,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Allow Bulk Edit", "length": 0, @@ -984,6 +1046,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "length": 0, "no_copy": 0, @@ -1016,6 +1079,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Perm Level", "length": 0, @@ -1053,6 +1117,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Ignore User Permissions", "length": 0, @@ -1086,6 +1151,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Allow on Submit", "length": 0, @@ -1122,6 +1188,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Report Hide", "length": 0, @@ -1159,6 +1226,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Remember Last Selected Value", "length": 0, @@ -1193,6 +1261,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Ignore XSS Filter", "length": 0, @@ -1226,6 +1295,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Display", "length": 0, @@ -1258,6 +1328,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "In Filter", "length": 0, @@ -1294,6 +1365,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "No Copy", "length": 0, @@ -1330,6 +1402,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Print Hide", "length": 0, @@ -1367,6 +1440,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Print Hide If No Value", "length": 0, @@ -1400,6 +1474,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Print Width", "length": 0, @@ -1432,6 +1507,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Width", "length": 0, @@ -1470,6 +1546,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "label": "Columns", "length": 0, @@ -1503,6 +1580,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "length": 0, "no_copy": 0, @@ -1534,6 +1612,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 1, + "in_preview": 0, "in_standard_filter": 0, "label": "Description", "length": 0, @@ -1570,6 +1649,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "length": 0, "no_copy": 0, @@ -1603,6 +1683,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, + "in_preview": 0, "in_standard_filter": 0, "length": 0, "no_copy": 0, @@ -1622,16 +1703,14 @@ } ], "has_web_view": 0, - "hide_heading": 0, "hide_toolbar": 0, "idx": 1, - "image_view": 0, "in_create": 0, "is_submittable": 0, "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2019-03-18 17:59:57.873790", + "modified": "2019-04-08 12:19:53.415372", "modified_by": "Administrator", "module": "Core", "name": "DocField", @@ -1639,7 +1718,6 @@ "permissions": [], "quick_entry": 0, "read_only": 0, - "read_only_onload": 0, "show_name_in_global_search": 0, "sort_order": "ASC", "track_changes": 0, 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
  1. field:[fieldname] - By Field
  2. naming_series: - By Naming Series (field called naming_series must be present
  3. Prompt - Prompt user for a name
  4. [series] - Series by prefix (separated by a dot); for example PRE.#####
  5. \n
  6. format: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:\n
  1. field:[fieldname] - By Field
  2. naming_series: - By Naming Series (field called naming_series must be present
  3. Prompt - Prompt user for a name
  4. [series] - Series by prefix (separated by a dot); for example PRE.#####
  5. \n
  6. format: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..acb43b5f19 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -23,6 +23,14 @@ import frappe.website.render import json class InvalidFieldNameError(frappe.ValidationError): pass +class UniqueFieldnameError(frappe.ValidationError): pass +class IllegalMandatoryError(frappe.ValidationError): pass +class DoctypeLinkError(frappe.ValidationError): pass +class WrongOptionsDoctypeLinkError(frappe.ValidationError): pass +class HiddenAndMandatoryWithoutDefaultError(frappe.ValidationError): pass +class NonUniqueError(frappe.ValidationError): pass +class CannotIndexedError(frappe.ValidationError): pass +class CannotCreateStandardDoctypeError(frappe.ValidationError): pass form_grid_templates = { "fields": "templates/form_grid/fields.html" @@ -101,7 +109,7 @@ class DocType(Document): return if not frappe.conf.get("developer_mode") and not self.custom: - frappe.throw(_("Not in Developer Mode! Set in site_config.json or make 'Custom' DocType.")) + frappe.throw(_("Not in Developer Mode! Set in site_config.json or make 'Custom' DocType."), CannotCreateStandardDoctypeError) def setup_fields_to_fetch(self): '''Setup query to update values for newly set fetch values''' @@ -542,7 +550,6 @@ def validate_fields_for_doctype(doctype): # this is separate because it is also called via custom field def validate_fields(meta): """Validate doctype fields. Checks - 1. There are no illegal characters in fieldnames 2. If fieldnames are unique. 3. Validate column length. @@ -562,38 +569,38 @@ def validate_fields(meta): def check_illegal_characters(fieldname): validate_column_name(fieldname) - def check_unique_fieldname(fieldname): + def check_unique_fieldname(docname, fieldname): duplicates = list(filter(None, map(lambda df: df.fieldname==fieldname and str(df.idx) or None, fields))) if len(duplicates) > 1: - frappe.throw(_("Fieldname {0} appears multiple times in rows {1}").format(fieldname, ", ".join(duplicates))) + frappe.throw(_("{0}: Fieldname {1} appears multiple times in rows {2}").format(docname, fieldname, ", ".join(duplicates)), UniqueFieldnameError) def check_fieldname_length(fieldname): validate_column_length(fieldname) - def check_illegal_mandatory(d): + def check_illegal_mandatory(docname, d): if (d.fieldtype in no_value_fields) and d.fieldtype not in table_fields and d.reqd: - frappe.throw(_("Field {0} of type {1} cannot be mandatory").format(d.label, d.fieldtype)) + frappe.throw(_("{0}: Field {1} of type {2} cannot be mandatory").format(docname, d.label, d.fieldtype), IllegalMandatoryError) - def check_link_table_options(d): + def check_link_table_options(docname, d): if d.fieldtype in ("Link",) + table_fields: if not d.options: - frappe.throw(_("Options required for Link or Table type field {0} in row {1}").format(d.label, d.idx)) + frappe.throw(_("{0}: Options required for Link or Table type field {1} in row {2}").format(docname, d.label, d.idx), DoctypeLinkError) if d.options=="[Select]" or d.options==d.parent: return if d.options != d.parent: options = frappe.db.get_value("DocType", d.options, "name") if not options: - frappe.throw(_("Options must be a valid DocType for field {0} in row {1}").format(d.label, d.idx)) + frappe.throw(_("{0}: Options must be a valid DocType for field {1} in row {2}").format(docname, d.label, d.idx), WrongOptionsDoctypeLinkError) elif not (options == d.options): - frappe.throw(_("Options {0} must be the same as doctype name {1} for the field {2}") - .format(d.options, options, d.label)) + frappe.throw(_("{0}: Options {1} must be the same as doctype name {2} for the field {3}", DoctypeLinkError) + .format(docname, d.options, options, d.label)) else: # fix case d.options = options - def check_hidden_and_mandatory(d): + def check_hidden_and_mandatory(docname, d): if d.hidden and d.reqd and not d.default: - frappe.throw(_("Field {0} in row {1} cannot be hidden and mandatory without default").format(d.label, d.idx)) + frappe.throw(_("{0}: Field {1} in row {2} cannot be hidden and mandatory without default").format(docname, d.label, d.idx), HiddenAndMandatoryWithoutDefaultError) def check_width(d): if d.fieldtype == "Currency" and cint(d.width) < 100: @@ -616,7 +623,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)) @@ -625,14 +634,14 @@ def validate_fields(meta): if d.fieldtype in ("Currency", "Float", "Percent") and d.precision is not None and not (1 <= cint(d.precision) <= 6): frappe.throw(_("Precision should be between 1 and 6")) - def check_unique_and_text(d): + def check_unique_and_text(docname, d): if meta.issingle: d.unique = 0 d.search_index = 0 if getattr(d, "unique", False): if d.fieldtype not in ("Data", "Link", "Read Only"): - frappe.throw(_("Fieldtype {0} for {1} cannot be unique").format(d.fieldtype, d.label)) + frappe.throw(_("{0}: Fieldtype {1} for {2} cannot be unique").format(docname, d.fieldtype, d.label), NonUniqueError) if not d.get("__islocal") and frappe.db.has_column(d.parent, d.fieldname): has_non_unique_values = frappe.db.sql("""select `{fieldname}`, count(*) @@ -641,10 +650,10 @@ def validate_fields(meta): doctype=d.parent, fieldname=d.fieldname)) if has_non_unique_values and has_non_unique_values[0][0]: - frappe.throw(_("Field '{0}' cannot be set as Unique as it has non-unique values").format(d.label)) + frappe.throw(_("{0}: Field '{1}' cannot be set as Unique as it has non-unique values").format(docname, d.label), NonUniqueError) if d.search_index and d.fieldtype in ("Text", "Long Text", "Small Text", "Code", "Text Editor"): - frappe.throw(_("Fieldtype {0} for {1} cannot be indexed").format(d.fieldtype, d.label)) + frappe.throw(_("{0}:Fieldtype {1} for {2} cannot be indexed").format(docname, d.fieldtype, d.label), CannotIndexedError) def check_fold(fields): fold_exists = False @@ -795,16 +804,16 @@ def validate_fields(meta): d.fieldname = d.fieldname.lower() check_illegal_characters(d.fieldname) - check_unique_fieldname(d.fieldname) + check_unique_fieldname(meta.get("name"), d.fieldname) check_fieldname_length(d.fieldname) - check_illegal_mandatory(d) - check_link_table_options(d) + check_illegal_mandatory(meta.get("name"), d) + check_link_table_options(meta.get("name"), d) check_dynamic_link_options(d) - check_hidden_and_mandatory(d) + check_hidden_and_mandatory(meta.get("name"), d) check_in_list_view(d) check_in_global_search(d) check_illegal_default(d) - check_unique_and_text(d) + check_unique_and_text(meta.get("name"), d) check_illegal_depends_on_conditions(d) check_table_multiselect_option(d) scrub_options_in_select(d) diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 4a0782340d..1b919106d5 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -5,6 +5,8 @@ from __future__ import unicode_literals import frappe import unittest +from frappe.core.doctype.doctype.doctype import UniqueFieldnameError, IllegalMandatoryError, DoctypeLinkError, WrongOptionsDoctypeLinkError,\ + HiddenAndMandatoryWithoutDefaultError, CannotIndexedError # test_records = frappe.get_test_records('DocType') @@ -226,3 +228,55 @@ class TestDocType(unittest.TestCase): raise finally: frappe.flags.allow_doctype_export = 0 + + def test_unique_field_name_for_two_fields(self): + doc = self.new_doctype('Test Unique Field') + field_1 = doc.append('fields', {}) + field_1.fieldname = 'some_fieldname_1' + field_1.fieldtype = 'Data' + + field_2 = doc.append('fields', {}) + field_2.fieldname = 'some_fieldname_1' + field_2.fieldtype = 'Data' + + self.assertRaises(UniqueFieldnameError, doc.insert) + + def test_illegal_mandatory_validation(self): + doc = self.new_doctype('Test Illegal mandatory') + field_1 = doc.append('fields', {}) + field_1.fieldname = 'some_fieldname_1' + field_1.fieldtype = 'Section Break' + field_1.reqd = 1 + + self.assertRaises(IllegalMandatoryError, doc.insert) + + def test_link_with_wrong_and_no_options(self): + doc = self.new_doctype('Test link') + field_1 = doc.append('fields', {}) + field_1.fieldname = 'some_fieldname_1' + field_1.fieldtype = 'Link' + + self.assertRaises(DoctypeLinkError, doc.insert) + + field_1.options = 'wrongdoctype' + + self.assertRaises(WrongOptionsDoctypeLinkError, doc.insert) + + def test_hidden_and_mandatory_without_default(self): + doc = self.new_doctype('Test hidden and mandatory') + field_1 = doc.append('fields', {}) + field_1.fieldname = 'some_fieldname_1' + field_1.fieldtype = 'Data' + field_1.reqd = 1 + field_1.hidden = 1 + + self.assertRaises(HiddenAndMandatoryWithoutDefaultError, doc.insert) + + def test_field_can_not_be_indexed_validation(self): + doc = self.new_doctype('Test index') + field_1 = doc.append('fields', {}) + field_1.fieldname = 'some_fieldname_1' + field_1.fieldtype = 'Long Text' + field_1.search_index = 1 + + self.assertRaises(CannotIndexedError, doc.insert) 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/feedback_request/feedback_request.js b/frappe/core/doctype/feedback_request/feedback_request.js deleted file mode 100644 index d32982a9f1..0000000000 --- a/frappe/core/doctype/feedback_request/feedback_request.js +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Feedback Request', { - refresh: function(frm) { - var rating_icons = frappe.render_template("rating_icons", {rating: frm.doc.rating, show_label: false}); - $(frm.fields_dict.feedback_rating.wrapper).html(rating_icons); - - if(frm.doc.reference_doctype && frm.doc.reference_name) { - frm.add_custom_button(__(frm.doc.reference_name), function() { - frappe.set_route("Form", frm.doc.reference_doctype, frm.doc.reference_name); - }); - } - - if(frm.doc.reference_communication){ - frm.add_custom_button(__("Communication"), function() { - frappe.set_route("Form", "Communication", frm.doc.reference_communication); - }); - } - } -}); diff --git a/frappe/core/doctype/feedback_request/feedback_request.json b/frappe/core/doctype/feedback_request/feedback_request.json deleted file mode 100644 index 6cbc27ac26..0000000000 --- a/frappe/core/doctype/feedback_request/feedback_request.json +++ /dev/null @@ -1,470 +0,0 @@ -{ - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2017-01-27 15:43:33.780808", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_sent", - "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 Sent", - "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": "is_feedback_submitted", - "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": "Feedback Submitted", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_2", - "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, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Is Feedback request triggered manually ?", - "fieldname": "is_manual", - "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 Manual", - "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": 1, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "key", - "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": "Key", - "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": "reference", - "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": "Reference", - "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": 2, - "fieldname": "reference_doctype", - "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": "Reference DocType", - "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": 2, - "fieldname": "reference_name", - "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": "Reference Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_5", - "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, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "feedback_trigger", - "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": "Feedback Trigger", - "length": 0, - "no_copy": 0, - "options": "Feedback Trigger", - "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, - "collapsible_depends_on": "eval: doc.rating", - "columns": 0, - "fieldname": "section_break_1", - "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": "Feedback Rating", - "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": 2, - "fieldname": "rating", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Rating", - "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": "feedback_rating", - "fieldtype": "HTML", - "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": "Feedback Rating", - "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": "reference_communication", - "fieldtype": "Data", - "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": "Reference Communication", - "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 - } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 1, - - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-03-03 08:11:09.718589", - "modified_by": "Administrator", - "module": "Core", - "name": "Feedback Request", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "reference_name", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/frappe/core/doctype/feedback_request/feedback_request.py b/frappe/core/doctype/feedback_request/feedback_request.py deleted file mode 100644 index 8605d10f8a..0000000000 --- a/frappe/core/doctype/feedback_request/feedback_request.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document -from frappe.utils import get_datetime - -class FeedbackRequest(Document): - def autoname(self): - """ feedback request name in the format Feedback for {doctype} {name} on {datetime}""" - - self.name = "Feedback for {doctype} {docname} on {datetime}".format( - doctype=self.reference_doctype, - docname=self.reference_name, - datetime=get_datetime() - ) - - def before_insert(self): - from frappe.utils import random_string - - self.key = random_string(32) - -@frappe.whitelist(allow_guest=True) -def is_valid_feedback_request(key=None): - if not key: - return False - - is_feedback_submitted = frappe.db.get_value("Feedback Request", { "key": key }, "is_feedback_submitted") - if is_feedback_submitted: - return False - else: - return True - -def delete_feedback_request(): - """ clear 100 days old feedback request """ - frappe.db.sql("""delete from `tabFeedback Request` where `creation` < (NOW() - INTERVAL '100' DAY)""") \ No newline at end of file diff --git a/frappe/core/doctype/feedback_request/feedback_request_list.js b/frappe/core/doctype/feedback_request/feedback_request_list.js deleted file mode 100644 index 69d511582f..0000000000 --- a/frappe/core/doctype/feedback_request/feedback_request_list.js +++ /dev/null @@ -1,16 +0,0 @@ -frappe.listview_settings['Feedback Request'] = { - colwidths: { - subject: 2, - }, - column_render: { - rating: function(doc) { - var html = "" - for (var i = 0; i < 5; i++) { - html += repl("", - {color: i
{{ doc.name }} Delivered
", - "fieldname": "subject", - "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": "Subject", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 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_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "description": "Send Feedback Request only if there is at least one communication is available for the document.", - "fieldname": "check_communication", - "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": "Check Communication", - "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_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Optional: The alert will be sent if this expression is true", - "fieldname": "condition", - "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": "Condition", - "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_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_7", - "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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "html_8", - "fieldtype": "HTML", - "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, - "options": "

Condition Examples:

\n
doc.status==\"Closed\"\ndoc.due_date==nowdate()\ndoc.total > 40000\n
", - "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_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_9", - "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": "Message", - "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_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "message", - "fieldtype": "Code", - "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": "Message", - "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "example", - "fieldtype": "HTML", - "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": "Example", - "length": 0, - "no_copy": 0, - "options": "
Message Example
\n\n
<h3>Issue Resolved</h3>\n\n<p>Issue {{ doc.name }} Is resolved. Please check and confirm the same.</p>\n\n<p> Your Feedback is important for us. Please give us your Feedback for {{ doc.name }}</p>\n\n<h4>Details</h4>
", - "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 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-05-29 16:36:04.178592", - "modified_by": "Administrator", - "module": "Core", - "name": "Feedback Trigger", - "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": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "document_type", - "track_changes": 1, - "track_seen": 0 -} diff --git a/frappe/core/doctype/feedback_trigger/feedback_trigger.py b/frappe/core/doctype/feedback_trigger/feedback_trigger.py deleted file mode 100644 index e2265913ad..0000000000 --- a/frappe/core/doctype/feedback_trigger/feedback_trigger.py +++ /dev/null @@ -1,215 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import json -import frappe -from frappe import _ -from frappe.utils import get_url -from frappe.model.document import Document -from frappe.utils.jinja import validate_template - -class FeedbackTrigger(Document): - def validate(self): - frappe.cache().delete_value('feedback_triggers') - validate_template(self.subject) - validate_template(self.message) - self.validate_condition() - - def on_trash(self): - frappe.cache().delete_value('feedback_triggers') - - def validate_condition(self): - temp_doc = frappe.new_doc(self.document_type) - if self.condition: - try: - frappe.safe_eval(self.condition, None, get_context(temp_doc)) - except: - frappe.throw(_("The condition '{0}' is invalid").format(self.condition)) - -def trigger_feedback_request(doc, method): - """Trigger the feedback alert, or delete feedback requests on delete""" - - def _get(): - triggers = {} - if not (frappe.flags.in_migrate or frappe.flags.in_install): - for d in frappe.get_all('Feedback Trigger', dict(enabled=1), ['name', 'document_type']): - triggers[d.document_type] = d.name - - return triggers - - feedback_triggers = frappe.cache().get_value('feedback_triggers', _get) - if doc.doctype in feedback_triggers: - if doc.flags.in_delete: - frappe.enqueue('frappe.core.doctype.feedback_trigger.feedback_trigger.delete_feedback_request_and_feedback', - reference_doctype=doc.doctype, reference_name=doc.name, now=frappe.flags.in_test) - else: - frappe.enqueue('frappe.core.doctype.feedback_trigger.feedback_trigger.send_feedback_request', - trigger=feedback_triggers[doc.doctype], reference_doctype=doc.doctype, - reference_name=doc.name, now=frappe.flags.in_test) - -@frappe.whitelist() -def send_feedback_request(reference_doctype, reference_name, trigger="Manual", details=None, is_manual=False): - """ send feedback alert """ - - if is_feedback_request_already_sent(reference_doctype, reference_name, is_manual=is_manual): - frappe.msgprint(_("Feedback Request is already sent to user")) - return None - - details = json.loads(details) if details else \ - get_feedback_request_details(reference_doctype, reference_name, trigger=trigger) - - if not details: - return None - - feedback_request, url = get_feedback_request_url(reference_doctype, - reference_name, details.get("recipients"), trigger) - - feedback_msg = frappe.render_template("templates/emails/feedback_request_url.html", { "url": url }) - - # appending feedback url to message body - message = "{message}{feedback_msg}".format( - message=details.get("message"), - feedback_msg=feedback_msg - ) - details.update({ - "message": message, - "header": [details.get('subject'), 'blue'] - }) - - if details: - frappe.sendmail(**details) - frappe.db.set_value("Feedback Request", feedback_request, "is_sent", 1) - - -@frappe.whitelist() -def get_feedback_request_details(reference_doctype, reference_name, trigger="Manual", request=None): - if not frappe.db.get_value(reference_doctype, reference_name): - # reference document is either deleted or renamed - return - elif not trigger and not request and not frappe.db.get_value("Feedback Trigger", { "document_type": reference_doctype }): - return - elif not trigger and request: - trigger = frappe.db.get_value("Feedback Request", request, "feedback_trigger") - else: - trigger = frappe.db.get_value("Feedback Trigger", { "document_type": reference_doctype }) - - if not trigger: - return - - feedback_trigger = frappe.get_doc("Feedback Trigger", trigger) - - doc = frappe.get_doc(reference_doctype, reference_name) - context = get_context(doc) - - recipients = doc.get(feedback_trigger.email_fieldname, None) - if feedback_trigger.check_communication: - communications = frappe.get_all("Communication", filters={ - "reference_doctype": reference_doctype, - "reference_name": reference_name, - "communication_type": "Communication", - "sent_or_received": "Sent" - }, fields=["name"]) - - if len(communications) < 1: - frappe.msgprint(_("At least one reply is mandatory before requesting feedback")) - return None - - if recipients and (not feedback_trigger.condition or \ - frappe.safe_eval(feedback_trigger.condition, None, context)): - subject = feedback_trigger.subject - context.update({ "feedback_trigger": feedback_trigger }) - - if "{" in subject: - subject = frappe.render_template(feedback_trigger.subject, context) - - feedback_request_message = frappe.render_template(feedback_trigger.message, context) - - return { - "subject": subject, - "recipients": recipients, - "reference_name":doc.name, - "reference_doctype":doc.doctype, - "message": feedback_request_message, - } - else: - frappe.msgprint(_("Feedback conditions do not match")) - return None - -def get_feedback_request_url(reference_doctype, reference_name, recipients, trigger="Manual"): - """ prepare the feedback request url """ - is_manual = 1 if trigger == "Manual" else 0 - feedback_request = frappe.get_doc({ - "is_manual": is_manual, - "feedback_trigger": trigger, - "doctype": "Feedback Request", - "reference_name": reference_name, - "reference_doctype": reference_doctype, - }).insert(ignore_permissions=True) - - feedback_url = "{base_url}/feedback?reference_doctype={doctype}&reference_name={docname}&email={email_id}&key={nonce}".format( - base_url=get_url(), - doctype=reference_doctype, - docname=reference_name, - email_id=recipients, - nonce=feedback_request.key - ) - - return [ feedback_request.name, feedback_url ] - -def is_feedback_request_already_sent(reference_doctype, reference_name, is_manual=False): - """ - check if feedback request mail is already sent but feedback is not submitted - to avoid sending multiple feedback request mail - """ - is_request_sent = False - filters = { - "is_sent": 1, - "reference_name": reference_name, - "is_manual": 1 if is_manual else 0, - "reference_doctype": reference_doctype - } - - if is_manual: - filters.update({ "is_feedback_submitted": 0 }) - - feedback_request = frappe.get_all("Feedback Request", filters=filters, fields=["name"]) - - if feedback_request: is_request_sent = True - return is_request_sent - -def get_enabled_feedback_trigger(): - """ get mapper of all the enable feedback trigger """ - - triggers = frappe.get_all("Feedback Trigger", filters={"enabled": 1}, - fields=["document_type", "name"], as_list=True) - - triggers = { dt[0]: dt[1] for dt in triggers } - return triggers - -def get_context(doc): - return { "doc": doc } - -def delete_feedback_request_and_feedback(reference_doctype, reference_name): - """ delete all the feedback request and feedback communication """ - if not all([reference_doctype, reference_name]): - return - - feedback_requests = frappe.get_all("Feedback Request", filters={ - "is_feedback_submitted": 0, - "reference_doctype": reference_doctype, - "reference_name": reference_name - }) - - communications = frappe.get_all("Communication", { - "communication_type": "Feedback", - "reference_doctype": reference_doctype, - "reference_name": reference_name - }) - - for request in feedback_requests: - frappe.delete_doc("Feedback Request", request.get("name"), ignore_permissions=True) - - for communication in communications: - frappe.delete_doc("Communication", communication.get("name"), ignore_permissions=True) diff --git a/frappe/core/doctype/feedback_trigger/test_feedback_trigger.py b/frappe/core/doctype/feedback_trigger/test_feedback_trigger.py deleted file mode 100644 index f44d1f96ae..0000000000 --- a/frappe/core/doctype/feedback_trigger/test_feedback_trigger.py +++ /dev/null @@ -1,137 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt -from __future__ import unicode_literals - -import frappe -import unittest - -# test_records = frappe.get_test_records('Feedback Trigger') -def get_feedback_request(todo, feedback_trigger): - return frappe.db.get_value("Feedback Request", { - "is_sent": 1, - "is_feedback_submitted": 0, - "reference_doctype": "ToDo", - "reference_name": todo, - "feedback_trigger": feedback_trigger - }, ["name", "key"]) - -class TestFeedbackTrigger(unittest.TestCase): - def setUp(self): - new_user = frappe.get_doc(dict(doctype='User', email='test-feedback@example.com', - first_name='Tester')).insert(ignore_permissions=True) - new_user.add_roles("System Manager") - - def tearDown(self): - frappe.db.sql("delete from tabContact where email_id='test-feedback@example.com'") - frappe.delete_doc("User", "test-feedback@example.com") - frappe.delete_doc("Feedback Trigger", "ToDo") - frappe.db.sql('delete from `tabEmail Queue`') - frappe.db.sql('delete from `tabFeedback Request`') - - def test_feedback_trigger(self): - """ Test feedback trigger """ - from frappe.www.feedback import accept - - frappe.delete_doc("Feedback Trigger", "ToDo") - frappe.db.sql('delete from `tabEmail Queue`') - frappe.db.sql('delete from `tabFeedback Request`') - - feedback_trigger = frappe.get_doc({ - "enabled": 1, - "doctype": "Feedback Trigger", - "document_type": "ToDo", - "email_field": "assigned_by", - "email_fieldname": "assigned_by", - "subject": "{{ doc.name }} Task Completed", - "condition": "doc.status == 'Closed'", - "message": """Task {{ doc.name }} is Completed by {{ doc.owner }}. - regarding the Task {{ doc.name }}""" - }).insert(ignore_permissions=True) - - # create a todo - todo = frappe.get_doc({ - "doctype": "ToDo", - "owner": "test-feedback@example.com", - "assigned_by": "test-feedback@example.com", - "description": "Unable To Submit Sales Order #SO-00001" - }).insert(ignore_permissions=True) - - # feedback alert mail should be sent only on 'Closed' status - email_queue = frappe.db.sql("""select name from `tabEmail Queue` where - reference_doctype='ToDo' and reference_name='{0}'""".format(todo.name)) - self.assertFalse(email_queue) - - # add a communication - frappe.get_doc({ - "reference_doctype": "ToDo", - "reference_name": todo.name, - "communication_type": "Communication", - "content": "Test Communication", - "subject": "Test Communication", - "doctype": "Communication" - }).insert(ignore_permissions=True) - - # check if feedback mail alert is triggered - todo.reload() - todo.status = "Closed" - todo.save(ignore_permissions=True) - - email_queue = frappe.db.sql("""select name from `tabEmail Queue` where - reference_doctype='ToDo' and reference_name='{0}'""".format(todo.name)) - self.assertTrue(email_queue) - - # test if feedback is submitted for the todo - feedback_request, request_key = get_feedback_request(todo.name, feedback_trigger.name) - self.assertTrue(feedback_request) - - # test if mail alerts are triggered multiple times for same document - todo.save(ignore_permissions=True) - email_queue = frappe.db.sql("""select name from `tabEmail Queue` where - reference_doctype='ToDo' and reference_name='{0}'""".format(todo.name)) - self.assertTrue(len(email_queue) == 1) - frappe.db.sql('delete from `tabEmail Queue`') - - - # Test if feedback is submitted sucessfully - result = accept(request_key, "test-feedback@example.com", "ToDo", todo.name, "Great Work !!", 4, fullname="Test User") - self.assertTrue(result) - - # test if feedback is saved in Communication - docname = frappe.db.get_value("Communication", { - "reference_doctype": "ToDo", - "reference_name": todo.name, - "communication_type": "Feedback", - "feedback_request": feedback_request - }) - - communication = frappe.get_doc("Communication", docname) - self.assertEqual(communication.rating, 4) - self.assertEqual(communication.content, "Great Work !!") - - # test if link expired after feedback submission - self.assertRaises(Exception, accept, key=request_key, sender="test-feedback@example.com", - reference_doctype="ToDo", reference_name=todo.name, feedback="Thank You !!", rating=4, fullname="Test User") - - # auto feedback request should trigger only once - todo.reload() - todo.save(ignore_permissions=True) - email_queue = frappe.db.sql("""select name from `tabEmail Queue` where - reference_doctype='ToDo' and reference_name='{0}'""".format(todo.name)) - self.assertFalse(email_queue) - frappe.delete_doc("ToDo", todo.name) - - # test if feedback requests and feedback communications are deleted? - communications = frappe.get_all("Communication", { - "reference_doctype": "ToDo", - "reference_name": todo.name, - "communication_type": "Feedback" - }) - self.assertFalse(communications) - - feedback_requests = frappe.get_all("Feedback Request", { - "reference_doctype": "ToDo", - "reference_name": todo.name, - "is_feedback_submitted": 0 - }) - self.assertFalse(feedback_requests) diff --git a/frappe/core/doctype/page/page.js b/frappe/core/doctype/page/page.js index 86fbbedaf3..d1d9600e59 100644 --- a/frappe/core/doctype/page/page.js +++ b/frappe/core/doctype/page/page.js @@ -3,9 +3,14 @@ frappe.ui.form.on('Page', { refresh: function(frm) { - if(!frappe.boot.developer_mode && user != 'Administrator') { + if (!frappe.boot.developer_mode && frappe.session.user != 'Administrator') { // make the document read-only frm.set_read_only(); } + if (!frm.is_new() && !frm.doc.istable) { + frm.add_custom_button(__('Go to {0} Page', [frm.doc.title || frm.doc.name]), () => { + frappe.set_route(frm.doc.name); + }); + } } }); diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index 012c313dda..29c069515f 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -25,7 +25,7 @@ class PreparedReport(Document): self.status = "Queued" self.report_start_time = frappe.utils.now() - def after_insert(self): + def enqueue_report(self): enqueue( run_background, prepared_report=self.name, timeout=6000 @@ -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/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index 6b40cd7d33..1b6957d057 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -100,7 +100,7 @@ class Report(Document): columns = [] out = [] - if self.report_type in ('Query Report', 'Script Report'): + if self.report_type in ('Query Report', 'Script Report', 'Custom Report'): # query and script reports data = frappe.desk.query_report.run(self.name, filters=filters, user=user) for d in data.get('columns'): diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index cf317edc42..161f0401e0 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, @@ -18,6 +19,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "localization", "fieldtype": "Section Break", "hidden": 0, @@ -49,6 +51,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "country", "fieldtype": "Link", "hidden": 0, @@ -82,6 +85,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "language", "fieldtype": "Link", "hidden": 0, @@ -114,6 +118,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_3", "fieldtype": "Column Break", "hidden": 0, @@ -145,6 +150,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "time_zone", "fieldtype": "Select", "hidden": 0, @@ -176,6 +182,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "is_first_startup", "fieldtype": "Check", "hidden": 1, @@ -208,6 +215,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "setup_complete", "fieldtype": "Check", "hidden": 1, @@ -240,6 +248,7 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "date_and_number_format", "fieldtype": "Section Break", "hidden": 0, @@ -271,6 +280,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "date_format", "fieldtype": "Select", "hidden": 0, @@ -303,6 +313,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_7", "fieldtype": "Column Break", "hidden": 0, @@ -334,6 +345,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "number_format", "fieldtype": "Select", "hidden": 0, @@ -366,6 +378,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "float_precision", "fieldtype": "Select", "hidden": 0, @@ -399,6 +412,7 @@ "collapsible": 0, "columns": 0, "description": "If not set, the currency precision will depend on number format", + "fetch_if_empty": 0, "fieldname": "currency_precision", "fieldtype": "Select", "hidden": 0, @@ -432,6 +446,7 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "sec_backup_limit", "fieldtype": "Section Break", "hidden": 0, @@ -466,6 +481,7 @@ "columns": 0, "default": "3", "description": "Older backups will be automatically deleted", + "fetch_if_empty": 0, "fieldname": "backup_limit", "fieldtype": "Int", "hidden": 0, @@ -498,6 +514,7 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "background_workers", "fieldtype": "Section Break", "hidden": 0, @@ -531,6 +548,7 @@ "collapsible": 0, "columns": 0, "description": "Run scheduled jobs only if checked", + "fetch_if_empty": 0, "fieldname": "enable_scheduler", "fieldtype": "Check", "hidden": 1, @@ -562,6 +580,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "scheduler_last_event", "fieldtype": "Data", "hidden": 1, @@ -594,6 +613,7 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "permissions", "fieldtype": "Section Break", "hidden": 0, @@ -628,6 +648,7 @@ "columns": 0, "default": "0", "description": "If Apply Strict User Permission is checked and User Permission is defined for a DocType for a User, then all the documents where value of the link is blank, will not be shown to that User", + "fetch_if_empty": 0, "fieldname": "apply_strict_user_permissions", "fieldtype": "Check", "hidden": 0, @@ -660,6 +681,7 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "security", "fieldtype": "Section Break", "hidden": 0, @@ -693,6 +715,7 @@ "columns": 0, "default": "06:00", "description": "Session Expiry in Hours e.g. 06:00", + "fetch_if_empty": 0, "fieldname": "session_expiry", "fieldtype": "Data", "hidden": 0, @@ -727,6 +750,7 @@ "columns": 0, "default": "720:00", "description": "In Hours", + "fetch_if_empty": 0, "fieldname": "session_expiry_mobile", "fieldtype": "Data", "hidden": 0, @@ -759,75 +783,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "1", - "description": "If enabled, the password strength will be enforced based on the Minimum Password Score value. A value of 2 being medium strong and 4 being very strong.", - "fieldname": "enable_password_policy", - "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": "Enable Password Policy", - "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": "2", - "depends_on": "eval:doc.enable_password_policy==1", - "fieldname": "minimum_password_score", - "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": "Minimum Password Score", - "length": 0, - "no_copy": 0, - "options": "2\n3\n4", - "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": "column_break_13", "fieldtype": "Column Break", "hidden": 0, @@ -860,6 +816,7 @@ "collapsible": 0, "columns": 0, "description": "Note: Multiple sessions will be allowed in case of mobile device", + "fetch_if_empty": 0, "fieldname": "deny_multiple_sessions", "fieldtype": "Check", "hidden": 0, @@ -894,6 +851,7 @@ "columns": 0, "default": "0", "description": "User can login using Email id or Mobile number", + "fetch_if_empty": 0, "fieldname": "allow_login_using_mobile_number", "fieldtype": "Check", "hidden": 0, @@ -928,6 +886,7 @@ "columns": 0, "default": "0", "description": "User can login using Email id or User Name", + "fetch_if_empty": 0, "fieldname": "allow_login_using_user_name", "fieldtype": "Check", "hidden": 0, @@ -962,6 +921,7 @@ "columns": 0, "default": "1", "description": "", + "fetch_if_empty": 0, "fieldname": "allow_error_traceback", "fieldtype": "Check", "hidden": 0, @@ -994,6 +954,177 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, + "fieldname": "password_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": "Password", + "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": "In Days", + "fetch_if_empty": 0, + "fieldname": "force_user_to_reset_password", + "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": "Force User to Reset Password", + "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": "column_break_31", + "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, + "default": "1", + "description": "If enabled, the password strength will be enforced based on the Minimum Password Score value. A value of 2 being medium strong and 4 being very strong.", + "fetch_if_empty": 0, + "fieldname": "enable_password_policy", + "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": "Enable Password Policy", + "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": "2", + "depends_on": "eval:doc.enable_password_policy==1", + "fetch_if_empty": 0, + "fieldname": "minimum_password_score", + "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": "Minimum Password Score", + "length": 0, + "no_copy": 0, + "options": "2\n3\n4", + "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": "brute_force_security", "fieldtype": "Section Break", "hidden": 0, @@ -1026,6 +1157,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "allow_consecutive_login_attempts", "fieldtype": "Int", "hidden": 0, @@ -1058,6 +1190,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_34", "fieldtype": "Column Break", "hidden": 0, @@ -1091,6 +1224,7 @@ "columns": 0, "default": "60", "description": "In seconds", + "fetch_if_empty": 0, "fieldname": "allow_login_after_fail", "fieldtype": "Int", "hidden": 0, @@ -1123,6 +1257,7 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "two_factor_authentication", "fieldtype": "Section Break", "hidden": 0, @@ -1155,6 +1290,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "enable_two_factor_auth", "fieldtype": "Check", "hidden": 0, @@ -1190,6 +1326,7 @@ "default": "0", "depends_on": "enable_two_factor_auth", "description": "If enabled, users who login from Restricted IP Address, won't be prompted for Two Factor Auth", + "fetch_if_empty": 0, "fieldname": "bypass_2fa_for_retricted_ip_users", "fieldtype": "Check", "hidden": 0, @@ -1224,6 +1361,7 @@ "columns": 0, "depends_on": "enable_two_factor_auth", "description": "If enabled, all users can login from any IP Address using Two Factor Auth. This can also be set only for specific user(s) in User Page", + "fetch_if_empty": 0, "fieldname": "bypass_restrict_ip_check_if_2fa_enabled", "fieldtype": "Check", "hidden": 0, @@ -1259,6 +1397,7 @@ "default": "OTP App", "depends_on": "", "description": "Choose authentication method to be used by all users", + "fetch_if_empty": 0, "fieldname": "two_factor_method", "fieldtype": "Select", "hidden": 0, @@ -1294,6 +1433,7 @@ "columns": 0, "depends_on": "eval:doc.two_factor_method == \"OTP App\"", "description": "Time in seconds to retain QR code image on server. Min:240", + "fetch_if_empty": 0, "fieldname": "lifespan_qrcode_image", "fieldtype": "Int", "hidden": 0, @@ -1328,6 +1468,7 @@ "columns": 0, "default": "Frappe Framework", "depends_on": "enable_two_factor_auth", + "fetch_if_empty": 0, "fieldname": "otp_issuer_name", "fieldtype": "Data", "hidden": 0, @@ -1361,6 +1502,7 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "email", "fieldtype": "Section Break", "hidden": 0, @@ -1394,6 +1536,7 @@ "collapsible": 0, "columns": 0, "description": "Your organization name and address for the email footer.", + "fetch_if_empty": 0, "fieldname": "email_footer_address", "fieldtype": "Small Text", "hidden": 0, @@ -1426,6 +1569,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_18", "fieldtype": "Column Break", "hidden": 0, @@ -1457,6 +1601,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "disable_standard_email_footer", "fieldtype": "Check", "hidden": 0, @@ -1489,6 +1634,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "hide_footer_in_auto_email_reports", "fieldtype": "Check", "hidden": 0, @@ -1521,6 +1667,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "chat", "fieldtype": "Section Break", "hidden": 0, @@ -1554,6 +1701,7 @@ "collapsible": 0, "columns": 0, "default": "1", + "fetch_if_empty": 0, "fieldname": "enable_chat", "fieldtype": "Check", "hidden": 0, @@ -1587,6 +1735,7 @@ "collapsible": 0, "columns": 0, "default": "1", + "fetch_if_empty": 0, "fieldname": "use_socketio_to_upload_file", "fieldtype": "Check", "hidden": 0, @@ -1624,7 +1773,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2019-01-30 11:02:41.011412", + "modified": "2019-04-16 13:26:09.247487", "modified_by": "Administrator", "module": "Core", "name": "System Settings", diff --git a/frappe/core/doctype/system_settings/system_settings.py b/frappe/core/doctype/system_settings/system_settings.py index 47a9226582..59c8275319 100644 --- a/frappe/core/doctype/system_settings/system_settings.py +++ b/frappe/core/doctype/system_settings/system_settings.py @@ -7,7 +7,7 @@ from frappe import _ from frappe.model.document import Document from frappe.model import no_value_fields from frappe.translate import set_default_language -from frappe.utils import cint +from frappe.utils import cint, today from frappe.utils.momentjs import get_all_timezones from frappe.twofactor import toggle_two_factor_auth @@ -35,6 +35,11 @@ class SystemSettings(Document): self.bypass_2fa_for_retricted_ip_users = 0 self.bypass_restrict_ip_check_if_2fa_enabled = 0 + frappe.flags.update_last_reset_password_date = False + if (self.force_user_to_reset_password and + not cint(frappe.db.get_single_value("System Settings", "force_user_to_reset_password"))): + frappe.flags.update_last_reset_password_date = True + def on_update(self): for df in self.meta.get("fields"): if df.fieldtype not in no_value_fields: @@ -47,6 +52,16 @@ class SystemSettings(Document): frappe.cache().delete_value('time_zone') frappe.local.system_settings = {} + if frappe.flags.update_last_reset_password_date: + update_last_reset_password_date() + +def update_last_reset_password_date(): + frappe.db.sql(""" UPDATE `tabUser` + SET + last_password_reset_date = %s + WHERE + last_password_reset_date is null or last_password_reset_date = ''""", today()) + @frappe.whitelist() def load(): if not "System Manager" in frappe.get_roles(): diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index e96cdf6f36..318b31d4a2 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -1144,6 +1144,39 @@ "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": "last_password_reset_date", + "fieldtype": "Date", + "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": "Last Password Reset Date", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "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, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -2437,7 +2470,7 @@ "issingle": 0, "istable": 0, "max_attachments": 5, - "modified": "2019-03-03 11:10:06.162541", + "modified": "2019-04-15 20:25:02.022893", "modified_by": "Administrator", "module": "Core", "name": "User", diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index adc054197a..ebd312fc07 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals, print_function import frappe from frappe.model.document import Document -from frappe.utils import cint, has_gravatar, format_datetime, now_datetime, get_formatted_email +from frappe.utils import cint, has_gravatar, format_datetime, now_datetime, get_formatted_email, today from frappe import throw, msgprint, _ from frappe.utils.password import update_password as _update_password from frappe.desk.notifications import clear_notifications @@ -218,13 +218,17 @@ class User(Document): def validate_reset_password(self): pass - def reset_password(self, send_email=False): + def reset_password(self, send_email=False, password_expired=False): from frappe.utils import random_string, get_url key = random_string(32) self.db_set("reset_password_key", key) - link = get_url("/update-password?key=" + key) + url = "/update-password?key=" + key + if password_expired: + url = "/update-password?key=" + key + '&password_expired=true' + + link = get_url(url) if send_email: self.password_reset_mail(link) @@ -591,6 +595,9 @@ def update_password(new_password, logout_all_sessions=0, key=None, old_password= frappe.local.login_manager.login_as(user) + frappe.db.set_value("User", user, + 'last_password_reset_date', today()) + if user_doc.user_type == "System User": return "/desk" else: diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py index af197dcf24..af2815a9ac 100644 --- a/frappe/core/doctype/user_permission/user_permission.py +++ b/frappe/core/doctype/user_permission/user_permission.py @@ -53,7 +53,7 @@ class UserPermission(Document): }, limit=1) if overlap_exists: ref_link = frappe.get_desk_link(self.doctype, overlap_exists[0].name) - frappe.throw(_("{0} has already assigned default vaue for {1}.".format(ref_link, self.allow))) + frappe.throw(_("{0} has already assigned default value for {1}.".format(ref_link, self.allow))) @frappe.whitelist() def get_user_permissions(user=None): @@ -236,4 +236,4 @@ def update_applicable(already_applied, to_apply, user, doctype, docname): AND `applicable_for`=%s AND `allow`=%s AND `for_value`=%s - """,(user, applied, doctype, docname)) \ No newline at end of file + """,(user, applied, doctype, docname)) diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index b4cb4d7222..c8b8fc94dc 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -60,7 +60,12 @@ class Dashboard { show_dashboard(current_dashboard_name) { if(this.dashboard_name !== current_dashboard_name) { this.dashboard_name = current_dashboard_name; - this.page.set_title(this.dashboard_name); + let title = this.dashboard_name; + if (!this.dashboard_name.toLowerCase().includes(__('dashboard'))) { + // ensure dashboard title has "dashboard" + title = __('{0} Dashboard', [title]); + } + this.page.set_title(title); this.set_dropdown(); this.container.empty(); this.refresh(); diff --git a/frappe/core/report/feedback_ratings/__init__.py b/frappe/core/report/feedback_ratings/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/core/report/feedback_ratings/feedback_ratings.js b/frappe/core/report/feedback_ratings/feedback_ratings.js deleted file mode 100644 index 7d42069e49..0000000000 --- a/frappe/core/report/feedback_ratings/feedback_ratings.js +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.query_reports["Feedback Ratings"] = { - "filters": [ - { - "fieldname": "document_type", - "label": __("Document Type"), - "fieldtype": "Link", - "options": "DocType", - "reqd": 1, - "default": "Issue", - "get_query": function() { - return { - "query": "frappe.core.report.feedback_ratings.feedback_ratings.get_document_type" - } - } - }, - { - "fieldname": "document_id", - "label": __("Document ID"), - "fieldtype": "Dynamic Link", - "get_options": function() { - var document_type = frappe.query_report.get_filter_value('document_type'); - if(!document_type) { - frappe.throw(__("Please select Document Type first")); - } - return document_type; - } - }, - { - "fieldname":"from_date", - "label": __("From Date"), - "fieldtype": "Date", - 'reqd': 1, - "default": frappe.datetime.add_days(frappe.datetime.nowdate(), -30) - }, - { - "fieldname":"to_date", - "label": __("To Date"), - "fieldtype": "Date", - 'reqd': 1, - "default":frappe.datetime.nowdate() - } - ], - - get_chart_data: function(columns, result) { - return { - data: { - x: 'Date', - columns: [ - ['Date'].concat($.map(result, function(d) { return d[0]; })), - ['Average Feedback'].concat($.map(result, function(d) { return d[1]; })) - ] - }, - chart_type: 'line', - - } - } -} diff --git a/frappe/core/report/feedback_ratings/feedback_ratings.json b/frappe/core/report/feedback_ratings/feedback_ratings.json deleted file mode 100644 index 48c10c2e6c..0000000000 --- a/frappe/core/report/feedback_ratings/feedback_ratings.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2017-02-05 20:38:21.890174", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 2, - "is_standard": "Yes", - "modified": "2017-02-24 19:56:51.141147", - "modified_by": "Administrator", - "module": "Core", - "name": "Feedback Ratings", - "owner": "Administrator", - "ref_doctype": "Feedback Trigger", - "report_name": "Feedback Ratings", - "report_type": "Script Report", - "roles": [ - { - "role": "System Manager" - } - ] -} \ No newline at end of file diff --git a/frappe/core/report/feedback_ratings/feedback_ratings.py b/frappe/core/report/feedback_ratings/feedback_ratings.py deleted file mode 100644 index dff832bfba..0000000000 --- a/frappe/core/report/feedback_ratings/feedback_ratings.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe - -def execute(filters=None): - columns, data = get_columns(filters), get_data(filters) - return columns, data - -def get_columns(filters): - return [ - "Date:Date", - "Average Rating", - ] - -def get_data(filters): - data = [] - document_type = filters.get("document_type") - party = filters.get("document_id") - filters = { - "reference_doctype": document_type, - "communication_type": "Feedback", - "creation": ["Between", [filters.get("from_date"), filters.get("to_date")]] - } - fields = ["DATE_FORMAT(DATE(creation),'%m-%d-%Y')", "avg(rating) as rating"] - - if not document_type: - return [] - - if party: - filters.update({ "reference_name": party }) - - party_details = frappe.get_list("Communication", filters=filters, fields=fields, - order_by="creation", group_by="DATE_FORMAT(DATE(creation),'%m-%d-%Y')", as_list=True) - - return party_details or [] - -@frappe.whitelist() -def get_document_type(doctype, txt, searchfield, start, page_len, filters): - """ get the document type """ - - document_type = [] - txt = "%%%s%%" % txt - - document_type = frappe.get_all("Feedback Trigger", filters={ "enabled": 1, "document_type": ("like", txt) }, - fields=["document_type"], as_list=True) - - document_type = map(list, document_type) - to_ignore = [ doc[0] for doc in document_type ] - - documents = frappe.get_all("Feedback Request", filters={ "reference_doctype": ["not in", to_ignore] }, - fields=["reference_doctype"], distinct=True, as_list=True) - - if documents: - document_type.extend(documents) - - return document_type diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index 462f1be56c..5f288fe99c 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -1,694 +1,182 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "DL.####", - "beta": 0, - "creation": "2013-01-29 17:55:08", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, + "autoname": "DL.####", + "creation": "2013-01-29 17:55:08", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "field_order": [ + "doc_type", + "properties", + "label", + "default_print_format", + "max_attachments", + "allow_copy", + "istable", + "editable_grid", + "quick_entry", + "track_changes", + "track_views", + "image_view", + "column_break_5", + "title_field", + "image_field", + "search_fields", + "section_break_8", + "sort_field", + "column_break_10", + "sort_order", + "fields_section_break", + "fields" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "doc_type", - "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": 0, - "label": "Enter Form Type", - "length": 0, - "no_copy": 0, - "options": "DocType", - "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, - "unique": 0 - }, + "fieldname": "doc_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Enter Form Type", + "options": "DocType" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "doc_type", - "fieldname": "properties", - "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, - "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, - "unique": 0 - }, + "depends_on": "doc_type", + "fieldname": "properties", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "label", - "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": "Change Label (via Custom Translation)", - "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 - }, + "fieldname": "label", + "fieldtype": "Data", + "label": "Change Label (via Custom Translation)" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "default_print_format", - "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": 0, - "label": "Default Print Format", - "length": 0, - "no_copy": 0, - "options": "Print Format", - "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, - "unique": 0 - }, + "fieldname": "default_print_format", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Default Print Format", + "options": "Print Format" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "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, - "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, - "unique": 0 - }, + "fieldname": "max_attachments", + "fieldtype": "Int", + "label": "Max Attachments" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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, - "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, - "unique": 0 - }, + "fieldname": "allow_copy", + "fieldtype": "Check", + "label": "Hide Copy" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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": 0, - "label": "Is Table", - "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": "istable", + "fieldtype": "Check", + "label": "Is Table", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "istable", - "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, - "unique": 0 - }, + "depends_on": "istable", + "fieldname": "editable_grid", + "fieldtype": "Check", + "label": "Editable Grid" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "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, - "unique": 0 - }, + "default": "1", + "fieldname": "quick_entry", + "fieldtype": "Check", + "label": "Quick Entry" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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, - "unique": 0 - }, + "fieldname": "track_changes", + "fieldtype": "Check", + "label": "Track Changes" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval: doc.image_field", - "fieldname": "image_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": "Image 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, - "unique": 0 - }, + "depends_on": "eval: doc.image_field", + "fieldname": "image_view", + "fieldtype": "Check", + "label": "Image View" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_5", - "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, - "unique": 0 - }, + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Use this fieldname to generate title", - "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, - "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 - }, + "description": "Use this fieldname to generate title", + "fieldname": "title_field", + "fieldtype": "Data", + "label": "Title Field" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Must be of type \"Attach Image\"", - "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, - "unique": 0 - }, + "description": "Must be of type \"Attach Image\"", + "fieldname": "image_field", + "fieldtype": "Data", + "label": "Image Field" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Fields separated by comma (,) will be included in the \"Search By\" list of Search dialog box", - "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": 1, - "in_standard_filter": 0, - "label": "Search Fields", - "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, - "unique": 0 - }, + "description": "Fields separated by comma (,) will be included in the \"Search By\" list of Search dialog box", + "fieldname": "search_fields", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Search Fields" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "doc_type", - "fieldname": "section_break_8", - "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 - }, + "depends_on": "doc_type", + "fieldname": "section_break_8", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sort_field", - "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": "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, - "unique": 0 - }, + "fieldname": "sort_field", + "fieldtype": "Select", + "label": "Sort Field" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_10", - "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, - "unique": 0 - }, + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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": "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, - "unique": 0 - }, + "fieldname": "sort_order", + "fieldtype": "Select", + "label": "Sort Order", + "options": "ASC\nDESC" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "doc_type", - "description": "Customize Label, Print Hide, Default etc.", - "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, - "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, - "unique": 0 - }, + "depends_on": "doc_type", + "description": "Customize Label, Print Hide, Default etc.", + "fieldname": "fields_section_break", + "fieldtype": "Section Break", + "label": "Fields" + }, { - "allow_bulk_edit": 1, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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, - "options": "Customize Form Field", - "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, - "unique": 0 + "allow_bulk_edit": 1, + "fieldname": "fields", + "fieldtype": "Table", + "label": "Fields", + "options": "Customize Form Field" + }, + { + "fieldname": "track_views", + "fieldtype": "Check", + "label": "Track Views" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 1, - "icon": "fa fa-glass", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2017-04-21 16:59:12.752428", - "modified_by": "Administrator", - "module": "Custom", - "name": "Customize Form", - "owner": "Administrator", + ], + "hide_toolbar": 1, + "icon": "fa fa-glass", + "idx": 1, + "issingle": 1, + "modified": "2019-05-13 18:54:40.610862", + "modified_by": "Administrator", + "module": "Custom", + "name": "Customize Form", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "doc_type", - "show_name_in_global_search": 0, - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "quick_entry": 1, + "search_fields": "doc_type", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 9be66c2583..800ce0d462 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -28,6 +28,7 @@ doctype_properties = { 'editable_grid': 'Check', 'max_attachments': 'Int', 'track_changes': 'Check', + 'track_views': 'Check', } docfield_properties = { @@ -87,6 +88,9 @@ class CustomizeForm(Document): if self.doc_type in core_doctypes_list: return frappe.msgprint(_("Core DocTypes cannot be customized.")) + if meta.issingle: + return frappe.msgprint(_("Single DocTypes cannot be customized.")) + if meta.custom: return frappe.msgprint(_("Only standard DocTypes are allowed to be customized from Customize Form.")) diff --git a/frappe/database/mariadb/framework_mariadb.sql b/frappe/database/mariadb/framework_mariadb.sql index eae479df05..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, @@ -49,6 +50,7 @@ CREATE TABLE `tabDocField` ( `description` text, `in_list_view` int(1) NOT NULL DEFAULT 0, `in_standard_filter` int(1) NOT NULL DEFAULT 0, + `in_preview` int(1) NOT NULL DEFAULT 0, `read_only` int(1) NOT NULL DEFAULT 0, `precision` varchar(255) DEFAULT NULL, `length` int(11) NOT NULL DEFAULT 0, diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index 036ee3f34a..b219aac13a 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -64,10 +64,10 @@ class PostgresDatabase(Database): def get_connection(self): # warnings.filterwarnings('ignore', category=psycopg2.Warning) - conn = psycopg2.connect('host={} dbname={} port={}'.format(self.host, self.user, self.port)) + conn = psycopg2.connect('host={} dbname={} user={} password={} port={}'.format( + self.host, self.user, self.user, self.password, self.port + )) conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) # TODO: Remove this - # conn = psycopg2.connect('host={} dbname={} user={} password={}'.format(self.host, - # self.user, self.user, self.password)) return conn @@ -310,4 +310,4 @@ def replace_locate_with_strpos(query): # strpos is the locate equivalent in postgres if re.search(r'locate\(', query, flags=re.IGNORECASE): query = re.sub(r'locate\(([^,]+),([^)]+)\)', r'strpos(\2, \1)', query, flags=re.IGNORECASE) - return query \ No newline at end of file + return query diff --git a/frappe/database/postgres/framework_postgres.sql b/frappe/database/postgres/framework_postgres.sql index eb82f3ed97..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, @@ -49,6 +50,7 @@ CREATE TABLE "tabDocField" ( "description" text, "in_list_view" smallint NOT NULL DEFAULT 0, "in_standard_filter" smallint NOT NULL DEFAULT 0, + "in_preview" smallint NOT NULL DEFAULT 0, "read_only" smallint NOT NULL DEFAULT 0, "precision" varchar(255) DEFAULT NULL, "length" bigint NOT NULL DEFAULT 0, 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/database/postgres/setup_db.py b/frappe/database/postgres/setup_db.py index 0209d65839..7ccd86a26e 100644 --- a/frappe/database/postgres/setup_db.py +++ b/frappe/database/postgres/setup_db.py @@ -1,4 +1,5 @@ import frappe, subprocess, os +from six.moves import input def setup_database(force, source_sql, verbose): root_conn = get_root_connection() @@ -10,9 +11,16 @@ def setup_database(force, source_sql, verbose): frappe.conf.db_password)) root_conn.sql("GRANT ALL PRIVILEGES ON DATABASE `{0}` TO {0}".format(frappe.conf.db_name)) + # we can't pass psql password in arguments in postgresql as mysql. So + # set password connection parameter in environment variable + subprocess_env = os.environ.copy() + subprocess_env['PGPASSWORD'] = str(frappe.conf.db_password) # bootstrap db - subprocess.check_output(['psql', frappe.conf.db_name, '-qf', - os.path.join(os.path.dirname(__file__), 'framework_postgres.sql')]) + subprocess.check_output([ + 'psql', frappe.conf.db_name, '-h', 'localhost', '-U', + frappe.conf.db_name, '-f', + os.path.join(os.path.dirname(__file__), 'framework_postgres.sql') + ], env=subprocess_env) frappe.connect() @@ -24,17 +32,20 @@ def setup_help_database(help_db_name): root_conn.sql("CREATE user {0} password '{1}'".format(help_db_name, help_db_name)) root_conn.sql("GRANT ALL PRIVILEGES ON DATABASE `{0}` TO {0}".format(help_db_name)) -def get_root_connection(root_login='postgres', root_password=None): +def get_root_connection(root_login=None, root_password=None): import getpass if not frappe.local.flags.root_connection: if not root_login: - root_login = 'root' + root_login = frappe.conf.get("root_login") or None + + if not root_login: + root_login = input("Enter postgres super user: ") if not root_password: root_password = frappe.conf.get("root_password") or None if not root_password: - root_password = getpass.getpass("Postgres root password: ") + root_password = getpass.getpass("Postgres super user password: ") frappe.local.flags.root_connection = frappe.database.get_db(user=root_login, password=root_password) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index c178bcfc79..ba00d81143 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -100,6 +100,9 @@ frappe.ui.form.on('Dashboard Chart', { // nothing is mandatory _df.reqd = 0; _df.default = null; + _df.read_only = 0; + _df.permlevel = 1; + _df.hidden = 0; frm.chart_filters.push(_df); } diff --git a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py index ca55d9254a..970f937020 100644 --- a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py @@ -8,6 +8,10 @@ from frappe.utils import getdate from frappe.desk.doctype.dashboard_chart.dashboard_chart import (get, get_period_ending) +from datetime import datetime +from dateutil.relativedelta import relativedelta +import calendar + class TestDashboardChart(unittest.TestCase): def test_period_ending(self): self.assertEqual(get_period_ending('2019-04-10', 'Daily'), @@ -48,21 +52,13 @@ class TestDashboardChart(unittest.TestCase): timeseries = 1 )).insert() - result = get(chart_name ='Test Dashboard Chart', - to_date = '2019-04-11', refresh = 1) - self.assertEqual(result.get('labels')[0], '2018-04-30') - self.assertEqual(result.get('labels')[1], '2018-05-31') - self.assertEqual(result.get('labels')[2], '2018-06-30') - self.assertEqual(result.get('labels')[3], '2018-07-31') - self.assertEqual(result.get('labels')[4], '2018-08-31') - self.assertEqual(result.get('labels')[5], '2018-09-30') - self.assertEqual(result.get('labels')[6], '2018-10-31') - self.assertEqual(result.get('labels')[7], '2018-11-30') - self.assertEqual(result.get('labels')[8], '2018-12-31') - self.assertEqual(result.get('labels')[9], '2019-01-31') - self.assertEqual(result.get('labels')[10], '2019-02-28') - self.assertEqual(result.get('labels')[11], '2019-03-31') - self.assertEqual(result.get('labels')[12], '2019-04-30') + cur_date = datetime.now() - relativedelta(years=1) + + result = get(chart_name ='Test Dashboard Chart', refresh = 1) + for idx in range(13): + month = str(cur_date.year) + '-' + str(cur_date.strftime('%m')) + '-' + str(calendar.monthrange(cur_date.year, cur_date.month)[1]) + self.assertEqual(result.get('labels')[idx], month) + cur_date += relativedelta(months=1) # self.assertEqual(result.get('datasets')[0].get('values')[:-1], # [44, 28, 8, 11, 2, 6, 18, 6, 4, 5, 15, 13]) @@ -87,21 +83,13 @@ class TestDashboardChart(unittest.TestCase): timeseries = 1 )).insert() - result = get(chart_name ='Test Empty Dashboard Chart', - to_date = '2019-04-11', refresh = 1) - self.assertEqual(result.get('labels')[0], '2018-04-30') - self.assertEqual(result.get('labels')[1], '2018-05-31') - self.assertEqual(result.get('labels')[2], '2018-06-30') - self.assertEqual(result.get('labels')[3], '2018-07-31') - self.assertEqual(result.get('labels')[4], '2018-08-31') - self.assertEqual(result.get('labels')[5], '2018-09-30') - self.assertEqual(result.get('labels')[6], '2018-10-31') - self.assertEqual(result.get('labels')[7], '2018-11-30') - self.assertEqual(result.get('labels')[8], '2018-12-31') - self.assertEqual(result.get('labels')[9], '2019-01-31') - self.assertEqual(result.get('labels')[10], '2019-02-28') - self.assertEqual(result.get('labels')[11], '2019-03-31') - self.assertEqual(result.get('labels')[12], '2019-04-30') + cur_date = datetime.now() - relativedelta(years=1) + + result = get(chart_name ='Test Empty Dashboard Chart', refresh = 1) + for idx in range(13): + month = str(cur_date.year) + '-' + str(cur_date.strftime('%m')) + '-' + str(calendar.monthrange(cur_date.year, cur_date.month)[1]) + self.assertEqual(result.get('labels')[idx], month) + cur_date += relativedelta(months=1) frappe.db.rollback() @@ -126,24 +114,16 @@ class TestDashboardChart(unittest.TestCase): timeseries = 1 )).insert() - result = get(chart_name ='Test Empty Dashboard Chart 2', - to_date = '2019-04-11', refresh = 1) - self.assertEqual(result.get('labels')[0], '2018-04-30') - self.assertEqual(result.get('labels')[1], '2018-05-31') - self.assertEqual(result.get('labels')[2], '2018-06-30') - self.assertEqual(result.get('labels')[3], '2018-07-31') - self.assertEqual(result.get('labels')[4], '2018-08-31') - self.assertEqual(result.get('labels')[5], '2018-09-30') - self.assertEqual(result.get('labels')[6], '2018-10-31') - self.assertEqual(result.get('labels')[7], '2018-11-30') - self.assertEqual(result.get('labels')[8], '2018-12-31') - self.assertEqual(result.get('labels')[9], '2019-01-31') - self.assertEqual(result.get('labels')[10], '2019-02-28') - self.assertEqual(result.get('labels')[11], '2019-03-31') - self.assertEqual(result.get('labels')[12], '2019-04-30') + cur_date = datetime.now() - relativedelta(years=1) + + result = get(chart_name ='Test Empty Dashboard Chart 2', refresh = 1) + for idx in range(13): + month = str(cur_date.year) + '-' + str(cur_date.strftime('%m')) + '-' + str(calendar.monthrange(cur_date.year, cur_date.month)[1]) + self.assertEqual(result.get('labels')[idx], month) + cur_date += relativedelta(months=1) # only 1 data point with value - self.assertEqual(result.get('datasets')[0].get('values')[2], 1) + self.assertEqual(result.get('datasets')[0].get('values')[2], 0) frappe.db.rollback() 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/document_follow.py b/frappe/desk/form/document_follow.py index de04ef35af..333e9e1333 100644 --- a/frappe/desk/form/document_follow.py +++ b/frappe/desk/form/document_follow.py @@ -54,8 +54,8 @@ def unfollow_document(doctype, doc_name, user): return 1 return 0 -def get_message(doc_name, doctype, frequency): - activity_list = get_version(doctype, doc_name, frequency) + get_comments(doctype, doc_name, frequency) +def get_message(doc_name, doctype, frequency, user): + activity_list = get_version(doctype, doc_name, frequency, user) + get_comments(doctype, doc_name, frequency, user) return sorted(activity_list, key=lambda k: k["time"], reverse=True) def send_email_alert(receiver, docinfo, timeline): @@ -98,7 +98,7 @@ def send_document_follow_mails(frequency): valid_document_follows = [] if user_frequency == frequency: for d in grouped_by_user[user]: - content = get_message(d.ref_docname, d.ref_doctype, frequency) + content = get_message(d.ref_docname, d.ref_doctype, frequency, user) if content: message = message + content valid_document_follows.append({ @@ -107,13 +107,13 @@ def send_document_follow_mails(frequency): "reference_url": get_url_to_form(d.ref_doctype, d.ref_docname) }) - if message: + if message and frappe.db.get_value("User", user, "document_follow_notify", ignore=True): send_email_alert(user, valid_document_follows, message) -def get_version(doctype, doc_name, frequency): +def get_version(doctype, doc_name, frequency, user): timeline = [] - filters = get_filters("docname", doc_name, frequency) + filters = get_filters("docname", doc_name, frequency, user) version = frappe.get_all("Version", filters=filters, fields=["ref_doctype", "data", "modified", "modified", "modified_by"] @@ -134,9 +134,9 @@ def get_version(doctype, doc_name, frequency): return timeline -def get_comments(doctype, doc_name, frequency): +def get_comments(doctype, doc_name, frequency, user): timeline = [] - filters = get_filters("reference_name", doc_name, frequency) + filters = get_filters("reference_name", doc_name, frequency, user) comments = frappe.get_all("Comment", filters=filters, fields=["content", "modified", "modified_by", "comment_type"] @@ -255,26 +255,29 @@ def send_daily_updates(): def send_weekly_updates(): send_document_follow_mails("Weekly") -def get_filters(search_by, name, frequency): +def get_filters(search_by, name, frequency, user): filters = [] if frequency == "Weekly": filters = [ [search_by, "=", name], ["modified", ">", frappe.utils.add_days(frappe.utils.nowdate(),-7)], - ["modified", "<", frappe.utils.nowdate()] + ["modified", "<", frappe.utils.nowdate()], + ["modified_by", "!=", user] ] elif frequency == "Daily": filters = [ [search_by, "=", name], ["modified", ">", frappe.utils.add_days(frappe.utils.nowdate(),-1)], - ["modified", "<", frappe.utils.nowdate()] + ["modified", "<", frappe.utils.nowdate()], + ["modified_by", "!=", user] ] elif frequency == "Hourly": filters = [ [search_by, "=", name], - ["modified", ">", frappe.utils.add_to_date(frappe.utils.now_datetime(), 0, 0, 0, -1)], - ["modified", "<", frappe.utils.now_datetime()] + ["modified", ">", frappe.utils.add_to_date(frappe.utils.now_datetime(), hours=-1)], + ["modified", "<", frappe.utils.now_datetime()], + ["modified_by", "!=", user] ] return filters diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index 00f29b6ffe..3b67b78144 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -47,9 +47,6 @@ def getdoc(doctype, name, user=None): frappe.errprint(frappe.utils.get_traceback()) raise - if doc and not name.startswith('_'): - frappe.get_user().update_recent(doctype, name) - doc.add_seen() frappe.response.docs.append(doc) @@ -100,7 +97,6 @@ def get_docinfo(doc=None, doctype=None, name=None): "assignments": get_assignments(doc.doctype, doc.name), "permissions": get_doc_permissions(doc), "shared": frappe.share.get_users(doc.doctype, doc.name), - "rating": get_feedback_rating(doc.doctype, doc.name), "views": get_view_logs(doc.doctype, doc.name), "energy_point_logs": get_point_logs(doc.doctype, doc.name), "milestones": get_milestones(doc.doctype, doc.name), @@ -164,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 in ('Communication', 'Feedback') - 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 AS 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 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 @@ -222,21 +241,6 @@ def run_onload(doc): doc.set("__onload", frappe._dict()) doc.run_method("onload") -def get_feedback_rating(doctype, docname): - """ get and return the latest feedback rating if available """ - - rating= frappe.get_all("Communication", filters={ - "reference_doctype": doctype, - "reference_name": docname, - "communication_type": "Feedback" - }, fields=["rating"], order_by="creation desc", as_list=True) - - if not rating: - return 0 - else: - return rating[0][0] - - def get_view_logs(doctype, docname): """ get and return the latest view logs if available """ logs = [] @@ -248,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/form/save.py b/frappe/desk/form/save.py index 1e07f10ba7..694b44b907 100644 --- a/frappe/desk/form/save.py +++ b/frappe/desk/form/save.py @@ -27,11 +27,8 @@ def savedocs(doc, action): # update recent documents run_onload(doc) - frappe.get_user().update_recent(doc.doctype, doc.name) send_updated_docs(doc) except Exception: - if not frappe.local.message_log: - frappe.msgprint(frappe._('Did not save')) frappe.errprint(frappe.utils.get_traceback()) raise diff --git a/frappe/desk/link_preview.py b/frappe/desk/link_preview.py new file mode 100644 index 0000000000..f8252f20bc --- /dev/null +++ b/frappe/desk/link_preview.py @@ -0,0 +1,28 @@ +import frappe +from frappe.model import no_value_fields +import json + +@frappe.whitelist() +def get_preview_data(doctype, docname, fields): + fields = json.loads(fields) + preview_fields = [field['name'] for field in fields if field['type'] not in no_value_fields] + preview_fields.append(frappe.get_meta(doctype).get_title_field()) + if 'name' not in fields: + preview_fields.append('name') + preview_fields.append(frappe.get_meta(doctype).image_field) + + preview_data = frappe.get_list(doctype, filters={ + 'name': docname + }, fields=preview_fields, limit=1) + if preview_data: + preview_data = preview_data[0] + + preview_data = {k: v for k, v in preview_data.items() if v is not None} + for k,v in preview_data.items(): + if frappe.get_meta(doctype).has_field(k): + preview_data[k] = frappe.format(v,frappe.get_meta(doctype).get_field(k).fieldtype) + + if not preview_data: + return None + return preview_data + 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/page/activity/activity.py b/frappe/desk/page/activity/activity.py index e25c94d0e5..31ade42e7c 100644 --- a/frappe/desk/page/activity/activity.py +++ b/frappe/desk/page/activity/activity.py @@ -38,7 +38,8 @@ def get_feed(start, page_length): {match_conditions_comment} ) X order by X.creation DESC - limit %(start)s, %(page_length)s""" + LIMIT %(page_length)s + OFFSET %(start)s""" .format(match_conditions_comment = match_conditions_comment, match_conditions_communication = match_conditions_communication), { "user": frappe.session.user, @@ -55,4 +56,4 @@ def get_heatmap_data(): where date(creation) > subdate(curdate(), interval 1 year) group by date(creation) - order by creation asc""")) \ No newline at end of file + order by creation asc""")) diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index cf8f5f502b..a18d4df9c4 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -389,7 +389,7 @@ def make_records(records, debug=False): # pass DuplicateEntryError and continue if e.args and e.args[0]==doc.doctype and e.args[1]==doc.name: # make sure DuplicateEntryError is for the exact same doc and not a related doc - pass + frappe.clear_messages() else: raise diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index f1c3535824..0890e2ad7a 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -132,6 +132,8 @@ def background_enqueue_run(report_name, filters=None, user=None): }) track_instance.insert(ignore_permissions=True) frappe.db.commit() + track_instance.enqueue_report() + return { "name": track_instance.name, "redirect_url": get_url_to_form("Prepared Report", track_instance.name) @@ -280,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/desk/reportview.py b/frappe/desk/reportview.py index 42409867fb..5ed4dc730b 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -108,7 +108,7 @@ def save_report(): d.report_type = "Report Builder" d.json = data['json'] frappe.get_doc(d).save() - frappe.msgprint(_("{0} is saved").format(d.name)) + frappe.msgprint(_("{0} is saved").format(d.name), alert=True) return d.name @frappe.whitelist() diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.js b/frappe/email/doctype/auto_email_report/auto_email_report.js index c4f5397ca0..fab5bd3a8a 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.js +++ b/frappe/email/doctype/auto_email_report/auto_email_report.js @@ -54,9 +54,9 @@ frappe.ui.form.on('Auto Email Report', { show_filters: function(frm) { var wrapper = $(frm.get_field('filters_display').wrapper); wrapper.empty(); - if(frm.doc.report_type !== 'Report Builder' + if(frm.doc.report_type === 'Custom Report' || (frm.doc.report_type !== 'Report Builder' && frappe.query_reports[frm.doc.report] - && frappe.query_reports[frm.doc.report].filters) { + && frappe.query_reports[frm.doc.report].filters)) { // make a table to show filters var table = $('\ @@ -65,7 +65,17 @@ frappe.ui.form.on('Auto Email Report', { $('

' + __("Click table to edit") + '

').appendTo(wrapper); var filters = JSON.parse(frm.doc.filters || '{}'); - var report_filters = frappe.query_reports[frm.doc.report].filters; + + let report_filters; + + if (frm.doc.report_type === 'Custom Report' + && frappe.query_reports[frm.doc.reference_report] + && frappe.query_reports[frm.doc.reference_report].filters) { + report_filters = frappe.query_reports[frm.doc.reference_report].filters; + } else { + report_filters = frappe.query_reports[frm.doc.report].filters; + } + if(report_filters && report_filters.length > 0) { frm.set_value('filter_meta', JSON.stringify(report_filters)); } diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.json b/frappe/email/doctype/auto_email_report/auto_email_report.json index b8ffc1be2f..4fbbe4e58e 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.json +++ b/frappe/email/doctype/auto_email_report/auto_email_report.json @@ -1,956 +1,237 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, "allow_rename": 1, - "autoname": "", - "beta": 0, "creation": "2016-09-01 01:34:34.985457", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "report", + "user", + "enabled", + "column_break_4", + "report_type", + "reference_report", + "filter_data", + "send_if_data", + "data_modified_till", + "no_of_rows", + "report_filters", + "filters_display", + "filters", + "filter_meta", + "dynamic_report_filters_section", + "from_date_field", + "to_date_field", + "column_break_17", + "dynamic_date_period", + "email_settings", + "email_to", + "day_of_week", + "column_break_13", + "frequency", + "format", + "section_break_15", + "description" + ], "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": "report", "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": "Report", - "length": 0, - "no_copy": 0, "options": "Report", - "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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "User", - "fetch_if_empty": 0, "fieldname": "user", "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": "Based on Permissions For User", - "length": 0, - "no_copy": 0, "options": "User", - "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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", - "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 + "label": "Enabled" }, { - "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_4", - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "report.report_type", - "fetch_if_empty": 0, "fieldname": "report_type", "fieldtype": "Read Only", - "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": "Report Type", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Report Type" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "filter_data", "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": "Filter Data", - "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 + "label": "Filter Data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", - "fetch_if_empty": 0, "fieldname": "send_if_data", "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": "Send only if there is any data", - "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 + "label": "Send only if there is any data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", "depends_on": "eval:doc.report_type=='Report Builder'", "description": "Zero means send records updated at anytime", - "fetch_if_empty": 0, "fieldname": "data_modified_till", "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": "Only Send Records Updated in Last X Hours", - "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 + "label": "Only Send Records Updated in Last X Hours" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "100", - "description": "", - "fetch_if_empty": 0, "fieldname": "no_of_rows", "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": "No of Rows (Max 500)", - "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 + "label": "No of Rows (Max 500)" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "depends_on": "eval:doc.report_type !== 'Report Builder'", - "fetch_if_empty": 0, "fieldname": "report_filters", "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": "Report Filters", - "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 + "label": "Report Filters" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "filters_display", "fieldtype": "HTML", - "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": "Filters Display", - "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 + "label": "Filters Display" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "filters", "fieldtype": "Text", "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": "Filters", - "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 + "label": "Filters" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "filter_meta", "fieldtype": "Text", "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": "Filter Meta", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "depends_on": "eval:doc.report_type !== 'Report Builder'", - "fetch_if_empty": 0, "fieldname": "dynamic_report_filters_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": "Dynamic Report Filters", - "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 + "label": "Dynamic Report Filters" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "from_date_field", "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": "From Date 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 + "label": "From Date Field" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "to_date_field", "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": "To Date 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 + "label": "To Date Field" }, { - "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_17", - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "dynamic_date_period", "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": "Period", - "length": 0, - "no_copy": 0, - "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nHalf Yearly\nYearly", - "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 + "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nHalf Yearly\nYearly" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "email_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": "Email 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 + "label": "Email Settings" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "email_to", "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": "Email To", - "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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Monday", "depends_on": "eval:doc.frequency=='Weekly'", - "fetch_if_empty": 0, "fieldname": "day_of_week", "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": "Day of Week", - "length": 0, - "no_copy": 0, - "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday", - "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 + "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday" }, { - "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_13", - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "frequency", "fieldtype": "Select", - "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": "Frequency", - "length": 0, - "no_copy": 0, "options": "Daily\nWeekdays\nWeekly\nMonthly", - "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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "format", "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": "Format", - "length": 0, - "no_copy": 0, "options": "HTML\nXLSX\nCSV", - "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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_15", "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": "Message", - "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 + "label": "Message" }, { - "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": "Text Editor", - "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": "Message", - "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 + "label": "Message" + }, + { + "fetch_from": "report.reference_report", + "fieldname": "reference_report", + "fieldtype": "Data", + "hidden": 1, + "label": "Reference Report", + "read_only": 1 } ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2019-04-18 14:58:12.522559", + "modified": "2019-05-09 22:38:27.570890", "modified_by": "Administrator", "module": "Email", "name": "Auto Email Report", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Report Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index b1ad7ede99..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) @@ -470,7 +470,7 @@ class EmailAccount(Document): parent = frappe.db.get_all(self.append_to, filters={ self.sender_field: email.from_email, self.subject_field: ("like", "%{0}%".format(subject)), - "creation": (">", (get_datetime() - relativedelta(days=10)).strftime(DATE_FORMAT)) + "creation": (">", (get_datetime() - relativedelta(days=60)).strftime(DATE_FORMAT)) }, fields="name") # match only subject field @@ -479,7 +479,7 @@ class EmailAccount(Document): if not parent and len(subject) > 10 and is_system_user(email.from_email): parent = frappe.db.get_all(self.append_to, filters={ self.subject_field: ("like", "%{0}%".format(subject)), - "creation": (">", (get_datetime() - relativedelta(days=10)).strftime(DATE_FORMAT)) + "creation": (">", (get_datetime() - relativedelta(days=60)).strftime(DATE_FORMAT)) }, fields="name") if parent: 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..8032e020d7 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 @@ -150,6 +155,7 @@ def get_context(context): reference_doctype = doc.doctype, reference_name = doc.name, attachments = attachments, + expose_recipients="header", print_letterhead = ((attachments and attachments[0].get('print_letterhead')) or False)) 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/hooks.py b/frappe/hooks.py index 0cd68d18f7..52e2e1e4a0 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -118,7 +118,6 @@ doc_events = { "frappe.core.doctype.activity_log.feed.update_feed", "frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions", "frappe.automation.doctype.assignment_rule.assignment_rule.apply", - "frappe.social.doctype.energy_point_rule.energy_point_rule.process_energy_points", "frappe.automation.doctype.milestone_tracker.milestone_tracker.evaluate_milestone" ], "after_rename": "frappe.desk.notifications.clear_doctype_notifications", @@ -131,8 +130,8 @@ doc_events = { "frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions" ], "on_change": [ - "frappe.core.doctype.feedback_trigger.feedback_trigger.trigger_feedback_request" - ] + "frappe.social.doctype.energy_point_rule.energy_point_rule.process_energy_points" + ], }, "Email Group Member": { "validate": "frappe.email.doctype.email_group.email_group.restrict_email_group" @@ -157,6 +156,7 @@ scheduler_events = { "frappe.utils.error.collect_error_snapshots", "frappe.desk.page.backups.backups.delete_downloadable_backups", "frappe.limits.update_space_usage", + "frappe.limits.update_site_usage", "frappe.desk.doctype.auto_repeat.auto_repeat.make_auto_repeat_entry", "frappe.deferred_insert.save_to_db", "frappe.desk.form.document_follow.send_hourly_updates" @@ -172,7 +172,6 @@ scheduler_events = { "frappe.utils.scheduler.disable_scheduler_on_expiry", "frappe.utils.scheduler.restrict_scheduler_events_if_dormant", "frappe.email.doctype.auto_email_report.auto_email_report.send_daily", - "frappe.core.doctype.feedback_request.feedback_request.delete_feedback_request", "frappe.core.doctype.activity_log.activity_log.clear_authentication_logs", "frappe.website.doctype.personal_data_deletion_request.personal_data_deletion_request.remove_unverified_record", "frappe.desk.form.document_follow.send_daily_updates", @@ -285,4 +284,4 @@ user_privacy_documents = [ 'applies_to_website_user': 1 }, -] \ No newline at end of file +] diff --git a/frappe/integrations/doctype/integration_request/integration_request.json b/frappe/integrations/doctype/integration_request/integration_request.json index f69668973e..c3123fb574 100644 --- a/frappe/integrations/doctype/integration_request/integration_request.json +++ b/frappe/integrations/doctype/integration_request/integration_request.json @@ -1,5 +1,7 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, "beta": 0, @@ -12,16 +14,20 @@ "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": "integration_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": "Integration Type", @@ -38,19 +44,24 @@ "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": "integration_request_service", "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": "Integration Request Service", @@ -67,26 +78,31 @@ "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": "Queued", + "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", "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": "Status", "length": 0, "no_copy": 0, - "options": "\nQueued\nAuthorized\nCompleted\nCancelled\nFailed\n", + "options": "\nQueued\nAuthorized\nCompleted\nCancelled\nFailed", "permlevel": 0, "precision": "", "print_hide": 0, @@ -97,19 +113,24 @@ "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": "data", "fieldtype": "Code", "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": "Data", @@ -125,19 +146,24 @@ "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": "output", "fieldtype": "Code", "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": "Output", @@ -153,19 +179,24 @@ "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": "error", "fieldtype": "Code", "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": "Error", @@ -181,19 +212,24 @@ "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": "reference_doctype", "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": "Reference Doctype", @@ -210,19 +246,24 @@ "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": "reference_docname", "fieldtype": "Dynamic 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": "Reference Docname", @@ -239,20 +280,21 @@ "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": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-10-09 14:40:00.783063", + "modified": "2019-04-25 16:38:21.084580", "modified_by": "Administrator", "module": "Integrations", "name": "Integration Request", @@ -261,7 +303,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -269,7 +310,6 @@ "export": 1, "if_owner": 0, "import": 0, - "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -284,9 +324,11 @@ "quick_entry": 0, "read_only": 0, "read_only_onload": 0, + "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", "title_field": "integration_request_service", "track_changes": 1, - "track_seen": 0 + "track_seen": 0, + "track_views": 0 } \ No newline at end of file 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/limits.py b/frappe/limits.py index fe800e3fe5..8295de5a34 100755 --- a/frappe/limits.py +++ b/frappe/limits.py @@ -5,7 +5,8 @@ from frappe.utils import now_datetime, getdate, flt, cint, get_fullname from frappe.installer import update_site_config from frappe.utils.data import formatdate from frappe.utils.user import get_enabled_system_users, disable_users -import os, subprocess +from frappe.utils.__init__ import get_site_info +import os, subprocess, json from six.moves.urllib.parse import parse_qsl, urlsplit, urlunsplit, urlencode from six import string_types @@ -225,3 +226,8 @@ def get_folder_size(path): if os.path.exists(path): return flt(subprocess.check_output(['du', '-ms', path]).split()[0], 2) +def update_site_usage(): + data = get_site_info() + with open(os.path.join(frappe.get_site_path(), 'site_data.json'), 'w') as outfile: + json.dump(data, outfile) + outfile.close() 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 ab15dab74e..cdf099e50e 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -66,6 +66,7 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa frappe.db.sql("delete from `tabProperty Setter` where doc_type = %s", name) frappe.db.sql("delete from `tabReport` where ref_doctype=%s", name) frappe.db.sql("delete from `tabCustom DocPerm` where parent=%s", name) + frappe.db.sql("delete from `__global_search` where doctype=%s", name) delete_from_table(doctype, name, ignore_doctypes, None) @@ -277,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') @@ -300,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 5fe1ba3bc6..f368aad72d 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -230,6 +230,9 @@ class Document(BaseDocument): self.set_docstatus() self.flags.in_insert = False + # follow document on document creation + + # run validate, on update etc. # parent @@ -259,6 +262,8 @@ class Document(BaseDocument): if hasattr(self, "__islocal"): delattr(self, "__islocal") + if not (frappe.flags.in_migrate or frappe.local.flags.in_install): + follow_document(self.doctype, self.name, frappe.session.user) return self def save(self, *args, **kwargs): @@ -1190,18 +1195,11 @@ 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 - }) + communications = frappe.get_list("Communication", filters={"reference_doctype": self.doctype, "reference_name": self.name}) + for communication in communications: + communication = frappe.get_doc("Communication", communication.name) + # 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 0549913589..713e6d56c2 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -230,9 +230,10 @@ frappe.patches.v11_0.delete_all_prepared_reports frappe.patches.v11_0.fix_order_by_in_reports_json execute:frappe.delete_doc('Page', 'applications', ignore_missing=True) frappe.patches.v11_0.set_missing_creation_and_modified_value_for_user_permissions -frappe.patches.v11_0.remove_doctype_user_permissions_for_page_and_report frappe.patches.v11_0.set_default_letter_head_source frappe.patches.v12_0.set_primary_key_in_series +execute:frappe.reload_doc('core', 'doctype', 'communication_link') +execute:frappe.reload_doc('core', 'doctype', 'communication') execute:frappe.delete_doc("Page", "modules", ignore_missing=True) frappe.patches.v11_0.set_default_letter_head_source frappe.patches.v12_0.setup_comments_from_communications @@ -240,3 +241,7 @@ frappe.patches.v12_0.init_desk_settings #11-03-2019 frappe.patches.v12_0.replace_null_values_in_tables 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 +frappe.patches.v12_0.move_form_attachments_to_attachments_folder +frappe.patches.v12_0.move_timeline_links_to_dynamic_links diff --git a/frappe/patches/v11_0/create_contact_for_user.py b/frappe/patches/v11_0/create_contact_for_user.py index d5e9c87e8b..c91caf9189 100644 --- a/frappe/patches/v11_0/create_contact_for_user.py +++ b/frappe/patches/v11_0/create_contact_for_user.py @@ -6,6 +6,7 @@ import re def execute(): """ Create Contact for each User if not present """ frappe.reload_doc('contacts', 'doctype', 'contact') + frappe.reload_doc('core', 'doctype', 'dynamic_link') users = frappe.get_all('User', filters={"name": ('not in', 'Administrator, Guest')}, fields=["*"]) for user in users: diff --git a/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py b/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py index c1dc1b79be..e2c2ef5f0e 100644 --- a/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py +++ b/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py @@ -5,5 +5,4 @@ from __future__ import unicode_literals import frappe def execute(): - if frappe.db.table_exists('User Permission for Page and Report'): - frappe.delete_doc("DocType", "User Permission for Page and Report") \ No newline at end of file + frappe.delete_doc_if_exists("DocType", "User Permission for Page and Report") \ 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_form_attachments_to_attachments_folder.py b/frappe/patches/v12_0/move_form_attachments_to_attachments_folder.py new file mode 100644 index 0000000000..c96741cc3b --- /dev/null +++ b/frappe/patches/v12_0/move_form_attachments_to_attachments_folder.py @@ -0,0 +1,9 @@ +import frappe + +def execute(): + frappe.db.sql(''' + UPDATE tabFile + SET folder = 'Home/Attachments' + WHERE ifnull(attached_to_doctype, '') != '' + AND folder = 'Home' + ''') 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/remove_feedback_rating.py b/frappe/patches/v12_0/remove_feedback_rating.py new file mode 100644 index 0000000000..5184bb7b79 --- /dev/null +++ b/frappe/patches/v12_0/remove_feedback_rating.py @@ -0,0 +1,9 @@ +import frappe + +def execute(): + ''' + Deprecate Feedback Trigger and Rating. This feature was not customizable. + Now can be achieved via custom Web Forms + ''' + frappe.delete_doc('DocType', 'Feedback Trigger') + frappe.delete_doc('DocType', 'Feedback Rating') 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/patches/v7_2/update_communications.py b/frappe/patches/v7_2/update_communications.py index 98c729ae41..f3d859b95a 100644 --- a/frappe/patches/v7_2/update_communications.py +++ b/frappe/patches/v7_2/update_communications.py @@ -7,18 +7,4 @@ def execute(): remove Guest None from sender full name setup feedback request trigger's is_manual field """ - frappe.reload_doc('core', 'doctype', 'dynamic_link') - frappe.reload_doc('email', 'doctype', 'contact') - - frappe.reload_doc("core", "doctype", "feedback_request") - frappe.reload_doc("core", "doctype", "communication") - - if frappe.db.has_column('Communication', 'feedback'): - frappe.db.sql("""update tabCommunication set content=ifnull(feedback, "feedback details not provided") - where communication_type="Feedback" and content is NULL""") - - frappe.db.sql(""" update tabCommunication set sender_full_name="" where communication_type="Feedback" - and sender_full_name='Guest None' """) - - frappe.db.sql(""" update `tabFeedback Request` set is_manual=1, feedback_trigger="Manual" - where ifnull(feedback_trigger, '')='' """) \ No newline at end of file + return diff --git a/frappe/patches/v7_2/update_feedback_request.py b/frappe/patches/v7_2/update_feedback_request.py index 68814e7a58..11e9eb8e92 100644 --- a/frappe/patches/v7_2/update_feedback_request.py +++ b/frappe/patches/v7_2/update_feedback_request.py @@ -7,29 +7,4 @@ def execute(): update the feedback request and save the rating and communication reference in Feedback Request document """ - - frappe.reload_doc("core", "doctype", "feedback_request") - feedback_requests = frappe.get_all("Feedback Request") - for request in feedback_requests: - communication, rating = frappe.db.get_value("Communication", { "feedback_request": request.get("name") }, - ["name", "rating"]) or [None, 0] - - if communication: - frappe.db.sql("""update `tabFeedback Request` set reference_communication='{communication}', - rating={rating} where name='{feedback_request}'""".format( - communication=communication, - rating=rating or 0, - feedback_request=request.get("name") - )) - - if "Feedback" not in request.get("name"): - # rename the feedback request doc - reference_name, creation = frappe.db.get_value("Feedback Request", request.get("name"), ["name", "creation"]) - oldname = request.get("name") - newname = "Feedback for {doctype} {docname} on {datetime}".format( - doctype="Feedback Request", - docname=reference_name, - datetime=creation - ) - frappe.rename_doc("Feedback Request", oldname, newname, ignore_permissions=True) - if communication: frappe.db.set_value("Communication", communication, "feedback_request", newname) \ No newline at end of file + return diff --git a/frappe/permissions.py b/frappe/permissions.py index 3809bb3e9c..27eeb9be28 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: - msgprint(('
').join(frappe.flags.get('has_permission_check_logs'))) + 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/printing/page/print_format_builder/print_format_builder.js b/frappe/printing/page/print_format_builder/print_format_builder.js index 7611f4d63b..160af10c6e 100644 --- a/frappe/printing/page/print_format_builder/print_format_builder.js +++ b/frappe/printing/page/print_format_builder/print_format_builder.js @@ -12,10 +12,9 @@ frappe.pages['print-format-builder'].on_page_show = function(wrapper) { }); } else if(frappe.route_options) { if(frappe.route_options.make_new) { - var doctype = frappe.route_options.doctype; - var name = frappe.route_options.name; + let { doctype, name, based_on } = frappe.route_options; frappe.route_options = null; - frappe.print_format_builder.setup_new_print_format(doctype, name); + frappe.print_format_builder.setup_new_print_format(doctype, name, based_on); } else { frappe.print_format_builder.print_format = frappe.route_options.doc; frappe.route_options = null; @@ -130,25 +129,22 @@ frappe.PrintFormatBuilder = Class.extend({ }); }, - setup_new_print_format: function(doctype, name) { - var me = this; + setup_new_print_format: function(doctype, name, based_on) { frappe.call({ - method: "frappe.client.insert", + method: 'frappe.printing.page.print_format_builder.print_format_builder.create_custom_format', args: { - doc: { - doctype: "Print Format", - name: name, - standard: "No", - doc_type: doctype, - print_format_builder: 1 + doctype: doctype, + name: name, + based_on: based_on + }, + callback: (r) => { + if(!r.exc) { + if(r.message) { + this.print_format = r.message; + this.refresh(); + } } }, - callback: function(r) { - frappe.model.with_doc('Print Format', r.message.name) - .then(() => $(document).trigger({ type: 'new-print-format', print_format: r.message.name })); - me.print_format = r.message; - me.refresh(); - } }); }, setup_print_format: function() { diff --git a/frappe/printing/page/print_format_builder/print_format_builder.py b/frappe/printing/page/print_format_builder/print_format_builder.py new file mode 100644 index 0000000000..d9f57762b0 --- /dev/null +++ b/frappe/printing/page/print_format_builder/print_format_builder.py @@ -0,0 +1,12 @@ +import frappe + +@frappe.whitelist() +def create_custom_format(doctype, name, based_on='Standard'): + doc = frappe.new_doc('Print Format') + doc.doc_type = doctype + doc.name = name + doc.print_format_builder = 1 + doc.format_data = frappe.db.get_value('Print Format', based_on, 'format_data') \ + if based_on != 'Standard' else None + doc.insert() + return doc \ No newline at end of file diff --git a/frappe/public/build.json b/frappe/public/build.json index 8e04fbf48a..200b2538d1 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -123,6 +123,7 @@ "public/less/page.less", "public/less/tree.less", "public/less/desktop.less", + "public/less/link_preview.less", "public/less/form.less", "public/less/mobile.less", "public/less/kanban.less", @@ -136,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", @@ -167,6 +173,7 @@ "public/js/frappe/ui/keyboard.js", "public/js/frappe/ui/colors.js", "public/js/frappe/ui/sidebar.js", + "public/js/frappe/ui/link_preview.js", "public/js/frappe/request.js", "public/js/frappe/socketio_client.js", @@ -252,7 +259,6 @@ "public/js/frappe/ui/comment.js", "public/js/frappe/misc/rating_icons.html", - "public/js/frappe/feedback.js", "public/js/frappe/chat.js", "public/js/frappe/social/social_factory.js", "public/js/frappe/misc/energy_point_utils.js" @@ -357,9 +363,7 @@ "public/js/lib/clusterize.min.js", "public/js/frappe/views/reports/report_factory.js", "public/js/frappe/views/reports/report_view.js", - "public/js/frappe/views/reports/reportview_footer.html", "public/js/frappe/views/reports/query_report.js", - "public/js/frappe/views/reports/grid_report.js", "public/js/frappe/views/reports/print_grid.html", "public/js/frappe/views/reports/print_tree.html", "public/js/frappe/ui/group_by/group_by.html", diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index b2c6493476..27b483b7aa 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -124,7 +124,7 @@ frappe.Application = Class.extend({ } } } - + this.link_preview = new frappe.ui.LinkPreview(); }, setup_frappe_vue() { @@ -269,7 +269,10 @@ frappe.Application = Class.extend({ refresh_notifications: function() { var me = this; if(frappe.session_alive && frappe.boot && frappe.boot.home_page !== 'setup-wizard') { - return frappe.call({ + if (this._refresh_notifications) { + this._refresh_notifications.abort(); + } + this._refresh_notifications = frappe.call({ type: 'GET', method: "frappe.desk.notifications.get_notifications", callback: function(r) { diff --git a/frappe/public/js/frappe/feedback.js b/frappe/public/js/frappe/feedback.js deleted file mode 100644 index 7a2e5d8156..0000000000 --- a/frappe/public/js/frappe/feedback.js +++ /dev/null @@ -1,102 +0,0 @@ -frappe.provide("frappe.utils") - -frappe.utils.Feedback = Class.extend({ - resend_feedback_request: function(doc) { - /* resend the feedback request email */ - var args = { - reference_name: doc.reference_name, - reference_doctype: doc.reference_doctype, - request: doc.feedback_request, - } - this.get_feedback_request_details(args, true) - }, - - manual_feedback_request: function(doc) { - var me = this; - - var args = { - reference_doctype: doc.doctype, - reference_name: doc.name - } - if(frappe.boot.feedback_triggers[doc.doctype]) { - var feedback_trigger = frappe.boot.feedback_triggers[doc.doctype] - $.extend(args, { trigger: feedback_trigger }) - me.get_feedback_request_details(args, false) - } else{ - me.make_feedback_request_dialog(args, false) - } - }, - - get_feedback_request_details: function(args, is_resend) { - var me = this; - - return frappe.call({ - method: "frappe.core.doctype.feedback_trigger.feedback_trigger.get_feedback_request_details", - 'args': args, - callback: function(r) { - if(r.message) { - me.make_feedback_request_dialog(r.message, is_resend) - } - } - }); - }, - - make_feedback_request_dialog: function(args, is_resend) { - var me = this; - var dialog = new frappe.ui.Dialog({ - title: __("{0} Feedback Request", [ is_resend? "Resend": "Send" ]), - fields: [ - { - "reqd": 1, - "label": __("Recipient"), - "fieldname": "recipients", - "fieldtype": "Data", - "options": "Email" - }, - { - "reqd": 1, - "label": __("Subject"), - "fieldname": "subject", - "fieldtype": "Data" - }, - { - "reqd": 1, - "label": __("Message"), - "fieldname": "message", - "fieldtype": "Text Editor" - } - ], - }); - - $.each(args, function(field, value){ - dialog.set_value(field, value); - }) - - dialog.set_primary_action(__("Send"), function() { - $.extend(args,{ details: dialog.get_values() }); - if(!args) - return; - - dialog.hide(); - me.send_feedback_request(args) - }); - - dialog.show(); - }, - - send_feedback_request: function(args) { - $.extend(args, { is_manual: true }) - return frappe.call({ - method: "frappe.core.doctype.feedback_trigger.feedback_trigger.send_feedback_request", - 'args': args, - freeze: true, - callback: function(r) { - if(r.message) { - frappe.msgprint(__("Feedback Request for {0} is sent to {1}", - [args.reference_name, args.details.recipients])); - } - } - }); - } -}) - diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 902948134b..22f3da73c6 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -41,6 +41,7 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ me.$link.toggle(false); }, 500); }); + this.$input.attr('data-target', this.df.options); this.input = this.$input.get(0); this.has_input = true; this.translate_values = true; @@ -173,13 +174,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 c26a4148e7..e616b9ef3e 100644 --- a/frappe/public/js/frappe/form/controls/text_editor.js +++ b/frappe/public/js/frappe/form/controls/text_editor.js @@ -55,6 +55,11 @@ Quill.register(AlignStyle, true); Quill.register(DirectionStyle, true); frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ + make_wrapper() { + this._super(); + this.$wrapper.find(".like-disabled-input").addClass('text-editor-print'); + }, + make_input() { this.has_input = true; this.make_quill_editor(); @@ -193,7 +198,78 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ let $ul = $('\ ', 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/router_history.js b/frappe/public/js/frappe/router_history.js index 93d4904c0b..fb3d09fe0b 100644 --- a/frappe/public/js/frappe/router_history.js +++ b/frappe/public/js/frappe/router_history.js @@ -1,6 +1,6 @@ frappe.provide('frappe.route'); frappe.route_history_queue = []; -const routes_to_skip = ['Form', 'social']; +const routes_to_skip = ['Form', 'social', 'setup-wizard']; const save_routes = frappe.utils.debounce(() => { const routes = frappe.route_history_queue; diff --git a/frappe/public/js/frappe/ui/group_by/group_by.html b/frappe/public/js/frappe/ui/group_by/group_by.html index 172d955807..dd8d3dd281 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.html +++ b/frappe/public/js/frappe/ui/group_by/group_by.html @@ -22,10 +22,6 @@
- - - - diff --git a/frappe/public/js/frappe/ui/group_by/group_by.js b/frappe/public/js/frappe/ui/group_by/group_by.js index 81b6a4914b..613b5aeb5b 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.js +++ b/frappe/public/js/frappe/ui/group_by/group_by.js @@ -2,7 +2,6 @@ frappe.provide('frappe.views'); frappe.ui.GroupBy = class { - constructor(report_view) { this.report_view = report_view; this.page = report_view.page; @@ -11,37 +10,38 @@ frappe.ui.GroupBy = class { } setup_group_by_area() { - this.make_group_by_button(); - this.report_view.setup_columns(); - let sql_aggregate_function = [{name:'count', label: 'Count'}, {name:'sum', label: 'Sum'}, {name:'avg', label:'Average'}]; + let sql_aggregate_function = [ + {name:'count', label: 'Count'}, + {name:'sum', label: 'Sum'}, + {name:'avg', label:'Average'} + ]; this.groupby_edit_area = $(frappe.render_template("group_by", { groupby_conditions: this.get_group_by_fields(), aggregate_function_conditions: sql_aggregate_function, })); - $(".aggregate-function").val("count"); + + this.groupby_select = this.groupby_edit_area.find('select.groupby'); + this.aggregate_function_select = this.groupby_edit_area.find('select.aggregate-function'); + this.aggregate_on_select = this.groupby_edit_area.find('select.aggregate-on'); + + // set default to count + this.aggregate_function_select.val("count"); this.page.wrapper.find(".frappe-list").append( this.groupby_edit_area); //Set aggregate on options as numeric fields if function is sum or average - $('.aggregate-function').on('change', () => { - this.report_view.meta.fields.forEach((field) => { - let fn = $('.aggregate-function option:selected').val(); - if(fn === 'sum' || fn === 'avg') { - if(frappe.model.is_numeric_field(field.fieldtype)) { - $('.aggregate-on') - .append($('
@@ -62,9 +102,15 @@ frappe.ui.GroupBy = class { } apply_group_by() { - this.group_by = this.page.wrapper.find('.groupby option:selected').val(); - this.aggregate_function = this.page.wrapper.find('.aggregate-function option:selected').val(); - this.aggregate_on = this.page.wrapper.find('.aggregate-on option:selected').val(); + this.group_by = this.groupby_select.val(); + this.aggregate_function = this.aggregate_function_select.val(); + + if (this.aggregate_function === 'count') { + this.aggregate_on = 'name'; + } else { + this.aggregate_on = this.aggregate_on_select.val(); + } + //All necessary fields must be set before applying group by if(!this.group_by) { @@ -78,60 +124,106 @@ frappe.ui.GroupBy = class { return; } - //If chosen group by field is not in fields, push it to fields - if(!this.report_view.columns.includes(this.group_by)) { - this.report_view.fields.push(this.group_by); - } + return true; - //If function is count add a new field for count - if(this.aggregate_function === 'count') { - let group_by_query = 'count(' + this.report_view.field_type + '.'+ this.group_by+') as ' + this.group_by + '_Count'; - this.report_view.fields.push([group_by_query, this.doctype]); - this.order_by = this.group_by + '_Count desc'; - } else { - //If chosen 'aggregate on' field is not in fields, push it to fields - if(!this.report_view.columns.includes(this.aggregate_on)) { - this.report_view.fields.push(this.aggregate_on); - } - this.order_by = this.aggregate_on + ' desc'; - } + } - $('.set-groupby-and-run').hide(); - - //Get current columns so that they can be restored when group by is removed - this.current_cols = this.report_view.columns; - - let remove_columns = this.report_view.columns.filter(column => ![this.aggregate_on, this.group_by].includes(column.field)); - remove_columns.forEach((col) => { - this.report_view.remove_column_from_datatable(col); - }); - - if(this.report_view.columns[0]) { - this.removed_previous_width = this.report_view.columns[0].docfield.width; - this.report_view.columns[0].docfield.width = 400; + apply_group_by_and_refresh() { + if (this.apply_group_by()) { + this.report_view.refresh(); } } + set_args(args) { + if (this.aggregate_function && this.group_by) { + let aggregate_column; + if(this.aggregate_function === 'count') { + aggregate_column = 'count(1)'; + } else { + aggregate_column = `${this.aggregate_function}(${this.aggregate_on})`; + } + + this.report_view.group_by = this.group_by; + this.report_view.sort_by = '_aggregate_column'; + this.report_view.sort_order = 'desc'; + + // save orignial fields + if(!this.report_view.fields.map(f => f[0]).includes('_aggregate_column')) { + this.original_fields = this.report_view.fields.map(f => f); + } + + this.report_view.fields = [ + [this.group_by, this.doctype] + ]; + + // rebuild fields for group by + args.fields = this.report_view.get_fields(); + + // add aggregate column in both query args and report view + this.report_view.fields.push(['_aggregate_column', this.doctype]); + args.fields.push(aggregate_column + ' as _aggregate_column'); + + // setup columns in datatable + this.report_view.setup_columns(); + + Object.assign(args, { + with_comment_count: false, + group_by: this.report_view.group_by || null, + order_by: '_aggregate_column desc' + }); + } + + } + + + get_group_by_docfield() { + // called from build_column + let docfield; + if (this.aggregate_function === 'count') { + docfield = { + fieldtype: 'Int', + label: __('Count'), + parent: this.doctype, + width: 120 + }; + } else { + // get properties of "aggregate_on", for example Net Total + docfield = Object.assign({}, frappe.meta.docfield_map[this.doctype][this.aggregate_on]); + if (this.aggregate_function === 'sum') { + docfield.label = __('Sum of {0}', [docfield.label]); + } else { + docfield.label = __('Average of {0}', [docfield.label]); + } + } + docfield.fieldname = '_aggregate_column'; + + return docfield; + } + remove_group_by() { - this.report_view.columns[0].docfield.width = this.removed_previous_width; - this.order_by = ''; this.groupby_edit_area.hide(); - $('.set-groupby-and-run').show(); + + this.order_by = ''; this.group_by = null; this.aggregate_function = null; this.aggregate_on = null; $(".groupby").val(""); $(".aggregate-function").val("count"); - $(".aggregate-on").val("").hide(); - //Add the removed columns - this.current_cols.forEach((col, i) => { - this.report_view.add_column_to_datatable(col.field, this.doctype, i); - }); - this.report_view.fields.pop(); + $(".aggregate-on").empty().val("").hide(); + + // restore original fields + if (this.original_fields) { + this.report_view.fields = this.original_fields; + } else { + this.report_view.set_default_fields(); + } this.report_view.setup_columns(); + this.original_fields = null; + this.report_view.refresh(); } get_group_by_fields() { return this.report_view.meta.fields.filter((f)=> ["Select", "Link"].includes(f.fieldtype)); } + }; diff --git a/frappe/public/js/frappe/ui/link_preview.js b/frappe/public/js/frappe/ui/link_preview.js new file mode 100644 index 0000000000..962adcf344 --- /dev/null +++ b/frappe/public/js/frappe/ui/link_preview.js @@ -0,0 +1,256 @@ +frappe.ui.LinkPreview = class { + + constructor() { + this.$links = []; + this.LINK_CLASSES = 'a[data-doctype], input[data-fieldtype="Link"], .popover'; + this.popover_timeout = null; + this.setup_events(); + } + + setup_events() { + $(document.body).on('mouseover', this.LINK_CLASSES, (e) => { + this.link_hovered = true; + this.element = $(e.currentTarget); + this.is_link = this.element.get(0).tagName.toLowerCase() === 'a'; + + if(!this.element.parents().find('.popover').length) { + this.identify_doc(); + this.popover = this.element.data("bs.popover"); + if(this.name && this.doctype) { + this.setup_popover_control(e); + } + } + }); + + } + + identify_doc() { + 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(); + } + } + + setup_popover_control(e) { + //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(() => { + this.create_popover(e, preview_fields); + }, 100); + } + }); + } else { + this.popover_timeout = setTimeout(() => { + 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) { + clearTimeout(this.popover_timeout); + } + + this.popover_timeout = setTimeout(() => { + if(this.popover) { + let new_content = this.get_popover_html(preview_data); + this.popover.options.content = new_content; + } else { + this.init_preview_popover(preview_data); + } + 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, () => { + // 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); + } + if(!this.link_hovered) this.clear_all_popovers(); + }); + + $(window).on('hashchange', () => { + this.clear_all_popovers(); + }); + } + + clear_all_popovers() { + this.$links.forEach($el => $el.popover('hide')); + } + + get_preview_fields() { + return new Promise((resolve) => { + let dt = this.doctype; + let fields = []; + frappe.model.with_doctype(dt, () => { + 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) { + fields.push({'name':field.fieldname,'type':field.fieldtype}); + } + }); + + // no preview fields defined, build list from mandatory fields + if(!fields.length) { + meta_fields.filter((field) => { + if(field.reqd) { + fields.push({'name':field.fieldname,'type':field.fieldtype}); + } + }); + } + resolve(fields); + }); + }); + } + + get_preview_fields_value(field_list) { + return frappe.xcall('frappe.desk.link_preview.get_preview_data', { + 'doctype': this.doctype, + 'docname': this.name, + 'fields': field_list, + }); + } + + init_preview_popover(preview_data) { + let popover_content = this.get_popover_html(preview_data); + this.element.popover({ + container: 'body', + html: true, + content: popover_content, + trigger: 'manual', + placement: 'top auto', + animation: false, + }); + + if(!this.is_link) { + this.element.data('bs.popover').tip().addClass('control-field-popover'); + } + + this.$links.push(this.element); + + } + + get_popover_html(preview_data) { + if(!this.href) { + this.href = window.location.href; + } + + if(this.href && this.href.includes(' ')) { + this.href = this.href.replace(new RegExp(' ', 'g'), '%20'); + } + + let image_html = ''; + let id_html = ''; + let content_html = ''; + let meta = frappe.get_meta(this.doctype); + let title = preview_data.title; + + if(preview_data[meta.image_field]) { + let image_url = encodeURI(preview_data[meta.image_field]); + image_html += ` +
+ +
`; + } + + + 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!=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}
`; + + let popover_content = + `
${image_html} +
+
+ ${title} + ${this.doctype} ${id_html} +
+
+
+
+
+ ${content_html} +
`; + + return popover_content; + } + + truncate_value(value) { + if (value.length > 280) { + value = value.slice(0,280) + '...'; + } + return value; + } + +}; diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index 395e11dc93..65cbc48af8 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -120,6 +120,11 @@ frappe.msgprint = function(msg, title) { } }); + // setup and bind an action to the primary button + if (data.primary_action) { + frappe.msg_dialog.set_primary_action(__(data.primary_action.label || "Done"), data.primary_action.action); + } + // class "msgprint" is used in tests frappe.msg_dialog.msg_area = $('
') .appendTo(frappe.msg_dialog.body); @@ -139,6 +144,12 @@ frappe.msgprint = function(msg, title) { msg = frappe.utils.replace_newlines(data.message); } + if (data.primary_action) { + frappe.msg_dialog.get_primary_btn().show(); + } else { + frappe.msg_dialog.get_primary_btn().hide(); + } + var msg_exists = false; if(data.clear) { frappe.msg_dialog.msg_area.empty(); diff --git a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js index c2b70c3256..bee5f3035d 100644 --- a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js +++ b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js @@ -68,6 +68,8 @@ frappe.search.AwesomeBar = Class.extend({ me.options = me.options.concat(me.build_options(txt)); me.options = me.options.concat(me.global_results); } else { + me.options = me.options.concat( + me.deduplicate(frappe.search.utils.get_recent_pages(txt || ""))); me.options = me.options.concat(frappe.search.utils.get_frequent_links()); } me.add_help(); diff --git a/frappe/public/js/frappe/ui/toolbar/search_utils.js b/frappe/public/js/frappe/ui/toolbar/search_utils.js index fcbaae4aef..f8b4c3a34b 100644 --- a/frappe/public/js/frappe/ui/toolbar/search_utils.js +++ b/frappe/public/js/frappe/ui/toolbar/search_utils.js @@ -6,6 +6,7 @@ frappe.search.utils = { }, get_recent_pages: function(keywords) { + if (keywords === null) keywords = ''; var me = this, values = [], options = []; function find(list, keywords, process) { @@ -44,7 +45,7 @@ frappe.search.utils = { values.push([route[1], route]); } } else if(route[0]) { - values.push([frappe.route_titles[route[0]] || route[0], route]); + values.push([frappe.route_titles[route.join('/')] || route[0], route]); } }); diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 7d90d80d5f..08174519a8 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -549,6 +549,7 @@ frappe.views.CommunicationComposer = Class.extend({ print_format: print_format, sender: form_values.sender, sender_full_name: form_values.sender?frappe.user.full_name():undefined, + email_template: form_values.email_template, attachments: selected_attachments, _lang : me.lang_code, read_receipt:form_values.send_read_receipt, diff --git a/frappe/public/js/frappe/views/components/Desktop.vue b/frappe/public/js/frappe/views/components/Desktop.vue index f8c1128b74..dd2f68911a 100644 --- a/frappe/public/js/frappe/views/components/Desktop.vue +++ b/frappe/public/js/frappe/views/components/Desktop.vue @@ -1,16 +1,15 @@
%(set_user_permissions)s