diff --git a/.eslintrc b/.eslintrc index cdd6be1c0b..3711a7dbe6 100644 --- a/.eslintrc +++ b/.eslintrc @@ -140,6 +140,7 @@ "expect": true, "context": true, "before": true, - "beforeEach": true + "beforeEach": true, + "qz": true } } diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 30ca6761a2..0000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,29 +0,0 @@ -include MANIFEST.in -include requirements.txt -include *.json -include *.md -include *.py -recursive-include frappe *.css -recursive-include frappe *.dat -recursive-include frappe *.eot -recursive-include frappe *.gif -recursive-include frappe *.html -recursive-include frappe *.jpg -recursive-include frappe *.js -recursive-include frappe *.json -recursive-include frappe *.md -recursive-include frappe *.otf -recursive-include frappe *.png -recursive-include frappe *.py -recursive-include frappe *.sql -recursive-include frappe *.svg -recursive-include frappe *.swf -recursive-include frappe *.ttf -recursive-include frappe *.woff -recursive-include frappe *.xml -recursive-include frappe *.csv -recursive-include frappe *.ico -recursive-include frappe *.less -recursive-include frappe *.txt -recursive-include frappe/public * -recursive-exclude * *.pyc diff --git a/cypress/integration/file_uploader.js b/cypress/integration/file_uploader.js new file mode 100644 index 0000000000..b58e0d49a8 --- /dev/null +++ b/cypress/integration/file_uploader.js @@ -0,0 +1,61 @@ +context('FileUploader', () => { + before(() => { + cy.login('Administrator', 'qwe'); + cy.visit('/desk'); + }); + + function open_upload_dialog() { + cy.window().its('frappe').then(frappe => { + new frappe.ui.FileUploader(); + }); + } + + it('upload dialog api works', () => { + open_upload_dialog(); + cy.get_open_dialog().should('contain', 'Drag and drop files'); + cy.hide_dialog(); + }); + + it('should accept dropped files', () => { + open_upload_dialog(); + + cy.fixture('example.json').then(fileContent => { + cy.get_open_dialog().find('.file-upload-area').upload( + { fileContent, fileName: 'example.json', mimeType: 'application/json' }, + { subjectType: 'drag-n-drop' }, + ); + cy.get_open_dialog().find('.file-info').should('contain', 'example.json'); + cy.server(); + cy.route('POST', '/api/method/upload_file').as('upload_file'); + cy.get_open_dialog().find('.btn-primary').click(); + cy.wait('@upload_file').its('status').should('be', 200); + cy.get('.modal:visible').should('not.exist'); + }); + }); + + it('should accept uploaded files', () => { + open_upload_dialog(); + + cy.get_open_dialog().find('a:contains("uploaded file")').click(); + cy.get_open_dialog().find('.tree-label:contains("example.json")').first().click(); + cy.server(); + cy.route('POST', '/api/method/upload_file').as('upload_file'); + cy.get_open_dialog().find('.btn-primary').click(); + cy.wait('@upload_file').its('response.body.message') + .should('have.property', 'file_url', '/private/files/example.json'); + cy.get('.modal:visible').should('not.exist'); + }); + + it('should accept web links', () => { + open_upload_dialog(); + + cy.get_open_dialog().find('a:contains("web link")').click(); + cy.get_open_dialog().find('.file-web-link input').type('https://github.com'); + cy.server(); + cy.route('POST', '/api/method/upload_file').as('upload_file'); + cy.get_open_dialog().find('.btn-primary').click(); + cy.wait('@upload_file').its('response.body.message') + .should('have.property', 'file_url', 'https://github.com'); + cy.get('.modal:visible').should('not.exist'); + }); +}); diff --git a/cypress/integration/table_multiselect.js b/cypress/integration/table_multiselect.js index e53584640b..cca9a6eb46 100644 --- a/cypress/integration/table_multiselect.js +++ b/cypress/integration/table_multiselect.js @@ -9,7 +9,7 @@ context('Table MultiSelect', () => { cy.new_form('Assignment Rule'); cy.fill_field('__newname', name); cy.fill_field('document_type', 'ToDo'); - cy.fill_field('assign_condition', 'status=="Open"'); + cy.fill_field('assign_condition', 'status=="Open"', 'Code'); cy.get('input[data-fieldname="users"]').focus().as('input'); cy.get('input[data-fieldname="users"] + ul').should('be.visible'); cy.get('@input').type('test{enter}', { delay: 100 }); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 8c2156e9f4..23bbc8af24 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -1,3 +1,4 @@ +import 'cypress-file-upload'; // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite @@ -38,7 +39,10 @@ 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`; } cy.get(selector).as('input'); @@ -46,7 +50,7 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype='Data') => { if (fieldtype === 'Select') { return cy.get('@input').select(value); } else { - return cy.get('@input').type(value); + return cy.get('@input').type(value, {waitForAnimations: false}); } }); @@ -75,3 +79,12 @@ Cypress.Commands.add('dialog', (title, fields) => { return d; }); }); + +Cypress.Commands.add('get_open_dialog', () => { + return cy.get('.modal:visible').last(); +}); + +Cypress.Commands.add('hide_dialog', () => { + cy.get_open_dialog().find('.btn-modal-close').click(); + cy.get('.modal:visible').should('not.exist'); +}); diff --git a/frappe/__init__.py b/frappe/__init__.py index c80f60ac3d..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(local.conf.slave_host, local.conf.slave_db_name, - local.conf.slave_db_password) + if local.conf.different_credentials_for_replica: + user = local.conf.replica_db_name + password = local.conf.replica_db_password + + local.replica_db = 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 @@ -1262,7 +1267,7 @@ def get_all(doctype, *args, **kwargs): :param fields: List of fields or `*`. Default is: `["name"]`. :param filters: List of filters (see example). :param order_by: Order By e.g. `modified desc`. - :param limit_page_start: Start results at record #. Default 0. + :param limit_start: Start results at record #. Default 0. :param limit_page_length: No of records in the page. Default 20. Example usage: @@ -1297,7 +1302,7 @@ def get_value(*args, **kwargs): def as_json(obj, indent=1): from frappe.utils.response import json_handler - return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler) + return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler, separators=(',', ': ')) def are_emails_muted(): from frappe.utils import cint @@ -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/automation/doctype/assignment_rule/assignment_rule.json b/frappe/automation/doctype/assignment_rule/assignment_rule.json index 1996281acf..984ca9928a 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.json +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.json @@ -22,6 +22,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "document_type", "fieldtype": "Link", "hidden": 0, @@ -56,6 +57,7 @@ "collapsible": 0, "columns": 0, "description": "Higher priority rule will be applied first", + "fetch_if_empty": 0, "fieldname": "priority", "fieldtype": "Int", "hidden": 0, @@ -88,6 +90,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "disabled", "fieldtype": "Check", "hidden": 0, @@ -120,6 +123,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_4", "fieldtype": "Column Break", "hidden": 0, @@ -153,6 +157,7 @@ "columns": 0, "default": "Automatic Assignment", "description": "Example: {{ subject }}", + "fetch_if_empty": 0, "fieldname": "description", "fieldtype": "Small Text", "hidden": 0, @@ -185,6 +190,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "assignment_rules_section", "fieldtype": "Section Break", "hidden": 0, @@ -218,8 +224,9 @@ "collapsible": 0, "columns": 0, "description": "Simple Python Expression, Example: status == 'Open' and type == 'Bug'", + "fetch_if_empty": 0, "fieldname": "assign_condition", - "fieldtype": "Small Text", + "fieldtype": "Code", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -250,6 +257,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_6", "fieldtype": "Column Break", "hidden": 0, @@ -282,8 +290,9 @@ "collapsible": 0, "columns": 0, "description": "Simple Python Expression, Example: Status in (\"Closed\", \"Cancelled\")", + "fetch_if_empty": 0, "fieldname": "unassign_condition", - "fieldtype": "Small Text", + "fieldtype": "Code", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -314,6 +323,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "assign_to_users_section", "fieldtype": "Section Break", "hidden": 0, @@ -346,6 +356,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "rule", "fieldtype": "Select", "hidden": 0, @@ -379,6 +390,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "users", "fieldtype": "Table MultiSelect", "hidden": 0, @@ -412,6 +424,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "last_user", "fieldtype": "Link", "hidden": 0, @@ -440,16 +453,14 @@ } ], "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": "2019-03-08 15:13:01.379471", + "modified": "2019-04-16 17:46:04.890120", "modified_by": "Administrator", "module": "Automation", "name": "Assignment Rule", @@ -478,7 +489,6 @@ ], "quick_entry": 0, "read_only": 0, - "read_only_onload": 0, "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py index 6c6ad3b68a..d729e87437 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.py @@ -7,13 +7,14 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.desk.form import assign_to +import frappe.cache_manager class AssignmentRule(Document): def on_update(self): # pylint: disable=no-self-use - frappe.cache().delete_value('assignment_rule') + frappe.cache_manager.clear_doctype_map('Assignment Rule', self.name) def after_rename(self): # pylint: disable=no-self-use - frappe.cache().delete_value('assignment_rule') + frappe.cache_manager.clear_doctype_map('Assignment Rule', self.name) def apply_unassign(self, doc, assignments): if (self.unassign_condition and @@ -113,14 +114,14 @@ def apply(doc, method): if frappe.flags.in_patch or frappe.flags.in_install: return - assignment_rules = frappe.cache().get_value('assignment_rule', get_assignment_rules) + assignment_rules = frappe.cache_manager.get_doctype_map('Assignment Rule', doc.doctype, dict( + document_type = doc.doctype, disabled = 0), order_by = 'priority desc') + assignment_rule_docs = [] - # build rules - if doc.doctype in assignment_rules: - # multiple auto assigns - for d in frappe.db.get_all('Assignment Rule', dict(document_type=doc.doctype, disabled = 0), order_by = 'priority desc'): - assignment_rule_docs.append(frappe.get_doc('Assignment Rule', d.name)) + # multiple auto assigns + for d in assignment_rules: + assignment_rule_docs.append(frappe.get_doc('Assignment Rule', d.name)) if not assignment_rule_docs: return diff --git a/frappe/core/doctype/feedback_request/__init__.py b/frappe/automation/doctype/milestone/__init__.py similarity index 100% rename from frappe/core/doctype/feedback_request/__init__.py rename to frappe/automation/doctype/milestone/__init__.py diff --git a/frappe/automation/doctype/milestone/milestone.js b/frappe/automation/doctype/milestone/milestone.js new file mode 100644 index 0000000000..9a1cf577ff --- /dev/null +++ b/frappe/automation/doctype/milestone/milestone.js @@ -0,0 +1,8 @@ +// Copyright (c) 2019, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Milestone', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/automation/doctype/milestone/milestone.json b/frappe/automation/doctype/milestone/milestone.json new file mode 100644 index 0000000000..8360ce7bf4 --- /dev/null +++ b/frappe/automation/doctype/milestone/milestone.json @@ -0,0 +1,230 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "", + "beta": 0, + "creation": "2019-04-17 09:39:15.647817", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "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": "reference_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": "Document Type", + "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": 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, + "fetch_if_empty": 0, + "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": "Document", + "length": 0, + "no_copy": 0, + "options": "", + "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 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "track_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": "Track Field", + "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": 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": "value", + "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": "Value", + "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": 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": "milestone_tracker", + "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": "Milestone Tracker", + "length": 0, + "no_copy": 0, + "options": "Milestone Tracker", + "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_toolbar": 0, + "idx": 0, + "in_create": 1, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2019-04-17 16:01:21.430344", + "modified_by": "Administrator", + "module": "Automation", + "name": "Milestone", + "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 + } + ], + "quick_entry": 1, + "read_only": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "ASC", + "title_field": "reference_type", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/frappe/automation/doctype/milestone/milestone.py b/frappe/automation/doctype/milestone/milestone.py new file mode 100644 index 0000000000..64c073a378 --- /dev/null +++ b/frappe/automation/doctype/milestone/milestone.py @@ -0,0 +1,14 @@ +# -*- 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 Milestone(Document): + pass + +def on_doctype_update(): + frappe.db.add_index("Milestone", ["reference_type", "reference_name"]) diff --git a/frappe/automation/doctype/milestone/test_milestone.py b/frappe/automation/doctype/milestone/test_milestone.py new file mode 100644 index 0000000000..75602d48db --- /dev/null +++ b/frappe/automation/doctype/milestone/test_milestone.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +#import frappe +import unittest + +class TestMilestone(unittest.TestCase): + pass diff --git a/frappe/core/doctype/feedback_trigger/__init__.py b/frappe/automation/doctype/milestone_tracker/__init__.py similarity index 100% rename from frappe/core/doctype/feedback_trigger/__init__.py rename to frappe/automation/doctype/milestone_tracker/__init__.py diff --git a/frappe/automation/doctype/milestone_tracker/milestone_tracker.js b/frappe/automation/doctype/milestone_tracker/milestone_tracker.js new file mode 100644 index 0000000000..2a74bfb070 --- /dev/null +++ b/frappe/automation/doctype/milestone_tracker/milestone_tracker.js @@ -0,0 +1,33 @@ +// Copyright (c) 2019, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Milestone Tracker', { + refresh: function(frm) { + frm.trigger('update_options'); + }, + document_type: function(frm) { + frm.trigger('update_options'); + }, + update_options: function(frm) { + // update select options for `track_field` + let doctype = frm.doc.document_type; + let track_fields = []; + + if (doctype) { + frappe.model.with_doctype(doctype, () => { + // get all date and datetime fields + frappe.get_meta(doctype).fields.map(df => { + if (['Link', 'Select'].includes(df.fieldtype)) { + track_fields.push({label: df.label, value: df.fieldname}); + } + }); + frm.set_df_property('track_field', 'options', track_fields); + }); + } else { + // update select options + frm.set_df_property('track_field', 'options', []); + } + + }, + +}); diff --git a/frappe/automation/doctype/milestone_tracker/milestone_tracker.json b/frappe/automation/doctype/milestone_tracker/milestone_tracker.json new file mode 100644 index 0000000000..8e22e3e199 --- /dev/null +++ b/frappe/automation/doctype/milestone_tracker/milestone_tracker.json @@ -0,0 +1,162 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "format:{document_type}-{track_field}", + "beta": 0, + "creation": "2019-04-17 09:36:41.774774", + "custom": 0, + "description": "Track milestones for any document", + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "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": "document_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": "Document Type to Track", + "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, + "translatable": 0, + "unique": 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": "track_field", + "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": 0, + "label": "Field to Track", + "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": "disabled", + "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": "Disabled", + "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_toolbar": 0, + "idx": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2019-04-22 16:03:32.848937", + "modified_by": "Administrator", + "module": "Automation", + "name": "Milestone Tracker", + "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 + } + ], + "quick_entry": 0, + "read_only": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "ASC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/frappe/automation/doctype/milestone_tracker/milestone_tracker.py b/frappe/automation/doctype/milestone_tracker/milestone_tracker.py new file mode 100644 index 0000000000..baa1bcc075 --- /dev/null +++ b/frappe/automation/doctype/milestone_tracker/milestone_tracker.py @@ -0,0 +1,35 @@ +# -*- 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 +import frappe.cache_manager + +class MilestoneTracker(Document): + def on_update(self): + frappe.cache_manager.clear_doctype_map('Milestone Tracker', self.name) + + def on_trash(self): + frappe.cache_manager.clear_doctype_map('Milestone Tracker', self.name) + + def apply(self, doc): + before_save = doc.get_doc_before_save() + from_value = before_save and before_save.get(self.track_field) or None + if from_value != doc.get(self.track_field): + frappe.get_doc(dict( + doctype = 'Milestone', + reference_type = doc.doctype, + reference_name = doc.name, + track_field = self.track_field, + from_value = from_value, + value = doc.get(self.track_field), + milestone_tracker = self.name, + )).insert(ignore_permissions=True) + +def evaluate_milestone(doc, event): + for d in frappe.cache_manager.get_doctype_map('Milestone Tracker', doc.doctype, + dict(document_type = doc.doctype, disabled=0)): + frappe.get_doc('Milestone Tracker', d.name).apply(doc) diff --git a/frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py b/frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py new file mode 100644 index 0000000000..c9bb6b7d5f --- /dev/null +++ b/frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestMilestoneTracker(unittest.TestCase): + def test_milestone(self): + frappe.db.sql('delete from `tabMilestone Tracker`') + frappe.get_doc(dict( + doctype = 'Milestone Tracker', + document_type = 'ToDo', + track_field = 'status' + )).insert() + + todo = frappe.get_doc(dict( + doctype = 'ToDo', + description = 'test milestone' + )).insert() + + milestones = frappe.get_all('Milestone', + fields = ['track_field', 'value', 'milestone_tracker'], + filters = dict(reference_type = todo.doctype, reference_name=todo.name)) + + self.assertEqual(len(milestones), 1) + self.assertEqual(milestones[0].track_field, 'status') + self.assertEqual(milestones[0].value, 'Open') + + todo.status = 'Closed' + todo.save() + + milestones = frappe.get_all('Milestone', + fields = ['track_field', 'value', 'milestone_tracker'], + filters = dict(reference_type = todo.doctype, reference_name=todo.name), + order_by = 'modified desc') + + self.assertEqual(len(milestones), 2) + self.assertEqual(milestones[0].track_field, 'status') + self.assertEqual(milestones[0].value, 'Closed') + diff --git a/frappe/boot.py b/frappe/boot.py index f166af91f7..4acb7ee3c1 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -16,8 +16,9 @@ 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 def get_bootinfo(): """build and return boot info""" @@ -75,11 +76,12 @@ 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)) bootinfo.energy_points_enabled = is_energy_point_enabled() + bootinfo.points = get_energy_points(frappe.session.user) + bootinfo.frequently_visited_links = frequently_visited_links() return bootinfo diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index a913f5fba9..6c9a59d375 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -3,31 +3,40 @@ from __future__ import unicode_literals -import frappe +import frappe, json import frappe.defaults from frappe.desk.notifications import (delete_notification_count_for, clear_notifications) common_default_keys = ["__default", "__global"] -def clear_user_cache(user=None): - cache = frappe.cache() +global_cache_keys = ("app_hooks", "installed_apps", + "app_modules", "module_app", "notification_config", 'system_settings', + 'scheduler_events', 'time_zone', 'webhooks', 'active_domains', + 'active_modules', 'assignment_rule') - groups = ("bootinfo", "user_recent", "roles", "user_doc", "lang", +user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang", "defaults", "user_permissions", "home_page", "linked_with", "desktop_icons", 'portal_menu_items') +doctype_cache_keys = ("meta", "form_meta", "table_columns", "last_modified", + "linked_doctypes", 'notifications', 'workflow' ,'energy_point_rule_map') + + +def clear_user_cache(user=None): + cache = frappe.cache() + # this will automatically reload the global cache # so it is important to clear this first clear_notifications(user) if user: - for name in groups: + for name in user_cache_keys: cache.hdel(name, user) cache.delete_keys("user:" + user) clear_defaults_cache(user) else: - for name in groups: + for name in user_cache_keys: cache.delete_key(name) clear_defaults_cache() clear_global_cache() @@ -37,10 +46,7 @@ def clear_global_cache(): clear_doctype_cache() clear_website_cache() - frappe.cache().delete_value(["app_hooks", "installed_apps", - "app_modules", "module_app", "notification_config", 'system_settings', - 'scheduler_events', 'time_zone', 'webhooks', 'active_domains', - 'active_modules', 'assignment_rule']) + frappe.cache().delete_value(global_cache_keys) frappe.setup_module_map() def clear_defaults_cache(user=None): @@ -63,11 +69,8 @@ def clear_doctype_cache(doctype=None): for key in ('is_table', 'doctype_modules'): cache.delete_value(key) - groups = ["meta", "form_meta", "table_columns", "last_modified", - "linked_doctypes", 'notifications', 'workflow'] - def clear_single(dt): - for name in groups: + for name in doctype_cache_keys: cache.hdel(name, dt) if doctype: @@ -84,9 +87,31 @@ def clear_doctype_cache(doctype=None): else: # clear all - for name in groups: + for name in doctype_cache_keys: cache.delete_value(name) # Clear all document's cache. To clear documents of a specific DocType document_cache should be restructured clear_document_cache() +def get_doctype_map(doctype, name, filters, order_by=None): + cache = frappe.cache() + cache_key = frappe.scrub(doctype) + '_map' + doctype_map = cache.hget(cache_key, name) + + if doctype_map: + # cached, return + items = json.loads(doctype_map) + else: + # non cached, build cache + try: + items = frappe.get_all(doctype, filters=filters, order_by = order_by) + cache.hset(cache_key, doctype, json.dumps(items)) + except frappe.db.TableMissingError: + # executed from inside patch, ignore + items = [] + + return items + +def clear_doctype_map(doctype, name): + cache_key = frappe.scrub(doctype) + '_map' + frappe.cache().hdel(cache_key, name) \ No newline at end of file diff --git a/frappe/chat/doctype/chat_profile/chat_profile.py b/frappe/chat/doctype/chat_profile/chat_profile.py index e922cb28e2..698d992d35 100644 --- a/frappe/chat/doctype/chat_profile/chat_profile.py +++ b/frappe/chat/doctype/chat_profile/chat_profile.py @@ -17,10 +17,6 @@ from frappe.chat.util import ( session = frappe.session class ChatProfile(Document): - def before_save(self): - if not self.is_new(): - self.get_doc_before_save() - def on_update(self): if not self.is_new(): b, a = self.get_doc_before_save(), self diff --git a/frappe/chat/doctype/chat_room/chat_room.py b/frappe/chat/doctype/chat_room/chat_room.py index db138ed08b..b5873fa2d0 100644 --- a/frappe/chat/doctype/chat_room/chat_room.py +++ b/frappe/chat/doctype/chat_room/chat_room.py @@ -69,10 +69,6 @@ class ChatRoom(Document): if self.type == "Group" and not self.room_name: frappe.throw(_('Group name cannot be empty.')) - def before_save(self): - if not self.is_new(): - self.get_doc_before_save() - def on_update(self): if not self.is_new(): before = self.get_doc_before_save() 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/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/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..68a0adb634 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": "Dynamic 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-20 14:14:01.514493", + "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..0fb2efc7ec 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,66 @@ 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"]) 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 +309,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..a783157dc0 100644 --- a/frappe/core/doctype/communication/test_communication.py +++ b/frappe/core/doctype/communication/test_communication.py @@ -44,28 +44,126 @@ 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): + 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 + + 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/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/boilerplate/test_controller._py b/frappe/core/doctype/doctype/boilerplate/test_controller._py index 44e030bd0e..8ed08ae15a 100644 --- a/frappe/core/doctype/doctype/boilerplate/test_controller._py +++ b/frappe/core/doctype/doctype/boilerplate/test_controller._py @@ -3,7 +3,7 @@ # See license.txt from __future__ import unicode_literals -import frappe +# import frappe import unittest class Test{classname}(unittest.TestCase): 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 484f12811c..7efdba3237 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -17,7 +17,10 @@ from frappe.desk.notifications import delete_notification_count_for from frappe.modules import make_boilerplate, get_doc_path from frappe.database.schema import validate_column_name, validate_column_length from frappe.model.docfield import supports_translation +from frappe.modules.import_file import get_file_path +from six import iteritems import frappe.website.render +import json class InvalidFieldNameError(frappe.ValidationError): pass @@ -252,7 +255,8 @@ class DocType(Document): self.update_fields_to_fetch() from frappe import conf - if not self.custom and not (frappe.flags.in_import or frappe.flags.in_test) and conf.get('developer_mode'): + allow_doctype_export = frappe.flags.allow_doctype_export or (not frappe.flags.in_test and conf.get('developer_mode')) + if not self.custom and not frappe.flags.in_import and allow_doctype_export: self.export_doc() self.make_controller_template() @@ -343,7 +347,8 @@ class DocType(Document): if merge: frappe.throw(_("DocType can not be merged")) - if not frappe.flags.in_test and not frappe.flags.in_patch: + # Do not rename and move files and folders for custom doctype + if not self.custom and not frappe.flags.in_test and not frappe.flags.in_patch: self.rename_files_and_folders(old, new) def after_rename(self, old, new, merge=False): @@ -403,6 +408,72 @@ class DocType(Document): if naming_series[0].default: make_property_setter(self.name, "naming_series", "default", naming_series[0].default, "Text", validate_fields_for_doctype=False) + def before_export(self, docdict): + # remove null and empty fields + def remove_null_fields(o): + to_remove = [] + for attr, value in iteritems(o): + if isinstance(value, list): + for v in value: + remove_null_fields(v) + elif not value: + to_remove.append(attr) + + for attr in to_remove: + del o[attr] + + remove_null_fields(docdict) + + # retain order of 'fields' table and change order in 'field_order' + docdict["field_order"] = [f.fieldname for f in self.fields] + + path = get_file_path(self.module, "DocType", self.name) + if os.path.exists(path): + try: + with open(path, 'r') as txtfile: + olddoc = json.loads(txtfile.read()) + + old_field_names = [f['fieldname'] for f in olddoc.get("fields", [])] + if old_field_names: + new_field_dicts = [] + remaining_field_names = [f.fieldname for f in self.fields] + + for fieldname in old_field_names: + field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict['fields'])) + if field_dict: + new_field_dicts.append(field_dict[0]) + remaining_field_names.remove(fieldname) + + for fieldname in remaining_field_names: + field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict['fields'])) + new_field_dicts.append(field_dict[0]) + + docdict['fields'] = new_field_dicts + except ValueError: + pass + + @staticmethod + def prepare_for_import(docdict): + # set order of fields from field_order + if docdict.get("field_order"): + new_field_dicts = [] + remaining_field_names = [f['fieldname'] for f in docdict.get('fields', [])] + + for fieldname in docdict.get('field_order'): + field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict.get('fields', []))) + if field_dict: + new_field_dicts.append(field_dict[0]) + remaining_field_names.remove(fieldname) + + for fieldname in remaining_field_names: + field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict.get('fields', []))) + new_field_dicts.append(field_dict[0]) + + docdict['fields'] = new_field_dicts + + if "field_order" in docdict: + del docdict["field_order"] + def export_doc(self): """Export to standard folder `[module]/doctype/[name]/[name].json`.""" from frappe.modules.export_file import export_to_files @@ -545,7 +616,9 @@ def validate_fields(meta): frappe.throw(_("Options 'Dynamic Link' type of field must point to another Link Field with options as 'DocType'")) def check_illegal_default(d): - if d.fieldtype == "Check" and d.default and d.default not in ('0', '1'): + if d.fieldtype == "Check" and not d.default: + d.default = '0' + if d.fieldtype == "Check" and d.default not in ('0', '1'): frappe.throw(_("Default for 'Check' type of field must be either '0' or '1'")) if d.fieldtype == "Select" and d.default and (d.default not in d.options.split("\n")): frappe.throw(_("Default for {0} must be an option").format(d.fieldname)) diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 41a4267c97..4a0782340d 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -104,4 +104,125 @@ class TestDocType(unittest.TestCase): for depends_on in ["depends_on", "collapsible_depends_on"]: condition = field.get(depends_on) if condition: - self.assertFalse(re.match(pattern, condition)) \ No newline at end of file + self.assertFalse(re.match(pattern, condition)) + + def test_sync_field_order(self): + from frappe.modules.import_file import get_file_path + import os + + # create test doctype + test_doctype = frappe.get_doc({ + "doctype": "DocType", + "module": "Core", + "fields": [ + { + "label": "Field 1", + "fieldname": "field_1", + "fieldtype": "Data" + }, + { + "label": "Field 2", + "fieldname": "field_2", + "fieldtype": "Data" + }, + { + "label": "Field 3", + "fieldname": "field_3", + "fieldtype": "Data" + }, + { + "label": "Field 4", + "fieldname": "field_4", + "fieldtype": "Data" + } + ], + "permissions": [{ + "role": "System Manager", + "read": 1 + }], + "name": "Test Field Order DocType", + "__islocal": 1 + }) + + path = get_file_path(test_doctype.module, test_doctype.doctype, test_doctype.name) + initial_fields_order = ['field_1', 'field_2', 'field_3', 'field_4'] + + frappe.delete_doc_if_exists("DocType", "Test Field Order DocType") + if os.path.isfile(path): + os.remove(path) + + try: + frappe.flags.allow_doctype_export = 1 + test_doctype.save() + + # assert that field_order list is being created with the default order + test_doctype_json = frappe.get_file_json(path) + self.assertTrue(test_doctype_json.get("field_order")) + self.assertEqual(len(test_doctype_json['fields']), len(test_doctype_json['field_order'])) + self.assertListEqual([f['fieldname'] for f in test_doctype_json['fields']], test_doctype_json['field_order']) + self.assertListEqual([f['fieldname'] for f in test_doctype_json['fields']], initial_fields_order) + self.assertListEqual(test_doctype_json['field_order'], initial_fields_order) + + # remove field_order to test reload_doc/sync/migrate is backwards compatible without field_order + del test_doctype_json['field_order'] + with open(path, 'w+') as txtfile: + txtfile.write(frappe.as_json(test_doctype_json)) + + # assert that field_order is actually removed from the json file + test_doctype_json = frappe.get_file_json(path) + self.assertFalse(test_doctype_json.get("field_order")) + + # make sure that migrate/sync is backwards compatible without field_order + frappe.reload_doctype(test_doctype.name, force=True) + test_doctype.reload() + + # assert that field_order list is being created with the default order again + test_doctype.save() + test_doctype_json = frappe.get_file_json(path) + self.assertTrue(test_doctype_json.get("field_order")) + self.assertEqual(len(test_doctype_json['fields']), len(test_doctype_json['field_order'])) + self.assertListEqual([f['fieldname'] for f in test_doctype_json['fields']], test_doctype_json['field_order']) + self.assertListEqual([f['fieldname'] for f in test_doctype_json['fields']], initial_fields_order) + self.assertListEqual(test_doctype_json['field_order'], initial_fields_order) + + # reorder fields: swap row 1 and 3 + test_doctype.fields[0], test_doctype.fields[2] = test_doctype.fields[2], test_doctype.fields[0] + for i, f in enumerate(test_doctype.fields): + f.idx = i + 1 + + # assert that reordering fields only affects `field_order` rather than `fields` attr + test_doctype.save() + test_doctype_json = frappe.get_file_json(path) + self.assertListEqual([f['fieldname'] for f in test_doctype_json['fields']], initial_fields_order) + self.assertListEqual(test_doctype_json['field_order'], ['field_3', 'field_2', 'field_1', 'field_4']) + + # reorder `field_order` in the json file: swap row 2 and 4 + test_doctype_json['field_order'][1], test_doctype_json['field_order'][3] = test_doctype_json['field_order'][3], test_doctype_json['field_order'][1] + with open(path, 'w+') as txtfile: + txtfile.write(frappe.as_json(test_doctype_json)) + + # assert that reordering `field_order` from json file is reflected in DocType upon migrate/sync + frappe.reload_doctype(test_doctype.name, force=True) + test_doctype.reload() + self.assertListEqual([f.fieldname for f in test_doctype.fields], ['field_3', 'field_4', 'field_1', 'field_2']) + + # insert row in the middle and remove first row (field 3) + test_doctype.append("fields", { + "label": "Field 5", + "fieldname": "field_5", + "fieldtype": "Data" + }) + test_doctype.fields[4], test_doctype.fields[3] = test_doctype.fields[3], test_doctype.fields[4] + test_doctype.fields[3], test_doctype.fields[2] = test_doctype.fields[2], test_doctype.fields[3] + test_doctype.remove(test_doctype.fields[0]) + for i, f in enumerate(test_doctype.fields): + f.idx = i + 1 + + test_doctype.save() + test_doctype_json = frappe.get_file_json(path) + self.assertListEqual([f['fieldname'] for f in test_doctype_json['fields']], ['field_1', 'field_2', 'field_4', 'field_5']) + self.assertListEqual(test_doctype_json['field_order'], ['field_4', 'field_5', 'field_1', 'field_2']) + except: + raise + finally: + frappe.flags.allow_doctype_export = 0 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/file/file.py b/frappe/core/doctype/file/file.py index 6942b72f6a..fff4a5e344 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -290,6 +290,8 @@ class File(NestedSet): zip_path = frappe.get_site_path(self.file_url.strip('/')) base_url = os.path.dirname(self.file_url) + + files = [] with zipfile.ZipFile(zip_path) as zf: zf.extractall(os.path.dirname(zip_path)) for info in zf.infolist(): @@ -308,8 +310,10 @@ class File(NestedSet): file_doc.attached_to_doctype = self.attached_to_doctype file_doc.attached_to_name = self.attached_to_name file_doc.save() + files.append(file_doc) frappe.delete_doc('File', self.name) + return files def get_file_url(self): @@ -888,7 +892,8 @@ def get_random_filename(extn=None, content_type=None): def unzip_file(name): '''Unzip the given file and make file records for each of the extracted files''' file_obj = frappe.get_doc('File', name) - file_obj.unzip() + files = file_obj.unzip() + return len(files) @frappe.whitelist() @@ -919,3 +924,10 @@ def validate_filename(filename): timestamp = now_datetime().strftime(" %Y-%m-%d %H:%M:%S") fname = get_file_name(filename, timestamp) return fname + +@frappe.whitelist() +def get_files_in_folder(folder): + return frappe.db.get_all('File', + { 'folder': folder }, + ['name', 'file_name', 'file_url', 'is_folder', 'modified'] + ) 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.json b/frappe/core/doctype/prepared_report/prepared_report.json index e1b122e68a..ec89c6327a 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.json +++ b/frappe/core/doctype/prepared_report/prepared_report.json @@ -21,6 +21,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "report_name", "fieldtype": "Data", "hidden": 1, @@ -53,6 +54,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "ref_report_doctype", "fieldtype": "Link", "hidden": 1, @@ -86,6 +88,8 @@ "bold": 0, "collapsible": 0, "columns": 0, + "default": "Queued", + "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", "hidden": 1, @@ -93,12 +97,12 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Status", "length": 0, "no_copy": 0, - "options": "Queued\nCompleted", + "options": "Error\nQueued\nCompleted", "permlevel": 0, "precision": "", "print_hide": 0, @@ -119,6 +123,39 @@ "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 + }, + { + "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_start_time", "fieldtype": "Datetime", "hidden": 0, @@ -151,6 +188,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "report_end_time", "fieldtype": "Datetime", "hidden": 0, @@ -183,6 +221,73 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "eval:doc.status == 'Error'", + "fetch_if_empty": 0, + "fieldname": "section_break_7", + "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": "error_message", + "fieldtype": "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": "Error Message", + "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, "fieldname": "filters_sb", "fieldtype": "Section Break", "hidden": 0, @@ -215,6 +320,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "filters", "fieldtype": "Small Text", "hidden": 1, @@ -247,6 +353,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "filter_values", "fieldtype": "HTML", "hidden": 0, @@ -279,6 +386,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "columns", "fieldtype": "Code", "hidden": 1, @@ -315,7 +423,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-10-23 16:58:14.879417", + "modified": "2019-04-19 12:39:47.211516", "modified_by": "Administrator", "module": "Core", "name": "Prepared Report", diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index 1cd106dabd..29c069515f 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -25,25 +25,43 @@ class PreparedReport(Document): self.status = "Queued" self.report_start_time = frappe.utils.now() - def after_insert(self): + def enqueue_report(self): enqueue( run_background, - instance=self, timeout=6000 + prepared_report=self.name, timeout=6000 ) def on_trash(self): remove_all("PreparedReport", self.name, from_delete=True) -def run_background(instance): +def run_background(prepared_report): + instance = frappe.get_doc("Prepared Report", prepared_report) report = frappe.get_doc("Report", instance.ref_report_doctype) - result = generate_report_result(report, filters=instance.filters, user=instance.owner) - create_json_gz_file(result['result'], 'Prepared Report', instance.name) - instance.status = "Completed" - instance.columns = json.dumps(result["columns"]) - instance.report_end_time = frappe.utils.now() - instance.save() + try: + report.custom_columns = [] + + if report.report_type == 'Custom Report': + custom_report_doc = report + reference_report = custom_report_doc.reference_report + report = frappe.get_doc("Report", reference_report) + report.custom_columns = custom_report_doc.json + + result = generate_report_result(report, filters=instance.filters, user=instance.owner) + create_json_gz_file(result['result'], 'Prepared Report', instance.name) + + instance.status = "Completed" + instance.columns = json.dumps(result["columns"]) + instance.report_end_time = frappe.utils.now() + 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(ignore_permissions=True) frappe.publish_realtime( 'report_generated', diff --git a/frappe/core/doctype/prepared_report/prepared_report_list.js b/frappe/core/doctype/prepared_report/prepared_report_list.js new file mode 100644 index 0000000000..8acb3bc75a --- /dev/null +++ b/frappe/core/doctype/prepared_report/prepared_report_list.js @@ -0,0 +1,12 @@ +frappe.listview_settings['Prepared Report'] = { + add_fields: ["status"], + get_indicator: function(doc) { + if(doc.status==="Completed"){ + return [__("Completed"), "green", "status,=,Completed"]; + } else if(doc.status ==="Error"){ + return [__("Error"), "red", "status,=,Error"]; + } else if(doc.status ==="Queued"){ + return [__("Queued"), "orange", "status,=,Queued"]; + } + } +}; \ No newline at end of file diff --git a/frappe/core/doctype/report/boilerplate/controller.js b/frappe/core/doctype/report/boilerplate/controller.js index a09dff068f..5148f34462 100644 --- a/frappe/core/doctype/report/boilerplate/controller.js +++ b/frappe/core/doctype/report/boilerplate/controller.js @@ -6,4 +6,4 @@ frappe.query_reports["{name}"] = {{ "filters": [ ] -}} +}}; diff --git a/frappe/core/doctype/report/boilerplate/controller.py b/frappe/core/doctype/report/boilerplate/controller.py index 5287ecc691..55c01e4f75 100644 --- a/frappe/core/doctype/report/boilerplate/controller.py +++ b/frappe/core/doctype/report/boilerplate/controller.py @@ -2,7 +2,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe +# import frappe def execute(filters=None): columns, data = [], [] 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/user/user.py b/frappe/core/doctype/user/user.py index 60a8119f10..adc054197a 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -12,6 +12,8 @@ from frappe.utils.user import get_system_managers import frappe.permissions import frappe.share import re +import json + from frappe.limits import get_limits from frappe.website.utils import is_signup_enabled from frappe.utils.background_jobs import enqueue @@ -1086,4 +1088,12 @@ def generate_keys(user): user_details.save() return {"api_secret": api_secret} - frappe.throw(frappe._("Not Permitted"), frappe.PermissionError) \ No newline at end of file + frappe.throw(frappe._("Not Permitted"), frappe.PermissionError) + +@frappe.whitelist() +def update_profile_info(profile_info): + profile_info = json.loads(profile_info) + user = frappe.get_doc('User', frappe.session.user) + user.update(profile_info) + user.save() + return user \ No newline at end of file diff --git a/frappe/core/doctype/user/user_list.js b/frappe/core/doctype/user/user_list.js index 116e8e6d86..5632edf0cc 100644 --- a/frappe/core/doctype/user/user_list.js +++ b/frappe/core/doctype/user/user_list.js @@ -16,4 +16,4 @@ frappe.listview_settings['User'] = { } }; -frappe.help.youtube_id["User"] = "fnBoRhBrwR4"; +frappe.help.youtube_id["User"] = "8Slw1hsTmUI"; diff --git a/frappe/core/doctype/user_permission/test_user_permission.py b/frappe/core/doctype/user_permission/test_user_permission.py index b83d103013..2fa9917daf 100644 --- a/frappe/core/doctype/user_permission/test_user_permission.py +++ b/frappe/core/doctype/user_permission/test_user_permission.py @@ -8,17 +8,26 @@ import frappe import unittest class TestUserPermission(unittest.TestCase): + def test_default_user_permission_validation(self): + user = create_user('test_default_permission@example.com') + param = get_params(user, 'User', user.name, is_default=1) + add_user_permissions(param) + #create a duplicate entry with default + perm_user = create_user('test_user_perm@example.com') + param = get_params(user, 'User', perm_user.name, is_default=1) + self.assertRaises(frappe.ValidationError, add_user_permissions, param) + def test_apply_to_all(self): ''' Create User permission for User having access to all applicable Doctypes''' - user = get_user() - param = get_params(user, apply = 1) + user = create_user('test_bulk_creation_update@example.com') + param = get_params(user, 'User', user.name) created = add_user_permissions(param) self.assertEquals(created, 1) def test_for_applicable_on_update_from_apply_to_all(self): ''' Update User Permission from all to some applicable Doctypes''' - user = get_user() - param = get_params(user, applicable = ["Chat Room", "Chat Message"]) + user = create_user('test_bulk_creation_update@example.com') + param = get_params(user, 'User', user.name , applicable = ["Chat Room", "Chat Message"]) create = add_user_permissions(param) frappe.db.commit() @@ -33,8 +42,8 @@ class TestUserPermission(unittest.TestCase): def test_for_apply_to_all_on_update_from_applicable(self): ''' Update User Permission from some to all applicable Doctypes''' - user = get_user() - param = get_params(user, apply = 1) + user = create_user('test_bulk_creation_update@example.com') + param = get_params(user, 'User', user.name) created = add_user_permissions(param) created_apply_to_all = frappe.db.exists("User Permission", get_exists_param(user)) removed_applicable_first = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Room")) @@ -46,26 +55,27 @@ class TestUserPermission(unittest.TestCase): self.assertIsNone(removed_applicable_second) self.assertEquals(created, 1) -def get_user(): - if frappe.db.exists('User', 'test_bulk_creation_update@example.com'): - return frappe.get_doc('User', 'test_bulk_creation_update@example.com') +def create_user(email): + ''' create user with role system manager ''' + if frappe.db.exists('User', email): + return frappe.get_doc('User', email) else: user = frappe.new_doc('User') - user.email = 'test_bulk_creation_update@example.com' - user.first_name = 'Test_Bulk_Creation' + user.email = email + user.first_name = email.split("@")[0] user.add_roles("System Manager") return user -def get_params(user, apply = None , applicable = None): +def get_params(user, doctype, docname, is_default=0, applicable=None): ''' Return param to insert ''' param = { "user": user.name, - "doctype":"User", - "docname":user.name + "doctype":doctype, + "docname":docname, + "is_default": is_default, + "apply_to_all_doctypes": 1, + "applicable_doctypes": [] } - if apply: - param.update({"apply_to_all_doctypes": 1}) - param.update({"applicable_doctypes": []}) if applicable: param.update({"apply_to_all_doctypes": 0}) param.update({"applicable_doctypes": applicable}) diff --git a/frappe/core/doctype/user_permission/user_permission.json b/frappe/core/doctype/user_permission/user_permission.json index c2ea05e731..33a8d58bbb 100644 --- a/frappe/core/doctype/user_permission/user_permission.json +++ b/frappe/core/doctype/user_permission/user_permission.json @@ -20,6 +20,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "user", "fieldtype": "Link", "hidden": 0, @@ -53,6 +54,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "allow", "fieldtype": "Link", "hidden": 0, @@ -86,6 +88,39 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_3", + "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": "for_value", "fieldtype": "Dynamic Link", "hidden": 0, @@ -119,6 +154,40 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, + "fieldname": "is_default", + "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 Default", + "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": "advanced_control_section", "fieldtype": "Section Break", "hidden": 0, @@ -152,6 +221,7 @@ "collapsible": 0, "columns": 0, "default": "1", + "fetch_if_empty": 0, "fieldname": "apply_to_all_doctypes", "fieldtype": "Check", "hidden": 0, @@ -185,6 +255,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.apply_to_all_doctypes", + "fetch_if_empty": 0, "fieldname": "applicable_for", "fieldtype": "Link", "hidden": 0, @@ -213,16 +284,14 @@ } ], "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": "2019-02-13 22:58:27.428741", + "modified": "2019-04-16 19:17:23.644724", "modified_by": "Administrator", "module": "Core", "name": "User Permission", @@ -251,7 +320,6 @@ ], "quick_entry": 0, "read_only": 0, - "read_only_onload": 0, "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py index 88144f8078..af2815a9ac 100644 --- a/frappe/core/doctype/user_permission/user_permission.py +++ b/frappe/core/doctype/user_permission/user_permission.py @@ -7,21 +7,14 @@ import frappe, json from frappe.model.document import Document from frappe.permissions import (get_valid_perms, update_permission_property) from frappe import _ +from frappe.utils import cstr from frappe.core.utils import find from frappe.desk.form.linked_with import get_linked_doctypes class UserPermission(Document): def validate(self): - duplicate_exists = frappe.db.get_all(self.doctype, filters={ - 'allow': self.allow, - 'for_value': self.for_value, - 'user': self.user, - 'applicable_for': self.applicable_for, - 'apply_to_all_doctypes': self.apply_to_all_doctypes, - 'name': ['!=', self.name] - }, limit=1) - if duplicate_exists: - frappe.throw(_("User permission already exists"), frappe.DuplicateEntryError) + self.validate_user_permission() + self.validate_default_permission() def on_update(self): frappe.cache().delete_value('user_permissions') @@ -31,6 +24,37 @@ class UserPermission(Document): frappe.cache().delete_value('user_permissions') frappe.publish_realtime('update_user_permissions') + def validate_user_permission(self): + ''' checks for duplicate user permission records''' + + duplicate_exists = frappe.db.get_all(self.doctype, filters={ + 'allow': self.allow, + 'for_value': self.for_value, + 'user': self.user, + 'applicable_for': cstr(self.applicable_for), + 'apply_to_all_doctypes': self.apply_to_all_doctypes, + 'name': ['!=', self.name] + }, limit=1) + if duplicate_exists: + frappe.throw(_("User permission already exists"), frappe.DuplicateEntryError) + + def validate_default_permission(self): + ''' validate user permission overlap for default value of a particular doctype ''' + overlap_exists = [] + if self.is_default: + overlap_exists = frappe.get_all(self.doctype, filters={ + 'allow': self.allow, + 'user': self.user, + 'is_default': 1, + 'name': ['!=', self.name] + }, or_filters={ + 'applicable_for': cstr(self.applicable_for), + 'apply_to_all_doctypes': 1 + }, limit=1) + if overlap_exists: + ref_link = frappe.get_desk_link(self.doctype, overlap_exists[0].name) + frappe.throw(_("{0} has already assigned default value for {1}.".format(ref_link, self.allow))) + @frappe.whitelist() def get_user_permissions(user=None): '''Get all users permissions for the user as a dict of doctype''' @@ -52,7 +76,7 @@ def get_user_permissions(user=None): out = {} - def add_doc_to_perm(perm, doc_name): + def add_doc_to_perm(perm, doc_name, is_default): # group rules for each type # for example if allow is "Customer", then build all allowed customers # in a list @@ -61,21 +85,22 @@ def get_user_permissions(user=None): out[perm.allow].append(frappe._dict({ 'doc': doc_name, - 'applicable_for': perm.get('applicable_for') + 'applicable_for': perm.get('applicable_for'), + 'is_default': is_default })) try: for perm in frappe.get_all('User Permission', - fields=['allow', 'for_value', 'applicable_for'], + fields=['allow', 'for_value', 'applicable_for', 'is_default'], filters=dict(user=user)): meta = frappe.get_meta(perm.allow) - add_doc_to_perm(perm, perm.for_value) + add_doc_to_perm(perm, perm.for_value, perm.is_default) if meta.is_nested_set(): decendants = frappe.db.get_descendants(perm.allow, perm.for_value) for doc in decendants: - add_doc_to_perm(perm, doc) + add_doc_to_perm(perm, doc, False) out = frappe._dict(out) frappe.cache().hset("user_permissions", user, out) @@ -160,24 +185,25 @@ def add_user_permissions(data): exists = frappe.db.exists("User Permission", {"user": data.user, "allow": data.doctype, "for_value": data.docname, "apply_to_all_doctypes": 1}) if data.apply_to_all_doctypes == 1 and not exists: remove_applicable(d, data.user, data.doctype, data.docname) - insert_user_perm(data.user, data.doctype, data.docname, apply_to_all = 1) + insert_user_perm(data.user, data.doctype, data.docname, data.is_default, apply_to_all = 1) return 1 else: remove_apply_to_all(data.user, data.doctype, data.docname) update_applicable(d, data.applicable_doctypes, data.user, data.doctype, data.docname) for applicable in data.applicable_doctypes : if applicable not in d: - insert_user_perm(data.user, data.doctype, data.docname, applicable = applicable) + insert_user_perm(data.user, data.doctype, data.docname, data.is_default, applicable = applicable) elif exists: - insert_user_perm(data.user, data.doctype, data.docname, applicable = applicable) + insert_user_perm(data.user, data.doctype, data.docname, data.is_default, applicable = applicable) return 1 return 0 -def insert_user_perm(user, doctype, docname, apply_to_all=None, applicable=None): +def insert_user_perm(user, doctype, docname, is_default=0, apply_to_all=None, applicable=None): user_perm = frappe.new_doc("User Permission") user_perm.user = user user_perm.allow = doctype user_perm.for_value = docname + user_perm.is_default = is_default if applicable: user_perm.applicable_for = applicable user_perm.apply_to_all_doctypes = 0 @@ -210,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/doctype/user_permission/user_permission_list.js b/frappe/core/doctype/user_permission/user_permission_list.js index 00d829b2a0..a0b553c43a 100644 --- a/frappe/core/doctype/user_permission/user_permission_list.js +++ b/frappe/core/doctype/user_permission/user_permission_list.js @@ -16,6 +16,7 @@ frappe.listview_settings['User Permission'] = { dialog.fields_dict.doctype.set_input(undefined); dialog.fields_dict.docname.set_input(undefined); dialog.set_df_property("docname", "hidden", 1); + dialog.set_df_property("is_default", "hidden", 1); dialog.set_df_property("apply_to_all_doctypes", "hidden", 1); dialog.set_df_property("applicable_doctypes", "hidden", 1); } @@ -53,11 +54,16 @@ frappe.listview_settings['User Permission'] = { } } }, + { + fieldname: 'is_default', + label: __('Is Default'), + fieldtype: 'Check', + hidden: 1 + }, { fieldname: 'apply_to_all_doctypes', label: __('Apply to all Documents Types'), fieldtype: 'Check', - checked: 1, hidden: 1, onchange: function() { if(dialog.fields_dict.doctype.value && dialog.fields_dict.docname.value && dialog.fields_dict.user.value){ @@ -205,8 +211,9 @@ frappe.listview_settings['User Permission'] = { on_doctype_change: function(dialog) { dialog.set_df_property("docname", "hidden", 0); dialog.set_df_property("docname", "reqd", 1); + dialog.set_df_property("is_default", "hidden", 0); dialog.set_df_property("apply_to_all_doctypes", "hidden", 0); - dialog.set_value("apply_to_all_doctypes","checked",1); + dialog.set_value("apply_to_all_doctypes", "checked", 1); }, on_docname_change: function(dialog, options, applicable) { diff --git a/frappe/core/doctype/version/version.py b/frappe/core/doctype/version/version.py index 28f3ea5364..284e79cf62 100644 --- a/frappe/core/doctype/version/version.py +++ b/frappe/core/doctype/version/version.py @@ -40,6 +40,9 @@ def get_diff(old, new, for_child=False): ], }''' + if not new: + return None + out = frappe._dict(changed = [], added = [], removed = [], row_changed = []) for df in new.meta.fields: if df.fieldtype in no_value_fields and df.fieldtype not in table_fields: 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/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.js b/frappe/custom/doctype/customize_form/customize_form.js index 0a6f6d4d54..c503c903eb 100644 --- a/frappe/custom/doctype/customize_form/customize_form.js +++ b/frappe/custom/doctype/customize_form/customize_form.js @@ -28,6 +28,11 @@ frappe.ui.form.on("Customize Form", { $(frm.wrapper).on("grid-make-sortable", function(e, frm) { frm.trigger("setup_sortable"); }); + + $(frm.wrapper).on("grid-move-row", function(e, frm) { + frm.trigger("setup_sortable"); + }); + }, doc_type: function(frm) { @@ -57,12 +62,13 @@ frappe.ui.form.on("Customize Form", { frm.doc.fields.forEach(function(f, i) { var data_row = frm.page.body.find('[data-fieldname="fields"] [data-idx="'+ f.idx +'"] .data-row'); - if(!f.is_custom_field) { - data_row.removeClass('sortable-handle'); - } else { + if(f.is_custom_field) { data_row.addClass("highlight"); + } else { + f._sortable = false; } }); + frm.fields_dict.fields.grid.refresh(); }, refresh: function(frm) { 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 333a967dd2..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.")) @@ -157,7 +161,7 @@ class CustomizeForm(Document): frappe.db.updatedb(self.doc_type) if not hasattr(self, 'hide_success') or not self.hide_success: - frappe.msgprint(_("{0} updated").format(_(self.doc_type))) + frappe.msgprint(_("{0} updated").format(_(self.doc_type)), alert=True) frappe.clear_cache(doctype=self.doc_type) self.fetch_to_customize() diff --git a/frappe/database/__init__.py b/frappe/database/__init__.py index f00c6bf0b4..9d05223044 100644 --- a/frappe/database/__init__.py +++ b/frappe/database/__init__.py @@ -23,14 +23,14 @@ def drop_user_and_database(db_name, root_login=None, root_password=None): import frappe.database.mariadb.setup_db return frappe.database.mariadb.setup_db.drop_user_and_database(db_name, root_login, root_password) -def get_db(host=None, user=None, password=None): +def get_db(host=None, user=None, password=None, port=None): import frappe if frappe.conf.db_type == 'postgres': import frappe.database.postgres.database - return frappe.database.postgres.database.PostgresDatabase(host, user, password) + return frappe.database.postgres.database.PostgresDatabase(host, user, password, port=port) else: import frappe.database.mariadb.database - return frappe.database.mariadb.database.MariaDBDatabase(host, user, password) + return frappe.database.mariadb.database.MariaDBDatabase(host, user, password, port=port) def setup_help_database(help_db_name): import frappe @@ -39,4 +39,4 @@ def setup_help_database(help_db_name): return frappe.database.postgres.setup_db.setup_help_database(help_db_name) else: import frappe.database.mariadb.setup_db - return frappe.database.mariadb.setup_db.setup_help_database(help_db_name) \ No newline at end of file + return frappe.database.mariadb.setup_db.setup_help_database(help_db_name) diff --git a/frappe/database/database.py b/frappe/database/database.py index d3b44913fc..a58026982a 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -46,9 +46,10 @@ class Database(object): class InvalidColumnName(frappe.ValidationError): pass - def __init__(self, host=None, user=None, password=None, ac_name=None, use_default=0): + def __init__(self, host=None, user=None, password=None, ac_name=None, use_default=0, port=None): self.setup_type_map() self.host = host or frappe.conf.db_host or 'localhost' + self.port = port or frappe.conf.db_port or '' self.user = user or frappe.conf.db_name self.db_name = frappe.conf.db_name self._conn = None @@ -153,6 +154,10 @@ class Database(object): frappe.log(values) frappe.log(">>>>") self._cursor.execute(query, values) + + if frappe.flags.in_migrate: + self.log_touched_tables(query, values) + else: if debug: if explain: @@ -165,6 +170,9 @@ class Database(object): self._cursor.execute(query) + if frappe.flags.in_migrate: + self.log_touched_tables(query) + if debug: time_end = time() frappe.errprint(("Execution time: {0} sec").format(round(time_end - time_start, 2))) @@ -173,6 +181,10 @@ class Database(object): if(frappe.conf.db_type == 'postgres'): self.rollback() + if frappe.conf.db_type == 'mariadb' and self.is_syntax_error(e): + frappe.errprint('Syntax error in query:') + frappe.errprint(query) + if ignore_ddl and (self.is_missing_column(e) or self.is_missing_table(e) or self.cant_drop_field_or_key(e)): pass else: @@ -833,7 +845,7 @@ class Database(object): """Returns list of column names from given doctype.""" columns = self.get_db_table_columns('tab' + doctype) if not columns: - raise self.ProgrammingError + raise self.TableMissingError return columns def has_column(self, doctype, column): @@ -912,6 +924,20 @@ class Database(object): else: frappe.throw('No conditions provided') + def log_touched_tables(self, query, values=None): + if values: + query = frappe.safe_decode(self._cursor.mogrify(query, values)) + if query.strip().lower().split()[0] in ('insert', 'delete', 'update', 'alter'): + # ([`\"']?) Captures ', " or ` at the begining of the table name (if provided) + # (tab([A-Z]\w+)( [A-Z]\w+)*) Captures table names that start with "tab" + # and are continued with multiple words that start with a captital letter + # e.g. 'tabXxx' or 'tabXxx Xxx' or 'tabXxx Xxx Xxx' and so on + # \1 matches the first captured group (quote character) at the end of the table name + tables = [groups[1] for groups in re.findall(r'([`"\']?)(tab([A-Z]\w+)( [A-Z]\w+)*)\1', query)] + if frappe.flags.touched_tables is None: + frappe.flags.touched_tables = set() + frappe.flags.touched_tables.update(tables) + def enqueue_jobs_after_commit(): if frappe.flags.enqueue_after_commit and len(frappe.flags.enqueue_after_commit) > 0: diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index 69a9cf5e9f..bce705f6a1 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -17,6 +17,7 @@ from frappe.database.mariadb.schema import MariaDBTable class MariaDBDatabase(Database): ProgrammingError = pymysql.err.ProgrammingError + TableMissingError = pymysql.err.ProgrammingError OperationalError = pymysql.err.OperationalError InternalError = pymysql.err.InternalError SQLError = pymysql.err.ProgrammingError @@ -80,11 +81,11 @@ class MariaDBDatabase(Database): if usessl: conn = pymysql.connect(self.host, self.user or '', self.password or '', - charset='utf8mb4', use_unicode = True, ssl=ssl_params, + port=self.port, charset='utf8mb4', use_unicode = True, ssl=ssl_params, conv = conversions, local_infile = frappe.conf.local_infile) else: conn = pymysql.connect(self.host, self.user or '', self.password or '', - charset='utf8mb4', use_unicode = True, conv = conversions, + port=self.port, charset='utf8mb4', use_unicode = True, conv = conversions, local_infile = frappe.conf.local_infile) # MYSQL_OPTION_MULTI_STATEMENTS_OFF = 1 @@ -163,6 +164,10 @@ class MariaDBDatabase(Database): def cant_drop_field_or_key(e): return e.args[0] == ER.CANT_DROP_FIELD_OR_KEY + @staticmethod + def is_syntax_error(e): + return e.args[0] == ER.PARSE_ERROR + def is_primary_key_violation(self, e): return self.is_duplicate_entry(e) and 'PRIMARY' in cstr(e.args[1]) @@ -282,4 +287,4 @@ class MariaDBDatabase(Database): self.begin() def get_database_list(self, target): - return [d[0] for d in self.sql("SHOW DATABASES;")] \ No newline at end of file + return [d[0] for d in self.sql("SHOW DATABASES;")] 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/mariadb/setup_db.py b/frappe/database/mariadb/setup_db.py index 3cc5365d79..bda3c8b0a7 100644 --- a/frappe/database/mariadb/setup_db.py +++ b/frappe/database/mariadb/setup_db.py @@ -4,6 +4,32 @@ import frappe import os, sys from frappe.database.db_manager import DbManager +expected_settings_10_2_earlier = { + "innodb_file_format": "Barracuda", + "innodb_file_per_table": "ON", + "innodb_large_prefix": "ON", + "character_set_server": "utf8mb4", + "collation_server": "utf8mb4_unicode_ci" +} + +expected_settings_10_3_later = { + "character_set_server": "utf8mb4", + "collation_server": "utf8mb4_unicode_ci" +} + + +def get_mariadb_versions(): + # MariaDB classifies their versions as Major (1st and 2nd number), and Minor (3rd number) + # Example: Version 10.3.13 is Major Version = 10.3, Minor Version = 13 + mariadb_variables = frappe._dict(frappe.db.sql("""show variables""")) + version_string = mariadb_variables.get('version').split('-')[0] + versions = {} + versions['major'] = version_string.split( + '.')[0] + '.' + version_string.split('.')[1] + versions['minor'] = version_string.split('.')[2] + return versions + + def setup_database(force, source_sql, verbose): frappe.local.session = frappe._dict({'user':'Administrator'}) @@ -54,7 +80,10 @@ def drop_user_and_database(db_name, root_login, root_password): def bootstrap_database(db_name, verbose, source_sql=None): frappe.connect(db_name=db_name) - check_if_ready_for_barracuda() + if not check_database_settings(): + print('Database settings do not match expected values; stopping database setup.') + sys.exit(1) + import_db_from_sql(source_sql, verbose) if not 'tabDefaultValue' in frappe.db.get_tables(): print('''Database not installed, this can due to lack of permission, or that the database name exists. @@ -69,38 +98,33 @@ def import_db_from_sql(source_sql=None, verbose=False): DbManager(frappe.local.db).restore_database(db_name, source_sql, db_name, frappe.conf.db_password) if verbose: print("Imported from database %s" % source_sql) -def check_if_ready_for_barracuda(): + +def check_database_settings(): + versions = get_mariadb_versions() + if versions['major'] <= '10.2': + expected_variables = expected_settings_10_2_earlier + else: + expected_variables = expected_settings_10_3_later + mariadb_variables = frappe._dict(frappe.db.sql("""show variables""")) - mariadb_minor_version = int(mariadb_variables.get('version').split('-')[0].split('.')[1]) - if mariadb_minor_version < 3: - check_database(mariadb_variables, { - "innodb_file_format": "Barracuda", - "innodb_file_per_table": "ON", - "innodb_large_prefix": "ON" - }) - check_database(mariadb_variables, { - "character_set_server": "utf8mb4", - "collation_server": "utf8mb4_unicode_ci" - }) + # Check each expected value vs. actuals: + result = True + for key, expected_value in expected_variables.items(): + if mariadb_variables.get(key) != expected_value: + print("For key %s. Expected value %s, found value %s" % + (key, expected_value, mariadb_variables.get(key))) + result = False + if not result: + site = frappe.local.site + msg = ("Creation of your site - {x} failed because MariaDB is not properly {sep}" + "configured. If using version 10.2.x or earlier, make sure you use the {sep}" + "the Barracuda storage engine. {sep}{sep}" + "Please verify the settings above in MariaDB's my.cnf. Restart MariaDB. And {sep}" + "then run `bench new-site {x}` again.{sep2}" + "").format(x=site, sep2="\n"*2, sep="\n") + print_db_config(msg) + return result -def check_database(mariadb_variables, variables_dict): - mariadb_minor_version = int(mariadb_variables.get('version').split('-')[0].split('.')[1]) - for key, value in variables_dict.items(): - if mariadb_variables.get(key) != value: - site = frappe.local.site - msg = ("Creation of your site - {x} failed because MariaDB is not properly {sep}" - "configured to use the Barracuda storage engine. {sep}" - "Please add the settings below to MariaDB's my.cnf, restart MariaDB then {sep}" - "run `bench new-site {x}` again.{sep2}" - "").format(x=site, sep2="\n"*2, sep="\n") - - if mariadb_minor_version < 3: - print_db_config(msg, expected_config_for_barracuda_2) - else: - print_db_config(msg, expected_config_for_barracuda_3) - raise frappe.exceptions.ImproperDBConfigurationError( - reason="MariaDB default file format is not Barracuda" - ) def get_root_connection(root_login, root_password): import getpass @@ -118,31 +142,8 @@ def get_root_connection(root_login, root_password): return frappe.local.flags.root_connection -def print_db_config(explanation, config_text): + +def print_db_config(explanation): print("="*80) print(explanation) - print(config_text) print("="*80) - -expected_config_for_barracuda_2 = """ -[mysqld] -innodb-file-format=barracuda -innodb-file-per-table=1 -innodb-large-prefix=1 -character-set-client-handshake = FALSE -character-set-server = utf8mb4 -collation-server = utf8mb4_unicode_ci - -[mysql] -default-character-set = utf8mb4 -""" - -expected_config_for_barracuda_3 = """ -[mysqld] -character-set-client-handshake = FALSE -character-set-server = utf8mb4 -collation-server = utf8mb4_unicode_ci - -[mysql] -default-character-set = utf8mb4 -""" diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index 4b8a4ae500..b219aac13a 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -21,6 +21,7 @@ psycopg2.extensions.register_type(DEC2FLOAT) class PostgresDatabase(Database): ProgrammingError = psycopg2.ProgrammingError + TableMissingError = psycopg2.ProgrammingError OperationalError = psycopg2.OperationalError InternalError = psycopg2.InternalError SQLError = psycopg2.ProgrammingError @@ -63,10 +64,10 @@ class PostgresDatabase(Database): def get_connection(self): # warnings.filterwarnings('ignore', category=psycopg2.Warning) - conn = psycopg2.connect('host={} dbname={}'.format(self.host, self.user)) + 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 @@ -309,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 68396f4c6a..ba00d81143 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -10,7 +10,7 @@ frappe.ui.form.on('Dashboard Chart', { }, refresh: function(frm) { - frm.filters = null; + frm.chart_filters = null; frm.set_df_property("filters_section", "hidden", 1); frm.trigger('update_options'); }, @@ -58,7 +58,7 @@ frappe.ui.form.on('Dashboard Chart', { if (['Date', 'Datetime'].includes(df.fieldtype)) { date_fields.push({label: df.label, value: df.fieldname}); } - if (['In', 'Float', 'Currency', 'Percent'].includes(df.fieldtype)) { + if (['Int', 'Float', 'Currency', 'Percent'].includes(df.fieldtype)) { value_fields.push({label: df.label, value: df.fieldname}); } }); @@ -72,21 +72,26 @@ frappe.ui.form.on('Dashboard Chart', { }, show_filters: function(frm) { - if (frm.filters) { + if (frm.chart_filters && frm.chart_filters.length) { frm.trigger('render_filters_table'); } else { if (frm.doc.chart_type==='Custom') { - frappe.xcall('frappe.desk.doctype.dashboard_chart_source.dashboard_chart_source.get_config', {name: frm.doc.source}) - .then(config => { - frappe.dom.eval(config); - frm.filters = frappe.dashboards.chart_sources[frm.doc.source].filters; - frm.trigger('render_filters_table'); - }); + if (frm.doc.source) { + frappe.xcall('frappe.desk.doctype.dashboard_chart_source.dashboard_chart_source.get_config', {name: frm.doc.source}) + .then(config => { + frappe.dom.eval(config); + frm.chart_filters = frappe.dashboards.chart_sources[frm.doc.source].filters; + frm.trigger('render_filters_table'); + }); + } else { + frm.chart_filters = []; + frm.trigger('render_filters_table'); + } } else { // standard filters if (frm.doc.document_type) { // allow all link and select fields as filters - frm.filters = []; + frm.chart_filters = []; frappe.model.with_doctype(frm.doc.document_type, () => { frappe.get_meta(frm.doc.document_type).fields.map(df => { if (['Link', 'Select'].includes(df.fieldtype)) { @@ -95,12 +100,11 @@ 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; - // no default - - if (!df.read_only && !df.hidden) { - frm.filters.push(_df); - } + frm.chart_filters.push(_df); } frm.trigger('render_filters_table'); }); @@ -113,7 +117,7 @@ frappe.ui.form.on('Dashboard Chart', { render_filters_table: function(frm) { frm.set_df_property("filters_section", "hidden", 0); - let fields = frm.filters; + let fields = frm.chart_filters; let wrapper = $(frm.get_field('filters_json').wrapper).empty(); let table = $(` 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..d99cc64436 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], + ["Dynamic Link", "link_doctype", "=", participant.reference_doctype], + ["Dynamic 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")], + ["Dynamic Link", "link_doctype", "=", deleted_participant.reference_doctype], + ["Dynamic 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.json b/frappe/desk/doctype/todo/todo.json index 09b0610215..4b70086648 100644 --- a/frappe/desk/doctype/todo/todo.json +++ b/frappe/desk/doctype/todo/todo.json @@ -21,6 +21,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "description_and_status", "fieldtype": "Section Break", "hidden": 0, @@ -53,6 +54,7 @@ "collapsible": 0, "columns": 0, "default": "Open", + "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", "hidden": 0, @@ -86,6 +88,7 @@ "collapsible": 0, "columns": 0, "default": "Medium", + "fetch_if_empty": 0, "fieldname": "priority", "fieldtype": "Select", "hidden": 0, @@ -120,6 +123,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_2", "fieldtype": "Column Break", "hidden": 0, @@ -150,6 +154,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "color", "fieldtype": "Color", "hidden": 0, @@ -157,7 +162,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 1, + "in_list_view": 0, "in_standard_filter": 0, "label": "Color", "length": 0, @@ -182,6 +187,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "date", "fieldtype": "Date", "hidden": 0, @@ -189,7 +195,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 1, + "in_list_view": 0, "in_standard_filter": 1, "label": "Due Date", "length": 0, @@ -215,6 +221,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "owner", "fieldtype": "Link", "hidden": 0, @@ -247,6 +254,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "description_section", "fieldtype": "Section Break", "hidden": 0, @@ -279,6 +287,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "description", "fieldtype": "Text Editor", "hidden": 0, @@ -314,6 +323,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "section_break_6", "fieldtype": "Section Break", "hidden": 0, @@ -345,6 +355,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "reference_type", "fieldtype": "Link", "hidden": 0, @@ -352,7 +363,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Reference Type", "length": 0, @@ -379,6 +390,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "reference_name", "fieldtype": "Dynamic Link", "hidden": 0, @@ -413,6 +425,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_10", "fieldtype": "Column Break", "hidden": 0, @@ -443,6 +456,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "role", "fieldtype": "Link", "hidden": 0, @@ -477,6 +491,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "assigned_by", "fieldtype": "Link", "hidden": 0, @@ -510,6 +525,7 @@ "collapsible": 0, "columns": 0, "fetch_from": "assigned_by.full_name", + "fetch_if_empty": 0, "fieldname": "assigned_by_full_name", "fieldtype": "Read Only", "hidden": 0, @@ -543,6 +559,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "sender", "fieldtype": "Data", "hidden": 1, @@ -575,6 +592,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "assignment_rule", "fieldtype": "Link", "hidden": 0, @@ -603,17 +621,16 @@ } ], "has_web_view": 0, - "hide_heading": 0, "hide_toolbar": 0, "icon": "fa fa-check", "idx": 2, - "image_view": 0, "in_create": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-03-07 16:11:25.764549", + "menu_index": 0, + "modified": "2019-04-24 15:45:23.290491", "modified_by": "Administrator", "module": "Desk", "name": "ToDo", @@ -660,7 +677,6 @@ ], "quick_entry": 1, "read_only": 0, - "read_only_onload": 0, "search_fields": "description, reference_type, reference_name", "show_name_in_global_search": 0, "sort_order": "DESC", diff --git a/frappe/desk/doctype/todo/todo.py b/frappe/desk/doctype/todo/todo.py index 45c3874806..16530c23dc 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 `tabDynamic 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/doctype/todo/todo_list.js b/frappe/desk/doctype/todo/todo_list.js index 6200e85dca..53564cc017 100644 --- a/frappe/desk/doctype/todo/todo_list.js +++ b/frappe/desk/doctype/todo/todo_list.js @@ -1,4 +1,7 @@ frappe.listview_settings['ToDo'] = { + hide_name_column: true, + add_fields: ["reference_type", "reference_name"], + onload: function(me) { if (!frappe.route_options) { frappe.route_options = { @@ -8,7 +11,22 @@ frappe.listview_settings['ToDo'] = { } me.page.set_title(__("To Do")); }, - hide_name_column: true, + + button: { + show: function(doc) { + return doc.reference_name; + }, + get_label: function() { + return __('Open'); + }, + get_description: function(doc) { + return __('Open {0}', [`${doc.reference_type} ${doc.reference_name}`]) + }, + action: function(doc) { + frappe.set_route('Form', doc.reference_type, doc.reference_name); + } + }, + refresh: function(me) { if (me.todo_sidebar_setup) return; @@ -19,5 +37,4 @@ frappe.listview_settings['ToDo'] = { me.todo_sidebar_setup = true; }, - add_fields: ["reference_type", "reference_name"], } \ No newline at end of file diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 4b3a607ec5..38856d5068 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.desk.form.document_follow import follow_document +from frappe.utils import cint import frappe.share class DuplicateToDoError(frappe.ValidationError): pass @@ -177,7 +178,7 @@ def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE', 'notify': notify } - if arg and arg.get("notify"): + if arg and cint(arg.get("notify")): _notify(arg) def _notify(args): 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 3107c97895..2985a8858e 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,13 +97,16 @@ 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), - "is_document_followed": is_document_followed(doc.doctype, doc.name, frappe.session.user), - "document_follow_enabled": frappe.db.get_value("User", frappe.session.user, "document_follow_notify") + "milestones": get_milestones(doc.doctype, doc.name), + "is_document_followed": is_document_followed(doc.doctype, doc.name, frappe.session.user) } +def get_milestones(doctype, name): + return frappe.db.get_all('Milestone', fields = ['creation', 'owner', 'track_field', 'value'], + filters=dict(reference_type=doctype, reference_name=name)) + def get_attachments(dt, dn): return frappe.get_all("File", fields=["name", "file_name", "file_url", "is_private"], filters = {"attached_to_name": dn, "attached_to_doctype": dt}) @@ -160,36 +160,50 @@ 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`, - `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`''' + fields = ''' + `tabCommunication`.name, `tabCommunication`.communication_type, `tabCommunication`.communication_medium, + `tabCommunication`.comment_type, `tabCommunication`.communication_date, `tabCommunication`.content, + `tabCommunication`.sender, `tabCommunication`.sender_full_name, `tabCommunication`.cc, `tabCommunication`.bcc, + `tabCommunication`.creation, `tabCommunication`.subject, `tabCommunication`.delivery_status, + `tabCommunication`._liked_by, `tabCommunication`.reference_doctype, `tabCommunication`.reference_name, + `tabCommunication`.read_by_recipient, `tabCommunication`.rating + ''' - conditions = '''communication_type in ('Communication', 'Feedback') - and ( - (reference_doctype=%(doctype)s and reference_name=%(name)s) + conditions = ''' + `tabCommunication`.communication_type in ('Communication', 'Feedback') + and ( + (`tabCommunication`.reference_doctype=%(doctype)s and `tabCommunication`.reference_name=%(name)s) or ( - (timeline_doctype=%(doctype)s and timeline_name=%(name)s) - and (communication_type='Communication') + (`tabDynamic Link`.link_doctype=%(doctype)s and `tabDynamic Link`.link_name=%(name)s) + and (`tabCommunication`.communication_type='Communication') ) - )''' - + ) + ''' if after: # find after a particular date - conditions+= ' and creation > {0}'.format(after) + conditions += ''' + and `tabCommunication`.creation > {0} + '''.format(after) if doctype=='User': - conditions+= " and not (reference_doctype='User' and communication_type='Communication')" + conditions += ''' + and not (`tabCommunication`.reference_doctype='User' and `tabCommunication`.communication_type='Communication') + ''' - communications = frappe.db.sql("""select {fields} + communications = frappe.db.sql(''' + select distinct {fields} from `tabCommunication` + inner join `tabDynamic Link` + on `tabCommunication`.name=`tabDynamic Link`.parent 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) + order by `tabCommunication`.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) return communications @@ -218,21 +232,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 = [] @@ -244,4 +243,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 11596d7058..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) @@ -224,7 +226,7 @@ def add_data_to_custom_columns(columns, result): fieldname = column['fieldname'] key = (column['doctype'], fieldname) link_field = column['link_field'] - row[fieldname] = custom_fields_data.get(key, {}).get(row[link_field]) + row[fieldname] = custom_fields_data.get(key, {}).get(row.get(link_field)) return data @@ -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 ba8ab37a99..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)); } @@ -99,6 +109,14 @@ frappe.ui.form.on('Auto Email Report', { dialog.show(); dialog.set_values(filters); }) + + // populate dynamic date field selection + let date_fields = report_filters + .filter(df => df.fieldtype === 'Date') + .map(df => ({ label: df.label, value: df.fieldname })); + frm.set_df_property('from_date_field', 'options', date_fields); + frm.set_df_property('to_date_field', 'options', date_fields); + frm.toggle_display('dynamic_report_filters_section', date_fields.length > 0); } } }); 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 f04f34cdba..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,771 +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", + "allow_rename": 1, + "creation": "2016-09-01 01:34:34.985457", + "doctype": "DocType", + "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, - "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 - }, + "fieldname": "report", + "fieldtype": "Link", + "label": "Report", + "options": "Report", + "reqd": 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": 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 - }, + "default": "User", + "fieldname": "user", + "fieldtype": "Link", + "label": "Based on Permissions For User", + "options": "User", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "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 - }, + "default": "1", + "fieldname": "enabled", + "fieldtype": "Check", + "label": "Enabled" + }, { - "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, - "fetch_from": "report.report_type", - "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 - }, + "fetch_from": "report.report_type", + "fieldname": "report_type", + "fieldtype": "Read Only", + "label": "Report Type" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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 - }, + "fieldname": "filter_data", + "fieldtype": "Section Break", + "label": "Filter Data" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "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 - }, + "default": "1", + "fieldname": "send_if_data", + "fieldtype": "Check", + "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", - "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 - }, + "depends_on": "eval:doc.report_type=='Report Builder'", + "description": "Zero means send records updated at anytime", + "fieldname": "data_modified_till", + "fieldtype": "Int", + "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": "", - "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 - }, + "default": "100", + "fieldname": "no_of_rows", + "fieldtype": "Int", + "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'", - "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 - }, + "collapsible": 1, + "depends_on": "eval:doc.report_type !== 'Report Builder'", + "fieldname": "report_filters", + "fieldtype": "Section Break", + "label": "Report Filters" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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 - }, + "fieldname": "filters_display", + "fieldtype": "HTML", + "label": "Filters Display" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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 - }, + "fieldname": "filters", + "fieldtype": "Text", + "hidden": 1, + "label": "Filters" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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 - }, + "fieldname": "filter_meta", + "fieldtype": "Text", + "hidden": 1, + "label": "Filter Meta", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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 - }, + "collapsible": 1, + "depends_on": "eval:doc.report_type !== 'Report Builder'", + "fieldname": "dynamic_report_filters_section", + "fieldtype": "Section Break", + "label": "Dynamic Report Filters" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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 - }, + "fieldname": "from_date_field", + "fieldtype": "Select", + "label": "From Date Field" + }, { - "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'", - "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 - }, + "fieldname": "to_date_field", + "fieldtype": "Select", + "label": "To Date Field" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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 - }, + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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 - }, + "fieldname": "dynamic_date_period", + "fieldtype": "Select", + "label": "Period", + "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, - "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 - }, + "fieldname": "email_settings", + "fieldtype": "Section Break", + "label": "Email Settings" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 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 - }, + "fieldname": "email_to", + "fieldtype": "Small Text", + "label": "Email To", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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 + "default": "Monday", + "depends_on": "eval:doc.frequency=='Weekly'", + "fieldname": "day_of_week", + "fieldtype": "Select", + "label": "Day of Week", + "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "fieldname": "frequency", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Frequency", + "options": "Daily\nWeekdays\nWeekly\nMonthly", + "reqd": 1 + }, + { + "fieldname": "format", + "fieldtype": "Select", + "label": "Format", + "options": "HTML\nXLSX\nCSV", + "reqd": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_15", + "fieldtype": "Section Break", + "label": "Message" + }, + { + "fieldname": "description", + "fieldtype": "Text Editor", + "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_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-11-13 01:59:17.816718", - "modified_by": "Administrator", - "module": "Email", - "name": "Auto Email Report", - "name_case": "", - "owner": "Administrator", + ], + "modified": "2019-05-09 22:38:27.570890", + "modified_by": "Administrator", + "module": "Email", + "name": "Auto Email Report", + "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, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, "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, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Report Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "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 + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.py b/frappe/email/doctype/auto_email_report/auto_email_report.py index c0a9c71d34..b06d3f7dfe 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.py +++ b/frappe/email/doctype/auto_email_report/auto_email_report.py @@ -5,14 +5,13 @@ from __future__ import unicode_literals import calendar -import json from datetime import timedelta import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import (format_time, get_link_to_form, get_url_to_report, - global_date_format, now, now_datetime, validate_email_address) + global_date_format, now, now_datetime, validate_email_address, today, add_to_date) from frappe.utils.csvutils import to_csv from frappe.utils.xlsxutils import make_xlsx @@ -58,10 +57,14 @@ class AutoEmailReport(Document): '''Returns file in for the report in given format''' report = frappe.get_doc('Report', self.report) + self.filters = frappe.parse_json(self.filters) if self.filters else {} + if self.report_type=='Report Builder' and self.data_modified_till: - self.filters = json.loads(self.filters) if self.filters else {} self.filters['modified'] = ('>', now_datetime() - timedelta(hours=self.data_modified_till)) + if self.report_type != 'Report Builder' and self.dynamic_date_filters_set(): + self.prepare_dynamic_filters() + columns, data = report.get_data(limit=self.no_of_rows or 100, user = self.user, filters = self.filters, as_dict=True) @@ -121,6 +124,24 @@ class AutoEmailReport(Document): def get_file_name(self): return "{0}.{1}".format(self.report.replace(" ", "-").replace("/", "-"), self.format.lower()) + def prepare_dynamic_filters(self): + self.filters = frappe.parse_json(self.filters) + + to_date = today() + from_date_value = { + 'Daily': ('days', -1), + 'Weekly': ('weeks', -1), + 'Monthly': ('months', -1), + 'Quarterly': ('months', -3), + 'Half Yearly': ('months', -6), + 'Yearly': ('years', -1) + }[self.dynamic_date_period] + + from_date = add_to_date(to_date, **{from_date_value[0]: from_date_value[1]}) + + self.filters[self.from_date_field] = from_date + self.filters[self.to_date_field] = to_date + def send(self): if self.filter_meta and not self.filters: frappe.throw(_("Please set filters value in Report Filter table.")) @@ -150,6 +171,9 @@ class AutoEmailReport(Document): reference_name = self.name ) + def dynamic_date_filters_set(self): + return self.dynamic_date_period and self.from_date_field and self.to_date_field + @frappe.whitelist() def download(name): '''Download report locally''' diff --git a/frappe/email/doctype/auto_email_report/test_auto_email_report.py b/frappe/email/doctype/auto_email_report/test_auto_email_report.py index 0595d9a43d..651fee9e51 100644 --- a/frappe/email/doctype/auto_email_report/test_auto_email_report.py +++ b/frappe/email/doctype/auto_email_report/test_auto_email_report.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest, json -from frappe.utils import get_link_to_form +from frappe.utils import get_link_to_form, today, add_to_date # test_records = frappe.get_test_records('Auto Email Report') @@ -13,17 +13,7 @@ class TestAutoEmailReport(unittest.TestCase): def test_auto_email(self): frappe.delete_doc('Auto Email Report', 'Permitted Documents For User') - auto_email_report = frappe.get_doc(dict( - doctype='Auto Email Report', - report='Permitted Documents For User', - report_type='Script Report', - user='Administrator', - enabled=1, - email_to='test@example.com', - format='HTML', - frequency='Daily', - filters=json.dumps(dict(user='Administrator', doctype='DocType')) - )).insert() + auto_email_report = get_auto_email_report() data = auto_email_report.get_report_content() @@ -38,3 +28,34 @@ class TestAutoEmailReport(unittest.TestCase): data = auto_email_report.get_report_content() + + def test_dynamic_date_filters(self): + auto_email_report = get_auto_email_report() + + auto_email_report.dynamic_date_period = 'Weekly' + auto_email_report.from_date_field = 'from_date' + auto_email_report.to_date_field = 'to_date' + + auto_email_report.prepare_dynamic_filters() + + self.assertEqual(auto_email_report.filters['from_date'], add_to_date(today(), weeks=-1)) + self.assertEqual(auto_email_report.filters['to_date'], today()) + + +def get_auto_email_report(): + if not frappe.db.exists('Auto Email Report', 'Permitted Documents For User'): + auto_email_report = frappe.get_doc(dict( + doctype='Auto Email Report', + report='Permitted Documents For User', + report_type='Script Report', + user='Administrator', + enabled=1, + email_to='test@example.com', + format='HTML', + frequency='Daily', + filters=json.dumps(dict(user='Administrator', doctype='DocType')) + )).insert() + else: + auto_email_report = frappe.get_doc('Auto Email Report', 'Permitted Documents For User') + + return auto_email_report \ 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..ac16f12477 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("Dynamic Link", {"parent": name.name}) \ No newline at end of file diff --git a/frappe/email/inbox.py b/frappe/email/inbox.py index 9df4218d71..9d15b387e3 100644 --- a/frappe/email/inbox.py +++ b/frappe/email/inbox.py @@ -118,67 +118,4 @@ def link_communication_to_document(doc, reference_doctype, reference_name, ignor doc.reference_doctype = reference_doctype doc.reference_name = reference_name doc.status = "Linked" - doc.save(ignore_permissions=True) - -@frappe.whitelist() -def make_issue_from_communication(communication, ignore_communication_links=False): - """ raise a issue from email """ - - doc = frappe.get_doc("Communication", communication) - issue = frappe.get_doc({ - "doctype": "Issue", - "subject": doc.subject, - "communication_medium": doc.communication_medium, - "raised_by": doc.sender or "", - "raised_by_phone": doc.phone_no or "" - }).insert(ignore_permissions=True) - - link_communication_to_document(doc, "Issue", issue.name, ignore_communication_links) - - return issue.name - -@frappe.whitelist() -def make_lead_from_communication(communication, ignore_communication_links=False): - """ raise a issue from email """ - - doc = frappe.get_doc("Communication", communication) - lead_name = None - if doc.sender: - lead_name = frappe.db.get_value("Lead", {"email_id": doc.sender}) - if not lead_name and doc.mobile_no: - lead_name = frappe.db.get_value("Lead", {"mobile_no": doc.phone_no}) - if not lead_name: - lead = frappe.get_doc({ - "doctype": "Lead", - "lead_name": doc.sender_full_name, - "email_id": doc.sender, - "mobile_no": doc.phone_no - }) - lead.flags.ignore_mandatory = True - lead.flags.ignore_permissions = True - lead.insert() - - lead_name = lead.name - - link_communication_to_document(doc, "Lead", lead_name, ignore_communication_links) - return lead_name - -@frappe.whitelist() -def make_opportunity_from_communication(communication, ignore_communication_links=False): - doc = frappe.get_doc("Communication", communication) - - lead = doc.reference_name if doc.reference_doctype == "Lead" else None - if not lead: - lead = make_lead_from_communication(communication, ignore_communication_links=True) - - enquiry_from = "Lead" - - opportunity = frappe.get_doc({ - "doctype": "Opportunity", - "enquiry_from": enquiry_from, - "lead": lead - }).insert(ignore_permissions=True) - - link_communication_to_document(doc, "Opportunity", opportunity.name, ignore_communication_links) - - return opportunity.name + doc.save(ignore_permissions=True) \ No newline at end of file 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/handler.py b/frappe/handler.py index 7a040871ad..71601028f1 100755 --- a/frappe/handler.py +++ b/frappe/handler.py @@ -8,6 +8,7 @@ import frappe.utils import frappe.sessions import frappe.desk.form.run_method from frappe.utils.response import build_response +from frappe.utils import cint from werkzeug.wrappers import Response from six import string_types @@ -138,6 +139,46 @@ def uploadfile(): return ret +@frappe.whitelist() +def upload_file(): + files = frappe.request.files + is_private = frappe.form_dict.is_private + doctype = frappe.form_dict.doctype + docname = frappe.form_dict.docname + fieldname = frappe.form_dict.fieldname + file_url = frappe.form_dict.file_url + folder = frappe.form_dict.folder or 'Home' + method = frappe.form_dict.method + content = None + filename = None + + if 'file' in files: + file = files['file'] + content = file.stream.read() + filename = file.filename + + frappe.local.uploaded_file = content + frappe.local.uploaded_filename = filename + + if method: + method = frappe.get_attr(method) + is_whitelisted(method) + return method() + else: + ret = frappe.get_doc({ + "doctype": "File", + "attached_to_doctype": doctype, + "attached_to_name": docname, + "attached_to_field": fieldname, + "folder": folder, + "file_name": filename, + "file_url": file_url, + "is_private": cint(is_private), + "content": content + }) + ret.save() + return ret + def get_attr(cmd): """get method object from cmd""" diff --git a/frappe/hooks.py b/frappe/hooks.py index 9865f6f4ac..52e2e1e4a0 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -118,7 +118,7 @@ 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", "on_cancel": [ @@ -130,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" @@ -156,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" @@ -171,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", @@ -284,4 +284,4 @@ user_privacy_documents = [ 'applies_to_website_user': 1 }, -] \ No newline at end of file +] diff --git a/frappe/integrations/doctype/gsuite_templates/gsuite_templates.js b/frappe/integrations/doctype/gsuite_templates/gsuite_templates.js index d046709230..ddaa740afd 100644 --- a/frappe/integrations/doctype/gsuite_templates/gsuite_templates.js +++ b/frappe/integrations/doctype/gsuite_templates/gsuite_templates.js @@ -3,6 +3,39 @@ frappe.ui.form.on('GSuite Templates', { refresh: function(frm) { - - } + if (frm.is_new()) { + // if doc is new, get all options immediately + frm.trigger('set_available_docs'); + frm.trigger('set_available_folders'); + } + }, + set_available_docs: function(frm) { + frappe.call({ + // get documents from Google Drive + method: 'frappe.integrations.doctype.gsuite_templates.gsuite_templates.get_gdrive_docs', + callback: function(res) { + // set available documents as options + set_gsuite_template_options(frm, 'template_id', res); + } + }); + }, + set_available_folders: function(frm) { + frappe.call({ + // get folders from Google Drive + method: 'frappe.integrations.doctype.gsuite_templates.gsuite_templates.get_gdrive_folders', + callback: function(res) { + // set available folders as options + set_gsuite_template_options(frm, 'destination_id', res); + } + }); + }, }); + +const set_gsuite_template_options = function(frm, field, data) { + var options = []; + (data.message || []).forEach(function(row){ + options.push({'value': row.id, 'label': row.name}); + }); + frm.set_df_property(field, 'options', options); +}; + diff --git a/frappe/integrations/doctype/gsuite_templates/gsuite_templates.json b/frappe/integrations/doctype/gsuite_templates/gsuite_templates.json index 6543e8847e..e0e047a671 100644 --- a/frappe/integrations/doctype/gsuite_templates/gsuite_templates.json +++ b/frappe/integrations/doctype/gsuite_templates/gsuite_templates.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, @@ -15,10 +16,12 @@ "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": "template_name", "fieldtype": "Data", "hidden": 0, @@ -26,7 +29,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Template Name", "length": 0, @@ -41,14 +44,17 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "unique": 0 + "translatable": 0, + "unique": 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": "related_doctype", "fieldtype": "Link", "hidden": 0, @@ -72,22 +78,25 @@ "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": "template_id", - "fieldtype": "Data", + "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Template ID", "length": 0, @@ -101,16 +110,19 @@ "report_hide": 0, "reqd": 1, "search_index": 0, - "set_only_once": 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": "New Document for {name} ", + "fetch_if_empty": 0, "fieldname": "document_name", "fieldtype": "Data", "hidden": 0, @@ -118,7 +130,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Document Name", "length": 0, @@ -133,16 +145,19 @@ "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": "destination_id", - "fieldtype": "Data", + "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -162,21 +177,20 @@ "report_hide": 0, "reqd": 0, "search_index": 0, - "set_only_once": 0, + "set_only_once": 1, + "translatable": 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-12 16:50:08.074882", + "modified": "2019-04-14 00:13:49.999149", "modified_by": "Administrator", "module": "Integrations", "name": "GSuite Templates", @@ -185,7 +199,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -206,10 +219,10 @@ ], "quick_entry": 0, "read_only": 0, - "read_only_onload": 0, "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", "track_changes": 1, - "track_seen": 0 + "track_seen": 0, + "track_views": 0 } \ No newline at end of file diff --git a/frappe/integrations/doctype/gsuite_templates/gsuite_templates.py b/frappe/integrations/doctype/gsuite_templates/gsuite_templates.py index ad9f8145de..66028a13d4 100644 --- a/frappe/integrations/doctype/gsuite_templates/gsuite_templates.py +++ b/frappe/integrations/doctype/gsuite_templates/gsuite_templates.py @@ -3,6 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals +import requests import frappe from frappe import _ from frappe.model.document import Document @@ -48,3 +49,45 @@ def create_gsuite_doc(doctype, docname, gs_template=None): "is_private": _file.is_private, "comment": comment.as_dict() if comment else {} } + +@frappe.whitelist() +def get_gdrive_docs(): + """ Return a list of Google Docs files in Google Drive """ + return get_gdrive_files('application/vnd.google-apps.document') + +@frappe.whitelist() +def get_gdrive_folders(): + """ Return a list of folders in Google Drive """ + return get_gdrive_files('application/vnd.google-apps.folder') + +def get_gdrive_files(mime_type): + """ Get a list of files of the specified mime_type from Google Drive + + returns [ + { + "kind": "drive#file", + "id": "sf_lk-U6lYhVvdgsdf98cvkbj87rl6piFtnLEN9oNsrg", + "name": "My File Name", + "mimeType": mime_type + } + ] + """ + settings = frappe.get_single("GSuite Settings") + token = settings.get_access_token() + url = 'https://www.googleapis.com/drive/v3/files' + + params = { + "q": "mimeType='{}'".format(mime_type) + } + + headers = { + 'Authorization': 'Bearer {}'.format(token), + 'Accept': 'application/json' + } + + try: + response = requests.get(url, params=params, headers=headers) + except requests.exceptions.RequestException as err: + frappe.throw(err) + + return response.json().get('files') 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/oauth2_logins.py b/frappe/integrations/oauth2_logins.py index 86a90078c6..aecbe3be66 100644 --- a/frappe/integrations/oauth2_logins.py +++ b/frappe/integrations/oauth2_logins.py @@ -2,14 +2,16 @@ # MIT License. See license.txt from __future__ import unicode_literals + import frappe import frappe.utils -from frappe.utils.oauth import login_via_oauth2, login_via_oauth2_id_token -import json +from frappe.utils.oauth import (login_via_oauth2, login_via_oauth2_id_token, + oauth_decoder) + @frappe.whitelist(allow_guest=True) def login_via_google(code, state): - login_via_oauth2("google", code, state, decoder=json.loads) + login_via_oauth2("google", code, state, decoder=oauth_decoder) @frappe.whitelist(allow_guest=True) def login_via_github(code, state): @@ -17,20 +19,20 @@ def login_via_github(code, state): @frappe.whitelist(allow_guest=True) def login_via_facebook(code, state): - login_via_oauth2("facebook", code, state, decoder=json.loads) + login_via_oauth2("facebook", code, state, decoder=oauth_decoder) @frappe.whitelist(allow_guest=True) def login_via_frappe(code, state): - login_via_oauth2("frappe", code, state, decoder=json.loads) + login_via_oauth2("frappe", code, state, decoder=oauth_decoder) @frappe.whitelist(allow_guest=True) def login_via_office365(code, state): - login_via_oauth2_id_token("office_365", code, state, decoder=json.loads) + login_via_oauth2_id_token("office_365", code, state, decoder=oauth_decoder) @frappe.whitelist(allow_guest=True) def login_via_salesforce(code, state): - login_via_oauth2("salesforce", code, state, decoder=json.loads) + login_via_oauth2("salesforce", code, state, decoder=oauth_decoder) @frappe.whitelist(allow_guest=True) def login_via_fairlogin(code, state): - login_via_oauth2("fairlogin", code, state, decoder=json.loads) + login_via_oauth2("fairlogin", code, state, decoder=oauth_decoder) 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/migrate.py b/frappe/migrate.py index 4057c99b63..1128f6eff3 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -3,6 +3,8 @@ from __future__ import unicode_literals +import json +import os import frappe import frappe.translate import frappe.modules.patch_handler @@ -10,9 +12,10 @@ import frappe.model.sync from frappe.utils.fixtures import sync_fixtures from frappe.cache_manager import clear_global_cache from frappe.desk.notifications import clear_notifications -from frappe.website import render, router +from frappe.website import render from frappe.core.doctype.language.language import sync_languages from frappe.modules.utils import sync_customizations +from frappe.utils import global_search def migrate(verbose=True, rebuild_website=False): '''Migrate all apps to the latest version, will: @@ -25,39 +28,52 @@ def migrate(verbose=True, rebuild_website=False): - sync web pages (from /www) - run after migrate hooks ''' - frappe.flags.in_migrate = True - clear_global_cache() - #run before_migrate hooks - for app in frappe.get_installed_apps(): - for fn in frappe.get_hooks('before_migrate', app_name=app): - frappe.get_attr(fn)() + touched_tables_file = frappe.get_site_path('touched_tables.json') + if os.path.exists(touched_tables_file): + os.remove(touched_tables_file) - # run patches - frappe.modules.patch_handler.run_all() - # sync - frappe.model.sync.sync_all(verbose=verbose) - frappe.translate.clear_cache() - sync_fixtures() - sync_customizations() - sync_languages() + try: + frappe.flags.touched_tables = set() + frappe.flags.in_migrate = True - frappe.get_doc('Portal Settings', 'Portal Settings').sync_menu() + clear_global_cache() - # syncs statics - render.clear_cache() + #run before_migrate hooks + for app in frappe.get_installed_apps(): + for fn in frappe.get_hooks('before_migrate', app_name=app): + frappe.get_attr(fn)() - # add static pages to global search - router.sync_global_search() + # run patches + frappe.modules.patch_handler.run_all() + # sync + frappe.model.sync.sync_all(verbose=verbose) + frappe.translate.clear_cache() + sync_fixtures() + sync_customizations() + sync_languages() - #run after_migrate hooks - for app in frappe.get_installed_apps(): - for fn in frappe.get_hooks('after_migrate', app_name=app): - frappe.get_attr(fn)() + frappe.get_doc('Portal Settings', 'Portal Settings').sync_menu() - frappe.db.commit() + # syncs statics + render.clear_cache() - clear_notifications() + # add static pages to global search + global_search.update_global_search_for_all_web_pages() + + #run after_migrate hooks + for app in frappe.get_installed_apps(): + for fn in frappe.get_hooks('after_migrate', app_name=app): + frappe.get_attr(fn)() + + frappe.db.commit() + + clear_notifications() + + frappe.publish_realtime("version-update") + frappe.flags.in_migrate = False + finally: + with open(touched_tables_file, 'w') as f: + json.dump(list(frappe.flags.touched_tables), f, sort_keys=True, indent=4) + frappe.flags.touched_tables.clear() - frappe.publish_realtime("version-update") - frappe.flags.in_migrate = False diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index 5f804e3c36..3a8868ec2c 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -76,27 +76,43 @@ 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 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) + # 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/create_new.py b/frappe/model/create_new.py index a4e50f704c..e882508c99 100644 --- a/frappe/model/create_new.py +++ b/frappe/model/create_new.py @@ -12,7 +12,7 @@ import frappe.defaults from frappe.model import data_fieldtypes from frappe.utils import nowdate, nowtime, now_datetime from frappe.core.doctype.user_permission.user_permission import get_user_permissions -from frappe.permissions import get_allowed_docs_for_doctype +from frappe.permissions import filter_allowed_docs_for_doctype def get_new_doc(doctype, parent_doc = None, parentfield = None, as_dict=False): if doctype not in frappe.local.new_doc_templates: @@ -56,12 +56,12 @@ def set_user_and_static_default_values(doc): if df.fieldtype in data_fieldtypes: # user permissions for link options doctype_user_permissions = user_permissions.get(df.options, []) - # Allowed records for the reference doctype (link field) - allowed_records = get_allowed_docs_for_doctype(doctype_user_permissions, df.parent) + # Allowed records for the reference doctype (link field) along with default doc + allowed_records, default_doc = filter_allowed_docs_for_doctype(doctype_user_permissions, df.parent, with_default_doc=True) - user_default_value = get_user_default_value(df, defaults, doctype_user_permissions, allowed_records) + user_default_value = get_user_default_value(df, defaults, doctype_user_permissions, allowed_records, default_doc) if user_default_value != None: - # do not set default if the field on which current field is dependent is not set + # do not set default if the field on which current field is dependent is not set if is_dependent_field_set(df.depends_on, doc): doc.set(df.fieldname, user_default_value) else: @@ -78,14 +78,14 @@ def is_dependent_field_set(fieldname, doc): if fieldname not in value_dict: return True return value_dict[fieldname] -def get_user_default_value(df, defaults, doctype_user_permissions, allowed_records): +def get_user_default_value(df, defaults, doctype_user_permissions, allowed_records, default_doc): # don't set defaults for "User" link field using User Permissions! if df.fieldtype == "Link" and df.options != "User": # 1 - look in user permissions only for document_type==Setup # We don't want to include permissions of transactions to be used for defaults. if (frappe.get_meta(df.options).document_type=="Setup" - and len(allowed_records)==1 and not df.ignore_user_permissions): - return allowed_records[0] + and not df.ignore_user_permissions and default_doc): + return default_doc # 2 - Look in user defaults user_default = defaults.get(df.fieldname) diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index 131374a1d6..8a557f3b3f 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -14,6 +14,8 @@ from frappe.model.naming import revert_series_if_last from frappe.utils.global_search import delete_for_document from six import string_types, integer_types +doctypes_to_skip = ("Communication", "ToDo", "DocShare", "Email Unsubscribe", "Activity Log", "File", "Version", "Document Follow", "Comment" , "View Log") + def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False, ignore_permissions=False, flags=None, ignore_on_trash=False, ignore_missing=True): """ @@ -64,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) @@ -195,7 +198,7 @@ def check_if_doc_is_linked(doc, method="Delete"): for item in frappe.db.get_values(link_dt, {link_field:doc.name}, ["name", "parent", "parenttype", "docstatus"], as_dict=True): linked_doctype = item.parenttype if item.parent else link_dt - if linked_doctype in ("Communication", "ToDo", "DocShare", "Email Unsubscribe", 'File', 'Version', "Activity Log", "Document Follow"): + if linked_doctype in doctypes_to_skip: # don't check for communication and todo! continue @@ -220,7 +223,7 @@ def check_if_doc_is_linked(doc, method="Delete"): def check_if_doc_is_dynamically_linked(doc, method="Delete"): '''Raise `frappe.LinkExistsError` if the document is dynamically linked''' for df in get_dynamic_link_map().get(doc.doctype, []): - if df.parent in ("Communication", "ToDo", "DocShare", "Email Unsubscribe", "Activity Log", 'File', 'Version', 'View Log', "Document Follow"): + if df.parent in doctypes_to_skip: # don't check for communication and todo! continue @@ -275,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') @@ -298,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 `tabDynamic Link` + where `tabDynamic Link`.link_doctype='{0}' and `tabDynamic 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 c71e8e1a16..e8add2d619 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -117,6 +117,12 @@ class Document(BaseDocument): # incorrect arguments. let's not proceed. raise ValueError('Illegal arguments') + @staticmethod + def whitelist(f): + """Decorator: Whitelist method to be called remotely via REST API.""" + f.whitelisted = True + return f + def reload(self): """Reload document from database""" self.load_from_db() @@ -224,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 @@ -253,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): @@ -370,13 +381,7 @@ class Document(BaseDocument): (self.name, self.doctype, fieldname)) def get_doc_before_save(self): - if not getattr(self, '_doc_before_save', None): - try: - self._doc_before_save = frappe.get_doc(self.doctype, self.name) - except frappe.DoesNotExistError: - self._doc_before_save = None - frappe.clear_last_message() - return self._doc_before_save + return getattr(self, '_doc_before_save', None) def set_new_name(self, force=False): """Calls `frappe.naming.se_new_name` for parent and child docs.""" @@ -834,11 +839,6 @@ class Document(BaseDocument): elif alert.event=='Method' and method == alert.method: _evaluate_alert(alert) - @staticmethod - def whitelist(f): - f.whitelisted = True - return f - @whitelist.__func__ def _submit(self): """Submit the document. Sets `docstatus` = 1, then saves.""" @@ -899,11 +899,12 @@ class Document(BaseDocument): def load_doc_before_save(self): '''Save load document from db before saving''' self._doc_before_save = None - if not (self.is_new() - and (getattr(self.meta, 'track_changes', False) - or self.meta.get_set_only_once_fields() - or self.meta.get_workflow())): - self.get_doc_before_save() + if not self.is_new(): + try: + self._doc_before_save = frappe.get_doc(self.doctype, self.name) + except frappe.DoesNotExistError: + self._doc_before_save = None + frappe.clear_last_message() def run_post_save_methods(self): """Run standard methods after `INSERT` or `UPDATE`. Standard Methods are: @@ -1018,12 +1019,6 @@ class Document(BaseDocument): if not frappe.flags.in_migrate: follow_document(self.doctype, self.name, frappe.session.user) - @staticmethod - def whitelist(f): - """Decorator: Whitelist method to be called remotely via REST API.""" - f.whitelisted = True - return f - @staticmethod def hook(f): """Decorator: Make method `hookable` (i.e. extensible by another app). 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/modules/export_file.py b/frappe/modules/export_file.py index 66a6a9f6b7..b904132530 100644 --- a/frappe/modules/export_file.py +++ b/frappe/modules/export_file.py @@ -23,6 +23,7 @@ def export_to_files(record_list=None, record_module=None, verbose=0, create_init def write_document_file(doc, record_module=None, create_init=True): newdoc = doc.as_dict(no_nulls=True) + doc.run_method("before_export", newdoc) # strip out default fields from children for df in doc.meta.get_table_fields(): @@ -38,7 +39,7 @@ def write_document_file(doc, record_module=None, create_init=True): # write the data file fname = scrub(doc.name) - with open(os.path.join(folder, fname +".json"),'w+') as txtfile: + with open(os.path.join(folder, fname + ".json"), 'w+') as txtfile: txtfile.write(frappe.as_json(newdoc)) def get_module_name(doc): diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index a1fa77d669..e250bfc42e 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals, print_function import frappe, os, json from frappe.modules import get_module_path, scrub_dt_dn from frappe.utils import get_datetime_str +from frappe.model.base_document import get_controller ignore_values = { "Report": ["disabled", "prepared_report"], @@ -97,8 +98,15 @@ def import_doc(docdict, force=False, data_import=False, pre_process=None, ignore_version=None, reset_permissions=False): frappe.flags.in_import = True docdict["__islocal"] = 1 + + controller = get_controller(docdict['doctype']) + if controller and hasattr(controller, 'prepare_for_import') and callable(getattr(controller, 'prepare_for_import')): + controller.prepare_for_import(docdict) + doc = frappe.get_doc(docdict) + doc.run_method("before_import") + doc.flags.ignore_version = ignore_version if pre_process: pre_process(doc) diff --git a/frappe/patches.txt b/frappe/patches.txt index d3b3ca02f1..911e793687 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -230,7 +230,6 @@ 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.delete_doc("Page", "modules", ignore_missing=True) @@ -238,4 +237,8 @@ frappe.patches.v11_0.set_default_letter_head_source frappe.patches.v12_0.setup_comments_from_communications 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 \ No newline at end of file +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_timeline_links_to_dynamic_links \ No newline at end of file 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_timeline_links_to_dynamic_links.py b/frappe/patches/v12_0/move_timeline_links_to_dynamic_links.py new file mode 100644 index 0000000000..873988c7f3 --- /dev/null +++ b/frappe/patches/v12_0/move_timeline_links_to_dynamic_links.py @@ -0,0 +1,46 @@ +from __future__ import unicode_literals + +import frappe + +def execute(): + frappe.reload_doc('core', 'doctype', 'communication') + + 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) + + for count, communication in enumerate(communications): + counter = 1 + if communication.timeline_doctype and communication.timeline_name: + values = [ + counter, frappe.generate_hash(length=10), 'timeline_links', 'Communication', communication.name, + communication.timeline_doctype, communication.timeline_name, communication.creation, communication.modified, + communication.modified_by + ] + execute_query(values) + counter += 1 + + if communication.link_doctype and communication.link_name: + values = [ + counter, frappe.generate_hash(length=10), 'timeline_links', 'Communication', communication.name, + communication.link_doctype, communication.link_name, communication.creation, communication.modified, + communication.modified_by + ] + execute_query(values) + +def execute_query(values): + try: + frappe.db.sql(""" + INSERT INTO `tabDynamic Link` + (`idx`, `name`, `parentfield`, `parenttype`, `parent`, `link_doctype`, `link_name`, `creation`, + `modified`, `modified_by`) + VALUES ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}) + """.format(values[0], values[1], values[2], values[3], values[4], values[5], values[6], values[7], values[8], values[9])) + except Exception as e: + values[1] = frappe.generate_hash(length=10) + execute_query(values) 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 new file mode 100644 index 0000000000..577dc68d94 --- /dev/null +++ b/frappe/patches/v12_0/update_print_format_type.py @@ -0,0 +1,13 @@ +import frappe + +def execute(): + frappe.db.sql(''' + UPDATE `tabPrint Format` + 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' + ''') diff --git a/frappe/patches/v5_0/convert_to_barracuda_and_utf8mb4.py b/frappe/patches/v5_0/convert_to_barracuda_and_utf8mb4.py index 0e4a334a76..0ea2ee2387 100644 --- a/frappe/patches/v5_0/convert_to_barracuda_and_utf8mb4.py +++ b/frappe/patches/v5_0/convert_to_barracuda_and_utf8mb4.py @@ -1,10 +1,10 @@ from __future__ import unicode_literals import frappe -from frappe.database.mariadb.setup_db import check_if_ready_for_barracuda +from frappe.database.mariadb.setup_db import check_database_settings from frappe.model.meta import trim_tables def execute(): - check_if_ready_for_barracuda() + check_database_settings() for table in frappe.db.get_tables(): frappe.db.sql_ddl("""alter table `{0}` ENGINE=InnoDB ROW_FORMAT=COMPRESSED""".format(table)) 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 6c3c5b7739..e5aa31d139 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -25,16 +25,18 @@ def print_has_permission_check_logs(func): frappe.flags['has_permission_check_logs'] = [] result = func(*args, **kwargs) self_perm_check = True if not kwargs.get('user') else kwargs.get('user') == frappe.session.user + raise_exception = False if kwargs.get('raise_exception') == False else True + # print only if access denied # and if user is checking his own permission - if not result and self_perm_check: + if not result and self_perm_check and raise_exception: msgprint(('
').join(frappe.flags.get('has_permission_check_logs'))) frappe.flags.pop('has_permission_check_logs', None) return result return inner @print_has_permission_check_logs -def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None): +def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None, raise_exception=True): """Returns True if user has permission `ptype` for given `doctype`. If `doc` is passed, it also checks user, share and owner permissions. @@ -395,7 +397,7 @@ def set_user_permission_if_allowed(doctype, name, user, with_message=False): if get_role_permissions(frappe.get_meta(doctype), user).set_user_permissions!=1: add_user_permission(doctype, name, user) -def add_user_permission(doctype, name, user, ignore_permissions=False, applicable_for=None): +def add_user_permission(doctype, name, user, ignore_permissions=False, applicable_for=None, is_default=0): '''Add user permission''' from frappe.core.doctype.user_permission.user_permission import user_permission_exists @@ -408,6 +410,7 @@ def add_user_permission(doctype, name, user, ignore_permissions=False, applicabl user=user, allow=doctype, for_value=name, + is_default=is_default, applicable_for=applicable_for, )).insert(ignore_permissions=ignore_permissions) @@ -523,9 +526,22 @@ def allow_everything(): return perm def get_allowed_docs_for_doctype(user_permissions, doctype): - '''Returns all the docs from the passed user_permission - that are allowed under provide doctype''' - return [d.get('doc') for d in user_permissions if not d.get('applicable_for') or d.get('applicable_for') == doctype] + ''' Returns all the docs from the passed user_permissions that are + allowed under provided doctype ''' + return filter_allowed_docs_for_doctype(user_permissions, doctype, with_default_doc=False) + +def filter_allowed_docs_for_doctype(user_permissions, doctype, with_default_doc=True): + ''' Returns all the docs from the passed user_permissions that are + allowed under provided doctype along with default doc value if with_default_doc is set ''' + allowed_doc = [] + default_doc = None + for doc in user_permissions: + if not doc.get('applicable_for') or doc.get('applicable_for') == doctype: + allowed_doc.append(doc.get('doc')) + if doc.get('is_default') or len(user_permissions) == 1 and with_default_doc: + default_doc = doc.get('doc') + + return (allowed_doc, default_doc) if with_default_doc else allowed_doc def push_perm_check_log(log): if frappe.flags.get('has_permission_check_logs') == None: return diff --git a/frappe/printing/doctype/print_format/print_format.json b/frappe/printing/doctype/print_format/print_format.json index f8c8f97105..d61bbda614 100644 --- a/frappe/printing/doctype/print_format/print_format.json +++ b/frappe/printing/doctype/print_format/print_format.json @@ -1,767 +1,905 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 1, - "autoname": "Prompt", - "beta": 0, - "creation": "2013-01-23 19:54:43", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 0, - "engine": "InnoDB", + "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-01-23 19:54:43", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "editable_grid": 0, + "engine": "InnoDB", "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "doc_type", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "DocType", - "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": 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": "module", - "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": "Module", - "length": 0, - "no_copy": 0, - "options": "Module Def", - "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": "disabled", - "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": "Disabled", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "No", - "fieldname": "standard", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Standard", - "length": 0, - "no_copy": 1, - "oldfieldname": "standard", - "oldfieldtype": "Select", - "options": "No\nYes", - "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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "custom_format", - "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 Format", - "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, - "depends_on": "custom_format", - "fieldname": "section_break_6", - "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, - "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": "Server", - "depends_on": "custom_format", - "description": "", - "fieldname": "print_format_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": "Print Format Type", - "length": 0, - "no_copy": 0, - "options": "Server\nClient\nJs", - "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": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "custom_format", - "fieldname": "html", - "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": "HTML", - "length": 0, - "no_copy": 0, - "oldfieldname": "html", - "oldfieldtype": "Text Editor", - "options": "HTML", - "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": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:!doc.custom_format", - "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": "Style 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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fieldname": "align_labels_right", - "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": "Align Labels to the Right", - "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": "0", - "fieldname": "show_section_headings", - "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": "Show Section Headings", - "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": "0", - "fieldname": "line_breaks", - "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": "Show Line Breaks after Sections", - "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_11", - "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, + "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": "doc_type", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "DocType", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "default_print_language", - "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": "Default Print Language", - "length": 0, - "no_copy": 0, - "options": "Language", - "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, + "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": 0, + "in_standard_filter": 0, + "label": "Module", + "length": 0, + "no_copy": 0, + "options": "Module Def", + "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Default", - "depends_on": "eval:!doc.custom_format", - "fieldname": "font", - "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": "Font", - "length": 0, - "no_copy": 0, - "options": "Default\nArial\nHelvetica\nVerdana\nMonospace", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "disabled", + "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": "Disabled", + "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "css_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, - "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, + "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_3", + "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "css", - "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": "Custom CSS", - "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, + "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": "standard", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Standard", + "length": 0, + "no_copy": 1, + "oldfieldname": "standard", + "oldfieldtype": "Select", + "options": "No\nYes", + "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "custom_html_help", - "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": "Custom HTML Help", - "length": 0, - "no_copy": 0, - "options": "

Custom CSS Help

\n\n

Notes:

\n\n
    \n
  1. All field groups (label + value) are set attributes data-fieldtype and data-fieldname
  2. \n
  3. All values are given class value
  4. \n
  5. All Section Breaks are given class section-break
  6. \n
  7. All Column Breaks are given class column-break
  8. \n
\n\n

Examples

\n\n

1. Left align integers

\n\n
[data-fieldtype=\"Int\"] .value { text-left: left; }
\n\n

1. Add border to sections except the last section

\n\n
.section-break { padding: 30px 0px; border-bottom: 1px solid #eee; }\n.section-break:last-child { padding-bottom: 0px; border-bottom: 0px;  }
\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, + "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_format", + "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 Format", + "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "custom_format", - "fieldname": "section_break_13", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "custom_format", + "fetch_if_empty": 0, + "fieldname": "section_break_6", + "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, + "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "custom_format", - "fieldname": "print_format_help", - "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": "Print Format Help", - "length": 0, - "no_copy": 0, - "options": "

Print Format Help

\n
\n

Introduction

\n

Print itemsFormats are rendered on the server side using the Jinja Templating Language. All forms have access to the doc object which contains information about the document that is being formatted. You can also access common utilities via the frappe module.

\n

For styling, the Boostrap CSS framework is provided and you can enjoy the full range of classes.

\n
\n

References

\n
    \n\t
  1. Jinja Tempalting Language: Reference
  2. \n\t
  3. Bootstrap CSS Framework
  4. \n
\n
\n

Example

\n
<h3>{{ doc.select_print_heading or \"Invoice\" }}</h3>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Customer Name</div>\n\t<div class=\"col-md-9\">{{ doc.customer_name }}</div>\n</div>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Date</div>\n\t<div class=\"col-md-9\">{{ doc.get_formatted(\"invoice_date\") }}</div>\n</div>\n<table class=\"table table-bordered\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<th>Sr</th>\n\t\t\t<th>Item Name</th>\n\t\t\t<th>Description</th>\n\t\t\t<th class=\"text-right\">Qty</th>\n\t\t\t<th class=\"text-right\">Rate</th>\n\t\t\t<th class=\"text-right\">Amount</th>\n\t\t</tr>\n\t\t{%- for row in doc.items -%}\n\t\t<tr>\n\t\t\t<td style=\"width: 3%;\">{{ row.idx }}</td>\n\t\t\t<td style=\"width: 20%;\">\n\t\t\t\t{{ row.item_name }}\n\t\t\t\t{% if row.item_code != row.item_name -%}\n\t\t\t\t<br>Item Code: {{ row.item_code}}\n\t\t\t\t{%- endif %}\n\t\t\t</td>\n\t\t\t<td style=\"width: 37%;\">\n\t\t\t\t<div style=\"border: 0px;\">{{ row.description }}</div></td>\n\t\t\t<td style=\"width: 10%; text-align: right;\">{{ row.qty }} {{ row.uom or row.stock_uom }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"rate\", doc) }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"amount\", doc) }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>
\n
\n

Common Functions

\n
\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n
doc.get_formatted(\"[fieldname]\", [parent_doc])Get document value formatted as Date, Currency etc. Pass parent doc for curreny type fields.
frappe.db.get_value(\"[doctype]\", \"[name]\", \"fieldname\")Get value from another document.
\n", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Jinja", + "depends_on": "custom_format", + "description": "", + "fetch_if_empty": 0, + "fieldname": "print_format_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": "Print Format Type", + "length": 0, + "no_copy": 0, + "options": "Jinja\nJS", + "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "format_data", - "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": "Format 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, + "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": "raw_printing", + "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": "Raw Printing", + "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "print_format_builder", - "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": "Print Format Builder", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:!doc.raw_printing", + "fetch_if_empty": 0, + "fieldname": "html", + "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": "HTML", + "length": 0, + "no_copy": 0, + "oldfieldname": "html", + "oldfieldtype": "Text Editor", + "options": "HTML", + "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": "raw_printing", + "description": "Any string-based printer languages can be used. Writing raw commands requires knowledge of the printer's native language provided by the printer manufacturer. Please refer to the developer manual provided by the printer manufacturer on how to write their native commands. These commands are rendered on the server side using the Jinja Templating Language.", + "fetch_if_empty": 0, + "fieldname": "raw_commands", + "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": "Raw Commands", + "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.custom_format", + "fetch_if_empty": 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": "Style 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, + "default": "0", + "fetch_if_empty": 0, + "fieldname": "align_labels_right", + "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": "Align Labels to the Right", + "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": "show_section_headings", + "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": "Show Section Headings", + "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": "line_breaks", + "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": "Show Line Breaks after Sections", + "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_11", + "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": "default_print_language", + "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": "Default Print Language", + "length": 0, + "no_copy": 0, + "options": "Language", + "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": "Default", + "depends_on": "eval:!doc.custom_format", + "fetch_if_empty": 0, + "fieldname": "font", + "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": "Font", + "length": 0, + "no_copy": 0, + "options": "Default\nArial\nHelvetica\nVerdana\nMonospace", + "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.raw_printing", + "fetch_if_empty": 0, + "fieldname": "css_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, + "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": "css", + "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": "Custom CSS", + "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_html_help", + "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": "Custom HTML Help", + "length": 0, + "no_copy": 0, + "options": "

Custom CSS Help

\n\n

Notes:

\n\n
    \n
  1. All field groups (label + value) are set attributes data-fieldtype and data-fieldname
  2. \n
  3. All values are given class value
  4. \n
  5. All Section Breaks are given class section-break
  6. \n
  7. All Column Breaks are given class column-break
  8. \n
\n\n

Examples

\n\n

1. Left align integers

\n\n
[data-fieldtype=\"Int\"] .value { text-left: left; }
\n\n

1. Add border to sections except the last section

\n\n
.section-break { padding: 30px 0px; border-bottom: 1px solid #eee; }\n.section-break:last-child { padding-bottom: 0px; border-bottom: 0px;  }
\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, + "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": "custom_format", + "fetch_if_empty": 0, + "fieldname": "section_break_13", + "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, + "depends_on": "custom_format", + "fetch_if_empty": 0, + "fieldname": "print_format_help", + "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": "Print Format Help", + "length": 0, + "no_copy": 0, + "options": "

Print Format Help

\n
\n

Introduction

\n

Print itemsFormats are rendered on the server side using the Jinja Templating Language. All forms have access to the doc object which contains information about the document that is being formatted. You can also access common utilities via the frappe module.

\n

For styling, the Boostrap CSS framework is provided and you can enjoy the full range of classes.

\n
\n

References

\n
    \n\t
  1. Jinja Tempalting Language: Reference
  2. \n\t
  3. Bootstrap CSS Framework
  4. \n
\n
\n

Example

\n
<h3>{{ doc.select_print_heading or \"Invoice\" }}</h3>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Customer Name</div>\n\t<div class=\"col-md-9\">{{ doc.customer_name }}</div>\n</div>\n<div class=\"row\">\n\t<div class=\"col-md-3 text-right\">Date</div>\n\t<div class=\"col-md-9\">{{ doc.get_formatted(\"invoice_date\") }}</div>\n</div>\n<table class=\"table table-bordered\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<th>Sr</th>\n\t\t\t<th>Item Name</th>\n\t\t\t<th>Description</th>\n\t\t\t<th class=\"text-right\">Qty</th>\n\t\t\t<th class=\"text-right\">Rate</th>\n\t\t\t<th class=\"text-right\">Amount</th>\n\t\t</tr>\n\t\t{%- for row in doc.items -%}\n\t\t<tr>\n\t\t\t<td style=\"width: 3%;\">{{ row.idx }}</td>\n\t\t\t<td style=\"width: 20%;\">\n\t\t\t\t{{ row.item_name }}\n\t\t\t\t{% if row.item_code != row.item_name -%}\n\t\t\t\t<br>Item Code: {{ row.item_code}}\n\t\t\t\t{%- endif %}\n\t\t\t</td>\n\t\t\t<td style=\"width: 37%;\">\n\t\t\t\t<div style=\"border: 0px;\">{{ row.description }}</div></td>\n\t\t\t<td style=\"width: 10%; text-align: right;\">{{ row.qty }} {{ row.uom or row.stock_uom }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"rate\", doc) }}</td>\n\t\t\t<td style=\"width: 15%; text-align: right;\">{{\n\t\t\t\trow.get_formatted(\"amount\", doc) }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>
\n
\n

Common Functions

\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n
doc.get_formatted(\"[fieldname]\", [parent_doc])Get document value formatted as Date, Currency etc. Pass parent doc for curreny type fields.
frappe.db.get_value(\"[doctype]\", \"[name]\", \"fieldname\")Get value from another document.
\n", + "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": "format_data", + "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": "Format 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 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "print_format_builder", + "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": "Print Format Builder", + "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, - "icon": "fa fa-print", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-09-05 14:02:05.658719", - "modified_by": "Administrator", - "module": "Printing", - "name": "Print Format", - "owner": "Administrator", + ], + "has_web_view": 0, + "hide_toolbar": 0, + "icon": "fa fa-print", + "idx": 1, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "menu_index": 0, + "modified": "2019-04-20 12:45:25.869180", + "modified_by": "Administrator", + "module": "Printing", + "name": "Print Format", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 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": 1, - "submit": 0, + "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": 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", - "track_changes": 1, - "track_seen": 0 -} + ], + "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 +} \ No newline at end of file diff --git a/frappe/printing/doctype/print_format/print_format.py b/frappe/printing/doctype/print_format/print_format.py index 8f25ab7cb8..8b48fb5705 100644 --- a/frappe/printing/doctype/print_format/print_format.py +++ b/frappe/printing/doctype/print_format/print_format.py @@ -27,7 +27,7 @@ class PrintFormat(Document): if not self.module: self.module = frappe.db.get_value('DocType', self.doc_type, 'module') - if self.html and self.print_format_type != 'Js': + if self.html and self.print_format_type != 'JS': validate_template(self.html) def extract_images(self): diff --git a/frappe/printing/doctype/print_settings/print_settings.json b/frappe/printing/doctype/print_settings/print_settings.json index 4f7039c7f8..397d9dda5d 100644 --- a/frappe/printing/doctype/print_settings/print_settings.json +++ b/frappe/printing/doctype/print_settings/print_settings.json @@ -1,843 +1,932 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2014-07-17 06:54:20.782907", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "System", - "editable_grid": 0, + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2014-07-17 06:54:20.782907", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "System", + "editable_grid": 0, "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "pdf_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": "PDF Settings", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "pdf_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": "PDF Settings", + "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", - "description": "Send Email Print Attachments as PDF (Recommended)", - "fieldname": "send_print_as_pdf", - "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 Print as PDF", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "description": "Send Email Print Attachments as PDF (Recommended)", + "fetch_if_empty": 0, + "fieldname": "send_print_as_pdf", + "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 Print as PDF", + "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", - "fieldname": "repeat_header_footer", - "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": "Repeat Header and Footer in PDF", - "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, + "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": "repeat_header_footer", + "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": "Repeat Header and Footer in PDF", + "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": "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, + "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 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "A4", - "fieldname": "pdf_page_size", - "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": "PDF Page Size", - "length": 0, - "no_copy": 0, - "options": "A4\nLetter", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "A4", + "fetch_if_empty": 0, + "fieldname": "pdf_page_size", + "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": "PDF Page Size", + "length": 0, + "no_copy": 0, + "options": "A4\nLetter", + "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, - "fieldname": "view_link_in_email", - "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": "Page 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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "view_link_in_email", + "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": "Page 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, - "default": "1", - "description": "", - "fieldname": "with_letterhead", - "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": "Print with letterhead", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "description": "", + "fetch_if_empty": 0, + "fieldname": "with_letterhead", + "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": "Print with letterhead", + "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", - "description": "", - "fieldname": "allow_print_for_draft", - "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 Print for Draft", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "description": "", + "fetch_if_empty": 0, + "fieldname": "allow_print_for_draft", + "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 Print for Draft", + "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": "", - "fieldname": "attach_view_link", - "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 document web view link in email", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "description": "", + "fetch_if_empty": 0, + "fieldname": "attach_view_link", + "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 document web view link in email", + "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": "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, - "translatable": 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_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, + "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", - "fieldname": "add_draft_heading", - "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": "Always add \"Draft\" Heading for printing draft documents", - "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, + "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": "add_draft_heading", + "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": "Always add \"Draft\" Heading for printing draft documents", + "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": "allow_page_break_inside_tables", - "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 page break inside tables", - "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, + "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_page_break_inside_tables", + "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 page break inside tables", + "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": "", - "fieldname": "allow_print_for_cancelled", - "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 Print for Cancelled", - "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, + "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_print_for_cancelled", + "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 Print for Cancelled", + "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": "", - "fieldname": "server_printer", - "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": "Print Server", - "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, + "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": "server_printer", + "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": "Print Server", + "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": "enable_print_server", - "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 Print Server", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "enable_print_server", + "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 Print Server", + "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": "localhost", - "depends_on": "enable_print_server", - "fieldname": "server_ip", - "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": "Server IP", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "localhost", + "depends_on": "enable_print_server", + "fetch_if_empty": 0, + "fieldname": "server_ip", + "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": "Server IP", + "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": "enable_print_server", - "fieldname": "printer_name", - "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": "Printer Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "enable_print_server", + "fetch_if_empty": 0, + "fieldname": "printer_name", + "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": "Printer Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "631", - "depends_on": "enable_print_server", - "fieldname": "port", - "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": "Port", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "631", + "depends_on": "enable_print_server", + "fetch_if_empty": 0, + "fieldname": "port", + "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": "Port", + "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": "print_style_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": "Print Style", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "raw_printing_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": "Raw Printing", + "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": "Modern", - "fieldname": "print_style", - "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": "Print Style", - "length": 0, - "no_copy": 0, - "options": "Print Style", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "enable_raw_printing", + "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 Raw Printing", + "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": "print_style_preview", - "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": "Print Style Preview", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "print_style_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": "Print Style", + "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, - "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, - "label": "Fonts", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Modern", + "fetch_if_empty": 0, + "fieldname": "print_style", + "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": "Print Style", + "length": 0, + "no_copy": 0, + "options": "Print Style", + "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": "Default", - "fieldname": "font", - "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": "Font", - "length": 0, - "no_copy": 0, - "options": "Default\nArial\nHelvetica\nVerdana\nMonospace", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "print_style_preview", + "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": "Print Style Preview", + "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": "In points. Default is 9.", - "fieldname": "font_size", - "fieldtype": "Float", - "hidden": 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": "Font Size", - "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, + "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_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, + "label": "Fonts", + "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": "Default", + "fetch_if_empty": 0, + "fieldname": "font", + "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": "Font", + "length": 0, + "no_copy": 0, + "options": "Default\nArial\nHelvetica\nVerdana\nMonospace", + "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 points. Default is 9.", + "fetch_if_empty": 0, + "fieldname": "font_size", + "fieldtype": "Float", + "hidden": 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": "Font Size", + "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 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-cog", - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2018-09-20 12:10:14.440598", - "modified_by": "Administrator", - "module": "Printing", - "name": "Print Settings", - "name_case": "", - "owner": "Administrator", + ], + "has_web_view": 0, + "hide_toolbar": 0, + "icon": "fa fa-cog", + "idx": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "menu_index": 0, + "modified": "2019-04-10 14:12:31.081187", + "modified_by": "Administrator", + "module": "Printing", + "name": "Print Settings", + "name_case": "", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, + ], + "quick_entry": 1, + "read_only": 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/printing/page/print_format_builder/print_format_builder.js b/frappe/printing/page/print_format_builder/print_format_builder.js index 8e21ee4f50..aa733cbe31 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,23 +129,16 @@ frappe.PrintFormatBuilder = Class.extend({ }); }, - setup_new_print_format: function(doctype, name) { - var me = this; - frappe.call({ - method: "frappe.client.insert", - args: { - doc: { - doctype: "Print Format", - name: name, - standard: "No", - doc_type: doctype, - print_format_builder: 1 - } - }, - callback: function(r) { - me.print_format = r.message; - me.refresh(); - } + setup_new_print_format: function(doctype, name, based_on) { + frappe.call('frappe.printing.page.print_format_builder.print_format_builder.create_custom_format', { + doctype, + name, + based_on + }).then((r) => { + frappe.model.with_doc('Print Format', r.message.name) + .then(() => $(document).trigger({ type: 'new-print-format', print_format: r.message.name })); + this.print_format = r.message; + this.refresh(); }); }, setup_print_format: function() { @@ -785,6 +777,8 @@ frappe.PrintFormatBuilder = Class.extend({ fieldname: "format_data", value: JSON.stringify(data), }, + freeze: true, + btn: this.page.btn_primary, callback: function(r) { me.print_format = r.message; frappe.show_alert({message: __("Saved"), indicator: 'green'}); 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..17baa9314e --- /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): + 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 diff --git a/frappe/public/build.json b/frappe/public/build.json index 620b360f47..cd8f04b2c9 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", @@ -167,6 +168,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 +254,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 +358,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/chat.js b/frappe/public/js/frappe/chat.js index 3dc6fe6505..533ca90856 100644 --- a/frappe/public/js/frappe/chat.js +++ b/frappe/public/js/frappe/chat.js @@ -2132,10 +2132,11 @@ class extends Component { icon: "file", label: "File", onclick: ( ) => { - const dialog = frappe.upload.make({ - args: { doctype: "Chat Room", docname: props.name }, - callback: (a, b, args) => { - const { file_url, filename } = args + new frappe.ui.FileUploader({ + doctype: "Chat Room", + docname: props.name, + on_success(file_doc) { + const { file_url, filename } = file_doc frappe.chat.message.send(props.name, { path: file_url, name: filename }, "File") } }) diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index db85dce81f..27b483b7aa 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -45,6 +45,7 @@ frappe.Application = Class.extend({ this.make_nav_bar(); this.set_favicon(); this.setup_analytics(); + this.set_fullwidth_if_enabled(); this.setup_energy_point_listeners(); @@ -123,7 +124,7 @@ frappe.Application = Class.extend({ } } } - + this.link_preview = new frappe.ui.LinkPreview(); }, setup_frappe_vue() { @@ -268,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) { @@ -507,6 +511,10 @@ frappe.Application = Class.extend({ } }, + set_fullwidth_if_enabled() { + frappe.ui.toolbar.set_fullwidth_if_enabled(); + }, + show_notes: function() { var me = this; if(frappe.boot.notes.length) { @@ -556,7 +564,7 @@ frappe.Application = Class.extend({ frappe.realtime.on('energy_point_alert', (message) => { frappe.show_alert(message); }); - } + }, }); frappe.get_module = function(m, default_module) { 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/file_uploader/FileBrowser.vue b/frappe/public/js/frappe/file_uploader/FileBrowser.vue new file mode 100644 index 0000000000..46bd61f120 --- /dev/null +++ b/frappe/public/js/frappe/file_uploader/FileBrowser.vue @@ -0,0 +1,136 @@ + + + + diff --git a/frappe/public/js/frappe/file_uploader/FilePreview.vue b/frappe/public/js/frappe/file_uploader/FilePreview.vue new file mode 100644 index 0000000000..08c38d60f5 --- /dev/null +++ b/frappe/public/js/frappe/file_uploader/FilePreview.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/frappe/public/js/frappe/file_uploader/FileUploader.vue b/frappe/public/js/frappe/file_uploader/FileUploader.vue new file mode 100644 index 0000000000..ae7f428327 --- /dev/null +++ b/frappe/public/js/frappe/file_uploader/FileUploader.vue @@ -0,0 +1,411 @@ + + + + diff --git a/frappe/public/js/frappe/file_uploader/TreeNode.vue b/frappe/public/js/frappe/file_uploader/TreeNode.vue new file mode 100644 index 0000000000..ef3dbd8990 --- /dev/null +++ b/frappe/public/js/frappe/file_uploader/TreeNode.vue @@ -0,0 +1,32 @@ + + diff --git a/frappe/public/js/frappe/file_uploader/WebLink.vue b/frappe/public/js/frappe/file_uploader/WebLink.vue new file mode 100644 index 0000000000..b0527f7d9f --- /dev/null +++ b/frappe/public/js/frappe/file_uploader/WebLink.vue @@ -0,0 +1,36 @@ + + + + diff --git a/frappe/public/js/frappe/file_uploader/index.js b/frappe/public/js/frappe/file_uploader/index.js new file mode 100644 index 0000000000..e08b952c6f --- /dev/null +++ b/frappe/public/js/frappe/file_uploader/index.js @@ -0,0 +1,76 @@ +import FileUploaderComponent from './FileUploader.vue'; + +export default class FileUploader { + constructor({ + wrapper, + method, + on_success, + doctype, + docname, + files, + folder, + restrictions, + upload_notes, + allow_multiple, + as_dataurl + } = {}) { + if (!wrapper) { + this.make_dialog(); + } else { + this.wrapper = wrapper.get ? wrapper.get(0) : wrapper; + } + + this.$fileuploader = new Vue({ + el: this.wrapper, + render: h => h(FileUploaderComponent, { + props: { + show_upload_button: !Boolean(this.dialog), + doctype, + docname, + method, + folder, + on_success, + restrictions, + upload_notes, + allow_multiple, + as_dataurl + } + }) + }); + + this.uploader = this.$fileuploader.$children[0]; + + if (files && files.length) { + this.uploader.add_files(files); + } + } + + upload_files() { + this.dialog && this.dialog.get_primary_btn().prop('disabled', true); + return this.uploader.upload_files() + .then(() => { + this.dialog && this.dialog.hide(); + }); + } + + make_dialog() { + this.dialog = new frappe.ui.Dialog({ + title: 'Upload', + fields: [ + { + fieldtype: 'HTML', + fieldname: 'upload_area' + } + ], + primary_action_label: __('Upload'), + primary_action: () => this.upload_files() + }); + + this.wrapper = this.dialog.fields_dict.upload_area.$wrapper[0]; + this.dialog.show(); + this.dialog.$wrapper.on('hidden.bs.modal', function() { + $(this).data('bs.modal', null); + $(this).remove(); + }); + } +} diff --git a/frappe/public/js/frappe/form/controls/attach.js b/frappe/public/js/frappe/form/controls/attach.js index 1f1eadc81d..00e9af4323 100644 --- a/frappe/public/js/frappe/form/controls/attach.js +++ b/frappe/public/js/frappe/form/controls/attach.js @@ -5,15 +5,15 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({ .html(__("Attach")) .prependTo(me.input_area) .on("click", function() { - me.onclick(); + me.on_attach_click(); }); this.$value = $( - `
+ ``) .prependTo(me.input_area) .toggle(false); @@ -21,7 +21,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({ this.set_input_attributes(); this.has_input = true; - this.$value.find(".close").on("click", function() { + this.$value.find(".clear-file").on("click", function() { me.clear_attachment(); }); }, @@ -40,113 +40,28 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({ this.refresh(); } }, - onclick: function() { - var me = this; - if(this.doc) { - var doc = this.doc.parent && frappe.model.get_doc(this.doc.parenttype, this.doc.parent) || this.doc; - if (doc.__islocal) { - frappe.msgprint(__("Please save the document before uploading.")); - return; - } - } - if(!this.dialog) { - this.dialog = new frappe.ui.Dialog({ - title: __(this.df.label || __("Upload")), - fields: [ - {fieldtype:"HTML", fieldname:"upload_area"}, - {fieldtype:"HTML", fieldname:"or_attach", options: __("Or")}, - {fieldtype:"Select", fieldname:"select", label:__("Select from existing attachments") }, - {fieldtype:"Button", fieldname:"clear", - label:__("Clear Attachment"), click: function() { - me.clear_attachment(); - me.dialog.hide(); - } - }, - ] - }); - } - - this.dialog.show(); - - this.dialog.get_field("upload_area").$wrapper.empty(); - - // select from existing attachments - var attachments = this.frm && this.frm.attachments.get_attachments() || []; - var select = this.dialog.get_field("select"); - if(attachments.length) { - attachments = $.map(attachments, function(o) { return o.file_url; }); - select.df.options = [""].concat(attachments); - select.toggle(true); - this.dialog.get_field("or_attach").toggle(true); - select.refresh(); - } else { - this.dialog.get_field("or_attach").toggle(false); - select.toggle(false); - } - select.$input.val(""); - - // show button if attachment exists - this.dialog.get_field('clear').$wrapper.toggle(this.get_model_value() ? true : false); - + on_attach_click() { this.set_upload_options(); - frappe.upload.make(this.upload_options); + new frappe.ui.FileUploader(this.upload_options); }, - - set_upload_options: function() { - var me = this; - this.upload_options = { - parent: this.dialog.get_field("upload_area").$wrapper, - args: {}, - allow_multiple: 0, - max_width: this.df.max_width, - max_height: this.df.max_height, - options: this.df.options, - btn: this.dialog.set_primary_action(__("Upload")), - on_no_attach: function() { - // if no attachmemts, - // check if something is selected - var selected = me.dialog.get_field("select").get_value(); - if(selected) { - me.parse_validate_and_set_in_model(selected); - me.dialog.hide(); - me.frm.save(); - } else { - frappe.msgprint(__("Please attach a file or set a URL")); - } - }, - callback: function(attachment) { - me.on_upload_complete(attachment); - me.dialog.hide(); - }, - onerror: function() { - me.dialog.hide(); + set_upload_options() { + let options = { + allow_multiple: false, + on_success: file => { + this.on_upload_complete(file); } }; - if ("is_private" in this.df) { - this.upload_options.is_private = this.df.is_private; + if (this.frm) { + options.doctype = this.frm.doctype; + options.docname = this.frm.docname; } - if(this.frm) { - this.upload_options.args = { - from_form: 1, - doctype: this.frm.doctype, - docname: this.frm.docname - }; - } else { - this.upload_options.on_attach = function(fileobj, dataurl) { - me.dialog.hide(); - me.fileobj = fileobj; - me.dataurl = dataurl; - if(me.on_attach) { - me.on_attach(); - } - if(me.df.on_attach) { - me.df.on_attach(fileobj, dataurl); - } - me.on_upload_complete(); - }; + if (this.df.options) { + Object.assign(options, this.df.options); } + + this.upload_options = options; }, set_input: function(value, dataurl) { @@ -168,29 +83,16 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({ }, get_value: function() { - if ( this.fileobj ) { - if ( this.fileobj.file_url ) { - return this.fileobj.file_url; - } else if ( this.fileobj.filename ) { - var dataURI = this.fileobj.filename + ',' + this.dataurl; - - return dataURI; - } - } else { - return this.value || null; - } + return this.value || null; }, on_upload_complete: function(attachment) { if(this.frm) { this.parse_validate_and_set_in_model(attachment.file_url); - this.refresh(); this.frm.attachments.update_attachment(attachment); this.frm.doc.docstatus == 1 ? this.frm.save('Update') : this.frm.save(); - } else { - this.value = this.get_value(); - this.refresh(); - frappe.hide_progress(); } + this.value = attachment.file_url; + this.refresh(); }, }); diff --git a/frappe/public/js/frappe/form/controls/attach_image.js b/frappe/public/js/frappe/form/controls/attach_image.js index 4691064d8f..17e43d4683 100644 --- a/frappe/public/js/frappe/form/controls/attach_image.js +++ b/frappe/public/js/frappe/form/controls/attach_image.js @@ -1,70 +1,25 @@ frappe.ui.form.ControlAttachImage = frappe.ui.form.ControlAttach.extend({ - make: function() { - var me = this; + make_input() { this._super(); - this.container = $('
').insertAfter($(this.wrapper)); - $(this.wrapper).detach(); - this.container.attr('data-fieldtype', this.df.fieldtype).append(this.wrapper); - if(this.df.align === 'center') { - this.container.addClass("flex-justify-center"); - } else if (this.df.align === 'right') { - this.container.addClass("flex-justify-end"); - } - - this.img_wrapper = $('
\ -
') - .appendTo(this.wrapper); - - this.img_container = $(`
`); - this.img = $(``) - .appendTo(this.img_container); - - this.img_overlay = $(`
- Change -
`).appendTo(this.img_container); - - this.remove_image_link = $('Remove'); - - this.img_wrapper.append(this.img_container).append(this.remove_image_link); - // this.img.toggle(false); - // this.img_overlay.toggle(false); - this.img_container.toggle(false); - this.remove_image_link.toggle(false); - - // propagate click to Attach button - this.img_wrapper.find(".missing-image").on("click", function() { me.$input.click(); }); - this.img_container.on("click", function() { me.$input.click(); }); - this.remove_image_link.on("click", function() { me.$value.find(".close").click(); }); - - this.set_image(); - }, - refresh_input: function() { - this._super(); - $(this.wrapper).find('.btn-attach').addClass('hidden'); - this.set_image(); - if(this.get_status()=="Read") { - $(this.disp_area).toggle(false); - } - }, - set_image: function() { - if(this.get_value()) { - $(this.img_wrapper).find(".missing-image").toggle(false); - // this.img.attr("src", this.dataurl ? this.dataurl : this.value).toggle(true); - // this.img_overlay.toggle(true); - this.img.attr("src", this.dataurl ? this.dataurl : this.value); - this.img_container.toggle(true); - this.remove_image_link.toggle(true); - } else { - $(this.img_wrapper).find(".missing-image").toggle(true); - // this.img.toggle(false); - // this.img_overlay.toggle(false); - this.img_container.toggle(false); - this.remove_image_link.toggle(false); - } + let $file_link = this.$value.find('.attached-file-link'); + $file_link.popover({ + trigger: 'hover', + placement: 'top', + content: () => { + return `
+ +
`; + }, + html: true + }); }, set_upload_options() { this._super(); - this.upload_options.restrict_to_images = true; + this.upload_options.restrictions = {}; + this.upload_options.restrictions.allowed_file_types = ['image/*']; } }); diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 86a99c970f..750ad7a64c 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; @@ -167,13 +168,12 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ } if(!me.df.only_select) { - if(frappe.model.can_create(doctype) - && me.df.fieldtype !== "Dynamic Link") { + if(frappe.model.can_create(doctype)) { // new item r.results.push({ label: "" + " " - + __("Create a new {0}", [__(me.df.options)]) + + __("Create a new {0}", [__(me.get_options())]) + "", value: "create_new__link_option", action: me.new_doc diff --git a/frappe/public/js/frappe/form/controls/rating.js b/frappe/public/js/frappe/form/controls/rating.js index 844e932665..9a68cec2be 100644 --- a/frappe/public/js/frappe/form/controls/rating.js +++ b/frappe/public/js/frappe/form/controls/rating.js @@ -3,11 +3,11 @@ frappe.ui.form.ControlRating = frappe.ui.form.ControlInt.extend({ this._super(); const star_template = `
- - - - - + + + + +
`; @@ -15,7 +15,7 @@ frappe.ui.form.ControlRating = frappe.ui.form.ControlInt.extend({ $(this.input_area).find('i').hover((ev) => { const el = $(ev.currentTarget); - let star_value = el.data('idx'); + let star_value = el.data('rating'); el.parent().children('i.fa').each( function(e){ if (e < star_value) { $(this).addClass('star-hover'); @@ -32,7 +32,7 @@ frappe.ui.form.ControlRating = frappe.ui.form.ControlInt.extend({ $(this.input_area).find('i').click((ev) => { const el = $(ev.currentTarget); - let star_value = el.data('idx'); + let star_value = el.data('rating'); el.parent().children('i.fa').each( function(e) { if (e < star_value){ $(this).addClass('star-click'); diff --git a/frappe/public/js/frappe/form/controls/text_editor.js b/frappe/public/js/frappe/form/controls/text_editor.js index 3e98528bfa..ef6afde262 100644 --- a/frappe/public/js/frappe/form/controls/text_editor.js +++ b/frappe/public/js/frappe/form/controls/text_editor.js @@ -38,24 +38,6 @@ class MyLink extends Link { Quill.register(MyLink); - -// hidden blot -class HiddenBlock extends Block { - static create(value) { - const node = super.create(value); - node.setAttribute('data-comment', value); - node.classList.add('hidden'); - return node; - } - - static formats(node) { - return node.getAttribute('data-comment'); - } -} -HiddenBlock.blotName = 'hiddenblot'; -HiddenBlock.tagName = 'SPAN'; -Quill.register(HiddenBlock, true); - // image uploader const Uploader = Quill.import('modules/uploader'); Uploader.DEFAULTS.mimetypes.push('image/gif'); @@ -73,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("ql-editor"); + }, + make_input() { this.has_input = true; this.make_quill_editor(); diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js index 8b15715361..20e90ac438 100644 --- a/frappe/public/js/frappe/form/dashboard.js +++ b/frappe/public/js/frappe/form/dashboard.js @@ -36,8 +36,8 @@ frappe.ui.form.Dashboard = Class.extend({ // clear custom this.wrapper.find('.custom').remove(); }, - set_headline: function(html) { - this.frm.layout.show_message(html); + set_headline: function(html, color) { + this.frm.layout.show_message(html, color); }, clear_headline: function() { this.frm.layout.show_message(); @@ -57,12 +57,9 @@ frappe.ui.form.Dashboard = Class.extend({ this.clear_headline(); }, - set_headline_alert: function(text, indicator_color) { - if (!indicator_color) { - indicator_color = 'orange'; - } + set_headline_alert: function(text, color) { if(text) { - this.set_headline(`
${text}
`); + this.set_headline(`
${text}
`, color); } else { this.clear_headline(); } @@ -310,6 +307,12 @@ frappe.ui.form.Dashboard = Class.extend({ var fieldname = this.data.non_standard_fieldnames ? (this.data.non_standard_fieldnames[doctype] || this.data.fieldname) : this.data.fieldname; + + if (this.data.dynamic_links && this.data.dynamic_links[fieldname]) { + let dynamic_fieldname = this.data.dynamic_links[fieldname][1]; + filter[dynamic_fieldname] = this.data.dynamic_links[fieldname][0]; + } + filter[fieldname] = this.frm.doc.name; return filter; }, diff --git a/frappe/public/js/frappe/form/document_follow.js b/frappe/public/js/frappe/form/document_follow.js index aaae2091d7..1743bc254a 100644 --- a/frappe/public/js/frappe/form/document_follow.js +++ b/frappe/public/js/frappe/form/document_follow.js @@ -20,7 +20,7 @@ frappe.ui.form.DocumentFollow = class DocumentFollow { render_sidebar() { const docinfo = this.frm.get_docinfo(); - const document_follow_enabled = docinfo && docinfo.document_follow_enabled; + const document_follow_enabled = frappe.boot.user.document_follow_notify; const document_can_be_followed = frappe.get_meta(this.frm.doctype).track_changes; if (frappe.session.user === 'Administrator' || !document_follow_enabled diff --git a/frappe/public/js/frappe/form/footer/attachment.html b/frappe/public/js/frappe/form/footer/attachment.html index 3598f8b9ef..c1fe3f3c85 100644 --- a/frappe/public/js/frappe/form/footer/attachment.html +++ b/frappe/public/js/frappe/form/footer/attachment.html @@ -1,4 +1,4 @@ -
  • +
  • × diff --git a/frappe/public/js/frappe/form/footer/attachments.js b/frappe/public/js/frappe/form/footer/attachments.js index 57cd29cad8..51636b4e82 100644 --- a/frappe/public/js/frappe/form/footer/attachments.js +++ b/frappe/public/js/frappe/form/footer/attachments.js @@ -63,12 +63,12 @@ frappe.ui.form.Attachments = Class.extend({ var me = this; - var $attach = $(frappe.render_template("attachment", { + var $attach = $(frappe.render_template("attachment", { "file_path": "/desk#Form/File/" + fileid, "icon": attachment.is_private ? "fa fa-lock" : "fa fa-unlock-alt", "file_name": file_name, "file_url": frappe.urllib.get_full_url(file_url) - })).insertAfter(this.attachments_label.addClass("has-attachments")); + })).insertAfter(this.attachments_label.addClass("has-attachments")); var $close = $attach.find(".close") @@ -146,12 +146,12 @@ frappe.ui.form.Attachments = Class.extend({ this.dialog.$wrapper.remove(); } - // make upload dialog - this.dialog = frappe.ui.get_upload_dialog({ - "args": me.get_args(), - "callback": function(attachment, r) { me.attachment_uploaded(attachment, r) }, - "max_width": me.frm.cscript ? me.frm.cscript.attachment_max_width : null, - "max_height": me.frm.cscript ? me.frm.cscript.attachment_max_height : null + new frappe.ui.FileUploader({ + doctype: this.frm.doctype, + docname: this.frm.docname, + on_success: (file_doc) => { + this.attachment_uploaded(file_doc); + } }); }, get_args: function() { @@ -161,7 +161,7 @@ frappe.ui.form.Attachments = Class.extend({ docname: this.frm.docname, } }, - attachment_uploaded: function(attachment, r) { + attachment_uploaded: function(attachment) { this.dialog && this.dialog.hide(); this.update_attachment(attachment); this.frm.reload_docinfo(); @@ -196,94 +196,3 @@ frappe.ui.form.Attachments = Class.extend({ this.refresh(); } }); - -frappe.ui.get_upload_dialog = function(opts){ - var dialog = new frappe.ui.Dialog({ - title: __('Upload Attachment'), - no_focus: true, - fields: [ - { - "fieldtype": "Section Break" - }, - { - "fieldtype": "Link" , - "fieldname": "file" , - "label": __("Select uploaded file"), - "options": "File", - onchange: function() { - frappe.call({ - 'method': 'frappe.client.get_value', - 'args': { - 'doctype': 'File', - 'fieldname': ['file_url','file_name','is_private'], - 'filters': { - 'name': dialog.get_value("file") - } - }, - callback: function(r){ - if(!r.message) { - dialog.$wrapper.find('[name="file_url"]').val(""); - return; - } - dialog.$wrapper.find('[name="file_url"]').val(r.message.file_url); - dialog.$wrapper.find('.private-file input').prop('checked', r.message.is_private); - opts.args.filename = r.message.file_name; - opts.args.is_private = r.message.is_private; - } - }); - } - }, - { - "hidden": !opts.args.doctype || !frappe.boot.gsuite_enabled, - "fieldtype": "Section Break", - "label": __("GSuite Document"), - }, - { - "fieldtype": "Link" , - "fieldname": "gs_template" , - "label": __("Select template"), - "options": "GSuite Templates", - "reqd" : false, - "filters": { - 'related_doctype': opts.args.doctype - }, - onchange: function(){ - opts.args.gs_template = this.get_value(); - } - }, - ], - }); - - - - - var btn = dialog.set_primary_action(__("Attach")); - btn.removeClass("btn-primary").addClass("btn-default"); - - dialog.show(); - var upload_area = $('
    ').prependTo(dialog.body); - - - - frappe.upload.make({ - parent: upload_area, - args: opts.args, - callback: function(attachment, r) { - dialog.hide(); - if(opts.callback){ - opts.callback(attachment, r); - } - }, - on_select: function() { - btn.removeClass("btn-default").addClass("btn-primary"); - }, - onerror: function() { - dialog.hide(); - }, - btn: btn, - max_width: opts.max_width, - max_height: opts.max_height, - }); - - return dialog; -} diff --git a/frappe/public/js/frappe/form/footer/timeline.js b/frappe/public/js/frappe/form/footer/timeline.js index 5f2acd3929..6eba1659a8 100644 --- a/frappe/public/js/frappe/form/footer/timeline.js +++ b/frappe/public/js/frappe/form/footer/timeline.js @@ -163,6 +163,9 @@ frappe.ui.form.Timeline = class Timeline { // append energy point logs timeline = timeline.concat(this.get_energy_point_logs()); + // append milestones + timeline = timeline.concat(this.get_milestones()); + // sort timeline .filter(a => a.content) @@ -286,28 +289,38 @@ frappe.ui.form.Timeline = class Timeline { } add_reply_btn_event($timeline_item, c) { - var me = this; - $timeline_item.find(".reply-link").on("click", function() { - var name = $(this).attr("data-name"); + $timeline_item.on('click', '.reply-link, .reply-link-all', (e) => { var last_email = null; - // find the email tor reply to - me.get_communications().forEach(function(c) { - if(c.name==name) { + const $target = $(e.currentTarget); + const name = $target.data().name; + + // find the email to reply to + this.get_communications().forEach(function(c) { + if(c.name == name) { last_email = c; return false; } }); - // make the composer - new frappe.views.CommunicationComposer({ - doc: me.frm.doc, + const opts = { + doc: this.frm.doc, txt: "", title: __('Reply'), - frm: me.frm, - last_email: last_email, + frm: this.frm, + last_email, is_a_reply: true - }); + }; + + if ($target.is('.reply-link-all')) { + if (last_email) { + opts.cc = last_email.cc; + opts.bcc = last_email.bcc; + } + } + + // make the composer + new frappe.views.CommunicationComposer(opts); }); } @@ -428,8 +441,6 @@ frappe.ui.form.Timeline = class Timeline { set_icon_and_color(c) { if(c.communication_type == "Feedback"){ c.icon = "octicon octicon-comment-discussion" - c.rating_icons = frappe.render_template("rating_icons", {rating: c.rating, show_label: true}) - c.color = "#f39c12" } else { c.icon = { "Email": "octicon octicon-mail", @@ -439,12 +450,12 @@ frappe.ui.form.Timeline = class Timeline { "Event": "fa fa-calendar", "Meeting": "octicon octicon-briefcase", "ToDo": "fa fa-check", - "Created": "octicon octicon-plus", "Submitted": "octicon octicon-lock", "Cancelled": "octicon octicon-x", "Assigned": "octicon octicon-person", "Assignment Completed": "octicon octicon-check", "Comment": "octicon octicon-comment-discussion", + "Milestone": "octicon octicon-milestone", "Workflow": "octicon octicon-git-branch", "Label": "octicon octicon-tag", "Attachment": "octicon octicon-cloud-upload", @@ -457,25 +468,6 @@ frappe.ui.form.Timeline = class Timeline { "Reply": "octicon octicon-mail-reply" }[c.comment_type || c.communication_medium] - c.color = { - "Email": "#3498db", - "Chat": "#3498db", - "Phone": "#3498db", - "SMS": "#3498db", - "Created": "#1abc9c", - "Submitted": "#1abc9c", - "Cancelled": "#c0392b", - "Assigned": "#f39c12", - "Assignment Completed": "#16a085", - "Comment": "#f39c12", - "Workflow": "#2c3e50", - "Label": "#2c3e50", - "Attachment": "#7f8c8d", - "Attachment Removed": "#eee", - "Relinked": "#16a085", - "Reply": "#8d99a6" - }[c.comment_type || c.communication_medium]; - c.icon_fg = { "Attachment Removed": "#333", }[c.comment_type || c.communication_medium] @@ -528,6 +520,21 @@ frappe.ui.form.Timeline = class Timeline { return energy_point_logs; } + get_milestones() { + let milestones = this.frm.get_docinfo().milestones; + milestones.map(log => { + log.color = 'dark'; + log.sender = log.owner; + log.comment_type = 'Milestone'; + log.content = __('{0} changed {1} to {2}', [ + frappe.user.full_name(log.owner).bold(), + frappe.meta.get_label(this.frm.doctype, log.track_field), + log.value.bold()]); + return log; + }); + return milestones; + } + cast_comment_as_communication(c) { c.sender = c.comment_email; c.sender_full_name = c.comment_by; @@ -836,4 +843,4 @@ $.extend(frappe.timeline, { return index; } -}) \ No newline at end of file +}); diff --git a/frappe/public/js/frappe/form/footer/timeline_item.html b/frappe/public/js/frappe/form/footer/timeline_item.html index 3a79bed370..d5f717f977 100755 --- a/frappe/public/js/frappe/form/footer/timeline_item.html +++ b/frappe/public/js/frappe/form/footer/timeline_item.html @@ -1,4 +1,5 @@ -
    + {% } else if(in_list(["Assignment Completed", "Assigned", "Shared", - "Unshared"], data.comment_type)) { %} + "Unshared", "Milestone"], data.comment_type)) { %}
    {% if (data.timeline_doctype===data.frm.doc.doctype @@ -162,7 +165,7 @@ {% if(data.link_doctype && data.link_name) { %} {% } %} - {%= __(data.content) %} + {{ __(data.content) }} {% if(data.link_doctype && data.link_name) { %} {% } %} diff --git a/frappe/public/js/frappe/form/form_sidebar.js b/frappe/public/js/frappe/form/form_sidebar.js index 293d83ceb4..f621f0f822 100644 --- a/frappe/public/js/frappe/form/form_sidebar.js +++ b/frappe/public/js/frappe/form/form_sidebar.js @@ -11,20 +11,19 @@ frappe.ui.form.Sidebar = Class.extend({ .html(sidebar_content) .appendTo(this.page.sidebar.empty()); - this.ratings = this.sidebar.find(".sidebar-rating"); this.comments = this.sidebar.find(".sidebar-comments"); this.user_actions = this.sidebar.find(".user-actions"); this.image_section = this.sidebar.find(".sidebar-image-section"); this.image_wrapper = this.image_section.find('.sidebar-image-wrapper'); this.make_assignments(); this.make_attachments(); + this.make_review(); this.make_shared(); this.make_viewers(); this.make_tags(); this.make_like(); this.make_follow(); - this.make_review(); this.bind_events(); frappe.ui.form.setup_user_image_event(this.frm); @@ -67,7 +66,6 @@ frappe.ui.form.Sidebar = Class.extend({ "
    " + comment_when(this.frm.doc.creation)])); this.refresh_like(); - this.setup_ratings(); frappe.ui.form.set_user_image(this.frm); } }, @@ -158,20 +156,10 @@ frappe.ui.form.Sidebar = Class.extend({ refresh_image: function() { }, - setup_ratings: function() { - var _ratings = this.frm.get_docinfo().rating || 0; - - if(_ratings) { - this.ratings.removeClass("hide"); - var rating_icons = frappe.render_template("rating_icons", {rating: _ratings, show_label: false}); - this.ratings.find(".rating-icons").html(rating_icons); - } - }, - make_review: function() { if (frappe.boot.energy_points_enabled && !this.frm.is_new()) { this.frm.reviews = new frappe.ui.form.Review({ - parent: this.sidebar.find(".form-attachments"), + parent: this.sidebar.find(".form-reviews"), frm: this.frm }); } diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index c8afb6466a..7d51bee9a4 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -117,11 +117,11 @@ frappe.form.formatters = { return repl('%(value)s', {onclick: docfield.link_onclick.replace(/"/g, '"'), value:value}); } else if(docfield && doctype) { - return repl('%(label)s', { - doctype: encodeURIComponent(doctype), - name: encodeURIComponent(original_value), - label: __(options && options.label || value) - }); + return ` + ${__(options && options.label || value)}` } else { return value; } diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 6dc18e9bd1..54957e1334 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -329,33 +329,32 @@ export default class Grid { this.grid_rows_by_docname[docname].refresh(); } make_sortable($rows) { - var me =this; - if ('ontouchstart' in window) { - return; - } - new Sortable($rows.get(0), { - group: {name: me.df.fieldname}, + group: {name: this.df.fieldname}, handle: '.sortable-handle', draggable: '.grid-row', + animation: 100, filter: 'li, a', - onUpdate: function(event, ui) { - me.frm.doc[me.df.fieldname] = []; - $rows.find(".grid-row").each(function(i, item) { - var doc = locals[me.doctype][$(item).attr('data-name')]; - doc.idx = i + 1; - me.frm.doc[me.df.fieldname].push(doc); - }); + onMove: (event) => { + // don't move if editable + if (!this.is_editable()) { + return false; + } - // re-order grid-rows by name - me.grid_rows = []; - me.frm.doc[me.df.fieldname].forEach(function(d) { - me.grid_rows.push(me.grid_rows_by_docname[d.name]); - }); - me.frm.script_manager.trigger(me.df.fieldname + "_move", me.df.options, me.frm.doc[me.df.fieldname][event.newIndex].name); - me.refresh(); - - me.frm.dirty(); + // prevent drag behaviour if _sortable property is "false" + let idx = $(event.dragged).closest('.grid-row').attr('data-idx'); + let doc = this.get_data()[idx - 1]; + if (doc && doc._sortable === false) { + return false; + } + }, + onUpdate: (event) => { + let idx = $(event.item).closest('.grid-row').attr('data-idx'); + let doc = this.get_data()[idx - 1]; + this.renumber_based_on_dom(); + this.frm.script_manager.trigger(this.df.fieldnathis + "_move", this.df.options, doc.nathis); + this.refresh(); + this.frm.dirty(); } }); @@ -502,6 +501,26 @@ export default class Grid { } } + renumber_based_on_dom() { + // renumber based on dom + let me = this; + let $rows = $(me.parent).find(".rows"); + + me.grid_rows = []; + me.frm.doc[me.df.fieldname] = []; + + $rows.find(".grid-row").each(function(i, item) { + + let $item = $(item); + let d = locals[me.doctype][$item.attr('data-name')]; + d.idx = i + 1; + $item.attr('data-idx', d.idx); + + me.frm.doc[me.df.fieldname].push(d); + me.grid_rows.push(me.grid_rows_by_docname[d.name]); + }); + } + duplicate_row(d, copy_doc) { $.each(copy_doc, function(key, value) { if(!["creation", "modified", "modified_by", "idx", "owner", @@ -640,9 +659,11 @@ export default class Grid { // upload frappe.flags.no_socketio = true; $(this.wrapper).find(".grid-upload").removeClass("hide").on("click", function() { - frappe.prompt({fieldtype:"Attach", label:"Upload File", fieldname: "upload_file"}, - function(data) { - var data = frappe.utils.csv_to_array(frappe.upload.get_string(data.upload_file)); + new frappe.ui.FileUploader({ + as_dataurl: true, + allow_multiple: false, + on_success(file) { + var data = frappe.utils.csv_to_array(frappe.utils.get_decoded_string(file.dataurl)); // row #2 contains fieldnames; var fieldnames = data[2]; @@ -680,8 +701,8 @@ export default class Grid { me.frm.refresh_field(me.df.fieldname); frappe.msgprint({message:__('Table updated'), title:__('Success'), indicator:'green'}) - - }, __("Edit via Upload"), __("Update")); + } + }); return false; }); } diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 8ae4767e3d..61cb1b8dbb 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -118,6 +118,35 @@ export default class GridRow { this.toggle_view(false); this.grid.add_new_row(idx, null, show, copy_doc); } + move() { + // promopt the user where they want to move this row + var me = this; + frappe.prompt({ + fieldname: 'move_to', + label: __('Move to Row Number'), + fieldtype: 'Int', + reqd: 1, + default: this.doc.idx, + }, function(values) { + if (me.doc._sortable === false) { + frappe.msgprint(__('Cannot move row')); + return; + } + + // renumber and refresh + let data = me.grid.get_data(); + data.move(me.doc.idx - 1, values.move_to - 1); + + // renum idx + for(let i=0; i\ ') - .appendTo($('
    ').appendTo(this.row)) + .appendTo($('
    ').appendTo(this.row)) .on('click', function() { me.toggle_view(); return false; }); if(this.is_too_small()) { diff --git a/frappe/public/js/frappe/form/grid_row_form.js b/frappe/public/js/frappe/form/grid_row_form.js index e6f7a282d1..73f0856c08 100644 --- a/frappe/public/js/frappe/form/grid_row_form.js +++ b/frappe/public/js/frappe/form/grid_row_form.js @@ -44,19 +44,24 @@ export default class GridRowForm { - - - - + + + + + + +
    @@ -96,6 +101,10 @@ export default class GridRowForm { .on('click', function() { me.row.insert(true, true, true); return false; }); + this.wrapper.find(".grid-move-row") + .on('click', function() { + me.row.move(); return false; + }); this.wrapper.find(".grid-append-row") .on('click', function() { me.row.toggle_view(false); @@ -108,7 +117,7 @@ export default class GridRowForm { }); } toggle_add_delete_button_display($parent) { - $parent.find(".grid-header-toolbar .btn, .grid-footer-toolbar .btn") + $parent.find(".row-actions") .toggle(this.row.grid.is_editable()); } refresh_field(fieldname) { @@ -133,4 +142,4 @@ export default class GridRowForm { } }, 500); } -}; +} diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index a829386450..bada3d354a 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -50,13 +50,19 @@ frappe.ui.form.Layout = Class.extend({ fields = fields.concat(frappe.meta.sort_docfields(frappe.meta.docfield_map[this.doctype])); return fields; }, - show_message: function(html) { + show_message: function(html, color) { + if (this.message_color) { + // remove previous color + this.message.removeClass(this.message_color); + } + this.message_color = (color && ['yellow', 'blue'].includes(color)) ? color : 'blue'; if(html) { if(html.substr(0, 1)!=='<') { // wrap in a block html = '
    ' + html + '
    '; } - $(html).appendTo(this.message.removeClass('hidden')); + this.message.removeClass('hidden').addClass(this.message_color); + $(html).appendTo(this.message); } else { this.message.empty().addClass('hidden'); } @@ -500,7 +506,7 @@ frappe.ui.form.Layout = Class.extend({ } else if(typeof(expression) === 'function') { out = expression(doc); - + } else if(expression.substr(0,5)=='eval:') { try { out = eval(expression.substr(5)); diff --git a/frappe/public/js/frappe/form/print.js b/frappe/public/js/frappe/form/print.js index f0a4226444..4a388841d5 100644 --- a/frappe/public/js/frappe/form/print.js +++ b/frappe/public/js/frappe/form/print.js @@ -11,6 +11,9 @@ frappe.ui.form.PrintPreview = Class.extend({ // only system manager can edit this.wrapper.find(".btn-print-edit").toggle(frappe.user.has_role("System Manager")); + if (frappe.model.get_doc(":Print Settings", "Print Settings").enable_raw_printing == "1") { + this.wrapper.find(".btn-printer-setting").toggle(true); + } }, bind_events: function () { var me = this; @@ -47,6 +50,10 @@ frappe.ui.form.PrintPreview = Class.extend({ me.multilingual_preview() }); + this.wrapper.find(".btn-printer-setting").click(function () { + me.printer_setting_dialog(); + }); + this.wrapper.find(".btn-print-print").click(function () { if (me.is_old_style()) { me.print_old_style(); @@ -81,25 +88,48 @@ frappe.ui.form.PrintPreview = Class.extend({ this.wrapper.find(".btn-print-edit").on("click", function () { let print_format = me.get_print_format(); - if (print_format && print_format.name) { - if (print_format.print_format_builder) { - frappe.set_route("print-format-builder", print_format.name); - } else { - frappe.set_route("Form", "Print Format", print_format.name); + let is_custom_format = print_format.name + && print_format.print_format_builder + && print_format.standard === 'No'; + let is_standard_but_editable = print_format.name && print_format.custom_format; + + if (is_standard_but_editable) { + frappe.set_route("Form", "Print Format", print_format.name); + return; + } + if (is_custom_format) { + frappe.set_route("print-format-builder", print_format.name); + return; + } + // start a new print format + frappe.prompt([ + { + label: __("New Print Format Name"), + fieldname: "print_format_name", + fieldtype: "Data", + reqd: 1, + }, + { + label: __('Based On'), + fieldname: 'based_on', + fieldtype: 'Read Only', + default: print_format.name || 'Standard' } - } else { - // start a new print format - frappe.prompt({ - fieldname: "print_format_name", fieldtype: "Data", reqd: 1, - label: "New Print Format Name" - }, function (data) { - frappe.route_options = { - make_new: true, - doctype: me.frm.doctype, - name: data.print_format_name - }; - frappe.set_route("print-format-builder"); - }, __("New Custom Print Format"), __("Start")); + ], (data) => { + frappe.route_options = { + make_new: true, + doctype: me.frm.doctype, + name: data.print_format_name, + based_on: data.based_on + }; + frappe.set_route("print-format-builder"); + }, __("New Custom Print Format"), __("Start")); + }); + + $(document).on('new-print-format', (e) => { + this.refresh_print_options(); + if (e.print_format) { + this.print_sel.val(e.print_format); } }); }, @@ -125,10 +155,16 @@ frappe.ui.form.PrintPreview = Class.extend({ multilingual_preview: function () { var me = this; if (this.is_old_style()) { + me.wrapper.find(".btn-print-preview").toggle(true); me.wrapper.find(".btn-download-pdf").toggle(false); me.set_style(); me.preview_old_style(); + } else if (this.is_raw_printing()) { + me.wrapper.find(".btn-print-preview").toggle(false); + me.wrapper.find(".btn-download-pdf").toggle(false); + me.preview(); } else { + me.wrapper.find(".btn-print-preview").toggle(true); me.wrapper.find(".btn-download-pdf").toggle(true); me.preview(); } @@ -190,6 +226,33 @@ frappe.ui.form.PrintPreview = Class.extend({ callback: function (data) { } }); + } else if (me.get_mapped_printer().length === 1) { + // printer is already mapped in localstorage (applies for both raw and pdf ) + if (me.is_raw_printing()) { + me.get_raw_commands(function (out) { + frappe.ui.form.qz_connect().then(function () { + let printer_map = me.get_mapped_printer()[0]; + let data = [out.raw_commands]; + let config = qz.configs.create(printer_map.printer); + return qz.print(config, data); + }).then(frappe.ui.form.qz_success).catch((err) => { + frappe.ui.form.qz_fail(err); + }); + }); + } else { + frappe.show_alert({ + message: __('PDF printing via "Raw Print" is not yet supported. Please remove the printer mapping in Printer Settings and try again.'), + indicator: 'blue' + }, 14); + //Note: need to solve "Error: Cannot parse (FILE) as a PDF file" to enable qz pdf printing. + } + } else if (me.is_raw_printing()) { + // printer not mapped in localstorage and the current print format is raw printing + frappe.show_alert({ + message: __('Please set a printer mapping for this print format in the Printer Settings'), + indicator: 'blue' + }, 14); + me.printer_setting_dialog(); } else { me.new_page_preview(true); } @@ -225,6 +288,41 @@ frappe.ui.form.PrintPreview = Class.extend({ } }); }, + get_raw_commands: function (callback) { + // fetches rendered raw commands from the server for the current print format. + frappe.call({ + method: "frappe.www.printview.get_rendered_raw_commands", + args: { + doc: this.frm.doc, + print_format: this.selected_format(), + _lang: this.lang_code + }, + callback: function (r) { + if (!r.exc) { + callback(r.message); + } + } + }); + }, + get_mapped_printer: function () { + // returns a list of "print format: printer" mapping filtered by the current print format + let print_format_printer_map = this.get_print_format_printer_map(); + if (print_format_printer_map[this.frm.doctype]) { + return print_format_printer_map[this.frm.doctype].filter( + (printer_map) => printer_map.print_format == this.selected_format()); + } else { + return []; + } + }, + get_print_format_printer_map: function () { + // returns the whole object "print_format_printer_map" stored in the localStorage. + try { + let print_format_printer_map = JSON.parse(localStorage.print_format_printer_map); + return print_format_printer_map; + } catch (e) { + return {}; + } + }, preview_old_style: function () { var me = this; this.with_old_style({ @@ -270,6 +368,9 @@ frappe.ui.form.PrintPreview = Class.extend({ is_old_style: function (format) { return this.get_print_format(format).print_format_type === "Client"; }, + is_raw_printing: function (format) { + return this.get_print_format(format).raw_printing === 1; + }, get_print_format: function (format) { if (!format) { format = this.selected_format(); @@ -286,6 +387,71 @@ frappe.ui.form.PrintPreview = Class.extend({ }, set_style: function (style) { frappe.dom.set_style(style || frappe.boot.print_css, "print-style"); + }, + printer_setting_dialog: function () { + // dialog for the Printer Settings + var me = this; + this.print_format_printer_map = me.get_print_format_printer_map(); + this.data = []; + this.data = this.print_format_printer_map[this.frm.doctype] || []; + this.printer_list = []; + frappe.ui.form.qz_get_printer_list().then((data) => { + this.printer_list = data; + const dialog = new frappe.ui.Dialog({ + title: __("Printer Settings"), + fields: [{ + fieldtype: 'Section Break' + }, + { + fieldname: "printer_mapping", + fieldtype: "Table", + label: __('Printer Mapping'), + in_place_edit: true, + data: this.data, + get_data: () => { + return this.data; + }, + fields: [{ + fieldtype: 'Select', + fieldname: "print_format", + default: 0, + options: this.print_formats, + read_only: 0, + in_list_view: 1, + label: __('Print Format') + }, { + fieldtype: 'Select', + fieldname: "printer", + default: 0, + options: this.printer_list, + read_only: 0, + in_list_view: 1, + label: __('Printer') + }] + }, + ], + primary_action: function () { + let printer_mapping = this.get_values()["printer_mapping"]; + if (printer_mapping && printer_mapping.length) { + let print_format_list = printer_mapping.map(a => a.print_format); + let has_duplicate = print_format_list.some((item, idx) => print_format_list.indexOf(item) != idx); + if (has_duplicate) + frappe.throw(__("Cannot have multiple printers mapped to a single print format.")); + } else { + printer_mapping = []; + } + this.print_format_printer_map = me.get_print_format_printer_map(); + this.print_format_printer_map[me.frm.doctype] = printer_mapping; + localStorage.print_format_printer_map = JSON.stringify(this.print_format_printer_map); + this.hide(); + }, + primary_action_label: __('Save') + }); + dialog.show(); + if (!(this.printer_list && this.printer_list.length)) { + frappe.throw(__("No Printer is Available.")); + } + }); } }); @@ -326,3 +492,119 @@ frappe.ui.get_print_settings = function (pdf, callback, letter_head) { callback(data); }, __("Print Settings")); } + + +// qz tray connection wrapper +// - allows active and inactive connections to resolve regardless +// - try to connect once before firing the mimetype launcher +// - if connection fails, catch the reject, fire the mimetype launcher +// - after mimetype launcher is fired, try to connect 3 more times +// - display success/fail message to user +frappe.ui.form.qz_connect = function () { + return new Promise(function (resolve, reject) { + frappe.ui.form.qz_init().then(() => { + if (qz.websocket.isActive()) { // if already active, resolve immediately + // frappe.show_alert({message: __('QZ Tray Connection Active!'), indicator: 'green'}); + resolve(); + } else { + // try to connect once before firing the mimetype launcher + frappe.show_alert({ + message: __('Attempting Connection to QZ Tray...'), + indicator: 'blue' + }); + qz.websocket.connect().then(() => { + frappe.show_alert({ + message: __('Connected to QZ Tray!'), + indicator: 'green' + }); + resolve(); + }, function retry(err) { + if (err.message === 'Unable to establish connection with QZ') { + // if a connect was not successful, launch the mimetype, try 3 more times + frappe.show_alert({ + message: __('Attempting to launch QZ Tray...'), + indicator: 'blue' + }, 14); + window.location.assign("qz:launch"); + qz.websocket.connect({ + retries: 3, + delay: 1 + }).then(() => { + frappe.show_alert({ + message: __('Connected to QZ Tray!'), + indicator: 'green' + }); + resolve(); + }, + () => { + frappe.throw(__('Error connecting to QZ Tray Application...

    You need to have QZ Tray application installed and running, to use the Raw Print feature.

    Click here to Download and install QZ Tray.
    Click here to learn more about Raw Printing.')); + reject(); + }); + } else { + frappe.show_alert({ + message: 'QZ Tray ' + err.toString(), + indicator: 'red' + }, 14); + reject(); + } + }); + } + }); + }); +} + +frappe.ui.form.qz_init = function () { + // Initializing qz tray library + return new Promise((resolve) => { + if (typeof qz === "object" && typeof qz.version === "string") { + // resolve immediately if already Initialized + resolve(); + } else { + let qz_required_assets = [ + "/assets/frappe/node_modules/js-sha256/build/sha256.min.js", + "/assets/frappe/node_modules/qz-tray/qz-tray.js" + ]; + frappe.require(qz_required_assets,() => { + qz.api.setPromiseType(function promise(resolver) { + return new Promise(resolver); + }); + qz.api.setSha256Type(function (data) { + // Codacy fix + /*global sha256*/ + return sha256(data); + }); + resolve(); + }); + // note 'frappe.require' does not have callback on fail. Hence, any failure cannot be communicated to the user. + } + + }); +} + +frappe.ui.form.qz_get_printer_list = function () { + // returns the list of printers that are available to the QZ Tray + return frappe.ui.form.qz_connect().then(function () { + return qz.printers.find(); + }).then((data) => { + return data; + }).catch((err) => { + frappe.ui.form.qz_fail(err); + }); +} + +frappe.ui.form.qz_success = function () { + // notify qz successful print + frappe.show_alert({ + message: __('Print Sent to the printer!'), + indicator: 'green' + }); +} + +frappe.ui.form.qz_fail = function (e) { + // notify qz errors + frappe.show_alert({ + message: __("QZ Tray Failed: ") + e.toString(), + indicator: 'red' + }, 20); +} + diff --git a/frappe/public/js/frappe/form/review.js b/frappe/public/js/frappe/form/review.js index 8447118644..90a458d442 100644 --- a/frappe/public/js/frappe/form/review.js +++ b/frappe/public/js/frappe/form/review.js @@ -7,29 +7,21 @@ frappe.ui.form.Review = class Review { constructor({parent, frm}) { this.parent = parent; this.frm = frm; - this.fetch_energy_points() - .then(() => { - this.make_review_container(); - this.add_review_button(); - this.update_reviewers(); - }); + this.points = frappe.boot.points; + this.make_review_container(); + this.add_review_button(); + this.update_reviewers(); } - fetch_energy_points() { + update_points() { return frappe.xcall('frappe.social.doctype.energy_point_log.energy_point_log.get_energy_points', { user: frappe.session.user }).then(data => { + frappe.boot.points = data; this.points = data; }); } make_review_container() { - this.$wrapper = this.parent.append(` - - `); - this.review_list_wrapper = this.$wrapper.find('.review-list'); + this.review_list_wrapper = this.parent.find('.review-list'); } add_review_button() { @@ -88,7 +80,6 @@ frappe.ui.form.Review = class Review { fieldtype: 'Autocomplete', label: __('To User'), options: user_options, - default: user_options.includes(doc_owner) ? doc_owner : user_options[0], description: __('Only users involved in the document are listed') }, { fieldname: 'review_type', @@ -115,6 +106,7 @@ frappe.ui.form.Review = class Review { label: __('Reason') }], primary_action: (values) => { + review_dialog.disable_primary_action(); if (values.points > this.points.review_points) { return frappe.msgprint(__('You do not have enough points')); } @@ -133,6 +125,9 @@ frappe.ui.form.Review = class Review { this.frm.get_docinfo().energy_point_logs.unshift(review); this.frm.timeline.refresh(); this.update_reviewers(); + this.update_points(); + }).finally(() => { + review_dialog.enable_primary_action(); }); }, primary_action_label: __('Submit') diff --git a/frappe/public/js/frappe/form/templates/form_sidebar.html b/frappe/public/js/frappe/form/templates/form_sidebar.html index 4f599ce34c..9b09565c37 100644 --- a/frappe/public/js/frappe/form/templates/form_sidebar.html +++ b/frappe/public/js/frappe/form/templates/form_sidebar.html @@ -1,6 +1,5 @@
  • {{ __("Help") }}
  • +
  • {{ __("Help") }}
  • {% } %} -
    +
    diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 5e016f0215..4237b83ef5 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -310,6 +310,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { before_refresh() { if (frappe.route_options) { this.filters = this.parse_filters_from_route_options(); + frappe.route_options = null; if (this.filters.length > 0) { return this.filter_area.clear(false) @@ -373,7 +374,10 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { if (this.data.length > 0) { // append rows this.$result.append( - this.data.map(doc => this.get_list_row_html(doc)).join('') + this.data.map((doc, i) => { + doc._idx = i; + return this.get_list_row_html(doc); + }).join('') ); } this.on_row_checked(); @@ -473,7 +477,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } get_list_row_html(doc) { - return this.get_list_row_html_skeleton(this.get_left_html(doc), this.get_right_html(doc)); + return this.get_list_row_html_skeleton(this.get_left_html(doc), + this.get_right_html(doc)); } get_list_row_html_skeleton(left = '', right = '') { @@ -513,6 +518,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { return formatters[fieldname](value, df, doc); } else if (df.fieldtype === 'Code') { return value; + } else if (df.fieldtype === 'Percent') { + return `
    +
    +
    +
    `; } else { return frappe.format(value, df, null, doc); } @@ -580,7 +592,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { get_meta_html(doc) { let html = ''; - if (doc[this.meta.title_field || ''] !== doc.name) { + if (!this.settings.hide_name_column && doc[this.meta.title_field || ''] !== doc.name) { html += ` `; } + + if (this.settings.button && this.settings.button.show(doc)) { + html += ` + + `; + } + const modified = comment_when(doc.modified, true); const last_assignee = JSON.parse(doc._assign || '[]').slice(-1)[0]; @@ -621,14 +646,23 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { get_count_str() { const current_count = this.data.length; + const filters = this.get_filters_for_args(); + const with_child_table_filter = filters.some(filter => { + return filter[0] !== this.doctype; + }); + + const fields = [ + // cannot break this line as it adds extra \n's and \t's which breaks the query + `count(${with_child_table_filter ? 'distinct': ''}${frappe.model.get_full_column_name('name', this.doctype)}) AS total_count` + ]; return frappe.call({ type: 'GET', method: this.method, args: { doctype: this.doctype, - filters: this.get_filters_for_args(), - fields: [`count(${frappe.model.get_full_column_name('name', this.doctype)}) as total_count`], + filters, + fields, } }).then(r => { this.total_count = r.message.values[0][0] || current_count; @@ -676,7 +710,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { - + ${subject} @@ -710,6 +744,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { this.setup_check_events(); this.setup_like(); this.setup_realtime_updates(); + this.setup_action_handler(); } setup_filterable() { @@ -762,6 +797,16 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { }); } + setup_action_handler() { + this.$result.on('click', '.btn-action', (e) => { + const $button = $(e.currentTarget); + const doc = this.data[$button.attr('data-idx')]; + this.settings.button.action(doc); + e.stopPropagation(); + return false; + }); + } + setup_check_events() { this.$result.on('change', 'input[type=checkbox]', e => { const $target = $(e.currentTarget); diff --git a/frappe/public/js/frappe/misc/datetime.js b/frappe/public/js/frappe/misc/datetime.js index df0d2efeea..8481fec5c5 100644 --- a/frappe/public/js/frappe/misc/datetime.js +++ b/frappe/public/js/frappe/misc/datetime.js @@ -74,6 +74,14 @@ $.extend(frappe.datetime, { return moment(d).add(months, "months").format(); }, + week_start: function() { + return moment().startOf("week").format(); + }, + + week_end: function() { + return moment().endOf("week").format(); + }, + month_start: function() { return moment().startOf("month").format(); }, diff --git a/frappe/public/js/frappe/misc/energy_point_utils.js b/frappe/public/js/frappe/misc/energy_point_utils.js index dc7df166ec..05b3347fdc 100644 --- a/frappe/public/js/frappe/misc/energy_point_utils.js +++ b/frappe/public/js/frappe/misc/energy_point_utils.js @@ -21,28 +21,30 @@ Object.assign(frappe.energy_points, { format_history_log(log) { // redundant code to honor readability and to avoid confusion const separator = ` - `; + const route = frappe.utils.get_form_link(log.reference_doctype, log.reference_name); const formatted_log = ` ${this.get_points(log.points)}  - ${this.get_history_log_message(log)} + ${this.get_history_log_message(log)} ${log.reason ? separator + log.reason: ''} ${separator + frappe.datetime.comment_when(log.creation)} `; return formatted_log; }, get_history_log_message(log) { - const doc_link = frappe.utils.get_form_link(log.reference_doctype, log.reference_name, true); const owner_name = frappe.user.full_name(log.owner).bold(); + const user = frappe.user.full_name(log.user).bold(); + const ref_doc = log.reference_name; + if (log.type === 'Appreciation') { - return __('{0} appreciated on {1}', [owner_name, doc_link]); + return __('{0} appreciated on {1}', [owner_name, ref_doc]); } if (log.type === 'Criticism') { - return __('{0} criticized on {1}', [owner_name, doc_link]); + return __('{0} criticized on {1}', [owner_name, ref_doc]); } if (log.type === 'Revert') { - return __('{0} reverted {1}', [owner_name, - frappe.utils.get_form_link('Energy Point Log', log.revert_of, true)]); + return __('{0} reverted {1}', [user, log.revert_of]); } - return __('via automatic rule {0} on {1}', [log.rule.bold(), doc_link]); + return __('via automatic rule {0} on {1}', [log.rule.bold(), ref_doc]); }, get_form_log_message(log) { // redundant code to honor readability and to avoid confusion diff --git a/frappe/public/js/frappe/misc/help.js b/frappe/public/js/frappe/misc/help.js index 1d3402c216..eb12ba758e 100644 --- a/frappe/public/js/frappe/misc/help.js +++ b/frappe/public/js/frappe/misc/help.js @@ -21,10 +21,13 @@ frappe.help.show_video = function(youtube_id, title) { } else { var size = [560, 315]; } - var dialog = frappe.msgprint('' + (frappe.help_feedback_link || ""), - title || __("Help")); + var dialog = frappe.msgprint({ + message: `` + (frappe.help_feedback_link || ""), + title: title || __("Help"), + wide: true + }); dialog.$wrapper.addClass("video-modal"); } diff --git a/frappe/public/js/frappe/misc/utils.js b/frappe/public/js/frappe/misc/utils.js index cdd8a614ad..b892ec994c 100644 --- a/frappe/public/js/frappe/misc/utils.js +++ b/frappe/public/js/frappe/misc/utils.js @@ -194,25 +194,6 @@ Object.assign(frappe.utils, { return list; } }, - set_intro: function(me, wrapper, txt, append, indicator) { - if(!me.intro_area) { - me.intro_area = $('
    ') - .prependTo(wrapper); - } - if(txt) { - if(!append) { - me.intro_area.empty(); - } - if(indicator) { - me.intro_area.html('
    '+txt+'
    ') - } else { - me.intro_area.html('

    '+txt+'

    ') - } - } else { - me.intro_area.remove(); - me.intro_area = null; - } - }, set_footnote: function(footnote_area, wrapper, txt) { if(!footnote_area) { footnote_area = $('
    ') @@ -667,11 +648,20 @@ Object.assign(frappe.utils, { }, get_route_label(route_str) { let route = route_str.split('/'); - if (['List', 'modules'].includes(route[0])){ - return `${route[1]} ${route[2] || route[0]}`; - } else { - return `${route[0]} ${route[1]}`; + + if (route[2] === 'Report' || route[0] === 'query-report') { + return __('{0} Report', [route[3] || route[1]]); } + if (route[0] === 'List') { + return __('{0} List', [route[1]]); + } + if (route[0] === 'modules') { + return __('{0} Modules', [route[1]]); + } + if (route[0] === 'dashboard') { + return __('{0} Dashboard', [route[1]]); + } + return __(frappe.utils.to_title_case(route[0], true)); }, report_column_total: function(values, column, type) { if (column.column.fieldtype == "Percent" || type === "mean") { @@ -688,6 +678,27 @@ Object.assign(frappe.utils, { deep_equal(a, b) { return deep_equal(a, b); }, + + file_name_ellipsis(filename, length) { + let first_part_length = length * 2 / 3; + let last_part_length = length - first_part_length; + let parts = filename.split('.'); + let extn = parts.pop(); + let name = parts.join(''); + let first_part = name.slice(0, first_part_length); + let last_part = name.slice(-last_part_length); + if (name.length > length) { + return `${first_part}...${last_part}.${extn}`; + } else { + return filename; + } + }, + get_decoded_string(dataURI) { + // decodes base64 to string + let parts = dataURI.split(','); + const encoded_data = parts[1]; + return decodeURIComponent(escape(atob(encoded_data))); + } }); // Array de duplicate @@ -701,4 +712,10 @@ if (!Array.prototype.uniqBy) { }); } }); + Object.defineProperty(Array.prototype, 'move', { + value: function(from, to) { + this.splice(to, 0, this.splice(from, 1)[0]); + } + }); } + diff --git a/frappe/public/js/frappe/model/create_new.js b/frappe/public/js/frappe/model/create_new.js index 90828ab5b0..74ce746123 100644 --- a/frappe/public/js/frappe/model/create_new.js +++ b/frappe/public/js/frappe/model/create_new.js @@ -125,8 +125,9 @@ $.extend(frappe.model, { var user_default = ""; var user_permissions = frappe.defaults.get_user_permissions(); let allowed_records = []; + let default_doc = null; if(user_permissions) { - allowed_records = frappe.perm.get_allowed_docs_for_doctype(user_permissions[df.options], doc.doctype); + ({allowed_records, default_doc} = frappe.perm.filter_allowed_docs_for_doctype(user_permissions[df.options], doc.doctype)); } var meta = frappe.get_meta(doc.doctype); var has_user_permissions = (df.fieldtype==="Link" @@ -139,8 +140,8 @@ $.extend(frappe.model, { // 1 - look in user permissions for document_type=="Setup". // We don't want to include permissions of transactions to be used for defaults. if (df.linked_document_type==="Setup" - && has_user_permissions && allowed_records.length===1) { - return allowed_records[0]; + && has_user_permissions && default_doc) { + return default_doc; } if(!df.ignore_user_permissions) { diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index 4ecb4541fc..8f6d4d95e3 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -196,12 +196,17 @@ $.extend(frappe.meta, { get_print_formats: function(doctype) { var print_format_list = ["Standard"]; var default_print_format = locals.DocType[doctype].default_print_format; - + let enable_raw_printing = frappe.model.get_doc(":Print Settings", "Print Settings").enable_raw_printing; var print_formats = frappe.get_list("Print Format", {doc_type: doctype}) .sort(function(a, b) { return (a > b) ? 1 : -1; }); $.each(print_formats, function(i, d) { - if(!in_list(print_format_list, d.name) && in_list(['Server', 'Client'], d.print_format_type)) + if ( + !in_list(print_format_list, d.name) + && d.print_format_type !== 'JS' + && (cint(enable_raw_printing) || !d.raw_printing) + ) { print_format_list.push(d.name); + } }); if(default_print_format && default_print_format != "Standard") { diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index 9d4ad3af1c..2b470c819d 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -126,7 +126,6 @@ $.extend(frappe.model, { cached_timestamp: cached_timestamp }, async: async, - freeze: true, callback: function(r) { if(r.exc) { frappe.msgprint(__("Unable to load: {0}", [__(doctype)])); @@ -183,7 +182,6 @@ $.extend(frappe.model, { doctype: doctype, name: name }, - freeze: true, callback: function(r) { callback && callback(name, r); resolve(frappe.get_doc(doctype, name)); diff --git a/frappe/public/js/frappe/model/perm.js b/frappe/public/js/frappe/model/perm.js index 2396962b94..f30368bbc3 100644 --- a/frappe/public/js/frappe/model/perm.js +++ b/frappe/public/js/frappe/model/perm.js @@ -254,8 +254,30 @@ $.extend(frappe.perm, { }, get_allowed_docs_for_doctype: (user_permissions, doctype) => { - return (user_permissions || []).filter(perm => { + // returns docs from the list of user permissions that are allowed under provided doctype + return frappe.perm.filter_allowed_docs_for_doctype(user_permissions, doctype, false); + }, + + filter_allowed_docs_for_doctype: (user_permissions, doctype, with_default_doc=true) => { + // returns docs from the list of user permissions that are allowed under provided doctype + // also returns default doc when with_default_doc is set + const filtered_perms = (user_permissions || []).filter(perm => { return (perm.applicable_for === doctype || !perm.applicable_for); - }).map(perm => perm.doc); + }); + + const allowed_docs = (filtered_perms).map(perm => perm.doc); + + if (with_default_doc) { + const default_doc = allowed_docs.length === 1 ? allowed_docs : filtered_perms + .filter(perm => perm.is_default) + .map(record => record.doc); + + return { + allowed_records: allowed_docs, + default_doc: default_doc[0] + }; + } else { + return allowed_docs; + } } -}); +}); \ No newline at end of file diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js index ee21f121bc..db0b4bbe46 100644 --- a/frappe/public/js/frappe/request.js +++ b/frappe/public/js/frappe/request.js @@ -30,7 +30,8 @@ frappe.call = function(opts) { indicator: 'orange', message: __('You are not connected to Internet. Retry after sometime.') }, 3); - return; + opts.always && opts.always(); + return $.ajax(); } if (typeof arguments[0]==='string') { opts = { @@ -384,14 +385,16 @@ frappe.after_ajax = function(fn) { frappe.request.report_error = function(xhr, request_opts) { var data = JSON.parse(xhr.responseText); + var exc; if (data.exc) { - var exc = (JSON.parse(data.exc) || []).join("\n"); - var locals = (JSON.parse(data.locals) || []).join("\n"); + try { + exc = (JSON.parse(data.exc) || []).join("\n"); + } catch (e) { + exc = data.exc; + } delete data.exc; - delete data.locals; } else { - var exc = ""; - locals = ""; + exc = ""; } var show_communication = function() { @@ -424,7 +427,7 @@ frappe.request.report_error = function(xhr, request_opts) { name: frappe.session.user } }); - communication_composer.dialog.$wrapper.css("z-index", cint(msg_dialog.$wrapper.css("z-index")) + 1); + communication_composer.dialog.$wrapper.css("z-index", cint(frappe.msg_dialog.$wrapper.css("z-index")) + 1); } if (exc) { diff --git a/frappe/public/js/frappe/roles_editor.js b/frappe/public/js/frappe/roles_editor.js index 0e5a85be68..50a3db776c 100644 --- a/frappe/public/js/frappe/roles_editor.js +++ b/frappe/public/js/frappe/roles_editor.js @@ -183,7 +183,7 @@ frappe.RoleEditor = Class.extend({ %(set_user_permissions)s\ ', perm)); } - + me.perm_dialog.set_title(role); me.perm_dialog.show(); } }); @@ -191,7 +191,7 @@ frappe.RoleEditor = Class.extend({ }, make_perm_dialog: function() { this.perm_dialog = new frappe.ui.Dialog({ - title:__('Role Permissions') + title: __('Role Permissions') }); this.perm_dialog.$wrapper.find('.modal-dialog').css("width", "800px"); diff --git a/frappe/public/js/frappe/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/social/Home.vue b/frappe/public/js/frappe/social/Home.vue index cf112dcb92..7aa752e6db 100644 --- a/frappe/public/js/frappe/social/Home.vue +++ b/frappe/public/js/frappe/social/Home.vue @@ -60,10 +60,6 @@ export default { this.show_preview = false; }) - frappe.app_updates.on('user_image_updated', () => { - this.$root.$emit('user_image_updated') - }) - this.update_primary_action(frappe.get_route()[1]) }, mounted() { diff --git a/frappe/public/js/frappe/social/components/EnergyPointHistory.vue b/frappe/public/js/frappe/social/components/EnergyPointHistory.vue index 87b9a3e339..bf9b765a4c 100644 --- a/frappe/public/js/frappe/social/components/EnergyPointHistory.vue +++ b/frappe/public/js/frappe/social/components/EnergyPointHistory.vue @@ -22,13 +22,13 @@ diff --git a/frappe/public/js/frappe/social/pages/UserList.vue b/frappe/public/js/frappe/social/pages/UserList.vue index 2007593ce2..ab3883a4a7 100644 --- a/frappe/public/js/frappe/social/pages/UserList.vue +++ b/frappe/public/js/frappe/social/pages/UserList.vue @@ -2,48 +2,57 @@
    • + - {{ __('User') }} + - - {{ __('Energy Points') }} - - - {{ __('Review Points') }} - - - {{ __('Points Given') }} + + +
    • -
    • +
    • + # + {{ __('User') }} + {{ __(title) }} +
    • +
    • -
      + + {{ index + 1 }} {{ user.fullname }} -
      - {{ frappe.ellipsis(user.bio, 100) || 'No Bio'}} -
      +
      {{ frappe.ellipsis(user.bio, 100) || 'No Bio'}}
      -
      - - {{ user.energy_points }} - - - {{ user.review_points }} - - - {{ user.given_points }} + {{ user[key] }}
      - + :from_date="from_date" + :key="user.name + user.energy_points" + >
    • {{__('No user found')}}
    @@ -61,17 +70,32 @@ export default { filter_users_by: null, sort_users_by: 'energy_points', sort_order: 'desc', - show_log_for: null - } + show_log_for: null, + period_options: ['Lifetime', 'This Month', 'This Week', 'Today'], + period: 'This Month' + }; }, computed: { + from_date() { + if (this.period === 'This Month') { + return frappe.datetime.month_start(); + } + if (this.period === 'This Week') { + return frappe.datetime.week_start(); + } + if (this.period === 'Today') { + return frappe.datetime.get_today(); + } + return null; + }, filtered_users() { let filtered = this.users.slice(); - if (this.filter_users_by) { filtered = filtered.filter(user => - user.fullname.toLowerCase().includes(this.filter_users_by.toLowerCase()) - ) + user.fullname + .toLowerCase() + .includes(this.filter_users_by.toLowerCase()) + ); } if (this.sort_users_by) { @@ -89,15 +113,23 @@ export default { } if (this.sort_order === 'desc') { - return_value = -return_value + return_value = -return_value; } - return return_value + return return_value; }); } return filtered; } }, + watch: { + period() { + this.fetch_users_energy_points_and_update_users(); + } + }, + mounted() { + $('[data-toggle="tooltip"]').tooltip(); + }, created() { const standard_users = ['Administrator', 'Guest', 'guest@example.com']; this.users = frappe.boot.user_info; @@ -111,37 +143,42 @@ export default { }, methods: { get_avatar(user) { - return frappe.avatar(user, 'avatar-medium') + return frappe.avatar(user, 'avatar-medium'); }, go_to_profile_page(user) { - frappe.set_route('social', 'profile', user) + frappe.set_route('social', 'profile', user); }, fetch_users_energy_points_and_update_users() { - frappe.xcall( - 'frappe.social.doctype.energy_point_log.energy_point_log.get_user_energy_and_review_points' - ).then(data => { - let users = this.users.slice(); - this.users = users.map(user => { - const points = data[user.name] || {}; - user.energy_points = points.energy_points || 0; - user.review_points = points.review_points || 0; - user.given_points = points.given_points || 0; - return user; + frappe + .xcall( + 'frappe.social.doctype.energy_point_log.energy_point_log.get_user_energy_and_review_points', + { + from_date: this.from_date + } + ) + .then(data => { + let users = this.users.slice(); + this.users = users.map(user => { + const points = data[user.name] || {}; + user.energy_points = points.energy_points || 0; + user.review_points = points.review_points || 0; + user.given_points = points.given_points || 0; + return user; + }); }); - }); }, toggle_log(user) { if (this.show_log_for === user) { - this.show_log_for = null + this.show_log_for = null; } else { - this.show_log_for = user + this.show_log_for = user; } } } -} +}; -

    This is a test html snippet

    +class TestPdf(unittest.TestCase): + @property + def html(self): + return """ +

    This is a test html snippet

    Test link 1 Test link 2 @@ -26,16 +28,22 @@ class TestPdfBorders(unittest.TestCase):
    Please mail us at email -
    - """ +
    """ - html, html_options = read_options_from_html(html) - self.assertTrue(html_options['margin-top'] == '0') - self.assertTrue(html_options['margin-left'] == '10') - self.assertTrue(html_options['margin-right'] == '0') + def runTest(self): + self.test_read_options_from_html() -# allows to run $ bench execute frappe.tests.test_pdf.run_tests -def run_tests(): - t = TestPdfBorders("test_pdf_borders") - t.test_pdf_borders() - return + def test_read_options_from_html(self): + _, html_options = pdfgen.read_options_from_html(self.html) + self.assertTrue(html_options['margin-top'] == '0') + self.assertTrue(html_options['margin-left'] == '10') + self.assertTrue(html_options['margin-right'] == '0') + + def test_pdf_encryption(self): + password = "qwe" + pdf = pdfgen.get_pdf(self.html, options={"password": password}) + reader = PdfFileReader(io.BytesIO(pdf)) + self.assertTrue(reader.isEncrypted) + if six.PY2: + password = frappe.safe_encode(password) + self.assertTrue(reader.decrypt(password)) \ No newline at end of file diff --git a/frappe/tests/test_permissions.py b/frappe/tests/test_permissions.py index 2a9ac69fe6..15597aef39 100644 --- a/frappe/tests/test_permissions.py +++ b/frappe/tests/test_permissions.py @@ -98,6 +98,12 @@ class TestPermissions(unittest.TestCase): doc = frappe.new_doc("Blog Post") self.assertFalse(doc.get("blog_category")) + # Fetch user permission set as default from multiple user permission + add_user_permission("Blog Category", "_Test Blog Category 2", "test2@example.com", ignore_permissions=True, is_default=1) + frappe.clear_cache() + doc = frappe.new_doc("Blog Post") + self.assertEqual(doc.get("blog_category"), "_Test Blog Category 2") + def test_user_link_match_doc(self): blogger = frappe.get_doc("Blogger", "_Test Blogger 1") blogger.user = "test2@example.com" diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index c47ebd01f0..9efd4d04a1 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -39,6 +39,14 @@ class TestWebsite(unittest.TestCase): dict(source=r'/testfromregex.*', target=r'://testto2'), dict(source=r'/testsub/(.*)', target=r'://testto3/\1') ] + + website_settings = frappe.get_doc('Website Settings') + website_settings.append('route_redirects', { + 'source': '/testsource', + 'target': '/testtarget' + }) + website_settings.save() + frappe.cache().delete_key('app_hooks') frappe.cache().delete_key('website_redirects') @@ -61,6 +69,11 @@ class TestWebsite(unittest.TestCase): response = render.render() self.assertEquals(response.status_code, 404) + set_request(method='GET', path='/testsource') + response = render.render() + self.assertEquals(response.status_code, 301) + self.assertEquals(response.headers.get('Location'), '/testtarget') + delattr(frappe.hooks, 'website_redirects') frappe.cache().delete_key('app_hooks') diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 78e6b56fe2..88d02be625 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -541,6 +541,8 @@ def get_site_info(): system_settings = frappe.db.get_singles_dict('System Settings') space_usage = frappe._dict((frappe.local.conf.limits or {}).get('space_usage', {})) + kwargs = {"fields": ["user", "creation", "full_name"], "filters":{"Operation": "Login", "Status": "Success"}, "limit": "10"} + site_info = { 'installed_apps': get_installed_apps_info(), 'users': users, @@ -555,7 +557,8 @@ def get_site_info(): 'space_used': flt((space_usage.total or 0) / 1024.0, 2), 'database_size': space_usage.database_size, 'backup_size': space_usage.backup_size, - 'files_size': space_usage.files_size + 'files_size': space_usage.files_size, + 'last_logins': frappe.get_all("Activity Log", **kwargs) } # from other apps diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 21d1790a1c..bad5407b4b 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -70,7 +70,7 @@ def to_timedelta(time_str): else: return time_str -def add_to_date(date, years=0, months=0, weeks=0, days=0, hours=0, as_string=False, as_datetime=False): +def add_to_date(date, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, seconds=0, as_string=False, as_datetime=False): """Adds `days` to the given date""" from dateutil.relativedelta import relativedelta @@ -86,7 +86,7 @@ def add_to_date(date, years=0, months=0, weeks=0, days=0, hours=0, as_string=Fal as_datetime = True date = parser.parse(date) - date = date + relativedelta(years=years, months=months, weeks=weeks, days=days, hours=hours) + date = date + relativedelta(years=years, months=months, weeks=weeks, days=days, hours=hours, minutes=minutes, seconds=seconds) if as_string: if as_datetime: @@ -108,6 +108,11 @@ def add_years(date, years): def date_diff(string_ed_date, string_st_date): return (getdate(string_ed_date) - getdate(string_st_date)).days +def month_diff(string_ed_date, string_st_date): + ed_date = getdate(string_ed_date) + st_date = getdate(string_st_date) + return (ed_date.year - st_date.year) * 12 + ed_date.month - st_date.month + 1 + def time_diff(string_ed_date, string_st_date): return get_datetime(string_ed_date) - get_datetime(string_st_date) diff --git a/frappe/utils/error.py b/frappe/utils/error.py index 8b4877cf4d..43579cf92e 100644 --- a/frappe/utils/error.py +++ b/frappe/utils/error.py @@ -199,15 +199,3 @@ def clear_old_snapshots(): def get_error_snapshot_path(): return frappe.get_site_path('error-snapshots') - -def get_frame_locals(): - traceback = sys.exc_info()[2] - frames = [] - if traceback: - frames = inspect.getinnerframes(traceback, context=0) - _locals = ['Locals (most recent call last):'] - for frame, filename, lineno, function, __, __ in frames: - if '/apps/' in filename: - _locals.append('File "{}", line {}, in {}\n{}'.format(filename, lineno, function, json.dumps(frame.f_locals, default=str, indent=4))) - - return '\n'.join(_locals) diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py index af38570021..b2f49d7d51 100644 --- a/frappe/utils/global_search.py +++ b/frappe/utils/global_search.py @@ -7,6 +7,8 @@ import frappe import re import redis import json +import os +from bs4 import BeautifulSoup from frappe.utils import cint, strip_html_tags from frappe.model.base_document import get_controller from six import text_type @@ -269,12 +271,66 @@ def update_global_search(doc): route=route ) - try: - # append to search queue if connected - frappe.cache().lpush('global_search_queue', json.dumps(value)) - except redis.exceptions.ConnectionError: - # not connected, sync directly - sync_value(value) + sync_value_in_queue(value) + +def update_global_search_for_all_web_pages(): + routes_to_index = get_routes_to_index() + for route in routes_to_index: + add_route_to_global_search(route) + sync_global_search() + + +def get_routes_to_index(): + apps = frappe.get_installed_apps() + + routes_to_index = [] + for app in apps: + base = frappe.get_app_path(app, 'www') + path_to_index = frappe.get_app_path(app, 'www') + + for dirpath, _, filenames in os.walk(path_to_index, topdown=True): + for f in filenames: + if f.endswith(('.md', '.html')): + filepath = os.path.join(dirpath, f) + + route = os.path.relpath(filepath, base) + route = route.split('.')[0] + + if route.endswith('index'): + route = route.rsplit('index', 1)[0] + + routes_to_index.append(route) + + return routes_to_index + + +def add_route_to_global_search(route): + from frappe.website.render import render_page + from frappe.tests.test_website import set_request + frappe.set_user('Guest') + frappe.local.no_cache = True + + try: + set_request(method='GET', path=route) + content = render_page(route) + soup = BeautifulSoup(content, 'html.parser') + page_content = soup.find(class_='page_content') + text_content = page_content.text if page_content else '' + title = soup.title.text.strip() if soup.title else route + + value = dict( + doctype='Static Web Page', + name=route, + content=text_content, + published=1, + title=title, + route=route + ) + sync_value_in_queue(value) + except (frappe.PermissionError, frappe.DoesNotExistError, frappe.ValidationError, Exception): + pass + + frappe.set_user('Administrator') def get_formatted_value(value, field): @@ -306,6 +362,14 @@ def sync_global_search(): value = json.loads(frappe.cache().lpop('global_search_queue').decode('utf-8')) sync_value(value) +def sync_value_in_queue(value): + try: + # append to search queue if connected + frappe.cache().lpush('global_search_queue', json.dumps(value)) + except redis.exceptions.ConnectionError: + # not connected, sync directly + sync_value(value) + def sync_value(value): ''' Sync a given document to global search @@ -393,10 +457,11 @@ def search(text, start=0, limit=20, doctype=""): @frappe.whitelist(allow_guest=True) -def web_search(text, start=0, limit=20): +def web_search(text, scope=None, start=0, limit=20): """ Search for given text in __global_search where published = 1 :param text: phrase to be searched + :param scope: search only in this route, for e.g /docs :param start: start results at, default 0 :param limit: number of results to return, default 20 :return: Array of result objects @@ -410,9 +475,13 @@ def web_search(text, start=0, limit=20): WHERE {conditions} LIMIT {limit} OFFSET {start}''' - mariadb_conditions = postgres_conditions = "`published` = 1 AND " + scope_condition = '`route` like "{}%" AND '.format(scope) if scope else '' + published_condition = '`published` = 1 AND ' + mariadb_conditions = postgres_conditions = ' '.join([published_condition, scope_condition]) - mariadb_conditions += 'MATCH(`content`) AGAINST ({} IN BOOLEAN MODE)'.format(frappe.db.escape('+' + text + '*')) + # https://mariadb.com/kb/en/library/full-text-index-overview/#in-boolean-mode + text = '"{}"'.format(text) + mariadb_conditions += 'MATCH(`content`) AGAINST ({} IN BOOLEAN MODE)'.format(frappe.db.escape(text)) postgres_conditions += 'TO_TSVECTOR("content") @@ PLAINTO_TSQUERY({})'.format(frappe.db.escape(text)) result = frappe.db.multisql({ @@ -425,4 +494,18 @@ def web_search(text, start=0, limit=20): tmp_result.append(i) results += tmp_result + # chart of accounts -> {chart, of, accounts} + # titles that match the most of these words will have high relevance + words = set(get_distinct_words(text)) + for r in results: + title_words = set(get_distinct_words(r.title)) + words_match = len(words.intersection(title_words)) + r.relevance = words_match + + results = sorted(results, key=lambda x: x.relevance, reverse=True) return results + +def get_distinct_words(text): + text = text.replace('"', '') + text = text.replace("'", '') + return [w.strip().lower() for w in text.split(' ')] diff --git a/frappe/utils/oauth.py b/frappe/utils/oauth.py index c688bdbb5c..f5cf5d81e8 100644 --- a/frappe/utils/oauth.py +++ b/frappe/utils/oauth.py @@ -270,7 +270,7 @@ def update_oauth_user(user, data, provider): elif provider=="fairlogin" and not user.get_social_login_userid(provider): save = True user.set_social_login_userid(provider, userid=data["preferred_username"]) - + if save: user.flags.ignore_permissions = True user.flags.no_welcome_mail = True @@ -291,3 +291,8 @@ def redirect_post_login(desk_user): # the #desktop is added to prevent a facebook redirect bug frappe.local.response["location"] = "/desk#desktop" if desk_user else "/" + +def oauth_decoder(data): + if isinstance(data, bytes): + data = data.decode("utf-8") + return json.loads(data) diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index 8c43bf33ce..54b90be6ed 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -5,27 +5,28 @@ from __future__ import unicode_literals import pdfkit, os, frappe from frappe.utils import scrub_urls from frappe import _ +import six, re, io from bs4 import BeautifulSoup -from PyPDF2 import PdfFileReader -import re +from PyPDF2 import PdfFileReader, PdfFileWriter -def get_pdf(html, options=None, output = None): +def get_pdf(html, options=None, output=None): html = scrub_urls(html) html, options = prepare_options(html, options) - fname = os.path.join("/tmp", "frappe-pdf-{0}.pdf".format(frappe.generate_hash())) options.update({ "disable-javascript": "", "disable-local-file-access": "", }) + filedata = '' + try: - pdfkit.from_string(html, fname, options=options or {}) - if output: - append_pdf(PdfFileReader(fname),output) - else: - with open(fname, "rb") as fileobj: - filedata = fileobj.read() + # Set filename property to false, so no file is actually created + filedata = pdfkit.from_string(html, False, options=options or {}) + + # https://pythonhosted.org/PyPDF2/PdfFileReader.html + # create in-memory binary streams from filedata and create a PdfFileReader object + reader = PdfFileReader(io.BytesIO(filedata)) except IOError as e: if ("ContentNotFoundError" in e.message @@ -34,29 +35,48 @@ def get_pdf(html, options=None, output = None): or "RemoteHostClosedError" in e.message): # allow pdfs with missing images if file got created - if os.path.exists(fname): - if output: - append_pdf(PdfFileReader(file(fname,"rb")),output) - else: - with open(fname, "rb") as fileobj: - filedata = fileobj.read() + if filedata: + if output: # output is a PdfFileWriter object + output.appendPagesFromReader(reader) else: frappe.throw(_("PDF generation failed because of broken image links")) else: raise - finally: - cleanup(fname, options) + if "password" in options: + password = options["password"] + if six.PY2: + password = frappe.safe_encode(password) if output: - return output + # Encrypt if required + if "password" in options: + output.encrypt(password) + return get_file_data_from_writer(output) + + writer = PdfFileWriter() + writer.appendPagesFromReader(reader) + + if "password" in options: + writer.encrypt(password) + + filedata = get_file_data_from_writer(writer) return filedata -def append_pdf(input,output): - # Merging multiple pdf files - [output.addPage(input.getPage(page_num)) for page_num in range(input.numPages)] +def get_file_data_from_writer(writer_obj): + + # https://docs.python.org/3/library/io.html + stream = io.BytesIO() + writer_obj.write(stream) + + # Change the stream position to start of the stream + stream.seek(0) + + # Read up to size bytes from the object and return them + return stream.read() + def prepare_options(html, options): if not options: diff --git a/frappe/utils/response.py b/frappe/utils/response.py index 8412ec6271..01c95006e8 100644 --- a/frappe/utils/response.py +++ b/frappe/utils/response.py @@ -105,8 +105,6 @@ def make_logs(response = None): if frappe.error_log: response['exc'] = json.dumps([frappe.utils.cstr(d["exc"]) for d in frappe.local.error_log]) - if frappe.conf.developer_mode: - response['locals'] = json.dumps([frappe.utils.cstr(d["locals"]) for d in frappe.local.error_log]) if frappe.local.message_log: response['_server_messages'] = json.dumps([frappe.utils.cstr(d) for diff --git a/frappe/utils/user.py b/frappe/utils/user.py index 504ce181ea..22f1e964e3 100755 --- a/frappe/utils/user.py +++ b/frappe/utils/user.py @@ -165,25 +165,6 @@ class UserPermissions: self.defaults = frappe.defaults.get_defaults(self.name) return self.defaults - # update recent documents - def update_recent(self, dt, dn): - rdl = frappe.cache().hget("user_recent", self.name) or [] - new_rd = [dt, dn] - - # clear if exists - for i in range(len(rdl)): - rd = rdl[i] - if rd==new_rd: - del rdl[i] - break - - if len(rdl) > 19: - rdl = rdl[:19] - - rdl = [new_rd] + rdl - - frappe.cache().hset("user_recent", self.name, rdl) - def _get(self, key): if not self.can_read: self.build_permissions() @@ -198,14 +179,13 @@ class UserPermissions: def load_user(self): d = frappe.db.sql("""select email, first_name, last_name, creation, email_signature, user_type, language, background_style, background_image, - mute_sounds, send_me_a_copy from tabUser where name = %s""", (self.name,), as_dict=1)[0] + mute_sounds, send_me_a_copy, document_follow_notify + from tabUser where name = %s""", (self.name,), as_dict=1)[0] if not self.can_read: self.build_permissions() d.name = self.name - d.recent = json.dumps(frappe.cache().hget("user_recent", self.name) or []) - d.roles = self.get_roles() d.defaults = self.get_defaults() @@ -331,12 +311,17 @@ def disable_users(limits=None): return if limits.get('users'): - system_manager = get_system_managers(only_name=True)[-1] + system_manager = get_system_managers(only_name=True) + user_list = ['Administrator', 'Guest'] + if system_manager: + user_list.append(system_manager[-1]) #exclude system manager from active user list - active_users = frappe.db.sql_list("""select name from tabUser - where name not in ('Administrator', 'Guest', %s) and user_type = 'System User' and enabled=1 - order by creation desc""", system_manager) + # active_users = frappe.db.sql_list("""select name from tabUser + # where name not in ('Administrator', 'Guest', %s) and user_type = 'System User' and enabled=1 + # order by creation desc""", system_manager) + + active_users = frappe.get_all("User", filters={"user_type":"System User", "enabled":1, "name": ["not in", user_list]}, fields=["name"]) user_limit = cint(limits.get('users')) - 1 @@ -385,4 +370,4 @@ def get_users_with_role(role): WHERE `tabHas Role`.`role`=%s AND `tabUser`.`name`!='Administrator' AND `tabHas Role`.`parent`=`tabUser`.`name` - AND `tabUser`.`enabled`=1""", role)] \ No newline at end of file + AND `tabUser`.`enabled`=1""", role)] diff --git a/frappe/website/doctype/blog_category/test_records.json b/frappe/website/doctype/blog_category/test_records.json index d4533ed27c..3334bbc4f9 100644 --- a/frappe/website/doctype/blog_category/test_records.json +++ b/frappe/website/doctype/blog_category/test_records.json @@ -1,14 +1,20 @@ [ - { - "category_name": "_Test Blog Category", - "doctype": "Blog Category", - "parent_website_route": "blog", - "title": "_Test Blog Category" - }, - { - "category_name": "_Test Blog Category 1", - "doctype": "Blog Category", - "parent_website_route": "blog", - "title": "_Test Blog Category 1" - } + { + "category_name": "_Test Blog Category", + "doctype": "Blog Category", + "parent_website_route": "blog", + "title": "_Test Blog Category" + }, + { + "category_name": "_Test Blog Category 1", + "doctype": "Blog Category", + "parent_website_route": "blog", + "title": "_Test Blog Category 1" + }, + { + "category_name": "_Test Blog Category 2", + "doctype": "Blog Category", + "parent_website_route": "blog", + "title": "_Test Blog Category 2" + } ] \ No newline at end of file diff --git a/frappe/website/doctype/blog_post/templates/blog_post.html b/frappe/website/doctype/blog_post/templates/blog_post.html index cbfc1a8a1f..0231f255c2 100644 --- a/frappe/website/doctype/blog_post/templates/blog_post.html +++ b/frappe/website/doctype/blog_post/templates/blog_post.html @@ -10,14 +10,8 @@

    {{ title }}

    -

    - {{ blogger_info and blogger_info.full_name or full_name }} - · - {{ frappe.format_date(published_on) }} - · - {{ category.title }} - · - {{ comment_text }} +

    + {{ frappe.format_date(published_on) }}

    diff --git a/frappe/website/doctype/personal_data_download_request/personal_data_download_request.json b/frappe/website/doctype/personal_data_download_request/personal_data_download_request.json index b73cdfb347..6a51a79b8c 100644 --- a/frappe/website/doctype/personal_data_download_request/personal_data_download_request.json +++ b/frappe/website/doctype/personal_data_download_request/personal_data_download_request.json @@ -1,164 +1,65 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, "creation": "2019-01-24 16:34:24.093128", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "user", + "user_name", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "depends_on": "", - "fetch_from": "", "fieldname": "user", "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": "User", - "length": 0, - "no_copy": 0, "options": "User", - "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 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "user.full_name", "fieldname": "user_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": "User 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 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "amended_from", "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": "Amended From", - "length": 0, "no_copy": 1, "options": "Personal Data Download Request", - "permlevel": 0, "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 + "read_only": 1 } ], - "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": "2019-02-01 15:21:06.247620", + "modified": "2019-05-14 13:02:58.175687", "modified_by": "Administrator", "module": "Website", "name": "Personal Data Download Request", - "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 + }, + { + "if_owner": 1, + "read": 1, + "role": "All" } ], - "quick_entry": 0, - "read_only": 0, - "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 + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/core/report/feedback_ratings/__init__.py b/frappe/website/doctype/website_route_redirect/__init__.py similarity index 100% rename from frappe/core/report/feedback_ratings/__init__.py rename to frappe/website/doctype/website_route_redirect/__init__.py diff --git a/frappe/website/doctype/website_route_redirect/website_route_redirect.json b/frappe/website/doctype/website_route_redirect/website_route_redirect.json new file mode 100644 index 0000000000..d561a1be10 --- /dev/null +++ b/frappe/website/doctype/website_route_redirect/website_route_redirect.json @@ -0,0 +1,35 @@ +{ + "creation": "2019-05-07 11:08:35.889625", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "source", + "target" + ], + "fields": [ + { + "fieldname": "source", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Source", + "reqd": 1 + }, + { + "fieldname": "target", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Target", + "reqd": 1 + } + ], + "istable": 1, + "modified": "2019-05-07 11:11:46.867684", + "modified_by": "Administrator", + "module": "Website", + "name": "Website Route Redirect", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "ASC" +} \ No newline at end of file diff --git a/frappe/website/doctype/website_route_redirect/website_route_redirect.py b/frappe/website/doctype/website_route_redirect/website_route_redirect.py new file mode 100644 index 0000000000..7255008dd0 --- /dev/null +++ b/frappe/website/doctype/website_route_redirect/website_route_redirect.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 WebsiteRouteRedirect(Document): + pass diff --git a/frappe/website/doctype/website_settings/website_settings.json b/frappe/website/doctype/website_settings/website_settings.json index 1c6ed8da17..38a4b84c5d 100644 --- a/frappe/website/doctype/website_settings/website_settings.json +++ b/frappe/website/doctype/website_settings/website_settings.json @@ -1,1364 +1,312 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, "creation": "2013-04-30 12:58:46", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Other", - "editable_grid": 0, + "field_order": [ + "sb0", + "home_page", + "cb4", + "title_prefix", + "section_break_6", + "website_theme", + "website_theme_image", + "website_theme_image_link", + "brand", + "banner_image", + "brand_html", + "set_banner_from_image", + "favicon", + "top_bar", + "navbar_search", + "top_bar_items", + "banner", + "banner_html", + "footer", + "copyright", + "address", + "footer_items", + "hide_footer_signup", + "integrations", + "google_analytics_id", + "column_break_17", + "misc_section", + "subdomain", + "disable_signup", + "section_break_38", + "head_html", + "robots_txt", + "route_redirects", + "section_break_39", + "chat_enable", + "chat_enable_from", + "chat_enable_to", + "chat_room_name", + "chat_welcome_message", + "chat_operators" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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": "Landing Page", - "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 + "label": "Landing Page" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Link that is the website home page. Standard Links (index, login, products, blog, about, contact)", "fieldname": "home_page", "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": "Home Page", - "length": 0, - "no_copy": 0, - "options": "", - "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 + "label": "Home Page" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "cb4", - "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 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Show title in browser window as \"Prefix - title\"", "fieldname": "title_prefix", "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": "Title Prefix", - "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 + "label": "Title Prefix" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_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": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Standard", "fieldname": "website_theme", "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": "Website Theme", - "length": 0, - "no_copy": 0, - "options": "Website Theme", - "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": "Website Theme" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "website_theme_image", "fieldtype": "Image", "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": "Website Theme Image", - "length": 0, - "no_copy": 0, - "options": "website_theme_image_link", - "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": "website_theme_image_link" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "website_theme_image_link", "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": "Website Theme Image Link", - "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": "Website Theme Image Link" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "brand", "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": "Brand", - "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": "Brand" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Select an image of approx width 150px with a transparent background for best results.", "fieldname": "banner_image", "fieldtype": "Attach Image", - "hidden": 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": "Brand Image", - "length": 0, - "no_copy": 0, - "options": "", - "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 + "label": "Brand Image" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Brand is what appears on the top-left of the toolbar. If it is an image, make sure it\nhas a transparent background and use the <img /> tag. Keep size as 200px x 30px", "fieldname": "brand_html", "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": "Brand HTML", - "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 + "label": "Brand HTML" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "set_banner_from_image", "fieldtype": "Button", - "hidden": 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": "Set Banner from Image", - "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 + "label": "Set Banner from Image" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, - "description": "", "fieldname": "top_bar", "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": "Top Bar", - "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 + "label": "Top Bar" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "navbar_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": "Include Search in Top Bar", - "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": "Include Search in Top Bar" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "top_bar_items", "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": "Top Bar Items", - "length": 0, - "no_copy": 0, - "options": "Top Bar Item", - "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 + "options": "Top Bar Item" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, - "description": "", "fieldname": "banner", "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": "Banner", - "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 + "label": "Banner" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Banner is above the Top Menu Bar.", "fieldname": "banner_html", "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": "Banner HTML", - "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 + "label": "Banner HTML" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "footer", "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": "Footer", - "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 + "label": "Footer" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "copyright", "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": "Copyright", - "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 + "label": "Copyright" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Address and other legal information you may want to put in the footer.", "fieldname": "address", "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": "Address", - "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 + "label": "Address" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "footer_items", "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": "Footer Items", - "length": 0, - "no_copy": 0, - "options": "Top Bar Item", - "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 + "options": "Top Bar Item" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "hide_footer_signup", "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 Footer Signup", - "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": "Hide Footer Signup" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "integrations", "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": "Integrations", - "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 + "label": "Integrations" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Add Google Analytics ID: eg. UA-89XXX57-1. Please search help on Google Analytics for more information.", "fieldname": "google_analytics_id", "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": "Google Analytics ID", - "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 + "label": "Google Analytics ID" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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, - "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": 1, - "columns": 0, "fieldname": "misc_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": "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 + "label": "Disable Signup" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "An icon file with .ico extension. Should be 16 x 16 px. Generated using a favicon generator. [favicon-generator.org]", "fieldname": "favicon", "fieldtype": "Attach", - "hidden": 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": "FavIcon", - "length": 0, - "no_copy": 0, - "options": "", - "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 + "label": "FavIcon" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Sub-domain provided by erpnext.com", "fieldname": "subdomain", "fieldtype": "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": "Subdomain", - "length": 0, - "no_copy": 0, - "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 + "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_28", - "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, "description": "Disable Customer Signup link in Login page", "fieldname": "disable_signup", "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": "Disable Signup", - "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 + "label": "Disable Signup" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "section_break_38", "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": "HTML Header & Robots", - "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": "HTML Header, Robots and Redirects" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Added HTML in the <head> section of the web page, primarily used for website verification and SEO", "fieldname": "head_html", "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": "<head> HTML", - "length": 0, - "no_copy": 0, - "options": "HTML", - "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": "HTML" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "robots_txt", "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": "Robots.txt", - "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": "Robots.txt" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "section_break_39", "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": "Chat", - "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": "Chat" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "chat_enable", "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 Chat", - "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": "Enable Chat" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "chat_enable", "fieldname": "chat_enable_from", "fieldtype": "Time", - "hidden": 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", - "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" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "chat_enable", "fieldname": "chat_enable_to", "fieldtype": "Time", - "hidden": 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, - "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" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Support", "depends_on": "chat_enable", "fieldname": "chat_room_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": "Chat Room 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 + "label": "Chat Room Name" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Hi, how may I help you?", "depends_on": "chat_enable", "fieldname": "chat_welcome_message", "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": "Welcome 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": "Welcome Message" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "chat_enable", "fieldname": "chat_operators", "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": "Chat Operators", - "length": 0, - "no_copy": 0, - "options": "Chat Room 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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Chat Room User" + }, + { + "fieldname": "route_redirects", + "fieldtype": "Table", + "label": "Route Redirects", + "options": "Website Route Redirect" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-cog", "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, "issingle": 1, - "istable": 0, "max_attachments": 10, - "modified": "2019-02-21 13:59:29.098096", - "modified_by": "abhay@march.com", + "modified": "2019-05-07 11:24:10.031140", + "modified_by": "Administrator", "module": "Website", "name": "Website Settings", "owner": "Administrator", "permissions": [ { - "amend": 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": "Website Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, - "report": 0, - "role": "All", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "All" } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_order": "ASC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/website/redirect.py b/frappe/website/redirect.py index 8006ebaed7..73e3c21727 100644 --- a/frappe/website/redirect.py +++ b/frappe/website/redirect.py @@ -21,6 +21,8 @@ def resolve_redirect(path): ] ''' redirects = frappe.get_hooks('website_redirects') + redirects += frappe.db.get_all('Website Route Redirect', ['source', 'target']) + if not redirects: return redirect_to = frappe.cache().hget('website_redirects', path) diff --git a/frappe/website/render.py b/frappe/website/render.py index ff2376b5f4..ed74ec8cab 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -30,6 +30,7 @@ def render(path=None, http_status_code=None): try: path = path.strip('/ ') + raise_if_disabled(path) resolve_redirect(path) path = resolve_path(path) data = None @@ -326,3 +327,17 @@ def add_csrf_token(data): frappe.local.session.data.csrf_token)) else: return data + +def raise_if_disabled(path): + routes = frappe.db.get_all('Portal Menu Item', + fields=['route', 'enabled'], + filters={ + 'enabled': 0, + 'route': ['like', '%{0}'.format(path)] + } + ) + + for r in routes: + _path = r.route.lstrip('/') + if path == _path and not r.enabled: + raise frappe.PermissionError diff --git a/frappe/website/router.py b/frappe/website/router.py index 377bd92d97..4e172b5290 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -16,6 +16,7 @@ def resolve_route(path): The only exceptions are `/about` and `/contact` these will be searched in Web Pages first before checking the standard pages.""" + if path not in ("about", "contact"): context = get_page_info_from_template(path) if context: @@ -352,50 +353,5 @@ def get_doctypes_with_web_view(): return frappe.cache().get_value('doctypes_with_web_view', _get) -def sync_global_search(): - '''Sync page content in global search''' - from frappe.website.render import render_page - from frappe.utils.global_search import sync_global_search - from bs4 import BeautifulSoup - - if frappe.flags.update_global_search: - sync_global_search() - frappe.flags.update_global_search = [] - frappe.session.user = 'Guest' - frappe.local.no_cache = True - - frappe.db.sql("DELETE FROM `__global_search` WHERE `doctype`='Static Web Page'") - - for app in frappe.get_installed_apps(frappe_last=True): - app_path = frappe.get_app_path(app) - - folders = get_start_folders() - - for start in folders: - for basepath, folders, files in os.walk(os.path.join(app_path, start)): - for f in files: - if f.endswith('.html') or f.endswith('.md'): - path = os.path.join(basepath, f.rsplit('.', 1)[0]) - try: - content = render_page(path) - soup = BeautifulSoup(content, 'html.parser') - text = '' - route = os.path.relpath(path, os.path.join(app_path, start)) - for div in soup.findAll("div", {'class':'page-content'}): - text += div.text - - frappe.flags.update_global_search.append( - dict(doctype='Static Web Page', - name=route, - content=text_type(text), - published=1, - title=text_type(soup.title.string), - route=route)) - - except Exception: - pass - - sync_global_search() - def get_start_folders(): return frappe.local.flags.web_pages_folders or ('www', 'templates/pages') \ No newline at end of file diff --git a/frappe/website/utils.py b/frappe/website/utils.py index be8bb2a4f8..3aa4930c30 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -210,7 +210,7 @@ def get_next_link(route, url_prefix=None, app=None): route = route.rstrip('/') children_map = get_full_index(app=app) parent_route = os.path.dirname(route) - children = children_map[parent_route] + children = children_map.get(parent_route, None) if parent_route and children: for i, c in enumerate(children): diff --git a/frappe/www/desk.html b/frappe/www/desk.html index 265adf68e0..95598309ba 100644 --- a/frappe/www/desk.html +++ b/frappe/www/desk.html @@ -24,7 +24,7 @@ {%- endfor -%} -
    +
    diff --git a/frappe/www/feedback.html b/frappe/www/feedback.html deleted file mode 100644 index c780e2c943..0000000000 --- a/frappe/www/feedback.html +++ /dev/null @@ -1,131 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %}{{ _("Feedback") }}{% endblock %} - -{% macro feedback_result(is_valid_request, subject, message='') %} - -{% endmacro %} - -{% block page_content %} -{% if is_valid_request %} - - {{ feedback_result(is_valid_request, "Thank You") }} -{% else %} - {{ feedback_result(is_valid_request, "Invalid Input", error_message) }} -{% endif %} - - - - -{% endblock %} diff --git a/frappe/www/feedback.py b/frappe/www/feedback.py deleted file mode 100644 index d3ceda8807..0000000000 --- a/frappe/www/feedback.py +++ /dev/null @@ -1,74 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe import _ -from frappe.core.doctype.feedback_request.feedback_request import is_valid_feedback_request - -no_cache = True - -def get_context(context): - reference_doctype = frappe.form_dict.get("reference_doctype") - reference_name = frappe.form_dict.get("reference_name") - - if not all([reference_name, reference_doctype]) or \ - not frappe.db.get_value(reference_doctype, reference_name): - - return { - "is_valid_request": False, - "error_message": "Invalid reference doctype and reference name" - } - - communications = frappe.get_all("Communication", filters={ - "reference_doctype": reference_doctype, - "reference_name": reference_name, - "communication_type": "Communication" - }, fields=["*"], limit_page_length=10, order_by="creation desc") - - return { - "reference_doctype": reference_doctype, - "reference_name": reference_name, - "comment_list": communications, - "is_communication": True, - "is_valid_request": True - } - -@frappe.whitelist(allow_guest=True) -def accept(key, sender, reference_doctype, reference_name, feedback, rating, fullname): - """ save the feedback in communication """ - if not reference_doctype and not reference_name or \ - not frappe.db.get_value(reference_doctype, reference_name): - - frappe.throw(_("Invalid Reference")) - - if not rating: - frappe.throw(_("Please add a rating")) - - if not is_valid_feedback_request(key): - frappe.throw(_("Expired link")) - - try: - feedback_request = frappe.db.get_value("Feedback Request", {"key": key}) - - communication = frappe.get_doc({ - "rating": rating, - "status": "Closed", - "content": feedback or "", - "doctype": "Communication", - "sender": sender or "Guest", - "sent_or_received": "Received", - "communication_type": "Feedback", - "reference_name": reference_name, - "sender_full_name": fullname or "", - "feedback_request": feedback_request, - "reference_doctype": reference_doctype, - "subject": "Feedback for {0} {1}".format(reference_doctype, reference_name), - }).insert(ignore_permissions=True) - - doc = frappe.get_doc("Feedback Request", feedback_request) - doc.is_feedback_submitted = 1 - doc.rating = rating - doc.reference_communication = communication.name - doc.save(ignore_permissions=True) - return True - except Exception: - frappe.log_error() - frappe.throw(_("Cannot submit feedback, please try again later")) diff --git a/frappe/www/login.html b/frappe/www/login.html index 9d132cb718..d621ab0fbb 100644 --- a/frappe/www/login.html +++ b/frappe/www/login.html @@ -24,9 +24,9 @@
    - - -
    + + +
    diff --git a/frappe/www/login.py b/frappe/www/login.py index c2f83e45c3..dd51dabeab 100644 --- a/frappe/www/login.py +++ b/frappe/www/login.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe import frappe.utils -from frappe.utils.oauth import get_oauth2_authorize_url, get_oauth_keys, login_via_oauth2, login_via_oauth2_id_token, login_oauth_user as _login_oauth_user, redirect_post_login +from frappe.utils.oauth import get_oauth2_authorize_url, get_oauth_keys, login_via_oauth2, login_via_oauth2_id_token, login_oauth_user as _login_oauth_user, redirect_post_login, oauth_decoder import json from frappe import _ from frappe.auth import LoginManager @@ -56,7 +56,7 @@ def get_context(context): @frappe.whitelist(allow_guest=True) def login_via_google(code, state): - login_via_oauth2("google", code, state, decoder=json.loads) + login_via_oauth2("google", code, state, decoder=oauth_decoder) @frappe.whitelist(allow_guest=True) def login_via_github(code, state): @@ -64,15 +64,15 @@ def login_via_github(code, state): @frappe.whitelist(allow_guest=True) def login_via_facebook(code, state): - login_via_oauth2("facebook", code, state, decoder=json.loads) + login_via_oauth2("facebook", code, state, decoder=oauth_decoder) @frappe.whitelist(allow_guest=True) def login_via_frappe(code, state): - login_via_oauth2("frappe", code, state, decoder=json.loads) + login_via_oauth2("frappe", code, state, decoder=oauth_decoder) @frappe.whitelist(allow_guest=True) def login_via_office365(code, state): - login_via_oauth2_id_token("office_365", code, state, decoder=json.loads) + login_via_oauth2_id_token("office_365", code, state, decoder=oauth_decoder) @frappe.whitelist(allow_guest=True) def login_oauth_user(data=None, provider=None, state=None, email_id=None, key=None, generate_login_token=False): diff --git a/frappe/www/printview.py b/frappe/www/printview.py index 070092112f..5da86cb225 100644 --- a/frappe/www/printview.py +++ b/frappe/www/printview.py @@ -34,7 +34,7 @@ def get_context(context): print_format = get_print_format_doc(None, meta = meta) return { - "body": get_html(doc, print_format = print_format, + "body": get_rendered_template(doc, print_format = print_format, meta=meta, trigger_print = frappe.form_dict.trigger_print, no_letterhead=frappe.form_dict.no_letterhead), "css": get_print_style(frappe.form_dict.style, print_format), @@ -58,7 +58,7 @@ def get_print_format_doc(print_format_name, meta): # if old name, return standard! return None -def get_html(doc, name=None, print_format=None, meta=None, +def get_rendered_template(doc, name=None, print_format=None, meta=None, no_letterhead=None, trigger_print=False): print_settings = frappe.db.get_singles_dict("Print Settings") @@ -181,12 +181,40 @@ def get_html_and_style(doc, name=None, print_format=None, meta=None, doc = frappe.get_doc(json.loads(doc)) print_format = get_print_format_doc(print_format, meta=meta or frappe.get_meta(doc.doctype)) + + if print_format and print_format.raw_printing: + return { + "html": '
    ' + + _("No Preview Available") + + '
    ' + } + return { - "html": get_html(doc, name=name, print_format=print_format, meta=meta, + "html": get_rendered_template(doc, name=name, print_format=print_format, meta=meta, no_letterhead=no_letterhead, trigger_print=trigger_print), "style": get_print_style(style=style, print_format=print_format) } +@frappe.whitelist() +def get_rendered_raw_commands(doc, name=None, print_format=None, meta=None, lang=None): + """Returns Rendered Raw Commands of print format, used to send directly to printer""" + + if isinstance(doc, string_types) and isinstance(name, string_types): + doc = frappe.get_doc(doc, name) + + if isinstance(doc, string_types): + doc = frappe.get_doc(json.loads(doc)) + + print_format = get_print_format_doc(print_format, meta=meta or frappe.get_meta(doc.doctype)) + + if not print_format or (print_format and not print_format.raw_printing): + frappe.throw(_("{0} is not a raw printing format.").format(print_format), + frappe.TemplateNotFoundError) + + return { + "raw_commands": get_rendered_template(doc, name=name, print_format=print_format, meta=meta) + } + def validate_print_permission(doc): if frappe.form_dict.get("key"): if frappe.form_dict.key == doc.get_signature(): @@ -218,8 +246,10 @@ def get_print_format(doctype, print_format): with open(path, "r") as pffile: return pffile.read() else: - if print_format.html: + if print_format.html and not print_format.raw_printing: return print_format.html + elif print_format.raw_commands and print_format.raw_printing: + return print_format.raw_commands else: frappe.throw(_("No template found at path: {0}").format(path), frappe.TemplateNotFoundError) diff --git a/frappe/www/search.py b/frappe/www/search.py index e545f4840f..19df1e48e0 100644 --- a/frappe/www/search.py +++ b/frappe/www/search.py @@ -13,13 +13,13 @@ def get_context(context): context.title = _('Search Results for ') context.query = query context.route = '/search' - context.update(get_search_results(query)) + context.update(get_search_results(query, frappe.form_dict.scope)) else: context.title = _('Search') @frappe.whitelist(allow_guest = True) -def get_search_results(text, start=0, as_html=False): - results = web_search(text, start, limit=21) +def get_search_results(text, scope=None, start=0, as_html=False): + results = web_search(text, scope, start, limit=21) out = frappe._dict() if len(results) == 21: @@ -27,22 +27,25 @@ def get_search_results(text, start=0, as_html=False): results = results[:20] for d in results: - d.content = html2text(d.content) - index = d.content.lower().index(text.lower()) - d.content = d.content[:index] + '' + d.content[index:][:len(text)] + '' + d.content[index + len(text):] + try: + d.content = html2text(d.content) + index = d.content.lower().index(text.lower()) + d.content = d.content[:index] + '' + d.content[index:][:len(text)] + '' + d.content[index + len(text):] - if index < 40: - start = 0 - prefix = '' - else: - start = index - 40 - prefix = '...' + if index < 40: + start = 0 + prefix = '' + else: + start = index - 40 + prefix = '...' - suffix = '' - if (index + len(text) + 47) < len(d.content): - suffix = '...' + suffix = '' + if (index + len(text) + 47) < len(d.content): + suffix = '...' - d.preview = prefix + d.content[start:start + len(text) + 87] + suffix + d.preview = prefix + d.content[start:start + len(text) + 87] + suffix + except Exception: + d.preview = html2text(d.content)[:97] + '...' out.results = results diff --git a/frappe/www/update-password.html b/frappe/www/update-password.html index 9689a1d3b9..6a69df9af7 100644 --- a/frappe/www/update-password.html +++ b/frappe/www/update-password.html @@ -118,7 +118,6 @@ frappe.ready(function() { } return frappe.call({ - type: 'GET', method: 'frappe.core.doctype.user.user.test_password_strength', args: args, callback: function(r) { diff --git a/package.json b/package.json index f0ae54aa59..59f402dae1 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "build": "node rollup/build.js", "production": "FRAPPE_ENV=production node rollup/build.js", "watch": "node rollup/watch.js", - "cypress:run": "cypress run --record --key 14ddd919-b01f-4d5f-b9d1-5af54d34c7f3", + "cypress:run": "cypress run --record --key 4a48f41c-11b3-425b-aa88-c58048fa69eb", "cypress:open": "cypress open" }, "repository": { @@ -24,14 +24,16 @@ "cookie": "^0.3.1", "express": "^4.16.2", "fast-deep-equal": "^2.0.1", - "frappe-datatable": "^1.12.0", + "frappe-datatable": "^1.12.2", "frappe-gantt": "^0.1.0", "fuse.js": "^3.2.0", "highlight.js": "^9.12.0", + "js-sha256": "^0.9.0", "jsbarcode": "^3.9.0", "moment": "^2.20.1", "moment-timezone": "^0.5.21", "quill": "2.0.0-dev.2", + "qz-tray": "^2.0.8", "redis": "^2.8.0", "showdown": "^1.8.6", "socket.io": "^2.0.4", @@ -44,6 +46,7 @@ "babel-runtime": "^6.26.0", "chalk": "^2.3.2", "cypress": "^3.1.1", + "cypress-file-upload": "^3.1.0", "less": "^3.0.4", "node-sass": "^4.11.0", "rollup": "^1.2.2", @@ -52,7 +55,7 @@ "rollup-plugin-multi-entry": "^2.0.2", "rollup-plugin-node-resolve": "^4.0.1", "rollup-plugin-postcss": "^2.0.3", - "rollup-plugin-uglify": "^3.0.0", + "rollup-plugin-terser": "^4.0.4", "rollup-plugin-vue": "4.2.0", "vue-template-compiler": "^2.6.8" } diff --git a/rollup/config.js b/rollup/config.js index f645eae498..93bb226927 100644 --- a/rollup/config.js +++ b/rollup/config.js @@ -8,7 +8,7 @@ const commonjs = require('rollup-plugin-commonjs'); const node_resolve = require('rollup-plugin-node-resolve'); const postcss = require('rollup-plugin-postcss'); const buble = require('rollup-plugin-buble'); -const uglify = require('rollup-plugin-uglify'); +const { terser } = require('rollup-plugin-terser'); const vue = require('rollup-plugin-vue'); const frappe_html = require('./frappe-html-plugin'); @@ -65,7 +65,7 @@ function get_rollup_options_for_js(output_file, input_files) { paths: node_resolve_paths } }), - production && uglify() + production && terser() ]; return { diff --git a/test_sites/test_site_postgres/site_config.json b/test_sites/test_site_postgres/site_config.json index 809468ff40..f7f3b9de2b 100644 --- a/test_sites/test_site_postgres/site_config.json +++ b/test_sites/test_site_postgres/site_config.json @@ -7,6 +7,7 @@ "mail_login": "test@example.com", "mail_password": "test", "admin_password": "admin", + "root_login": "postgres", "root_password": "travis", "run_selenium_tests": 1, "host_name": "http://test_site_postgres:8000" diff --git a/yarn.lock b/yarn.lock index 98cd290a82..7ee8775740 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,22 @@ # yarn lockfile v1 +"@babel/code-frame@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" + integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/highlight@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" + integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + "@babel/runtime@^7.0.0-beta.46": version "7.3.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83" @@ -533,6 +549,11 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + builtin-modules@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.0.0.tgz#1e587d44b006620d90286cc7a9238bbc6129cab1" @@ -652,7 +673,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -803,10 +824,10 @@ commander@2.11.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ== -commander@~2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" - integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA== +commander@^2.19.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" + integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== common-tags@1.4.0: version "1.4.0" @@ -1124,6 +1145,11 @@ currently-unhandled@^0.4.1: dependencies: array-find-index "^1.0.1" +cypress-file-upload@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-3.1.0.tgz#9da7ed60619631231bd2caaf9844874e7cec6f69" + integrity sha512-zJh6Qwh+BZz6j3oCxMgRmfSsHJ+vSm2FrsZ1j/hG3s1O+6UPhOCDGeJucMUGBivWb1IsHrLKJP4LfgHSooPZHw== + cypress@^3.1.1: version "3.1.5" resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.1.5.tgz#5227b2ce9306c47236d29e703bad9055d7218042" @@ -1450,6 +1476,11 @@ estree-walker@^0.6.0: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.0.tgz#5d865327c44a618dde5699f763891ae31f257dae" integrity sha512-peq1RfVAVzr3PU/jL31RaOjUKLoZJpObQWJJ+LgfcxDUifyLZ1RjPQZTl0pzj2uJ45b7A7XpyppXvxdEqzo4rw== +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= + etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" @@ -1722,10 +1753,10 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -frappe-datatable@^1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.12.0.tgz#2273535ead4404e5b165b6564c622acbacfdf61e" - integrity sha512-rrsRaxP9+CwPdJiYzmgmYD5ud+0pWzon8n+DKBrnrbFheN5SFnbuRdR58G8qA4/psHIN3rrSEximiQsbTUNdzw== +frappe-datatable@^1.12.2: + version "1.12.2" + resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.12.2.tgz#cb7e32defd38426c63278249843baa91dcba79aa" + integrity sha512-e4prKv4klBW9I5iVIesoeJgt4LofzZDfDxP+eqvQN2GZRVs8KYEXgyPhcaH89FfuhIkzVK6kDbMHw+KbGfOJIA== dependencies: hyperlist "^1.0.0-beta" lodash "^4.17.5" @@ -2447,11 +2478,29 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= +jest-worker@^24.0.0: + version "24.6.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.6.0.tgz#7f81ceae34b7cde0c9827a6980c35b7cdc0161b3" + integrity sha512-jDwgW5W9qGNvpI1tNnvajh0a5IE/PuGLFmHk6aR/BZFz8tSgGw17GsDPXAJ6p91IvYDjOw8GpFbvvZGAK+DPQQ== + dependencies: + merge-stream "^1.0.1" + supports-color "^6.1.0" + js-base64@^2.1.8, js-base64@^2.1.9: version "2.5.1" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121" integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw== +js-sha256@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" + integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + js-yaml@^3.12.0, js-yaml@^3.9.0: version "3.12.2" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.2.tgz#ef1d067c5a9d9cb65bd72f285b5d8105c77f14fc" @@ -2825,6 +2874,13 @@ merge-source-map@^1.1.0: dependencies: source-map "^0.6.1" +merge-stream@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" + integrity sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE= + dependencies: + readable-stream "^2.0.1" + methods@^1.1.1, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -3852,6 +3908,11 @@ quill@2.0.0-dev.2: parchment quilljs/parchment#487850f7eb030a6c4e750ba809e58b09444e0bdb quill-delta "^3.6.2" +qz-tray@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/qz-tray/-/qz-tray-2.0.8.tgz#5e15d102cf3a11a31ddb332891c7f8a6af8f6ad5" + integrity sha512-lncGYzz7/sTORZuC1S3ukNlMPCMOmsHWNvJF4FjMCZ2+0UV3txi6kgPd754B7kDFKm0J587sIODgxIlFY7qU4w== + ramda@0.24.1: version "0.24.1" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.24.1.tgz#c3b7755197f35b8dc3502228262c4c91ddb6b857" @@ -4210,12 +4271,15 @@ rollup-plugin-postcss@^2.0.3: rollup-pluginutils "^2.0.1" style-inject "^0.3.0" -rollup-plugin-uglify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/rollup-plugin-uglify/-/rollup-plugin-uglify-3.0.0.tgz#a34eca24617709c6bf1778e9653baafa06099b86" - integrity sha512-dehLu9eRRoV4l09aC+ySntRw1OAfoyKdbk8Nelblj03tHoynkSybqyEpgavemi1LBOH6S1vzI58/mpxkZIe1iQ== +rollup-plugin-terser@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-4.0.4.tgz#6f661ef284fa7c27963d242601691dc3d23f994e" + integrity sha512-wPANT5XKVJJ8RDUN0+wIr7UPd0lIXBo4UdJ59VmlPCtlFsE20AM+14pe+tk7YunCsWEiuzkDBY3QIkSCjtrPXg== dependencies: - uglify-es "^3.3.7" + "@babel/code-frame" "^7.0.0" + jest-worker "^24.0.0" + serialize-javascript "^1.6.1" + terser "^3.14.1" rollup-plugin-vue@4.2.0: version "4.2.0" @@ -4324,6 +4388,11 @@ send@0.16.2: range-parser "~1.2.0" statuses "~1.4.0" +serialize-javascript@^1.6.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65" + integrity sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA== + serve-static@1.13.2: version "1.13.2" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" @@ -4492,12 +4561,20 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" +source-map-support@~0.5.10: + version "0.5.12" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" + integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= -source-map@0.6.*, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: +source-map@0.6.*, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -4766,6 +4843,15 @@ tar@^2.0.0: fstream "^1.0.2" inherits "2" +terser@^3.14.1: + version "3.17.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2" + integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ== + dependencies: + commander "^2.19.0" + source-map "~0.6.1" + source-map-support "~0.5.10" + throttleit@~0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" @@ -4872,14 +4958,6 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -uglify-es@^3.3.7: - version "3.3.9" - resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" - integrity sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ== - dependencies: - commander "~2.13.0" - source-map "~0.6.1" - unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"