Merge branch 'develop' into fix-load-more-btn
This commit is contained in:
commit
b764e68e7a
54 changed files with 1521 additions and 1154 deletions
|
|
@ -48,3 +48,7 @@ pull_request_rules:
|
|||
actions:
|
||||
merge:
|
||||
method: squash
|
||||
commit_message_template: |
|
||||
{{ title }} (#{{ number }})
|
||||
|
||||
{{ body }}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ context('Workspace 2.0', () => {
|
|||
// check if sidebar item is added in pubic section
|
||||
cy.get('.sidebar-item-container[item-name="Test Private Page"]').should('have.attr', 'item-public', '0');
|
||||
|
||||
cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click();
|
||||
cy.get('.standard-actions .btn-primary[data-label="Save"]').click();
|
||||
cy.wait(300);
|
||||
cy.get('.sidebar-item-container[item-name="Test Private Page"]').should('have.attr', 'item-public', '0');
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ context('Workspace 2.0', () => {
|
|||
cy.get('.ce-block:last .dropdown-item').contains('Expand').click();
|
||||
cy.get(".ce-block:last").should('have.class', 'col-xs-12');
|
||||
|
||||
cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click();
|
||||
cy.get('.standard-actions .btn-primary[data-label="Save"]').click();
|
||||
});
|
||||
|
||||
it('Delete Private Page', () => {
|
||||
|
|
@ -80,7 +80,7 @@ context('Workspace 2.0', () => {
|
|||
.find('.dropdown-item[title="Delete Workspace"]').click({force: true});
|
||||
cy.wait(300);
|
||||
cy.get('.modal-footer > .standard-actions > .btn-modal-primary:visible').first().click();
|
||||
cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click();
|
||||
cy.get('.standard-actions .btn-primary[data-label="Save"]').click();
|
||||
cy.get('.codex-editor__redactor .ce-block');
|
||||
cy.get('.sidebar-item-container[item-name="Test Private Page"]').should('not.exist');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, as_list=False,
|
|||
response JSON and shown in a pop-up / modal.
|
||||
|
||||
:param msg: Message.
|
||||
:param title: [optional] Message title.
|
||||
:param title: [optional] Message title. Default: "Message".
|
||||
:param raise_exception: [optional] Raise given exception and show message.
|
||||
:param as_table: [optional] If `msg` is a list of lists, render as HTML table.
|
||||
:param as_list: [optional] If `msg` is a list, render as un-ordered list.
|
||||
|
|
@ -395,8 +395,7 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, as_list=False,
|
|||
if flags.print_messages and out.message:
|
||||
print(f"Message: {strip_html_tags(out.message)}")
|
||||
|
||||
if title:
|
||||
out.title = title
|
||||
out.title = title or _("Message", context="Default title of the message dialog")
|
||||
|
||||
if not indicator and raise_exception:
|
||||
indicator = 'red'
|
||||
|
|
|
|||
|
|
@ -294,7 +294,6 @@ def serve(port=8000, profile=False, no_reload=False, no_threading=False, site=No
|
|||
_sites_path = sites_path
|
||||
|
||||
from werkzeug.serving import run_simple
|
||||
patch_werkzeug_reloader()
|
||||
|
||||
if profile or os.environ.get('USE_PROFILER'):
|
||||
application = ProfilerMiddleware(application, sort_by=('cumtime', 'calls'))
|
||||
|
|
@ -325,23 +324,3 @@ def serve(port=8000, profile=False, no_reload=False, no_threading=False, site=No
|
|||
use_debugger=not in_test_env,
|
||||
use_evalex=not in_test_env,
|
||||
threaded=not no_threading)
|
||||
|
||||
def patch_werkzeug_reloader():
|
||||
"""
|
||||
This function monkey patches Werkzeug reloader to ignore reloading files in
|
||||
the __pycache__ directory.
|
||||
|
||||
To be deprecated when upgrading to Werkzeug 2.
|
||||
"""
|
||||
|
||||
from werkzeug._reloader import WatchdogReloaderLoop
|
||||
|
||||
trigger_reload = WatchdogReloaderLoop.trigger_reload
|
||||
|
||||
def custom_trigger_reload(self, filename):
|
||||
if os.path.basename(os.path.dirname(filename)) == "__pycache__":
|
||||
return
|
||||
|
||||
return trigger_reload(self, filename)
|
||||
|
||||
WatchdogReloaderLoop.trigger_reload = custom_trigger_reload
|
||||
|
|
|
|||
|
|
@ -70,6 +70,19 @@ class TestComment(unittest.TestCase):
|
|||
reference_name = test_blog.name
|
||||
))), 0)
|
||||
|
||||
# test for filtering html and css injection elements
|
||||
frappe.db.delete("Comment", {"reference_doctype": "Blog Post"})
|
||||
|
||||
frappe.form_dict.comment = '<script>alert(1)</script>Comment'
|
||||
frappe.form_dict.comment_by = 'hacker'
|
||||
|
||||
add_comment()
|
||||
|
||||
self.assertEqual(frappe.get_all('Comment', fields = ['content'], filters = dict(
|
||||
reference_doctype = test_blog.doctype,
|
||||
reference_name = test_blog.name
|
||||
))[0]['content'], 'Comment')
|
||||
|
||||
test_blog.delete()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
# License: MIT. See LICENSE
|
||||
|
||||
from collections import Counter
|
||||
from typing import List
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
|
@ -367,15 +368,8 @@ def get_permission_query_conditions_for_communication(user):
|
|||
return """`tabCommunication`.email_account in ({email_accounts})"""\
|
||||
.format(email_accounts=','.join(email_accounts))
|
||||
|
||||
def get_contacts(email_strings, auto_create_contact=False):
|
||||
email_addrs = []
|
||||
|
||||
for email_string in email_strings:
|
||||
if email_string:
|
||||
result = getaddresses([email_string])
|
||||
for email in result:
|
||||
email_addrs.append(email[1])
|
||||
|
||||
def get_contacts(email_strings: List[str], auto_create_contact=False) -> List[str]:
|
||||
email_addrs = get_emails(email_strings)
|
||||
contacts = []
|
||||
for email in email_addrs:
|
||||
email = get_email_without_link(email)
|
||||
|
|
@ -404,6 +398,17 @@ def get_contacts(email_strings, auto_create_contact=False):
|
|||
|
||||
return contacts
|
||||
|
||||
def get_emails(email_strings: List[str]) -> List[str]:
|
||||
email_addrs = []
|
||||
|
||||
for email_string in email_strings:
|
||||
if email_string:
|
||||
result = getaddresses([email_string])
|
||||
for email in result:
|
||||
email_addrs.append(email[1])
|
||||
|
||||
return email_addrs
|
||||
|
||||
def add_contact_links_to_communication(communication, contact_name):
|
||||
contact_links = frappe.get_all("Dynamic Link", filters={
|
||||
"parenttype": "Contact",
|
||||
|
|
@ -449,8 +454,12 @@ def get_email_without_link(email):
|
|||
if not frappe.get_all("Email Account", filters={"enable_automatic_linking": 1}):
|
||||
return email
|
||||
|
||||
email_id = email.split("@")[0].split("+")[0]
|
||||
email_host = email.split("@")[1]
|
||||
try:
|
||||
_email = email.split("@")
|
||||
email_id = _email[0].split("+")[0]
|
||||
email_host = _email[1]
|
||||
except IndexError:
|
||||
return email
|
||||
|
||||
return "{0}@{1}".format(email_id, email_host)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from urllib.parse import quote
|
|||
|
||||
import frappe
|
||||
from frappe.email.doctype.email_queue.email_queue import EmailQueue
|
||||
from frappe.core.doctype.communication.communication import get_emails
|
||||
|
||||
test_records = frappe.get_test_records('Communication')
|
||||
|
||||
|
|
@ -201,6 +202,19 @@ class TestCommunication(unittest.TestCase):
|
|||
|
||||
self.assertIn(("Note", note.name), doc_links)
|
||||
|
||||
def parse_emails(self):
|
||||
emails = get_emails(
|
||||
[
|
||||
'comm_recipient+DocType+DocName@example.com',
|
||||
'"First, LastName" <first.lastname@email.com>',
|
||||
'test@user.com'
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(emails[0], "comm_recipient+DocType+DocName@example.com")
|
||||
self.assertEqual(emails[1], "first.lastname@email.com")
|
||||
self.assertEqual(emails[2], "test@user.com")
|
||||
|
||||
class TestCommunicationEmailMixin(unittest.TestCase):
|
||||
def new_communication(self, recipients=None, cc=None, bcc=None):
|
||||
recipients = ', '.join(recipients or [])
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"hide_days",
|
||||
"hide_seconds",
|
||||
"reqd",
|
||||
"is_virtual",
|
||||
"search_index",
|
||||
"column_break_18",
|
||||
"options",
|
||||
|
|
@ -534,13 +535,19 @@
|
|||
"fieldname": "show_dashboard",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Dashboard"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_virtual",
|
||||
"fieldtype": "Check",
|
||||
"label": "Virtual"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-01-03 11:56:19.812863",
|
||||
"modified": "2022-01-27 21:22:20.529072",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocField",
|
||||
|
|
|
|||
|
|
@ -781,29 +781,31 @@ def validate_series(dt, autoname=None, name=None):
|
|||
|
||||
def validate_links_table_fieldnames(meta):
|
||||
"""Validate fieldnames in Links table"""
|
||||
if frappe.flags.in_patch: return
|
||||
if frappe.flags.in_fixtures: return
|
||||
if not meta.links: return
|
||||
if not meta.links or frappe.flags.in_patch or frappe.flags.in_fixtures:
|
||||
return
|
||||
|
||||
for index, link in enumerate(meta.links):
|
||||
fieldnames = tuple(field.fieldname for field in meta.fields)
|
||||
for index, link in enumerate(meta.links, 1):
|
||||
link_meta = frappe.get_meta(link.link_doctype)
|
||||
if not link_meta.get_field(link.link_fieldname):
|
||||
message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format(index+1, frappe.bold(link.link_fieldname), frappe.bold(link.link_doctype))
|
||||
message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format(index, frappe.bold(link.link_fieldname), frappe.bold(link.link_doctype))
|
||||
frappe.throw(message, InvalidFieldNameError, _("Invalid Fieldname"))
|
||||
|
||||
if link.is_child_table and not meta.get_field(link.table_fieldname):
|
||||
message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format(index+1, frappe.bold(link.table_fieldname), frappe.bold(meta.name))
|
||||
if not link.is_child_table:
|
||||
continue
|
||||
|
||||
if not link.parent_doctype:
|
||||
message = _("Document Links Row #{0}: Parent DocType is mandatory for internal links").format(index)
|
||||
frappe.throw(message, frappe.ValidationError, _("Parent Missing"))
|
||||
|
||||
if not link.table_fieldname:
|
||||
message = _("Document Links Row #{0}: Table Fieldname is mandatory for internal links").format(index)
|
||||
frappe.throw(message, frappe.ValidationError, _("Table Fieldname Missing"))
|
||||
|
||||
if link.table_fieldname not in fieldnames:
|
||||
message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format(index, frappe.bold(link.table_fieldname), frappe.bold(meta.name))
|
||||
frappe.throw(message, frappe.ValidationError, _("Invalid Table Fieldname"))
|
||||
|
||||
if link.is_child_table:
|
||||
if not link.parent_doctype:
|
||||
message = _("Document Links Row #{0}: Parent DocType is mandatory for internal links").format(index+1)
|
||||
frappe.throw(message, frappe.ValidationError, _("Parent Missing"))
|
||||
|
||||
if not link.table_fieldname:
|
||||
message = _("Document Links Row #{0}: Table Fieldname is mandatory for internal links").format(index+1)
|
||||
frappe.throw(message, frappe.ValidationError, _("Table Fieldname Missing"))
|
||||
|
||||
def validate_fields_for_doctype(doctype):
|
||||
meta = frappe.get_meta(doctype, cached=False)
|
||||
validate_links_table_fieldnames(meta)
|
||||
|
|
@ -1076,6 +1078,9 @@ def validate_fields(meta):
|
|||
field.fetch_from = field.fetch_from.strip('\n').strip()
|
||||
|
||||
def validate_data_field_type(docfield):
|
||||
if docfield.get("is_virtual"):
|
||||
return
|
||||
|
||||
if docfield.fieldtype == "Data" and not (docfield.oldfieldtype and docfield.oldfieldtype != "Data"):
|
||||
if docfield.options and (docfield.options not in data_field_options):
|
||||
df_str = frappe.bold(_(docfield.label))
|
||||
|
|
@ -1321,10 +1326,9 @@ def make_module_and_roles(doc, perm_fieldname="permissions"):
|
|||
else:
|
||||
raise
|
||||
|
||||
def check_fieldname_conflicts(doctype, fieldname):
|
||||
def check_fieldname_conflicts(docfield):
|
||||
"""Checks if fieldname conflicts with methods or properties"""
|
||||
|
||||
doc = frappe.get_doc({"doctype": doctype})
|
||||
doc = frappe.get_doc({"doctype": docfield.dt})
|
||||
available_objects = [x for x in dir(doc) if isinstance(x, str)]
|
||||
property_list = [
|
||||
x for x in available_objects if isinstance(getattr(type(doc), x, None), property)
|
||||
|
|
@ -1332,9 +1336,10 @@ def check_fieldname_conflicts(doctype, fieldname):
|
|||
method_list = [
|
||||
x for x in available_objects if x not in property_list and callable(getattr(doc, x))
|
||||
]
|
||||
msg = _("Fieldname {0} conflicting with meta object").format(docfield.fieldname)
|
||||
|
||||
if fieldname in method_list + property_list:
|
||||
frappe.throw(_("Fieldname {0} conflicting with meta object").format(fieldname))
|
||||
if docfield.fieldname in method_list + property_list:
|
||||
frappe.msgprint(msg, raise_exception=not docfield.is_virtual)
|
||||
|
||||
def clear_linked_doctype_cache():
|
||||
frappe.cache().delete_value('linked_doctypes_without_ignore_user_permissions_enabled')
|
||||
|
|
|
|||
|
|
@ -406,7 +406,7 @@ class TestFile(unittest.TestCase):
|
|||
test_file.reload()
|
||||
test_file.file_url = frappe.utils.get_url('unknown.jpg')
|
||||
test_file.make_thumbnail(suffix="xs")
|
||||
self.assertEqual(json.loads(frappe.message_log[0]), {"message": f"File '{frappe.utils.get_url('unknown.jpg')}' not found"})
|
||||
self.assertEqual(json.loads(frappe.message_log[0]).get("message"), f"File '{frappe.utils.get_url('unknown.jpg')}' not found")
|
||||
self.assertEquals(test_file.thumbnail_url, None)
|
||||
|
||||
def test_file_unzip(self):
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
import frappe, json, os
|
||||
import unittest
|
||||
from frappe.desk.query_report import run, save_report
|
||||
from frappe.desk.reportview import delete_report, save_report as _save_report
|
||||
from frappe.custom.doctype.customize_form.customize_form import reset_customization
|
||||
from frappe.core.doctype.user_permission.test_user_permission import create_user
|
||||
|
||||
test_records = frappe.get_test_records('Report')
|
||||
test_dependencies = ['User']
|
||||
|
|
@ -30,6 +32,60 @@ class TestReport(unittest.TestCase):
|
|||
self.assertEqual(columns[1].get('label'), 'Module')
|
||||
self.assertTrue('User' in [d.get('name') for d in data])
|
||||
|
||||
def test_save_or_delete_report(self):
|
||||
'''Test for validations when editing / deleting report of type Report Builder'''
|
||||
|
||||
try:
|
||||
report = frappe.get_doc({
|
||||
'doctype': 'Report',
|
||||
'ref_doctype': 'User',
|
||||
'report_name': 'Test Delete Report',
|
||||
'report_type': 'Report Builder',
|
||||
'is_standard': 'No',
|
||||
}).insert()
|
||||
|
||||
# Check for PermissionError
|
||||
create_user("test_report_owner@example.com", "Website Manager")
|
||||
frappe.set_user("test_report_owner@example.com")
|
||||
self.assertRaises(frappe.PermissionError, delete_report, report.name)
|
||||
|
||||
# Check for Report Type
|
||||
frappe.set_user("Administrator")
|
||||
report.db_set("report_type", "Custom Report")
|
||||
self.assertRaisesRegex(
|
||||
frappe.ValidationError,
|
||||
"Only reports of type Report Builder can be deleted",
|
||||
delete_report,
|
||||
report.name
|
||||
)
|
||||
|
||||
# Check if creating and deleting works with proper validations
|
||||
frappe.set_user("test@example.com")
|
||||
report_name = _save_report(
|
||||
'Dummy Report',
|
||||
'User',
|
||||
json.dumps([{
|
||||
'fieldname': 'email',
|
||||
'fieldtype': 'Data',
|
||||
'label': 'Email',
|
||||
'insert_after_index': 0,
|
||||
'link_field': 'name',
|
||||
'doctype': 'User',
|
||||
'options': 'Email',
|
||||
'width': 100,
|
||||
'id':'email',
|
||||
'name': 'Email'
|
||||
}])
|
||||
)
|
||||
|
||||
doc = frappe.get_doc("Report", report_name)
|
||||
delete_report(doc.name)
|
||||
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.rollback()
|
||||
|
||||
|
||||
def test_custom_report(self):
|
||||
reset_customization('User')
|
||||
custom_report_name = save_report(
|
||||
|
|
|
|||
|
|
@ -359,6 +359,7 @@ class TestUser(unittest.TestCase):
|
|||
json.loads(frappe.message_log[0]).get("message"),
|
||||
"Password reset instructions have been sent to your email"
|
||||
)
|
||||
|
||||
sendmail.assert_called_once()
|
||||
self.assertEqual(sendmail.call_args[1]["recipients"], "test2@example.com")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,458 +1,468 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"creation": "2013-01-10 16:34:01",
|
||||
"description": "Adds a custom field to a DocType",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"dt",
|
||||
"module",
|
||||
"label",
|
||||
"label_help",
|
||||
"fieldname",
|
||||
"insert_after",
|
||||
"length",
|
||||
"column_break_6",
|
||||
"fieldtype",
|
||||
"precision",
|
||||
"hide_seconds",
|
||||
"hide_days",
|
||||
"options",
|
||||
"fetch_from",
|
||||
"fetch_if_empty",
|
||||
"options_help",
|
||||
"section_break_11",
|
||||
"collapsible",
|
||||
"collapsible_depends_on",
|
||||
"default",
|
||||
"depends_on",
|
||||
"mandatory_depends_on",
|
||||
"read_only_depends_on",
|
||||
"properties",
|
||||
"non_negative",
|
||||
"reqd",
|
||||
"unique",
|
||||
"read_only",
|
||||
"ignore_user_permissions",
|
||||
"hidden",
|
||||
"print_hide",
|
||||
"print_hide_if_no_value",
|
||||
"print_width",
|
||||
"no_copy",
|
||||
"allow_on_submit",
|
||||
"in_list_view",
|
||||
"in_standard_filter",
|
||||
"in_global_search",
|
||||
"in_preview",
|
||||
"bold",
|
||||
"report_hide",
|
||||
"search_index",
|
||||
"allow_in_quick_entry",
|
||||
"ignore_xss_filter",
|
||||
"translatable",
|
||||
"hide_border",
|
||||
"description",
|
||||
"permlevel",
|
||||
"width",
|
||||
"columns"
|
||||
],
|
||||
"fields": [{
|
||||
"bold": 1,
|
||||
"fieldname": "dt",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Document",
|
||||
"oldfieldname": "dt",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_filter": 1,
|
||||
"label": "Label",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "label",
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"fieldname": "label_help",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Label Help",
|
||||
"oldfieldtype": "HTML"
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldname",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "fieldname",
|
||||
"oldfieldtype": "Data",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "Select the label after which you want to insert new field.",
|
||||
"fieldname": "insert_after",
|
||||
"fieldtype": "Select",
|
||||
"label": "Insert After",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "insert_after",
|
||||
"oldfieldtype": "Select"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"default": "Data",
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Select",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Field Type",
|
||||
"oldfieldname": "fieldtype",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature\nTab Break",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
|
||||
"description": "Set non-standard precision for a Float or Currency field",
|
||||
"fieldname": "precision",
|
||||
"fieldtype": "Select",
|
||||
"label": "Precision",
|
||||
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
|
||||
},
|
||||
{
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Options",
|
||||
"oldfieldname": "options",
|
||||
"oldfieldtype": "Text"
|
||||
},
|
||||
{
|
||||
"fieldname": "fetch_from",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Fetch From"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
|
||||
"fieldname": "fetch_if_empty",
|
||||
"fieldtype": "Check",
|
||||
"label": "Fetch If Empty"
|
||||
},
|
||||
{
|
||||
"fieldname": "options_help",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Options Help",
|
||||
"oldfieldtype": "HTML"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_11",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fieldname": "collapsible",
|
||||
"fieldtype": "Check",
|
||||
"label": "Collapsible"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fieldname": "collapsible_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Collapsible Depends On"
|
||||
},
|
||||
{
|
||||
"fieldname": "default",
|
||||
"fieldtype": "Text",
|
||||
"label": "Default Value",
|
||||
"oldfieldname": "default",
|
||||
"oldfieldtype": "Text"
|
||||
},
|
||||
{
|
||||
"fieldname": "depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Depends On",
|
||||
"length": 255
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"label": "Field Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Text",
|
||||
"print_width": "300px",
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "permlevel",
|
||||
"fieldtype": "Int",
|
||||
"label": "Permission Level",
|
||||
"oldfieldname": "permlevel",
|
||||
"oldfieldtype": "Int"
|
||||
},
|
||||
{
|
||||
"fieldname": "width",
|
||||
"fieldtype": "Data",
|
||||
"label": "Width",
|
||||
"oldfieldname": "width",
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)",
|
||||
"fieldname": "columns",
|
||||
"fieldtype": "Int",
|
||||
"label": "Columns"
|
||||
},
|
||||
{
|
||||
"fieldname": "properties",
|
||||
"fieldtype": "Column Break",
|
||||
"oldfieldtype": "Column Break",
|
||||
"print_width": "50%",
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "reqd",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Is Mandatory Field",
|
||||
"oldfieldname": "reqd",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "unique",
|
||||
"fieldtype": "Check",
|
||||
"label": "Unique"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "read_only",
|
||||
"fieldtype": "Check",
|
||||
"label": "Read Only"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype===\"Link\"",
|
||||
"fieldname": "ignore_user_permissions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore User Permissions"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hidden",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hidden"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "print_hide",
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Hide",
|
||||
"oldfieldname": "print_hide",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
|
||||
"fieldname": "print_hide_if_no_value",
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Hide If No Value"
|
||||
},
|
||||
{
|
||||
"fieldname": "print_width",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Print Width",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "no_copy",
|
||||
"fieldtype": "Check",
|
||||
"label": "No Copy",
|
||||
"oldfieldname": "no_copy",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_on_submit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow on Submit",
|
||||
"oldfieldname": "allow_on_submit",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_list_view",
|
||||
"fieldtype": "Check",
|
||||
"label": "In List View"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_standard_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Standard Filter"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
|
||||
"fieldname": "in_global_search",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Global Search"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "bold",
|
||||
"fieldtype": "Check",
|
||||
"label": "Bold"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "report_hide",
|
||||
"fieldtype": "Check",
|
||||
"label": "Report Hide",
|
||||
"oldfieldname": "report_hide",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "search_index",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Index",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
|
||||
"fieldname": "ignore_xss_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore XSS Filter"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
|
||||
"fieldname": "translatable",
|
||||
"fieldtype": "Check",
|
||||
"label": "Translatable"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)",
|
||||
"fieldname": "length",
|
||||
"fieldtype": "Int",
|
||||
"label": "Length"
|
||||
},
|
||||
{
|
||||
"fieldname": "mandatory_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Mandatory Depends On",
|
||||
"length": 255
|
||||
},
|
||||
{
|
||||
"fieldname": "read_only_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Read Only Depends On",
|
||||
"length": 255
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_in_quick_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow in Quick Entry"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);",
|
||||
"fieldname": "in_preview",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Preview"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_seconds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Seconds"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_days",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Days"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Section Break'",
|
||||
"fieldname": "hide_border",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Border"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:in_list([\"Int\", \"Float\", \"Currency\"], doc.fieldtype)",
|
||||
"fieldname": "non_negative",
|
||||
"fieldtype": "Check",
|
||||
"label": "Non Negative"
|
||||
},
|
||||
{
|
||||
"fieldname": "module",
|
||||
"fieldtype": "Link",
|
||||
"label": "Module (for export)",
|
||||
"options": "Module Def"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-04 12:45:23.810120",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Custom Field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"search_fields": "dt,label,fieldtype,options",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"creation": "2013-01-10 16:34:01",
|
||||
"description": "Adds a custom field to a DocType",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"dt",
|
||||
"module",
|
||||
"label",
|
||||
"label_help",
|
||||
"fieldname",
|
||||
"insert_after",
|
||||
"length",
|
||||
"column_break_6",
|
||||
"fieldtype",
|
||||
"precision",
|
||||
"hide_seconds",
|
||||
"hide_days",
|
||||
"options",
|
||||
"fetch_from",
|
||||
"fetch_if_empty",
|
||||
"options_help",
|
||||
"section_break_11",
|
||||
"collapsible",
|
||||
"collapsible_depends_on",
|
||||
"default",
|
||||
"depends_on",
|
||||
"mandatory_depends_on",
|
||||
"read_only_depends_on",
|
||||
"properties",
|
||||
"non_negative",
|
||||
"reqd",
|
||||
"unique",
|
||||
"is_virtual",
|
||||
"read_only",
|
||||
"ignore_user_permissions",
|
||||
"hidden",
|
||||
"print_hide",
|
||||
"print_hide_if_no_value",
|
||||
"print_width",
|
||||
"no_copy",
|
||||
"allow_on_submit",
|
||||
"in_list_view",
|
||||
"in_standard_filter",
|
||||
"in_global_search",
|
||||
"in_preview",
|
||||
"bold",
|
||||
"report_hide",
|
||||
"search_index",
|
||||
"allow_in_quick_entry",
|
||||
"ignore_xss_filter",
|
||||
"translatable",
|
||||
"hide_border",
|
||||
"description",
|
||||
"permlevel",
|
||||
"width",
|
||||
"columns"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"bold": 1,
|
||||
"fieldname": "dt",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Document",
|
||||
"oldfieldname": "dt",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_filter": 1,
|
||||
"label": "Label",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "label",
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"fieldname": "label_help",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Label Help",
|
||||
"oldfieldtype": "HTML"
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldname",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "fieldname",
|
||||
"oldfieldtype": "Data",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "Select the label after which you want to insert new field.",
|
||||
"fieldname": "insert_after",
|
||||
"fieldtype": "Select",
|
||||
"label": "Insert After",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "insert_after",
|
||||
"oldfieldtype": "Select"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"default": "Data",
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Select",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Field Type",
|
||||
"oldfieldname": "fieldtype",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature\nTab Break",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
|
||||
"description": "Set non-standard precision for a Float or Currency field",
|
||||
"fieldname": "precision",
|
||||
"fieldtype": "Select",
|
||||
"label": "Precision",
|
||||
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
|
||||
},
|
||||
{
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Options",
|
||||
"oldfieldname": "options",
|
||||
"oldfieldtype": "Text"
|
||||
},
|
||||
{
|
||||
"fieldname": "fetch_from",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Fetch From"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
|
||||
"fieldname": "fetch_if_empty",
|
||||
"fieldtype": "Check",
|
||||
"label": "Fetch If Empty"
|
||||
},
|
||||
{
|
||||
"fieldname": "options_help",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Options Help",
|
||||
"oldfieldtype": "HTML"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_11",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fieldname": "collapsible",
|
||||
"fieldtype": "Check",
|
||||
"label": "Collapsible"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fieldname": "collapsible_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Collapsible Depends On"
|
||||
},
|
||||
{
|
||||
"fieldname": "default",
|
||||
"fieldtype": "Text",
|
||||
"label": "Default Value",
|
||||
"oldfieldname": "default",
|
||||
"oldfieldtype": "Text"
|
||||
},
|
||||
{
|
||||
"fieldname": "depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Depends On",
|
||||
"length": 255
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"label": "Field Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Text",
|
||||
"print_width": "300px",
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "permlevel",
|
||||
"fieldtype": "Int",
|
||||
"label": "Permission Level",
|
||||
"oldfieldname": "permlevel",
|
||||
"oldfieldtype": "Int"
|
||||
},
|
||||
{
|
||||
"fieldname": "width",
|
||||
"fieldtype": "Data",
|
||||
"label": "Width",
|
||||
"oldfieldname": "width",
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)",
|
||||
"fieldname": "columns",
|
||||
"fieldtype": "Int",
|
||||
"label": "Columns"
|
||||
},
|
||||
{
|
||||
"fieldname": "properties",
|
||||
"fieldtype": "Column Break",
|
||||
"oldfieldtype": "Column Break",
|
||||
"print_width": "50%",
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "reqd",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Is Mandatory Field",
|
||||
"oldfieldname": "reqd",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "unique",
|
||||
"fieldtype": "Check",
|
||||
"label": "Unique"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_virtual",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Virtual"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "read_only",
|
||||
"fieldtype": "Check",
|
||||
"label": "Read Only"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype===\"Link\"",
|
||||
"fieldname": "ignore_user_permissions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore User Permissions"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hidden",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hidden"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "print_hide",
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Hide",
|
||||
"oldfieldname": "print_hide",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
|
||||
"fieldname": "print_hide_if_no_value",
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Hide If No Value"
|
||||
},
|
||||
{
|
||||
"fieldname": "print_width",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Print Width",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "no_copy",
|
||||
"fieldtype": "Check",
|
||||
"label": "No Copy",
|
||||
"oldfieldname": "no_copy",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_on_submit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow on Submit",
|
||||
"oldfieldname": "allow_on_submit",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_list_view",
|
||||
"fieldtype": "Check",
|
||||
"label": "In List View"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_standard_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Standard Filter"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
|
||||
"fieldname": "in_global_search",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Global Search"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "bold",
|
||||
"fieldtype": "Check",
|
||||
"label": "Bold"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "report_hide",
|
||||
"fieldtype": "Check",
|
||||
"label": "Report Hide",
|
||||
"oldfieldname": "report_hide",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "search_index",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Index",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
|
||||
"fieldname": "ignore_xss_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore XSS Filter"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
|
||||
"fieldname": "translatable",
|
||||
"fieldtype": "Check",
|
||||
"label": "Translatable"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)",
|
||||
"fieldname": "length",
|
||||
"fieldtype": "Int",
|
||||
"label": "Length"
|
||||
},
|
||||
{
|
||||
"fieldname": "mandatory_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Mandatory Depends On",
|
||||
"length": 255
|
||||
},
|
||||
{
|
||||
"fieldname": "read_only_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Read Only Depends On",
|
||||
"length": 255
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_in_quick_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow in Quick Entry"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);",
|
||||
"fieldname": "in_preview",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Preview"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_seconds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Seconds"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_days",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Days"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Section Break'",
|
||||
"fieldname": "hide_border",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Border"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:in_list([\"Int\", \"Float\", \"Currency\"], doc.fieldtype)",
|
||||
"fieldname": "non_negative",
|
||||
"fieldtype": "Check",
|
||||
"label": "Non Negative"
|
||||
},
|
||||
{
|
||||
"fieldname": "module",
|
||||
"fieldtype": "Link",
|
||||
"label": "Module (for export)",
|
||||
"options": "Module Def"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-01-27 21:47:01.065556",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Custom Field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"search_fields": "dt,label,fieldtype,options",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ class CustomField(Document):
|
|||
old_fieldtype = self.db_get('fieldtype')
|
||||
is_fieldtype_changed = (not self.is_new()) and (old_fieldtype != self.fieldtype)
|
||||
|
||||
if is_fieldtype_changed and not CustomizeForm.allow_fieldtype_change(old_fieldtype, self.fieldtype):
|
||||
if not self.is_virtual and is_fieldtype_changed and not CustomizeForm.allow_fieldtype_change(old_fieldtype, self.fieldtype):
|
||||
frappe.throw(_("Fieldtype cannot be changed from {0} to {1}").format(old_fieldtype, self.fieldtype))
|
||||
|
||||
if not self.fieldname:
|
||||
|
|
@ -65,7 +65,7 @@ class CustomField(Document):
|
|||
|
||||
if not self.flags.ignore_validate:
|
||||
from frappe.core.doctype.doctype.doctype import check_fieldname_conflicts
|
||||
check_fieldname_conflicts(self.dt, self.fieldname)
|
||||
check_fieldname_conflicts(self)
|
||||
|
||||
def on_update(self):
|
||||
if not frappe.flags.in_setup_wizard:
|
||||
|
|
|
|||
|
|
@ -418,6 +418,9 @@ class CustomizeForm(Document):
|
|||
return property_value
|
||||
|
||||
def validate_fieldtype_change(self, df, old_value, new_value):
|
||||
if df.is_virtual:
|
||||
return
|
||||
|
||||
allowed = self.allow_fieldtype_change(old_value, new_value)
|
||||
if allowed:
|
||||
old_value_length = cint(frappe.db.type_map.get(old_value)[1])
|
||||
|
|
@ -430,7 +433,8 @@ class CustomizeForm(Document):
|
|||
self.validate_fieldtype_length()
|
||||
else:
|
||||
self.flags.update_db = True
|
||||
if not allowed:
|
||||
|
||||
else:
|
||||
frappe.throw(_("Fieldtype cannot be changed from {0} to {1} in row {2}").format(old_value, new_value, df.idx))
|
||||
|
||||
def validate_fieldtype_length(self):
|
||||
|
|
@ -558,7 +562,8 @@ docfield_properties = {
|
|||
'allow_in_quick_entry': 'Check',
|
||||
'hide_border': 'Check',
|
||||
'hide_days': 'Check',
|
||||
'hide_seconds': 'Check'
|
||||
'hide_seconds': 'Check',
|
||||
'is_virtual': 'Check',
|
||||
}
|
||||
|
||||
doctype_link_properties = {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
"non_negative",
|
||||
"reqd",
|
||||
"unique",
|
||||
"is_virtual",
|
||||
"in_list_view",
|
||||
"in_standard_filter",
|
||||
"in_global_search",
|
||||
|
|
@ -115,6 +116,12 @@
|
|||
"fieldtype": "Check",
|
||||
"label": "Unique"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_virtual",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Virtual"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_list_view",
|
||||
|
|
@ -436,7 +443,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-01-03 14:50:32.035768",
|
||||
"modified": "2022-01-27 21:45:22.349776",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form Field",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: MIT. See LICENSE
|
||||
|
||||
import frappe
|
||||
|
|
@ -18,53 +18,19 @@ class PropertySetter(Document):
|
|||
|
||||
def validate(self):
|
||||
self.validate_fieldtype_change()
|
||||
|
||||
if self.is_new():
|
||||
delete_property_setter(self.doc_type, self.property, self.field_name, self.row_name)
|
||||
|
||||
# clear cache
|
||||
frappe.clear_cache(doctype = self.doc_type)
|
||||
|
||||
def validate_fieldtype_change(self):
|
||||
if self.field_name in not_allowed_fieldtype_change and \
|
||||
self.property == 'fieldtype':
|
||||
frappe.throw(_("Field type cannot be changed for {0}").format(self.field_name))
|
||||
|
||||
def get_property_list(self, dt):
|
||||
return frappe.db.get_all('DocField',
|
||||
fields=['fieldname', 'label', 'fieldtype'],
|
||||
filters={
|
||||
'parent': dt,
|
||||
'fieldtype': ['not in', ('Section Break', 'Column Break', 'Tab Break', 'HTML', 'Read Only', 'Fold') + frappe.model.table_fields],
|
||||
'fieldname': ['!=', '']
|
||||
},
|
||||
order_by='label asc',
|
||||
as_dict=1
|
||||
)
|
||||
|
||||
def get_setup_data(self):
|
||||
return {
|
||||
'doctypes': frappe.get_all("DocType", pluck="name"),
|
||||
'dt_properties': self.get_property_list('DocType'),
|
||||
'df_properties': self.get_property_list('DocField')
|
||||
}
|
||||
|
||||
def get_field_ids(self):
|
||||
return frappe.db.get_values(
|
||||
"DocField",
|
||||
filters={"parent": self.doc_type},
|
||||
fieldname=["name", "fieldtype", "label", "fieldname"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
def get_defaults(self):
|
||||
if not self.field_name:
|
||||
return frappe.get_all("DocType", filters={"name": self.doc_type}, fields="*")[0]
|
||||
else:
|
||||
return frappe.db.get_values(
|
||||
"DocField",
|
||||
filters={"fieldname": self.field_name, "parent": self.doc_type},
|
||||
fieldname="*",
|
||||
)[0]
|
||||
if (
|
||||
self.property == 'fieldtype'
|
||||
and self.field_name in not_allowed_fieldtype_change
|
||||
):
|
||||
frappe.throw(
|
||||
_("Field type cannot be changed for {0}").format(self.field_name)
|
||||
)
|
||||
|
||||
def on_update(self):
|
||||
if frappe.flags.in_patch:
|
||||
|
|
@ -74,6 +40,7 @@ class PropertySetter(Document):
|
|||
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype
|
||||
validate_fields_for_doctype(self.doc_type)
|
||||
|
||||
|
||||
def make_property_setter(doctype, fieldname, property, value, property_type, for_doctype = False,
|
||||
validate_fields_for_doctype=True):
|
||||
# WARNING: Ignores Permissions
|
||||
|
|
@ -91,6 +58,7 @@ def make_property_setter(doctype, fieldname, property, value, property_type, for
|
|||
property_setter.insert()
|
||||
return property_setter
|
||||
|
||||
|
||||
def delete_property_setter(doc_type, property, field_name=None, row_name=None):
|
||||
"""delete other property setters on this, if this is new"""
|
||||
filters = dict(doc_type=doc_type, property=property)
|
||||
|
|
@ -100,4 +68,3 @@ def delete_property_setter(doc_type, property, field_name=None, row_name=None):
|
|||
filters["row_name"] = row_name
|
||||
|
||||
frappe.db.delete('Property Setter', filters)
|
||||
|
||||
|
|
|
|||
|
|
@ -177,6 +177,8 @@ class Database(object):
|
|||
raise frappe.QueryTimeoutError(e)
|
||||
|
||||
elif frappe.conf.db_type == 'postgres':
|
||||
# TODO: added temporarily
|
||||
print(e)
|
||||
raise
|
||||
|
||||
if ignore_ddl and (self.is_missing_column(e) or self.is_missing_table(e) or self.cant_drop_field_or_key(e)):
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class DBTable:
|
|||
"""
|
||||
get columns from docfields and custom fields
|
||||
"""
|
||||
fields = self.meta.get_fieldnames_with_value(True)
|
||||
fields = self.meta.get_fieldnames_with_value(with_field_meta=True)
|
||||
|
||||
# optional fields like _comments
|
||||
if not self.meta.get('istable'):
|
||||
|
|
@ -85,6 +85,9 @@ class DBTable:
|
|||
})
|
||||
|
||||
for field in fields:
|
||||
if field.get("is_virtual"):
|
||||
continue
|
||||
|
||||
self.columns[field.get('fieldname')] = DbColumn(
|
||||
self,
|
||||
field.get('fieldname'),
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ frappe.ui.form.on('Form Tour', {
|
|||
|
||||
frm.add_custom_button(__('Show Tour'), async () => {
|
||||
const issingle = await check_if_single(frm.doc.reference_doctype);
|
||||
const name = await get_first_document(frm.doc.reference_doctype);
|
||||
let route_changed = null;
|
||||
|
||||
|
||||
if (issingle) {
|
||||
route_changed = frappe.set_route('Form', frm.doc.reference_doctype);
|
||||
} else if (frm.doc.first_document) {
|
||||
const name = await get_first_document(frm.doc.reference_doctype);
|
||||
route_changed = frappe.set_route('Form', frm.doc.reference_doctype, name);
|
||||
} else {
|
||||
route_changed = frappe.set_route('Form', frm.doc.reference_doctype, 'new');
|
||||
|
|
@ -53,73 +53,69 @@ frappe.ui.form.on('Form Tour', {
|
|||
};
|
||||
});
|
||||
|
||||
frm.set_query("field", "steps", function() {
|
||||
return {
|
||||
query: "frappe.desk.doctype.form_tour.form_tour.get_docfield_list",
|
||||
filters: {
|
||||
doctype: frm.doc.reference_doctype,
|
||||
hidden: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("parent_field", "steps", function() {
|
||||
return {
|
||||
query: "frappe.desk.doctype.form_tour.form_tour.get_docfield_list",
|
||||
filters: {
|
||||
doctype: frm.doc.reference_doctype,
|
||||
fieldtype: "Table",
|
||||
hidden: 0,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.trigger('reference_doctype');
|
||||
},
|
||||
|
||||
reference_doctype(frm) {
|
||||
if (!frm.doc.reference_doctype) return;
|
||||
|
||||
frappe.db.get_list('DocField', {
|
||||
filters: {
|
||||
parent: frm.doc.reference_doctype,
|
||||
parenttype: 'DocType',
|
||||
fieldtype: 'Table'
|
||||
},
|
||||
fields: ['options']
|
||||
}).then(res => {
|
||||
if (Array.isArray(res)) {
|
||||
frm.child_doctypes = res.map(r => r.options);
|
||||
}
|
||||
frm.set_fields_as_options(
|
||||
"fieldname",
|
||||
frm.doc.reference_doctype,
|
||||
df => !df.hidden
|
||||
).then(options => {
|
||||
frm.fields_dict.steps.grid.update_docfield_property(
|
||||
"fieldname",
|
||||
"options",
|
||||
[""].concat(options)
|
||||
);
|
||||
});
|
||||
|
||||
frm.set_fields_as_options(
|
||||
'parent_fieldname',
|
||||
frm.doc.reference_doctype,
|
||||
(df) => df.fieldtype == "Table" && !df.hidden,
|
||||
).then(options => {
|
||||
frm.fields_dict.steps.grid.update_docfield_property(
|
||||
"parent_fieldname",
|
||||
"options",
|
||||
[""].concat(options)
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Form Tour Step', {
|
||||
parent_field(frm, cdt, cdn) {
|
||||
form_render(frm, cdt, cdn) {
|
||||
if (locals[cdt][cdn].is_table_field) {
|
||||
frm.trigger('parent_fieldname', cdt, cdn);
|
||||
}
|
||||
},
|
||||
parent_fieldname(frm, cdt, cdn) {
|
||||
const child_row = locals[cdt][cdn];
|
||||
frappe.model.set_value(cdt, cdn, 'field', '');
|
||||
const field_control = get_child_field("steps", cdn, "field");
|
||||
field_control.get_query = function() {
|
||||
return {
|
||||
query: "frappe.desk.doctype.form_tour.form_tour.get_docfield_list",
|
||||
filters: {
|
||||
doctype: child_row.child_doctype,
|
||||
hidden: 0
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const parent_fieldname_df = frappe
|
||||
.get_meta(frm.doc.reference_doctype)
|
||||
.fields.find(df => df.fieldname == child_row.parent_fieldname);
|
||||
|
||||
frm.set_fields_as_options(
|
||||
'fieldname',
|
||||
parent_fieldname_df.options,
|
||||
(df) => !df.hidden,
|
||||
).then(options => {
|
||||
frm.fields_dict.steps.grid.update_docfield_property(
|
||||
"fieldname",
|
||||
"options",
|
||||
[""].concat(options)
|
||||
);
|
||||
if (child_row.fieldname) {
|
||||
frappe.model.set_value(cdt, cdn, 'fieldname', child_row.fieldname);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function get_child_field(child_table, child_name, fieldname) {
|
||||
// gets the field from grid row form
|
||||
const grid = cur_frm.fields_dict[child_table].grid;
|
||||
const grid_row = grid.grid_rows_by_docname[child_name];
|
||||
return grid_row.grid_form.fields_dict[fieldname];
|
||||
}
|
||||
|
||||
async function check_if_single(doctype) {
|
||||
const { message } = await frappe.db.get_value('DocType', doctype, 'issingle');
|
||||
return message.issingle || 0;
|
||||
|
|
|
|||
|
|
@ -5,58 +5,23 @@ import frappe
|
|||
from frappe.model.document import Document
|
||||
from frappe.modules.export_file import export_to_files
|
||||
|
||||
|
||||
class FormTour(Document):
|
||||
def before_insert(self):
|
||||
if not self.is_standard:
|
||||
return
|
||||
def before_save(self):
|
||||
meta = frappe.get_meta(self.reference_doctype)
|
||||
for step in self.steps:
|
||||
if step.is_table_field and step.parent_fieldname:
|
||||
parent_field_df = meta.get_field(step.parent_fieldname)
|
||||
step.child_doctype = parent_field_df.options
|
||||
|
||||
# while syncing, set proper docfield reference
|
||||
for d in self.steps:
|
||||
if not frappe.db.exists('DocField', d.field):
|
||||
d.field = frappe.db.get_value('DocField', {
|
||||
'fieldname': d.fieldname, 'parent': self.reference_doctype, 'fieldtype': d.fieldtype
|
||||
}, "name")
|
||||
|
||||
if d.is_table_field and not frappe.db.exists('DocField', d.parent_field):
|
||||
d.parent_field = frappe.db.get_value('DocField', {
|
||||
'fieldname': d.parent_fieldname, 'parent': self.reference_doctype, 'fieldtype': 'Table'
|
||||
}, "name")
|
||||
field_df = frappe.get_meta(step.child_doctype).get_field(step.fieldname)
|
||||
step.label = field_df.label
|
||||
step.fieldtype = field_df.fieldtype
|
||||
else:
|
||||
field_df = meta.get_field(step.fieldname)
|
||||
step.label = field_df.label
|
||||
step.fieldtype = field_df.fieldtype
|
||||
|
||||
def on_update(self):
|
||||
if frappe.conf.developer_mode and self.is_standard:
|
||||
export_to_files([['Form Tour', self.name]], self.module)
|
||||
|
||||
def before_export(self, doc):
|
||||
for d in doc.steps:
|
||||
d.field = ""
|
||||
d.parent_field = ""
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_docfield_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
or_filters = [
|
||||
['fieldname', 'like', '%' + txt + '%'],
|
||||
['label', 'like', '%' + txt + '%'],
|
||||
['fieldtype', 'like', '%' + txt + '%']
|
||||
]
|
||||
|
||||
parent_doctype = filters.get('doctype')
|
||||
fieldtype = filters.get('fieldtype')
|
||||
if not fieldtype:
|
||||
excluded_fieldtypes = ['Column Break']
|
||||
excluded_fieldtypes += filters.get('excluded_fieldtypes', [])
|
||||
fieldtype_filter = ['not in', excluded_fieldtypes]
|
||||
else:
|
||||
fieldtype_filter = fieldtype
|
||||
|
||||
docfields = frappe.get_all(
|
||||
doctype,
|
||||
fields=["name as value", "label", "fieldtype"],
|
||||
filters={'parent': parent_doctype, 'fieldtype': fieldtype_filter},
|
||||
or_filters=or_filters,
|
||||
limit_start=start,
|
||||
limit_page_length=page_len,
|
||||
order_by="idx",
|
||||
as_list=1,
|
||||
)
|
||||
return docfields
|
||||
export_to_files([["Form Tour", self.name]], self.module)
|
||||
|
|
|
|||
|
|
@ -6,19 +6,17 @@
|
|||
"field_order": [
|
||||
"is_table_field",
|
||||
"section_break_2",
|
||||
"parent_field",
|
||||
"field",
|
||||
"parent_fieldname",
|
||||
"fieldname",
|
||||
"title",
|
||||
"description",
|
||||
"column_break_2",
|
||||
"position",
|
||||
"label",
|
||||
"fieldtype",
|
||||
"has_next_condition",
|
||||
"next_step_condition",
|
||||
"section_break_13",
|
||||
"fieldname",
|
||||
"parent_fieldname",
|
||||
"fieldtype",
|
||||
"child_doctype"
|
||||
],
|
||||
"fields": [
|
||||
|
|
@ -38,23 +36,13 @@
|
|||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: (!doc.is_table_field || (doc.is_table_field && doc.parent_field))",
|
||||
"fieldname": "field",
|
||||
"fieldtype": "Link",
|
||||
"label": "Field",
|
||||
"options": "DocField",
|
||||
"depends_on": "eval: (!doc.is_table_field || (doc.is_table_field && doc.parent_fieldname))",
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Select",
|
||||
"label": "Fieldname",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "field.fieldname",
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Fieldname",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "field.label",
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
|
|
@ -88,10 +76,8 @@
|
|||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fetch_from": "field.fieldtype",
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Fieldtype",
|
||||
"read_only": 1
|
||||
},
|
||||
|
|
@ -105,14 +91,6 @@
|
|||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "is_table_field",
|
||||
"fieldname": "parent_field",
|
||||
"fieldtype": "Link",
|
||||
"label": "Parent Field",
|
||||
"mandatory_depends_on": "is_table_field",
|
||||
"options": "DocField"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_13",
|
||||
"fieldtype": "Section Break",
|
||||
|
|
@ -120,7 +98,6 @@
|
|||
"label": "Hidden Fields"
|
||||
},
|
||||
{
|
||||
"fetch_from": "parent_field.options",
|
||||
"fieldname": "child_doctype",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
|
|
@ -128,18 +105,17 @@
|
|||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "parent_field.fieldname",
|
||||
"depends_on": "is_table_field",
|
||||
"fieldname": "parent_fieldname",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Parent Fieldname",
|
||||
"read_only": 1
|
||||
"fieldtype": "Select",
|
||||
"label": "Parent Field",
|
||||
"mandatory_depends_on": "is_table_field"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-06 20:52:21.076972",
|
||||
"modified": "2022-01-27 15:18:36.481801",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Form Tour Step",
|
||||
|
|
@ -147,5 +123,6 @@
|
|||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -20,13 +20,13 @@
|
|||
"hide_custom",
|
||||
"public",
|
||||
"content",
|
||||
"section_break_2",
|
||||
"tab_break_2",
|
||||
"charts",
|
||||
"section_break_15",
|
||||
"tab_break_15",
|
||||
"shortcuts",
|
||||
"section_break_18",
|
||||
"tab_break_18",
|
||||
"links",
|
||||
"roles_section",
|
||||
"roles_tab",
|
||||
"roles"
|
||||
],
|
||||
"fields": [
|
||||
|
|
@ -40,8 +40,8 @@
|
|||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "charts",
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldname": "tab_break_2",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Dashboards"
|
||||
},
|
||||
{
|
||||
|
|
@ -78,15 +78,15 @@
|
|||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "shortcuts",
|
||||
"fieldname": "section_break_15",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldname": "tab_break_15",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Shortcuts"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "links",
|
||||
"fieldname": "section_break_18",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldname": "tab_break_18",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Link Cards"
|
||||
},
|
||||
{
|
||||
|
|
@ -152,14 +152,14 @@
|
|||
"options": "Has Role"
|
||||
},
|
||||
{
|
||||
"fieldname": "roles_section",
|
||||
"fieldtype": "Section Break",
|
||||
"fieldname": "roles_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Roles"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2021-12-15 19:33:00.805265",
|
||||
"modified": "2022-01-27 12:06:13.111743",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Workspace",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from frappe.utils import strip, cint
|
|||
from frappe.translate import (set_default_language, get_dict, send_translations)
|
||||
from frappe.geo.country_info import get_country_info
|
||||
from frappe.utils.password import update_password
|
||||
from werkzeug.useragents import UserAgent
|
||||
from . import install_fixtures
|
||||
|
||||
def get_setup_stages(args):
|
||||
|
|
@ -315,17 +314,10 @@ def prettify_args(args):
|
|||
return pretty_args
|
||||
|
||||
def email_setup_wizard_exception(traceback, args):
|
||||
if not frappe.local.conf.setup_wizard_exception_email:
|
||||
if not frappe.conf.setup_wizard_exception_email:
|
||||
return
|
||||
|
||||
pretty_args = prettify_args(args)
|
||||
|
||||
if frappe.local.request:
|
||||
user_agent = UserAgent(frappe.local.request.headers.get('User-Agent', ''))
|
||||
|
||||
else:
|
||||
user_agent = frappe._dict()
|
||||
|
||||
message = """
|
||||
|
||||
#### Traceback
|
||||
|
|
@ -349,18 +341,15 @@ def email_setup_wizard_exception(traceback, args):
|
|||
#### Basic Information
|
||||
|
||||
- **Site:** {site}
|
||||
- **User:** {user}
|
||||
- **Browser:** {user_agent.platform} {user_agent.browser} version: {user_agent.version} language: {user_agent.language}
|
||||
- **Browser Languages**: `{accept_languages}`""".format(
|
||||
- **User:** {user}""".format(
|
||||
site=frappe.local.site,
|
||||
traceback=traceback,
|
||||
args="\n".join(pretty_args),
|
||||
user=frappe.session.user,
|
||||
user_agent=user_agent,
|
||||
headers=frappe.local.request.headers,
|
||||
accept_languages=", ".join(frappe.local.request.accept_languages.values()))
|
||||
headers=frappe.request.headers,
|
||||
)
|
||||
|
||||
frappe.sendmail(recipients=frappe.local.conf.setup_wizard_exception_email,
|
||||
frappe.sendmail(recipients=frappe.conf.setup_wizard_exception_email,
|
||||
sender=frappe.session.user,
|
||||
subject="Setup failed: {}".format(frappe.local.site),
|
||||
message=message,
|
||||
|
|
|
|||
|
|
@ -262,22 +262,66 @@ def compress(data, args=None):
|
|||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def save_report():
|
||||
"""save report"""
|
||||
def save_report(name, doctype, report_settings):
|
||||
"""Save reports of type Report Builder from Report View"""
|
||||
|
||||
data = frappe.local.form_dict
|
||||
if frappe.db.exists('Report', data['name']):
|
||||
d = frappe.get_doc('Report', data['name'])
|
||||
if frappe.db.exists('Report', name):
|
||||
report = frappe.get_doc('Report', name)
|
||||
if report.is_standard == "Yes":
|
||||
frappe.throw(_("Standard Reports cannot be edited"))
|
||||
|
||||
if report.report_type != "Report Builder":
|
||||
frappe.throw(_("Only reports of type Report Builder can be edited"))
|
||||
|
||||
if (
|
||||
report.owner != frappe.session.user
|
||||
and not frappe.has_permission("Report", "write")
|
||||
):
|
||||
frappe.throw(
|
||||
_("Insufficient Permissions for editing Report"),
|
||||
frappe.PermissionError
|
||||
)
|
||||
else:
|
||||
d = frappe.new_doc('Report')
|
||||
d.report_name = data['name']
|
||||
d.ref_doctype = data['doctype']
|
||||
report = frappe.new_doc('Report')
|
||||
report.report_name = name
|
||||
report.ref_doctype = doctype
|
||||
|
||||
d.report_type = "Report Builder"
|
||||
d.json = data['json']
|
||||
frappe.get_doc(d).save()
|
||||
frappe.msgprint(_("{0} is saved").format(d.name), alert=True)
|
||||
return d.name
|
||||
report.report_type = "Report Builder"
|
||||
report.json = report_settings
|
||||
report.save(ignore_permissions=True)
|
||||
frappe.msgprint(
|
||||
_("Report {0} saved").format(frappe.bold(report.name)),
|
||||
indicator="green",
|
||||
alert=True,
|
||||
)
|
||||
return report.name
|
||||
|
||||
@frappe.whitelist()
|
||||
def delete_report(name):
|
||||
"""Delete reports of type Report Builder from Report View"""
|
||||
|
||||
report = frappe.get_doc("Report", name)
|
||||
if report.is_standard == "Yes":
|
||||
frappe.throw(_("Standard Reports cannot be deleted"))
|
||||
|
||||
if report.report_type != "Report Builder":
|
||||
frappe.throw(_("Only reports of type Report Builder can be deleted"))
|
||||
|
||||
if (
|
||||
report.owner != frappe.session.user
|
||||
and not frappe.has_permission("Report", "delete")
|
||||
):
|
||||
frappe.throw(
|
||||
_("Insufficient Permissions for deleting Report"),
|
||||
frappe.PermissionError
|
||||
)
|
||||
|
||||
report.delete(ignore_permissions=True)
|
||||
frappe.msgprint(
|
||||
_("Report {0} deleted").format(frappe.bold(report.name)),
|
||||
indicator="green",
|
||||
alert=True,
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: MIT. See LICENSE
|
||||
import datetime
|
||||
|
||||
import frappe
|
||||
import datetime
|
||||
from frappe import _
|
||||
from frappe.model import default_fields, table_fields, child_table_fields
|
||||
from frappe.model import child_table_fields, default_fields, display_fieldtypes, table_fields
|
||||
from frappe.model.naming import set_new_name
|
||||
from frappe.model.utils.link_count import notify_link_count
|
||||
from frappe.modules import load_doctype_module
|
||||
from frappe.model import display_fieldtypes
|
||||
from frappe.utils import (cint, flt, now, cstr, strip_html,
|
||||
sanitize_html, sanitize_email, cast_fieldtype)
|
||||
from frappe.utils import cast_fieldtype, cint, cstr, flt, now, sanitize_html, strip_html
|
||||
from frappe.utils.html_utils import unescape_html
|
||||
from frappe.model.docstatus import DocStatus
|
||||
|
||||
|
|
@ -254,7 +252,22 @@ class BaseDocument(object):
|
|||
continue
|
||||
|
||||
df = self.meta.get_field(fieldname)
|
||||
if df:
|
||||
|
||||
if df and df.get("is_virtual"):
|
||||
from frappe.utils.safe_exec import get_safe_globals
|
||||
|
||||
if d[fieldname] is None:
|
||||
if df.get("options"):
|
||||
d[fieldname] = frappe.safe_eval(
|
||||
code=df.get("options"),
|
||||
eval_globals=get_safe_globals(),
|
||||
eval_locals={"doc": self},
|
||||
)
|
||||
else:
|
||||
_val = getattr(self, fieldname, None)
|
||||
if _val and not callable(_val):
|
||||
d[fieldname] = _val
|
||||
elif df:
|
||||
if df.fieldtype=="Check":
|
||||
d[fieldname] = 1 if cint(d[fieldname]) else 0
|
||||
|
||||
|
|
@ -328,6 +341,7 @@ class BaseDocument(object):
|
|||
def as_dict(self, no_nulls=False, no_default_fields=False, convert_dates_to_str=False, no_child_table_fields=False):
|
||||
doc = self.get_valid_dict(convert_dates_to_str=convert_dates_to_str)
|
||||
doc["doctype"] = self.doctype
|
||||
|
||||
for df in self.meta.get_table_fields():
|
||||
children = self.get(df.fieldname) or []
|
||||
doc[df.fieldname] = [
|
||||
|
|
@ -375,12 +389,24 @@ class BaseDocument(object):
|
|||
fieldname = [df.fieldname for df in self.meta.get_table_fields() if df.options==doctype]
|
||||
return fieldname[0] if fieldname else None
|
||||
|
||||
def db_insert(self):
|
||||
"""INSERT the document (with valid columns) in the database."""
|
||||
def db_insert(self, ignore_if_duplicate=False):
|
||||
"""INSERT the document (with valid columns) in the database.
|
||||
|
||||
args:
|
||||
ignore_if_duplicate: ignore primary key collision
|
||||
at database level (postgres)
|
||||
in python (mariadb)
|
||||
"""
|
||||
if not self.name:
|
||||
# name will be set by document class in most cases
|
||||
set_new_name(self)
|
||||
|
||||
conflict_handler = ""
|
||||
# On postgres we can't implcitly ignore PK collision
|
||||
# So instruct pg to ignore `name` field conflicts
|
||||
if ignore_if_duplicate and frappe.db.db_type == "postgres":
|
||||
conflict_handler = "on conflict (name) do nothing"
|
||||
|
||||
if not self.creation:
|
||||
self.creation = self.modified = now()
|
||||
self.created_by = self.modified_by = frappe.session.user
|
||||
|
|
@ -391,10 +417,11 @@ class BaseDocument(object):
|
|||
columns = list(d)
|
||||
try:
|
||||
frappe.db.sql("""INSERT INTO `tab{doctype}` ({columns})
|
||||
VALUES ({values})""".format(
|
||||
doctype = self.doctype,
|
||||
columns = ", ".join("`"+c+"`" for c in columns),
|
||||
values = ", ".join(["%s"] * len(columns))
|
||||
VALUES ({values}) {conflict_handler}""".format(
|
||||
doctype=self.doctype,
|
||||
columns=", ".join("`"+c+"`" for c in columns),
|
||||
values=", ".join(["%s"] * len(columns)),
|
||||
conflict_handler=conflict_handler
|
||||
), list(d.values()))
|
||||
except Exception as e:
|
||||
if frappe.db.is_primary_key_violation(e):
|
||||
|
|
@ -407,8 +434,11 @@ class BaseDocument(object):
|
|||
self.db_insert()
|
||||
return
|
||||
|
||||
frappe.msgprint(_("{0} {1} already exists").format(self.doctype, frappe.bold(self.name)), title=_("Duplicate Name"), indicator="red")
|
||||
raise frappe.DuplicateEntryError(self.doctype, self.name, e)
|
||||
if not ignore_if_duplicate:
|
||||
frappe.msgprint(_("{0} {1} already exists")
|
||||
.format(self.doctype, frappe.bold(self.name)),
|
||||
title=_("Duplicate Name"), indicator="red")
|
||||
raise frappe.DuplicateEntryError(self.doctype, self.name, e)
|
||||
|
||||
elif frappe.db.is_unique_key_violation(e):
|
||||
# unique constraint
|
||||
|
|
|
|||
|
|
@ -249,11 +249,7 @@ class Document(BaseDocument):
|
|||
if getattr(self.meta, "issingle", 0):
|
||||
self.update_single(self.get_valid_dict())
|
||||
else:
|
||||
try:
|
||||
self.db_insert()
|
||||
except frappe.DuplicateEntryError as e:
|
||||
if not ignore_if_duplicate:
|
||||
raise e
|
||||
self.db_insert(ignore_if_duplicate=ignore_if_duplicate)
|
||||
|
||||
# children
|
||||
for d in self.get_all_children():
|
||||
|
|
|
|||
|
|
@ -444,9 +444,16 @@ class Meta(Document):
|
|||
self.permissions = [Document(d) for d in custom_perms]
|
||||
|
||||
def get_fieldnames_with_value(self, with_field_meta=False):
|
||||
return [df if with_field_meta else df.fieldname \
|
||||
for df in self.fields if df.fieldtype not in no_value_fields]
|
||||
def is_value_field(docfield):
|
||||
return not (
|
||||
docfield.get("is_virtual")
|
||||
or docfield.fieldtype in no_value_fields
|
||||
)
|
||||
|
||||
if with_field_meta:
|
||||
return [df for df in self.fields if is_value_field(df)]
|
||||
|
||||
return [df.fieldname for df in self.fields if is_value_field(df)]
|
||||
|
||||
def get_fields_to_check_permissions(self, user_permission_doctypes):
|
||||
fields = self.get("fields", {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ frappe.ui.form.Control = class BaseControl {
|
|||
if (this.df.get_status) {
|
||||
return this.df.get_status(this);
|
||||
}
|
||||
if (this.df.is_virtual) {
|
||||
return "Read";
|
||||
}
|
||||
|
||||
if ((!this.doctype && !this.docname) || this.df.parenttype === 'Web Form' || this.df.is_web_form) {
|
||||
// like in case of a dialog box
|
||||
|
|
@ -52,7 +55,7 @@ frappe.ui.form.Control = class BaseControl {
|
|||
if(explain) console.log("By Hidden Dependency: None"); // eslint-disable-line no-console
|
||||
return "None";
|
||||
|
||||
} else if (cint(this.df.read_only)) {
|
||||
} else if (cint(this.df.read_only || this.df.is_virtual)) {
|
||||
// eslint-disable-next-line
|
||||
if (explain) console.log("By Read Only: Read"); // eslint-disable-line no-console
|
||||
return "Read";
|
||||
|
|
|
|||
|
|
@ -549,14 +549,14 @@ frappe.ui.form.Dashboard = class FormDashboard {
|
|||
render_graph(args) {
|
||||
this.chart_area.show();
|
||||
this.chart_area.body.empty();
|
||||
$.extend({
|
||||
$.extend(args, {
|
||||
type: 'line',
|
||||
colors: ['green'],
|
||||
truncateLegends: 1,
|
||||
axisOptions: {
|
||||
shortenYAxisNumbers: 1
|
||||
}
|
||||
}, args);
|
||||
});
|
||||
this.show();
|
||||
|
||||
this.chart = new frappe.Chart('.form-graph', args);
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ frappe.ui.form.FormTour = class FormTour {
|
|||
|
||||
const curr_step = step_info;
|
||||
const next_step = this.tour.steps[curr_step.idx];
|
||||
const is_next_field_in_curr_table = next_step.parent_field == curr_step.field;
|
||||
const is_next_field_in_curr_table = next_step.parent_fieldname == curr_step.fieldname;
|
||||
|
||||
if (!is_next_field_in_curr_table) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ frappe.ui.form.QuickEntryForm = class QuickEntryForm {
|
|||
|
||||
// prepare a list of mandatory, bold and allow in quick entry fields
|
||||
this.mandatory = fields.filter(df => {
|
||||
return ((df.reqd || df.bold || df.allow_in_quick_entry) && !df.read_only);
|
||||
return ((df.reqd || df.bold || df.allow_in_quick_entry) && !df.read_only && !df.is_virtual);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -192,9 +192,18 @@ frappe.ui.form.ScriptManager = class ScriptManager {
|
|||
}
|
||||
|
||||
function setup_add_fetch(df) {
|
||||
if ((['Data', 'Read Only', 'Text', 'Small Text', 'Currency', 'Check', 'Attach Image',
|
||||
'Text Editor', 'Code', 'Link', 'Float', 'Int', 'Date', 'Select', 'Duration'].includes(df.fieldtype) || df.read_only==1)
|
||||
&& df.fetch_from && df.fetch_from.indexOf(".")!=-1) {
|
||||
let is_read_only_field = (
|
||||
['Data', 'Read Only', 'Text', 'Small Text', 'Currency', 'Check', 'Text Editor', 'Attach Image',
|
||||
'Code', 'Link', 'Float', 'Int', 'Date', 'Select', 'Duration'].includes(df.fieldtype)
|
||||
|| df.read_only == 1
|
||||
|| df.is_virtual == 1
|
||||
)
|
||||
|
||||
if (
|
||||
is_read_only_field
|
||||
&& df.fetch_from
|
||||
&& df.fetch_from.indexOf(".") != -1
|
||||
) {
|
||||
var parts = df.fetch_from.split(".");
|
||||
me.frm.add_fetch(parts[0], parts[1], df.fieldname, df.parent);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1672,7 +1672,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
frappe.model.is_value_type(field_doc) &&
|
||||
field_doc.fieldtype !== "Read Only" &&
|
||||
!field_doc.hidden &&
|
||||
!field_doc.read_only
|
||||
!field_doc.read_only &&
|
||||
!field_doc.is_virtual
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ frappe.msgprint = function(msg, title, is_minimizable) {
|
|||
if(data.title || !msg_exists) {
|
||||
// set title only if it is explicitly given
|
||||
// and no existing title exists
|
||||
frappe.msg_dialog.set_title(data.title || __('Message'));
|
||||
frappe.msg_dialog.set_title(data.title || __('Message', null, 'Default title of the message dialog'));
|
||||
}
|
||||
|
||||
// show / hide indicator
|
||||
|
|
|
|||
|
|
@ -283,12 +283,13 @@ class NotificationsView extends BaseNotificationsView {
|
|||
e.stopImmediatePropagation();
|
||||
this.mark_as_read(field.name, item_html);
|
||||
});
|
||||
|
||||
item_html.on('click', () => {
|
||||
this.mark_as_read(field.name, item_html);
|
||||
});
|
||||
}
|
||||
|
||||
item_html.on('click', () => {
|
||||
!field.read && this.mark_as_read(field.name, item_html);
|
||||
this.notifications_icon.trigger('click');
|
||||
});
|
||||
|
||||
return item_html;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -259,8 +259,16 @@ frappe.utils.xss_sanitise = function (string, options) {
|
|||
'/': '/'
|
||||
};
|
||||
const REGEX_SCRIPT = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi; // used in jQuery 1.7.2 src/ajax.js Line 14
|
||||
const REGEX_ALERT = /confirm\(.*\)|alert\(.*\)|prompt\(.*\)/gi; // captures alert, confirm, prompt
|
||||
options = Object.assign({}, DEFAULT_OPTIONS, options); // don't deep copy, immutable beauty.
|
||||
|
||||
// Rule 3 - TODO: Check event handlers?
|
||||
// script and alert should be checked first or else it will be escaped
|
||||
if (options.strategies.includes('js')) {
|
||||
sanitised = sanitised.replace(REGEX_SCRIPT, "");
|
||||
sanitised = sanitised.replace(REGEX_ALERT, "");
|
||||
}
|
||||
|
||||
// Rule 1
|
||||
if (options.strategies.includes('html')) {
|
||||
for (let char in HTML_ESCAPE_MAP) {
|
||||
|
|
@ -270,11 +278,6 @@ frappe.utils.xss_sanitise = function (string, options) {
|
|||
}
|
||||
}
|
||||
|
||||
// Rule 3 - TODO: Check event handlers?
|
||||
if (options.strategies.includes('js')) {
|
||||
sanitised = sanitised.replace(REGEX_SCRIPT, "");
|
||||
}
|
||||
|
||||
return sanitised;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
setup_defaults() {
|
||||
super.setup_defaults();
|
||||
this.page_title = __('Report:') + ' ' + this.page_title;
|
||||
this.menu_items = this.report_menu_items();
|
||||
this.view = 'Report';
|
||||
|
||||
const route = frappe.get_route();
|
||||
|
|
@ -52,6 +51,11 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
this.page.main.addClass('report-view');
|
||||
}
|
||||
|
||||
setup_page() {
|
||||
this.menu_items = this.report_menu_items();
|
||||
super.setup_page();
|
||||
}
|
||||
|
||||
toggle_side_bar() {
|
||||
super.toggle_side_bar();
|
||||
// refresh datatable when sidebar is toggled to accomodate extra space
|
||||
|
|
@ -644,6 +648,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
// not a cancelled doc
|
||||
&& data.docstatus !== 2
|
||||
&& !df.read_only
|
||||
&& !df.is_virtual
|
||||
&& !df.hidden
|
||||
// not a standard field i.e., owner, modified_by, etc.
|
||||
&& !frappe.model.std_fields_list.includes(df.fieldname))
|
||||
|
|
@ -1025,7 +1030,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
title += ` (${__(doctype)})`;
|
||||
}
|
||||
|
||||
const editable = frappe.model.is_non_std_field(fieldname) && !docfield.read_only;
|
||||
const editable = frappe.model.is_non_std_field(fieldname) && !docfield.read_only && !docfield.is_virtual;
|
||||
|
||||
const align = (() => {
|
||||
const is_numeric = frappe.model.is_numeric_field(docfield);
|
||||
|
|
@ -1207,7 +1212,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
args: {
|
||||
name: name,
|
||||
doctype: this.doctype,
|
||||
json: JSON.stringify(report_settings)
|
||||
report_settings: JSON.stringify(report_settings)
|
||||
},
|
||||
callback:(r) => {
|
||||
if(r.exc) {
|
||||
|
|
@ -1244,6 +1249,17 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
}
|
||||
}
|
||||
|
||||
delete_report() {
|
||||
return frappe.call({
|
||||
method: 'frappe.desk.reportview.delete_report',
|
||||
args: { name: this.report_name },
|
||||
callback(response) {
|
||||
if (response.exc) return;
|
||||
window.history.back();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get_column_widths() {
|
||||
if (this.datatable) {
|
||||
return this.datatable
|
||||
|
|
@ -1465,12 +1481,42 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
|
|||
}
|
||||
});
|
||||
|
||||
// save buttons
|
||||
if(frappe.user.is_report_manager()) {
|
||||
items = items.concat([
|
||||
{ label: __('Save'), action: () => this.save_report('save') },
|
||||
{ label: __('Save As'), action: () => this.save_report('save_as') }
|
||||
]);
|
||||
const can_edit_or_delete = (action) => {
|
||||
const method = action == "delete" ? "can_delete" : "can_write";
|
||||
return (
|
||||
this.report_doc
|
||||
&& this.report_doc.is_standard !== "Yes"
|
||||
&& (
|
||||
frappe.model[method]("Report")
|
||||
|| this.report_doc.owner === frappe.session.user
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// A user with role Report Manager or Report Owner can save
|
||||
if (can_edit_or_delete()) {
|
||||
items.push({
|
||||
label: __("Save"),
|
||||
action: () => this.save_report('save')
|
||||
});
|
||||
}
|
||||
|
||||
// anyone can save as
|
||||
items.push({
|
||||
label: __('Save As'),
|
||||
action: () => this.save_report('save_as')
|
||||
});
|
||||
|
||||
// A user with role Report Manager or Report Owner can delete
|
||||
if (can_edit_or_delete("delete")) {
|
||||
items.push({
|
||||
label: __("Delete"),
|
||||
action: () => frappe.confirm(
|
||||
"Are you sure you want to delete this report?",
|
||||
() => this.delete_report(),
|
||||
),
|
||||
shortcut: "Shift+Ctrl+D"
|
||||
});
|
||||
}
|
||||
|
||||
// user permissions
|
||||
|
|
|
|||
|
|
@ -343,7 +343,7 @@ frappe.views.TreeView = class TreeView {
|
|||
this.ignore_fields = this.opts.ignore_fields || [];
|
||||
|
||||
var mandatory_fields = $.map(me.opts.meta.fields, function(d) {
|
||||
return (d.reqd || d.bold && !d.read_only) ? d : null });
|
||||
return (d.reqd || d.bold && !d.read_only && !!d.is_virtual) ? d : null });
|
||||
|
||||
var opts_field_names = this.fields.map(function(d) {
|
||||
return d.fieldname
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export default class Paragraph extends Block {
|
|||
this.show_hide_block_list();
|
||||
});
|
||||
div.addEventListener('blur', () => {
|
||||
setTimeout(() => this.show_hide_block_list(true), 10);
|
||||
!this.over_block_list_item && this.show_hide_block_list(true);
|
||||
});
|
||||
div.dataset.placeholder = this.api.i18n.t(this._placeholder);
|
||||
div.addEventListener('keyup', this.onKeyUp);
|
||||
|
|
@ -95,6 +95,12 @@ export default class Paragraph extends Block {
|
|||
this.api.caret.setToBlock(index);
|
||||
});
|
||||
|
||||
$block_list_item.mouseenter(() => {
|
||||
this.over_block_list_item = true;
|
||||
}).mouseleave(() => {
|
||||
this.over_block_list_item = false;
|
||||
});
|
||||
|
||||
$block_list_container.append($block_list_item);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ frappe.views.Workspace = class Workspace {
|
|||
this.clear_page_actions();
|
||||
|
||||
page.is_editable && this.page.set_primary_action(
|
||||
__("Save Customizations"),
|
||||
__("Save"),
|
||||
() => {
|
||||
this.clear_page_actions();
|
||||
this.save_page(page).then((saved) => {
|
||||
|
|
@ -1158,7 +1158,7 @@ frappe.views.Workspace = class Workspace {
|
|||
item.data.card_name !== 'Custom Reports')
|
||||
);
|
||||
|
||||
if (page.content == JSON.stringify(blocks)) {
|
||||
if (page.content == JSON.stringify(blocks) && Object.keys(new_widgets).length === 0) {
|
||||
this.setup_customization_buttons(page);
|
||||
frappe.show_alert({ message: __("No changes made on the page"), indicator: "warning" });
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ export default class Widget {
|
|||
let title = max_chars ? frappe.ellipsis(base, max_chars) : base;
|
||||
|
||||
if (this.icon) {
|
||||
let icon = frappe.utils.icon(this.icon);
|
||||
let icon = frappe.utils.icon(this.icon, "lg");
|
||||
this.title_field[0].innerHTML = `${icon} <span class="ellipsis" title="${title}">${title}</span>`;
|
||||
} else {
|
||||
this.title_field[0].innerHTML = `<span class="ellipsis" title="${title}">${title}</span>`;
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ class CardDialog extends WidgetDialog {
|
|||
{
|
||||
fieldtype: "Data",
|
||||
fieldname: "label",
|
||||
label: "Label",
|
||||
label: "Label"
|
||||
},
|
||||
{
|
||||
fieldname: 'links',
|
||||
|
|
@ -174,7 +174,7 @@ class CardDialog extends WidgetDialog {
|
|||
},
|
||||
{
|
||||
fieldname: "icon",
|
||||
fieldtype: "Data",
|
||||
fieldtype: "Icon",
|
||||
label: "Icon"
|
||||
},
|
||||
{
|
||||
|
|
@ -182,6 +182,7 @@ class CardDialog extends WidgetDialog {
|
|||
fieldtype: "Select",
|
||||
in_list_view: 1,
|
||||
label: "Link Type",
|
||||
reqd: 1,
|
||||
options: ["DocType", "Page", "Report"]
|
||||
},
|
||||
{
|
||||
|
|
@ -189,9 +190,9 @@ class CardDialog extends WidgetDialog {
|
|||
fieldtype: "Dynamic Link",
|
||||
in_list_view: 1,
|
||||
label: "Link To",
|
||||
reqd: 1,
|
||||
get_options: (df) => {
|
||||
return df.doc.link_type;
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -227,6 +228,31 @@ class CardDialog extends WidgetDialog {
|
|||
}
|
||||
|
||||
process_data(data) {
|
||||
data.links.map((item, idx) => {
|
||||
let message = '';
|
||||
let row = idx+1;
|
||||
|
||||
if (!item.link_type) {
|
||||
message = "Following fields have missing values: <br><br><ul>";
|
||||
message += `<li>Link Type in Row ${row}</li>`;
|
||||
}
|
||||
|
||||
if (!item.link_to) {
|
||||
message += `<li>Link To in Row ${row}</li>`;
|
||||
}
|
||||
|
||||
if (message) {
|
||||
message += "</ul>";
|
||||
frappe.throw({
|
||||
message: __(message),
|
||||
title: __("Missing Values Required"),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
|
||||
item.label = item.label ? item.label : item.link_to;
|
||||
});
|
||||
|
||||
data.label = data.label ? data.label : data.chart_name;
|
||||
return data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@ body {
|
|||
svg {
|
||||
flex: none;
|
||||
margin-right: 6px;
|
||||
margin-left: -2px;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -560,21 +561,29 @@ body {
|
|||
}
|
||||
|
||||
&.links-widget-box {
|
||||
padding: 18px 12px;
|
||||
|
||||
.link-item {
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
font-size: var(--text-md);
|
||||
color: var(--text-color);
|
||||
padding: var(--padding-xs);
|
||||
margin-left: -5px;
|
||||
padding: 4px;
|
||||
margin-left: -4px;
|
||||
margin-bottom: 4px;
|
||||
border-radius: var(--border-radius-md);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg-color);
|
||||
background-color: var(--fg-hover-color);
|
||||
|
||||
.indicator-pill {
|
||||
background-color: var(--fg-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 15px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
|
|
@ -601,6 +610,8 @@ body {
|
|||
|
||||
.indicator-pill {
|
||||
margin-right: var(--margin-sm);
|
||||
height: 20px;
|
||||
padding: 3px 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -850,10 +861,16 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
.layout-main-section-wrapper {
|
||||
margin-top: -5px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.layout-main-section {
|
||||
background-color: var(--fg-color);
|
||||
padding: var(--padding-sm);
|
||||
box-shadow: var(--card-shadow);
|
||||
border-radius: var(--border-radius-lg);
|
||||
padding: var(--padding-sm);
|
||||
}
|
||||
|
||||
.block-menu-item-icon svg{
|
||||
|
|
|
|||
|
|
@ -104,10 +104,10 @@ body[data-route^="Module"] .main-menu {
|
|||
}
|
||||
|
||||
.sidebar-image-section {
|
||||
width: min(100%, 170px);
|
||||
cursor: pointer;
|
||||
|
||||
.sidebar-image {
|
||||
width: min(100%, 170px);
|
||||
height: auto;
|
||||
max-height: 170px;
|
||||
object-fit: cover;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from frappe.website.utils import clear_cache
|
|||
from frappe.rate_limiter import rate_limit
|
||||
from frappe.utils import add_to_date, now
|
||||
from frappe.website.doctype.blog_settings.blog_settings import get_comment_limit
|
||||
from frappe.utils.html_utils import clean_html
|
||||
|
||||
from frappe import _
|
||||
|
||||
|
|
@ -29,7 +30,7 @@ def add_comment(comment, comment_email, comment_by, reference_doctype, reference
|
|||
return False
|
||||
|
||||
comment = doc.add_comment(
|
||||
text=comment,
|
||||
text=clean_html(comment),
|
||||
comment_email=comment_email,
|
||||
comment_by=comment_by)
|
||||
|
||||
|
|
|
|||
|
|
@ -291,6 +291,16 @@ class TestDB(unittest.TestCase):
|
|||
|
||||
frappe.db.MAX_WRITES_PER_TRANSACTION = Database.MAX_WRITES_PER_TRANSACTION
|
||||
|
||||
def test_pk_collision_ignoring(self):
|
||||
# note has `name` generated from title
|
||||
for _ in range(3):
|
||||
frappe.get_doc(doctype="Note", title="duplicate name").insert(ignore_if_duplicate=True)
|
||||
|
||||
with savepoint():
|
||||
self.assertRaises(frappe.DuplicateEntryError, frappe.get_doc(doctype="Note", title="duplicate name").insert)
|
||||
# recover transaction to continue other tests
|
||||
raise Exception
|
||||
|
||||
|
||||
@run_only_if(db_type_is.MARIADB)
|
||||
class TestDDLCommandsMaria(unittest.TestCase):
|
||||
|
|
|
|||
|
|
@ -1,11 +1,20 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: MIT. See LICENSE
|
||||
import os
|
||||
import unittest
|
||||
from contextlib import contextmanager
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
import frappe
|
||||
from frappe.utils import cint
|
||||
from frappe.model.naming import revert_series_if_last, make_autoname, parse_naming_series
|
||||
from frappe.desk.doctype.note.note import Note
|
||||
from frappe.model.naming import make_autoname, parse_naming_series, revert_series_if_last
|
||||
from frappe.utils import cint, now_datetime
|
||||
|
||||
|
||||
class CustomTestNote(Note):
|
||||
@property
|
||||
def age(self):
|
||||
return now_datetime() - self.creation
|
||||
|
||||
|
||||
class TestDocument(unittest.TestCase):
|
||||
|
|
@ -256,4 +265,50 @@ class TestDocument(unittest.TestCase):
|
|||
def test_limit_for_get(self):
|
||||
doc = frappe.get_doc("DocType", "DocType")
|
||||
# assuming DocType has more that 3 Data fields
|
||||
self.assertEquals(len(doc.get("fields", filters={"fieldtype": "Data"}, limit=3)), 3)
|
||||
self.assertEquals(len(doc.get("fields", filters={"fieldtype": "Data"}, limit=3)), 3)
|
||||
|
||||
def test_virtual_fields(self):
|
||||
"""Virtual fields are accessible via API and Form views, whenever .as_dict is invoked
|
||||
"""
|
||||
frappe.db.delete("Custom Field", {"dt": "Note", "fieldname":"age"})
|
||||
|
||||
def patch_note():
|
||||
return patch("frappe.controllers", new={frappe.local.site: {'Note': CustomTestNote}})
|
||||
|
||||
@contextmanager
|
||||
def customize_note(with_options=False):
|
||||
options = "frappe.utils.now_datetime() - doc.creation" if with_options else ""
|
||||
custom_field = frappe.get_doc({
|
||||
"doctype": "Custom Field",
|
||||
"dt": "Note",
|
||||
"fieldname": "age",
|
||||
"fieldtype": "Data",
|
||||
"read_only": True,
|
||||
"is_virtual": True,
|
||||
"options": options,
|
||||
})
|
||||
|
||||
try:
|
||||
yield custom_field.insert(ignore_if_duplicate=True)
|
||||
finally:
|
||||
custom_field.delete()
|
||||
|
||||
with patch_note():
|
||||
doc = frappe.get_last_doc("Note")
|
||||
self.assertIsInstance(doc, CustomTestNote)
|
||||
self.assertIsInstance(doc.age, timedelta)
|
||||
self.assertIsNone(doc.as_dict().get("age"))
|
||||
self.assertIsNone(doc.get_valid_dict().get("age"))
|
||||
|
||||
with customize_note(), patch_note():
|
||||
doc = frappe.get_last_doc("Note")
|
||||
self.assertIsInstance(doc, CustomTestNote)
|
||||
self.assertIsInstance(doc.age, timedelta)
|
||||
self.assertIsInstance(doc.as_dict().get("age"), timedelta)
|
||||
self.assertIsInstance(doc.get_valid_dict().get("age"), timedelta)
|
||||
|
||||
with customize_note(with_options=True):
|
||||
doc = frappe.get_last_doc("Note")
|
||||
self.assertIsInstance(doc, Note)
|
||||
self.assertIsInstance(doc.as_dict().get("age"), timedelta)
|
||||
self.assertIsInstance(doc.get_valid_dict().get("age"), timedelta)
|
||||
|
|
|
|||
|
|
@ -148,9 +148,6 @@ def create_form_tour():
|
|||
if frappe.db.exists('Form Tour', {'name': 'Test Form Tour'}):
|
||||
return
|
||||
|
||||
def get_docfield_name(filters):
|
||||
return frappe.db.get_value('DocField', filters, "name")
|
||||
|
||||
tour = frappe.get_doc({
|
||||
'doctype': 'Form Tour',
|
||||
'title': 'Test Form Tour',
|
||||
|
|
@ -161,7 +158,6 @@ def create_form_tour():
|
|||
"description": "Test Description 1",
|
||||
"has_next_condition": 1,
|
||||
"next_step_condition": "eval: doc.first_name",
|
||||
"field": get_docfield_name({'parent': 'Contact', 'fieldname': 'first_name'}),
|
||||
"fieldname": "first_name",
|
||||
"fieldtype": "Data"
|
||||
},{
|
||||
|
|
@ -169,21 +165,18 @@ def create_form_tour():
|
|||
"description": "Test Description 2",
|
||||
"has_next_condition": 1,
|
||||
"next_step_condition": "eval: doc.last_name",
|
||||
"field": get_docfield_name({'parent': 'Contact', 'fieldname': 'last_name'}),
|
||||
"fieldname": "last_name",
|
||||
"fieldtype": "Data"
|
||||
},{
|
||||
"title": "Test Title 3",
|
||||
"description": "Test Description 3",
|
||||
"field": get_docfield_name({'parent': 'Contact', 'fieldname': 'phone_nos'}),
|
||||
"fieldname": "phone_nos",
|
||||
"fieldtype": "Table"
|
||||
},{
|
||||
"title": "Test Title 4",
|
||||
"description": "Test Description 4",
|
||||
"is_table_field": 1,
|
||||
"parent_field": get_docfield_name({'parent': 'Contact', 'fieldname': 'phone_nos'}),
|
||||
"field": get_docfield_name({'parent': 'Contact Phone', 'fieldname': 'phone'}),
|
||||
"parent_fieldname": "phone_nos",
|
||||
"next_step_condition": "eval: doc.phone",
|
||||
"has_next_condition": 1,
|
||||
"fieldname": "phone",
|
||||
|
|
|
|||
|
|
@ -148,6 +148,8 @@ More Information,Mehr Informationen,
|
|||
More...,Mehr...,
|
||||
Move,Bewegen,
|
||||
My Account,Mein Konto,
|
||||
My Profile,Mein Profil,
|
||||
My Settings,Meine Einstellungen,
|
||||
New Address,Neue Adresse,
|
||||
New Contact,Neuer Kontakt,
|
||||
Next,Weiter,
|
||||
|
|
@ -406,7 +408,7 @@ Allow Self Approval,Erlaube Selbstgenehmigung,
|
|||
Allow approval for creator of the document,Genehmigung für den Ersteller des Dokuments zulassen,
|
||||
Allow events in timeline,Ereignisse in der Zeitleiste zulassen,
|
||||
Allow in Quick Entry,In Schnelleingabe zulassen,
|
||||
Allow on Submit,Beim Übertragen zulassen,
|
||||
Allow on Submit,Änderungen zulassen wenn gebucht,
|
||||
Allow only one session per user,Nur eine Sitzung pro Benutzer zulassen,
|
||||
Allow page break inside tables,Seitenumbruch innerhalb von Tabellen erlauben,
|
||||
Allow saving if mandatory fields are not filled,Speichern trotz leerer Pflichtfelder zulassen,
|
||||
|
|
|
|||
|
|
|
@ -60,7 +60,7 @@ frappe.ui.form.on("Web Form", {
|
|||
options: field.options,
|
||||
reqd: field.reqd,
|
||||
default: field.default,
|
||||
read_only: field.read_only,
|
||||
read_only: field.read_only || field.is_virtual,
|
||||
depends_on: field.depends_on,
|
||||
mandatory_depends_on: field.mandatory_depends_on,
|
||||
read_only_depends_on: field.read_only_depends_on,
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
"localforage": "^1.9.0",
|
||||
"moment": "^2.20.1",
|
||||
"moment-timezone": "^0.5.28",
|
||||
"node-sass": "^4.14.1",
|
||||
"node-sass": "^7.0.0",
|
||||
"plyr": "^3.6.2",
|
||||
"popper.js": "^1.16.0",
|
||||
"quagga": "^0.12.1",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ googlemaps~=4.4.5
|
|||
gunicorn~=20.1.0
|
||||
html2text==2020.1.16
|
||||
html5lib~=1.1
|
||||
ipython~=7.27.0
|
||||
ipython~=7.31.1
|
||||
Jinja2~=3.0.1
|
||||
ldap3~=2.9
|
||||
markdown2~=2.4.0
|
||||
|
|
@ -32,7 +32,7 @@ openpyxl~=3.0.7
|
|||
passlib~=1.7.4
|
||||
paytmchecksum~=1.7.0
|
||||
pdfkit~=0.6.1
|
||||
Pillow~=8.2.0
|
||||
Pillow~=9.0.0
|
||||
premailer~=3.8.0
|
||||
psutil~=5.8.0
|
||||
psycopg2-binary~=2.9.1
|
||||
|
|
@ -63,7 +63,7 @@ sqlparse~=0.4.1
|
|||
stripe~=2.56.0
|
||||
terminaltables~=3.1.0
|
||||
urllib3~=1.26.4
|
||||
Werkzeug~=0.16.1
|
||||
Werkzeug~=2.0.3
|
||||
Whoosh~=2.7.4
|
||||
wrapt~=1.12.1
|
||||
xlrd~=2.0.1
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue