Merge branch 'develop' into image-processing

This commit is contained in:
Mitul David 2021-08-06 18:45:59 +05:30 committed by GitHub
commit 4f86cdb8f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 402 additions and 284 deletions

View file

@ -0,0 +1,57 @@
context('Sidebar', () => {
before(() => {
cy.visit('/login');
cy.login();
cy.visit('/app/doctype');
});
it('Test for checking "Assigned To" counter value, adding filter and adding & removing an assignment', () => {
cy.click_sidebar_button(0);
//To check if no filter is available in "Assigned To" dropdown
cy.get('.empty-state').should('contain', 'No filters found');
cy.click_sidebar_button(1);
//To check if "Created By" dropdown contains filter
cy.get('.group-by-item > .dropdown-item').should('contain', 'Me');
//Assigning a doctype to a user
cy.click_listview_row_item(0);
cy.get('.form-assignments > .flex > .text-muted').click();
cy.get_field('assign_to_me', 'Check').click();
cy.get('.modal-footer > .standard-actions > .btn-primary').click();
cy.visit('/app/doctype');
cy.click_sidebar_button(0);
//To check if filter is added in "Assigned To" dropdown after assignment
cy.get('.group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item').should('contain', '1');
//To check if there is no filter added to the listview
cy.get('.filter-selector > .btn').should('contain', 'Filter');
//To add a filter to display data into the listview
cy.get('.group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item').click();
//To check if filter is applied
cy.click_filter_button().should('contain', '1 filter');
cy.get('.fieldname-select-area > .awesomplete > .form-control').should('have.value', 'Assigned To');
cy.get('.condition').should('have.value', 'like');
cy.get('.filter-field > .form-group > .input-with-feedback').should('have.value', '%Administrator%');
//To remove the applied filter
cy.get('.filter-action-buttons > div > .btn-secondary').contains('Clear Filters').click();
cy.click_filter_button();
cy.get('.filter-selector > .btn').should('contain', 'Filter');
//To remove the assignment
cy.visit('/app/doctype');
cy.click_listview_row_item(0);
cy.get('.assignments > .avatar-group > .avatar > .avatar-frame').click();
cy.get('.remove-btn').click({force: true});
cy.get('.modal.show > .modal-dialog > .modal-content > .modal-header > .modal-actions > .btn-modal-close').click();
cy.visit('/app/doctype');
cy.click_sidebar_button(0);
cy.get('.empty-state').should('contain', 'No filters found');
});
});

View file

@ -9,6 +9,7 @@ context('Table MultiSelect', () => {
cy.new_form('Assignment Rule');
cy.fill_field('__newname', name);
cy.fill_field('document_type', 'Blog Post');
cy.get('.section-head').contains('Assignment Rules').scrollIntoView();
cy.fill_field('assign_condition', 'status=="Open"', 'Code');
cy.get('input[data-fieldname="users"]').focus().as('input');
cy.get('input[data-fieldname="users"] + ul').should('be.visible');

View file

@ -0,0 +1,53 @@
context('Timeline', () => {
before(() => {
cy.visit('/login');
cy.login();
cy.visit('/app/todo');
});
it('Adding new ToDo, adding new comment, verifying comment addition & deletion and deleting ToDo', () => {
//Adding new ToDo
cy.click_listview_primary_button('Add ToDo');
cy.get('.modal-footer > .custom-actions > .btn').contains('Edit in full page').click();
cy.get('.row > .section-body > .form-column > form > .frappe-control > .form-group > .control-input-wrapper > .control-input > .ql-container > .ql-editor').eq(0).type('Test ToDo', {force: true});
cy.wait(200);
cy.get('#page-ToDo > .page-head > .container > .row > .col > .standard-actions > .primary-action').contains('Save').click();
cy.wait(700);
cy.visit('/app/todo');
cy.get('.list-row > .level-left > .list-subject > .level-item.ellipsis > .ellipsis').eq(0).click();
//To check if the comment box is initially empty and tying some text into it
cy.get('.comment-input-container > .frappe-control > .ql-container > .ql-editor').should('contain', '').type('Testing Timeline');
//Adding new comment
cy.get('.comment-input-wrapper > .btn').contains('Comment').click();
//To check if the commented text is visible in the timeline content
cy.get('.timeline-content').should('contain', 'Testing Timeline');
//Editing comment
cy.click_timeline_action_btn(0);
cy.get('.timeline-content > .timeline-message-box > .comment-edit-box > .frappe-control > .ql-container > .ql-editor').first().type(' 123');
cy.click_timeline_action_btn(0);
//To check if the edited comment text is visible in timeline content
cy.get('.timeline-content').should('contain', 'Testing Timeline 123');
//Discarding comment
cy.click_timeline_action_btn(0);
cy.get('.actions > .btn').eq(1).first().click();
//To check if after discarding the timeline content is same as previous
cy.get('.timeline-content').should('contain', 'Testing Timeline 123');
//Deleting the added comment
cy.get('.actions > .btn > .icon').first().click();
cy.get('.modal-footer > .standard-actions > .btn-primary').contains('Yes').click();
cy.click_modal_primary_button('Yes');
//Deleting the added ToDo
cy.get('#page-ToDo > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .btn').click({force: true});
cy.get('.menu-btn-group > .dropdown-menu > li > .grey-link').eq(17).click({force: true});
cy.get('.modal.show > .modal-dialog > .modal-content > .modal-footer > .standard-actions > .btn-primary').contains('Yes').click({force: true});
});
});

View file

@ -0,0 +1,71 @@
context('Timeline Email', () => {
before(() => {
cy.visit('/login');
cy.login();
cy.visit('/app/todo');
});
it('Adding new ToDo, adding email and verifying timeline content for email attachment, deleting attachment and ToDo', () => {
//Adding new ToDo
cy.click_listview_primary_button('Add ToDo');
cy.get('.custom-actions > .btn').trigger('click', {delay: 500});
cy.get('.row > .section-body > .form-column > form > .frappe-control > .form-group > .control-input-wrapper > .control-input > .ql-container > .ql-editor').eq(0).type('Test ToDo', {force: true});
cy.wait(500);
//cy.click_listview_primary_button('Save');
cy.get('.primary-action').contains('Save').click({force: true});
cy.wait(700);
cy.visit('/app/todo');
cy.get('.list-row > .level-left > .list-subject > .level-item.ellipsis > .ellipsis').eq(0).click();
//Creating a new email
cy.get('.timeline-actions > .btn').click();
cy.fill_field('recipients', 'test@example.com', 'MultiSelect');
cy.get('.modal.show > .modal-dialog > .modal-content > .modal-body > :nth-child(1) > .form-layout > .form-page > :nth-child(3) > .section-body > .form-column > form > [data-fieldtype="Text Editor"] > .form-group > .control-input-wrapper > .control-input > .ql-container > .ql-editor').type('Test Mail');
//Adding attachment to the email
cy.get('.add-more-attachments > .btn').click();
cy.get('.mt-2 > .btn > .mt-1').eq(2).click();
cy.get('.input-group > .form-control').type('https://wallpaperplay.com/walls/full/8/2/b/72402.jpg');
cy.get('.btn-primary').contains('Upload').click();
//Sending the email
cy.click_modal_primary_button('Send', {delay: 500});
//To check if the sent mail content is shown in the timeline content
cy.get('[data-doctype="Communication"] > .timeline-content').should('contain', 'Test Mail');
//To check if the attachment of email is shown in the timeline content
cy.get('.timeline-content').should('contain', 'Added 72402.jpg');
//Deleting the sent email
cy.get('[title="Open Communication"] > .icon').first().click({force: true});
cy.get('#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .btn').click();
cy.get('#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .dropdown-menu > li > .grey-link').eq(9).click();
cy.get('.modal.show > .modal-dialog > .modal-content > .modal-footer > .standard-actions > .btn-primary').click();
cy.visit('/app/todo');
cy.get('.list-row > .level-left > .list-subject > .level-item.ellipsis > .ellipsis').eq(0).click();
//Removing the added attachment
cy.get('.attachment-row > .data-pill > .remove-btn > .icon').click();
cy.get('.modal-dialog > .modal-content > .modal-footer > .standard-actions > .btn-primary').contains('Yes').click();
//To check if the removed attachment is shown in the timeline content
cy.get('.timeline-content').should('contain', 'Removed 72402.jpg');
cy.wait(500);
//To check if the discard button functionality in email is working correctly
cy.get('.timeline-actions > .btn').click();
cy.fill_field('recipients', 'test@example.com', 'MultiSelect');
cy.get('.modal-footer > .standard-actions > .btn-secondary').contains('Discard').click();
cy.wait(500);
cy.get('.timeline-actions > .btn').click();
cy.wait(500);
cy.get_field('recipients', 'MultiSelect').should('have.text', '');
cy.get('.modal.show > .modal-dialog > .modal-content > .modal-header > .modal-actions > .btn-modal-close > .icon').click();
//Deleting the added ToDo
cy.get('#page-ToDo > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .btn').click();
cy.get('.menu-btn-group > .dropdown-menu > li > .grey-link').eq(17).click();
cy.get('.modal.show > .modal-dialog > .modal-content > .modal-footer > .standard-actions > .btn-primary').click();
});
});

View file

@ -192,16 +192,16 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype = 'Data') => {
});
Cypress.Commands.add('get_field', (fieldname, fieldtype = 'Data') => {
let selector = `.form-control[data-fieldname="${fieldname}"]`;
let selector = `[data-fieldname="${fieldname}"] input:visible`;
if (fieldtype === 'Text Editor') {
selector = `[data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]`;
selector = `[data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]:visible`;
}
if (fieldtype === 'Code') {
selector = `[data-fieldname="${fieldname}"] .ace_text-input`;
}
return cy.get(selector);
return cy.get(selector).first();
});
Cypress.Commands.add('fill_table_field', (tablefieldname, row_idx, fieldname, value, fieldtype = 'Data') => {
@ -323,4 +323,30 @@ Cypress.Commands.add('clear_filters', () => {
cy.window().its('cur_list').then(cur_list => {
cur_list && cur_list.filter_area && cur_list.filter_area.clear();
});
});
Cypress.Commands.add('click_modal_primary_button', (btn_name) => {
cy.get('.modal-footer > .standard-actions > .btn-primary').contains(btn_name).trigger('click', {force: true});
});
Cypress.Commands.add('click_sidebar_button', (btn_no) => {
cy.get('.list-group-by-fields > .group-by-field > .btn').eq(btn_no).click();
});
Cypress.Commands.add('click_listview_row_item', (row_no) => {
cy.get('.list-row > .level-left > .list-subject > .bold > .ellipsis').eq(row_no).click({force: true});
});
Cypress.Commands.add('click_filter_button', () => {
cy.get('.filter-selector > .btn').click();
});
Cypress.Commands.add('click_listview_primary_button', (btn_name) => {
cy.get('.primary-action').contains(btn_name).click({force: true});
});
Cypress.Commands.add('click_timeline_action_btn', (btn_no) => {
cy.get('.timeline-content > .timeline-message-box > .justify-between > .actions > .btn').eq(btn_no).first().click();
});

View file

@ -72,6 +72,7 @@
"fieldtype": "Code",
"in_list_view": 1,
"label": "Assign Condition",
"options": "PythonExpression",
"reqd": 1
},
{
@ -82,7 +83,8 @@
"description": "Simple Python Expression, Example: Status in (\"Closed\", \"Cancelled\")",
"fieldname": "unassign_condition",
"fieldtype": "Code",
"label": "Unassign Condition"
"label": "Unassign Condition",
"options": "PythonExpression"
},
{
"fieldname": "assign_to_users_section",
@ -120,7 +122,8 @@
"description": "Simple Python Expression, Example: Status in (\"Invalid\")",
"fieldname": "close_condition",
"fieldtype": "Code",
"label": "Close Condition"
"label": "Close Condition",
"options": "PythonExpression"
},
{
"fieldname": "sb",
@ -151,7 +154,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-10-20 14:47:20.662954",
"modified": "2021-07-16 22:51:35.505575",
"modified_by": "Administrator",
"module": "Automation",
"name": "Assignment Rule",

View file

@ -193,7 +193,7 @@ def install_app(context, apps):
print("App {} is Incompatible with Site {}{}".format(app, site, err_msg))
exit_code = 1
except Exception as err:
err_msg = ":\n{}".format(err if str(err) else frappe.get_traceback())
err_msg = ": {}\n{}".format(str(err), frappe.get_traceback())
print("An error occurred while installing {}{}".format(app, err_msg))
exit_code = 1

View file

@ -722,6 +722,19 @@ def validate_links_table_fieldnames(meta):
message = _("Row #{0}: Could not find field {1} in {2} DocType").format(index+1, 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 = _("Row #{0}: Could not find field {1} in {2} DocType").format(index+1, 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 = _("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 = _("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)

View file

@ -7,8 +7,11 @@
"field_order": [
"link_doctype",
"link_fieldname",
"parent_doctype",
"table_fieldname",
"group",
"hidden",
"is_child_table",
"custom"
],
"fields": [
@ -45,12 +48,33 @@
"fieldtype": "Check",
"hidden": 1,
"label": "Custom"
},
{
"depends_on": "is_child_table",
"fieldname": "parent_doctype",
"fieldtype": "Link",
"label": "Parent DocType",
"mandatory_depends_on": "is_child_table",
"options": "DocType"
},
{
"default": "0",
"fetch_from": "link_doctype.istable",
"fieldname": "is_child_table",
"fieldtype": "Check",
"label": "Is Child Table",
"read_only": 1
},
{
"fieldname": "table_fieldname",
"fieldtype": "Data",
"label": "Table Fieldname"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-09-24 14:19:25.189511",
"modified": "2021-07-31 15:23:12.237491",
"modified_by": "Administrator",
"module": "Core",
"name": "DocType Link",

View file

@ -15,7 +15,6 @@ from frappe import _
class ServerScript(Document):
def validate(self):
frappe.only_for("Script Manager", True)
self.validate_script()
self.sync_scheduled_jobs()
self.clear_scheduled_events()
@ -36,10 +35,6 @@ class ServerScript(Document):
fields=["name", "stopped"],
)
def validate_script(self):
"""Utilizes the ast module to check for syntax errors
"""
ast.parse(self.script)
def sync_scheduled_jobs(self):
"""Sync Scheduled Job Type statuses if Server Script's disabled status is changed

View file

@ -232,6 +232,32 @@ class TestCustomizeForm(unittest.TestCase):
testdt.delete()
testdt1.delete()
def test_custom_internal_links(self):
# add a custom internal link
frappe.clear_cache()
d = self.get_customize_form("User Group")
d.append('links', dict(link_doctype='User Group Member', parent_doctype='User',
link_fieldname='user', table_fieldname='user_group_members', group='Tests', custom=1))
d.run_method("save_customization")
frappe.clear_cache()
user_group = frappe.get_meta('User Group')
# check links exist
self.assertTrue([d.name for d in user_group.links if d.link_doctype == 'User Group Member'])
self.assertTrue([d.name for d in user_group.links if d.parent_doctype == 'User'])
# remove the link
d = self.get_customize_form("User Group")
d.links = []
d.run_method("save_customization")
frappe.clear_cache()
user_group = frappe.get_meta('Event')
self.assertFalse([d.name for d in (user_group.links or []) if d.link_doctype == 'User Group Member'])
def test_custom_action(self):
test_route = '/app/List/DocType'

View file

@ -543,7 +543,7 @@ class Database(object):
"""
if not doctype in self.value_cache:
self.value_cache = self.value_cache[doctype] = {}
self.value_cache[doctype] = {}
if fieldname in self.value_cache[doctype]:
return self.value_cache[doctype][fieldname]

View file

@ -727,6 +727,18 @@ class BaseDocument(object):
if abs(cint(value)) > max_length:
self.throw_length_exceeded_error(df, max_length, value)
def _validate_code_fields(self):
for field in self.meta.get_code_fields():
code_string = self.get(field.fieldname)
language = field.get("options")
if language == "Python":
frappe.utils.validate_python_code(code_string, fieldname=field.label, is_expression=False)
elif language == "PythonExpression":
frappe.utils.validate_python_code(code_string, fieldname=field.label)
def throw_length_exceeded_error(self, df, max_length, value):
if self.parentfield and self.idx:
reference = _("{0}, Row {1}").format(_(self.doctype), self.idx)

View file

@ -495,6 +495,7 @@ class Document(BaseDocument):
self._validate_selects()
self._validate_non_negative()
self._validate_length()
self._validate_code_fields()
self._extract_images_from_text_editor()
self._sanitize_content()
self._save_passwords()

View file

@ -141,6 +141,9 @@ class Meta(Document):
def get_image_fields(self):
return self.get("fields", {"fieldtype": "Attach Image"})
def get_code_fields(self):
return self.get("fields", {"fieldtype": "Code"})
def get_set_only_once_fields(self):
'''Return fields with `set_only_once` set'''
if not hasattr(self, "_set_only_once_fields"):
@ -504,6 +507,9 @@ class Meta(Document):
if not data.non_standard_fieldnames:
data.non_standard_fieldnames = {}
if not data.internal_links:
data.internal_links = {}
for link in dashboard_links:
link.added = False
if link.hidden:
@ -511,24 +517,32 @@ class Meta(Document):
for group in data.transactions:
group = frappe._dict(group)
# For internal links parent doctype will be the key
doctype = link.parent_doctype or link.link_doctype
# group found
if link.group and group.label == link.group:
if link.link_doctype not in group.get('items'):
group.get('items').append(link.link_doctype)
if doctype not in group.get('items'):
group.get('items').append(doctype)
link.added = True
if not link.added:
# group not found, make a new group
data.transactions.append(dict(
label = link.group,
items = [link.link_doctype]
items = [link.parent_doctype or link.link_doctype]
))
if link.link_fieldname != data.fieldname:
if data.fieldname:
data.non_standard_fieldnames[link.link_doctype] = link.link_fieldname
else:
if not link.is_child_table:
if link.link_fieldname != data.fieldname:
if data.fieldname:
data.non_standard_fieldnames[link.link_doctype] = link.link_fieldname
else:
data.fieldname = link.link_fieldname
elif link.is_child_table:
if not data.fieldname:
data.fieldname = link.link_fieldname
data.internal_links[link.parent_doctype] = [link.table_fieldname, link.link_fieldname]
def get_row_template(self):

View file

@ -6,6 +6,7 @@ import frappe
from frappe.utils import evaluate_filters, money_in_words, scrub_urls, get_url
from frappe.utils import validate_url, validate_email_address
from frappe.utils import ceil, floor
from frappe.utils.data import validate_python_code
from PIL import Image
from frappe.utils.image import strip_exif_data, optimize_image
@ -201,4 +202,31 @@ class TestImage(unittest.TestCase):
self.assertLessEqual(width, 500)
self.assertLessEqual(height, 500)
self.assertLess(len(optimized_content), len(original_content))
self.assertLess(len(optimized_content), len(original_content))
class TestPythonExpressions(unittest.TestCase):
def test_validation_for_good_python_expression(self):
valid_expressions = [
"foo == bar",
"foo == 42",
"password != 'hunter2'",
"complex != comparison and more_complex == condition",
"escaped_values == 'str with newline\\n'",
"check_box_field",
]
for expr in valid_expressions:
try:
validate_python_code(expr)
except Exception as e:
self.fail(f"Invalid error thrown for valid expression: {expr}: {str(e)}")
def test_validation_for_bad_python_expression(self):
invalid_expressions = [
"these_are && js_conditions",
"more || js_conditions",
"curly_quotes_bad == “const”",
"oops = forgot_equals",
]
for expr in invalid_expressions:
self.assertRaises(frappe.ValidationError, validate_python_code, expr)

View file

@ -5,6 +5,7 @@ import frappe
import operator
import json
import re, datetime, math, time
from code import compile_command
from urllib.parse import quote, urljoin
from frappe.desk.utils import slug
@ -1511,6 +1512,34 @@ def get_user_info_for_avatar(user_id):
return user_info
def validate_python_code(string: str, fieldname=None, is_expression: bool = True) -> None:
""" Validate python code fields by using compile_command to ensure that expression is valid python.
args:
fieldname: name of field being validated.
is_expression: true for validating simple single line python expression, else validated as script.
"""
if not string:
return
try:
compile_command(string, symbol="eval" if is_expression else "exec")
except SyntaxError as se:
line_no = se.lineno - 1 or 0
offset = se.offset - 1 or 0
error_line = string if is_expression else string.split("\n")[line_no]
msg = (frappe._("{} Invalid python code on line {}")
.format(fieldname + ":" if fieldname else "", line_no+1))
msg += f"<br><pre>{error_line}</pre>"
msg += f"<pre>{' ' * offset}^</pre>"
frappe.throw(msg, title=frappe._("Syntax Error"))
except Exception as e:
frappe.msgprint(frappe._("{} Possibly invalid python code. <br>{}")
.format(fieldname + ": " or "", str(e)), indicator="orange")
class UnicodeWithAttrs(str):
def __init__(self, text):
self.toc_html = text.toc_html

View file

@ -77,7 +77,7 @@
"label": "Landing Page"
},
{
"description": "Link that is the website home page. Standard Links (index, login, products, blog, about, contact)",
"description": "Link that is the website home page. Standard Links (home, login, products, blog, about, contact)",
"fieldname": "home_page",
"fieldtype": "Data",
"in_list_view": 1,
@ -433,7 +433,7 @@
"issingle": 1,
"links": [],
"max_attachments": 10,
"modified": "2021-04-14 17:39:56.609771",
"modified": "2021-07-15 17:39:56.609771",
"modified_by": "Administrator",
"module": "Website",
"name": "Website Settings",
@ -457,4 +457,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
}
}

View file

@ -23,7 +23,7 @@ class WebsiteSettings(Document):
return
from frappe.website.path_resolver import PathResolver
if self.home_page and not PathResolver(self.home_page).is_valid_path():
frappe.msgprint(_("Invalid Home Page") + " (Standard pages - index, login, products, blog, about, contact)")
frappe.msgprint(_("Invalid Home Page") + " (Standard pages - home, login, products, blog, about, contact)")
self.home_page = ''
def validate_top_bar_items(self):

View file

@ -1,335 +1,100 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"actions": [],
"creation": "2013-02-22 01:27:36",
"custom": 0,
"description": "Defines actions on states and the next step and allowed roles.",
"docstatus": 0,
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"state",
"action",
"next_state",
"allowed",
"allow_self_approval",
"conditions",
"condition",
"column_break_7",
"example"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "state",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "State",
"length": 0,
"no_copy": 0,
"options": "Workflow State",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "200px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "200px"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "action",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Action",
"length": 0,
"no_copy": 0,
"options": "Workflow Action Master",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "200px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "200px"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "next_state",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Next State",
"length": 0,
"no_copy": 0,
"options": "Workflow State",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "200px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "200px"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allowed",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Allowed",
"length": 0,
"no_copy": 0,
"options": "Role",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "200px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "200px"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"description": "Allow approval for creator of the document",
"fieldname": "allow_self_approval",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allow Self Approval",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Allow Self Approval"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "conditions",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Conditions",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Conditions"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "condition",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Condition",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "PythonExpression"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_7",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "example",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Example",
"length": 0,
"no_copy": 0,
"options": "<pre><code>doc.grand_total &gt; 0</code></pre>\n\n<p>Conditions should be written in simple Python. Please use properties available in the form only.</p>\n<p>Allowed functions: \n</p><ul>\n<li>frappe.db.get_value</li>\n<li>frappe.db.get_list</li>\n<li>frappe.session</li>\n<li>frappe.utils.now_datetime</li>\n<li>frappe.utils.get_datetime</li>\n<li>frappe.utils.add_to_date</li>\n<li>frappe.utils.now</li>\n</ul>\n<p>Example: </p><pre><code>doc.creation &gt; frappe.utils.add_to_date(frappe.utils.now_datetime(), days=-5, as_string=True, as_datetime=True) </code></pre><p></p>",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"options": "<pre><code>doc.grand_total &gt; 0</code></pre>\n\n<p>Conditions should be written in simple Python. Please use properties available in the form only.</p>\n<p>Allowed functions: \n</p><ul>\n<li>frappe.db.get_value</li>\n<li>frappe.db.get_list</li>\n<li>frappe.session</li>\n<li>frappe.utils.now_datetime</li>\n<li>frappe.utils.get_datetime</li>\n<li>frappe.utils.add_to_date</li>\n<li>frappe.utils.now</li>\n</ul>\n<p>Example: </p><pre><code>doc.creation &gt; frappe.utils.add_to_date(frappe.utils.now_datetime(), days=-5, as_string=True, as_datetime=True) </code></pre><p></p>"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2020-11-08 12:11:00.294908",
"links": [],
"modified": "2021-07-21 13:24:59.084836",
"modified_by": "Administrator",
"module": "Workflow",
"name": "Workflow Transition",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"track_changes": 0,
"track_seen": 0
"sort_field": "modified",
"sort_order": "DESC"
}