diff --git a/.github/helper/install.sh b/.github/helper/install.sh index f6f0cad31a..93189d2b1f 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -17,6 +17,7 @@ if [ "$TYPE" == "server" ]; then fi if [ "$DB" == "mariadb" ];then + sudo apt install mariadb-client-10.3 mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"; mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"; @@ -58,4 +59,4 @@ cd ../.. bench start & bench --site test_site reinstall --yes if [ "$TYPE" == "server" ]; then bench --site test_site_producer reinstall --yes; fi -bench build --app frappe \ No newline at end of file +bench build --app frappe diff --git a/.github/workflows/docs-checker.yml b/.github/workflows/docs-checker.yml index 90453cd1b4..02a01bf4e4 100644 --- a/.github/workflows/docs-checker.yml +++ b/.github/workflows/docs-checker.yml @@ -12,7 +12,7 @@ jobs: - name: 'Setup Environment' uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: 3.7 - name: 'Clone repo' uses: actions/checkout@v2 diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml index 0dd4cd51d8..3ac5cfa349 100644 --- a/.github/workflows/patch-mariadb-tests.yml +++ b/.github/workflows/patch-mariadb-tests.yml @@ -9,7 +9,7 @@ concurrency: jobs: test: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest name: Patch Test diff --git a/.github/workflows/publish-assets-develop.yml b/.github/workflows/publish-assets-develop.yml index a23885b508..85f3f7c3b0 100644 --- a/.github/workflows/publish-assets-develop.yml +++ b/.github/workflows/publish-assets-develop.yml @@ -18,7 +18,7 @@ jobs: node-version: 14 - uses: actions/setup-python@v2 with: - python-version: '3.6' + python-version: '3.7' - name: Set up bench and build assets run: | npm install -g yarn diff --git a/.github/workflows/publish-assets-releases.yml b/.github/workflows/publish-assets-releases.yml index a697517c23..a5cc1f8872 100644 --- a/.github/workflows/publish-assets-releases.yml +++ b/.github/workflows/publish-assets-releases.yml @@ -21,7 +21,7 @@ jobs: python-version: '12.x' - uses: actions/setup-python@v2 with: - python-version: '3.6' + python-version: '3.7' - name: Set up bench and build assets run: | npm install -g yarn diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml index 72b085c495..0b187fc44c 100644 --- a/.github/workflows/server-mariadb-tests.yml +++ b/.github/workflows/server-mariadb-tests.yml @@ -13,7 +13,7 @@ concurrency: jobs: test: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest strategy: fail-fast: false @@ -127,4 +127,4 @@ jobs: name: MariaDB fail_ci_if_error: true files: /home/runner/frappe-bench/sites/coverage.xml - verbose: true + verbose: true \ No newline at end of file diff --git a/.github/workflows/server-postgres-tests.yml b/.github/workflows/server-postgres-tests.yml index ddc41c049c..a5630121a4 100644 --- a/.github/workflows/server-postgres-tests.yml +++ b/.github/workflows/server-postgres-tests.yml @@ -12,7 +12,7 @@ concurrency: jobs: test: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/.github/workflows/translation_linter.yml b/.github/workflows/translation_linter.yml deleted file mode 100644 index 4becaebd6b..0000000000 --- a/.github/workflows/translation_linter.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Frappe Linter -on: - pull_request: - branches: - - develop - - version-12-hotfix - - version-11-hotfix -jobs: - check_translation: - name: Translation Syntax Check - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v2 - - name: Setup python3 - uses: actions/setup-python@v1 - with: - python-version: 3.6 - - name: Validating Translation Syntax - run: | - git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q - files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) - python $GITHUB_WORKSPACE/.github/helper/translation.py $files diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 2e8aca4e31..2cbe4cce41 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -12,7 +12,7 @@ concurrency: jobs: test: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/cypress/fixtures/doctype_with_tab_break.js b/cypress/fixtures/doctype_with_tab_break.js new file mode 100644 index 0000000000..bc346e8fb8 --- /dev/null +++ b/cypress/fixtures/doctype_with_tab_break.js @@ -0,0 +1,59 @@ +export default { + name: 'Form With Tab Break', + custom: 1, + actions: [], + doctype: 'DocType', + engine: 'InnoDB', + fields: [ + { + fieldname: 'username', + fieldtype: 'Data', + label: 'Name', + options: 'Name' + }, + { + fieldname: 'tab', + fieldtype: 'Tab Break', + label: 'Tab 2', + }, + { + fieldname: 'Phone', + fieldtype: 'Data', + label: 'Phone', + options: 'Phone', + reqd: 1 + }, + ], + links: [ + { + "group": "Profile", + "link_doctype": "Contact", + "link_fieldname": "user" + }, + { + "group": "Profile", + "link_doctype": "Chat Profile", + "link_fieldname": "user" + }, + ], + modified_by: 'Administrator', + module: 'Custom', + owner: 'Administrator', + permissions: [ + { + create: 1, + delete: 1, + email: 1, + print: 1, + read: 1, + role: 'System Manager', + share: 1, + write: 1 + } + ], + quick_entry: 1, + autoname: "format: Test-{####}", + sort_field: 'modified', + sort_order: 'ASC', + track_changes: 1 +}; diff --git a/cypress/integration/control_float.js b/cypress/integration/control_float.js new file mode 100644 index 0000000000..670d1fe73e --- /dev/null +++ b/cypress/integration/control_float.js @@ -0,0 +1,93 @@ +context("Control Float", () => { + before(() => { + cy.login(); + cy.visit("/app/website"); + }); + + function get_dialog_with_float() { + return cy.dialog({ + title: "Float Check", + fields: [ + { + fieldname: "float_number", + fieldtype: "Float", + Label: "Float" + } + ] + }); + } + + it("check value changes", () => { + get_dialog_with_float().as("dialog"); + + let data = get_data(); + data.forEach(x => { + cy.window() + .its("frappe") + .then(frappe => { + frappe.boot.sysdefaults.number_format = x.number_format; + }); + x.values.forEach(d => { + cy.get_field("float_number", "Float").clear(); + cy.fill_field("float_number", d.input, "Float").blur(); + cy.get_field("float_number", "Float").should( + "have.value", + d.blur_expected + ); + + cy.get_field("float_number", "Float").focus(); + cy.get_field("float_number", "Float").blur(); + cy.get_field("float_number", "Float").focus(); + cy.get_field("float_number", "Float").should( + "have.value", + d.focus_expected + ); + }); + }); + }); + + function get_data() { + return [ + { + number_format: "#.###,##", + values: [ + { + input: "364.87,334", + blur_expected: "36.487,334", + focus_expected: "36487.334" + }, + { + input: "36487,334", + blur_expected: "36.487,334", + focus_expected: "36487.334" + }, + { + input: "100", + blur_expected: "100,000", + focus_expected: "100" + } + ] + }, + { + number_format: "#,###.##", + values: [ + { + input: "364,87.334", + blur_expected: "36,487.334", + focus_expected: "36487.334" + }, + { + input: "36487.334", + blur_expected: "36,487.334", + focus_expected: "36487.334" + }, + { + input: "100", + blur_expected: "100.000", + focus_expected: "100" + } + ] + } + ]; + } +}); diff --git a/cypress/integration/dashboard_links.js b/cypress/integration/dashboard_links.js index 155f4986be..16ffd41cf4 100644 --- a/cypress/integration/dashboard_links.js +++ b/cypress/integration/dashboard_links.js @@ -9,17 +9,20 @@ context('Dashboard links', () => { cy.clear_filters(); cy.visit('/app/user'); - cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click(); + cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click({ force: true }); //To check if initially the dashboard contains only the "Contact" link and there is no counter cy.get('[data-doctype="Contact"]').should('contain', 'Contact'); //Adding a new contact - cy.get('.btn[data-doctype="Contact"]').click(); + cy.get('.document-link-badge[data-doctype="Contact"]').click(); + cy.wait(300); + cy.findByRole('button', {name: 'Add Contact'}).should('be.visible'); + cy.findByRole('button', {name: 'Add Contact'}).click(); cy.get('[data-doctype="Contact"][data-fieldname="first_name"]').type('Admin'); cy.findByRole('button', {name: 'Save'}).click(); cy.visit('/app/user'); - cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click(); + cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click({ force: true }); //To check if the counter for contact doc is "1" after adding the contact cy.get('[data-doctype="Contact"] > .count').should('contain', '1'); @@ -27,7 +30,7 @@ context('Dashboard links', () => { //Deleting the newly created contact cy.visit('/app/contact'); - cy.get('.list-subject > .select-like > .list-row-checkbox').eq(0).click(); + cy.get('.list-subject > .select-like > .list-row-checkbox').eq(0).click({ force: true }); cy.findByRole('button', {name: 'Actions'}).click(); cy.get('.actions-btn-group [data-label="Delete"]').click(); cy.findByRole('button', {name: 'Yes'}).click({delay: 700}); @@ -36,7 +39,7 @@ context('Dashboard links', () => { //To check if the counter from the "Contact" doc link is removed cy.wait(700); cy.visit('/app/user'); - cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click(); + cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click({ force: true }); cy.get('[data-doctype="Contact"]').should('contain', 'Contact'); }); diff --git a/cypress/integration/folder_navigation.js b/cypress/integration/folder_navigation.js index 1b7c02d98c..cec7edb59f 100644 --- a/cypress/integration/folder_navigation.js +++ b/cypress/integration/folder_navigation.js @@ -71,7 +71,7 @@ context('Folder Navigation', () => { it('Deleting Test Folder from the home', () => { //Deleting the Test Folder added in the home directory cy.visit('/app/file/view/home'); - cy.get('.level-left > .list-subject > .list-row-checkbox').eq(0).click({force: true, delay: 500}); + cy.get('.level-left > .list-subject > .file-select >.list-row-checkbox').eq(0).click({force: true, delay: 500}); cy.findByRole('button', {name: 'Actions'}).click(); cy.get('.actions-btn-group [data-label="Delete"]').click(); cy.findByRole('button', {name: 'Yes'}).click(); diff --git a/cypress/integration/form.js b/cypress/integration/form.js index d20750b1d5..f860a742ef 100644 --- a/cypress/integration/form.js +++ b/cypress/integration/form.js @@ -8,7 +8,10 @@ context('Form', () => { }); it('create a new form', () => { cy.visit('/app/todo/new'); - cy.fill_field('description', 'this is a test todo', 'Text Editor'); + cy.get('[data-fieldname="description"] .ql-editor') + .first() + .click() + .type('this is a test todo'); cy.wait(300); cy.get('.page-title').should('contain', 'Not Saved'); cy.intercept({ diff --git a/cypress/integration/form_tab_break.js b/cypress/integration/form_tab_break.js new file mode 100644 index 0000000000..45c3c92084 --- /dev/null +++ b/cypress/integration/form_tab_break.js @@ -0,0 +1,31 @@ +import doctype_with_tab_break from '../fixtures/doctype_with_tab_break'; +const doctype_name = doctype_with_tab_break.name; +context("Form Tab Break", () => { + before(() => { + cy.login(); + cy.visit('/app/website'); + return cy.insert_doc('DocType', doctype_with_tab_break, true); + }); + it("Should switch tab and open correct tabs on validation error", () => { + cy.new_form(doctype_name); + // test tab switch + cy.findByRole("tab", {name: "Tab 2"}).click(); + cy.findByText("Phone"); + cy.findByRole("tab", {name: "Details"}).click(); + cy.findByText("Name"); + + // form should switch to the tab with un-filled mandatory field + cy.fill_field("username", "Test"); + cy.findByRole("button", {name: "Save"}).click(); + cy.findByText("Missing Fields"); + cy.hide_dialog(); + cy.findByText("Phone"); + cy.fill_field("phone", "12345678"); + cy.findByRole("button", {name: "Save"}).click(); + + // After save, first tab should have dashboard + cy.get(".form-tabs > .nav-item").eq(0).click(); + cy.findByText("Connections"); + + }); +}); \ No newline at end of file diff --git a/cypress/integration/list_view.js b/cypress/integration/list_view.js index 298bb20432..ce9e87274b 100644 --- a/cypress/integration/list_view.js +++ b/cypress/integration/list_view.js @@ -6,6 +6,23 @@ context('List View', () => { return frappe.xcall("frappe.tests.ui_test_helpers.setup_workflow"); }); }); + + it('Keep checkbox checked after Bulk Update', () => { + cy.go_to_list('ToDo'); + cy.get('.list-row-container .list-row-checkbox').click({ multiple: true, force: true }); + cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click(); + cy.get('.dropdown-menu li:visible .dropdown-item .menu-item-label[data-label="Edit"]').click(); + + cy.get('.modal-body .form-control[data-fieldname="field"]').first().select('Due Date').wait(200); + cy.fill_field('value', '09-28-21', 'Date'); + + cy.get('.modal-footer .standard-actions .btn-primary').click(); + cy.wait(500); + + cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click(); + cy.get('.list-row-container .list-row-checkbox:checked').should('be.visible'); + }); + it('enables "Actions" button', () => { const actions = ['Approve', 'Reject', 'Edit', 'Export', 'Assign To', 'Apply Assignment Rule', 'Add Tags', 'Print', 'Delete']; cy.go_to_list('ToDo'); @@ -24,10 +41,11 @@ context('List View', () => { }).as('real-time-update'); cy.wrap(elements).contains('Approve').click(); cy.wait(['@bulk-approval', '@real-time-update']); - cy.hide_dialog(); + cy.wait(300); + cy.get_open_dialog().find('.btn-modal-close').click(); + cy.reload(); cy.clear_filters(); cy.get('.list-row-container:visible').should('contain', 'Approved'); }); }); }); - diff --git a/cypress/integration/multi_select_dialog.js b/cypress/integration/multi_select_dialog.js new file mode 100644 index 0000000000..a45fba8d32 --- /dev/null +++ b/cypress/integration/multi_select_dialog.js @@ -0,0 +1,58 @@ +context('MultiSelectDialog', () => { + before(() => { + cy.login(); + cy.visit('/app'); + }); + + function open_multi_select_dialog() { + cy.window().its('frappe').then(frappe => { + new frappe.ui.form.MultiSelectDialog({ + doctype: "Assignment Rule", + target: {}, + setters: { + document_type: null, + priority: null + }, + add_filters_group: 1, + allow_child_item_selection: 1, + child_fieldname: "assignment_days", + child_columns: ["day"] + }); + }); + } + + it('multi select dialog api works', () => { + open_multi_select_dialog(); + cy.get_open_dialog().should('contain', 'Select Assignment Rules'); + }); + + it('checks for filters', () => { + ['search_term', 'document_type', 'priority'].forEach(fieldname => { + cy.get_open_dialog().get(`.frappe-control[data-fieldname="${fieldname}"]`).should('exist'); + }); + + // add_filters_group: 1 should add a filter group + cy.get_open_dialog().get(`.frappe-control[data-fieldname="filter_area"]`).should('exist'); + + }); + + it('checks for child item selection', () => { + cy.get_open_dialog() + .get(`.dt-row-header`).should('not.exist'); + + cy.get_open_dialog() + .get(`.frappe-control[data-fieldname="allow_child_item_selection"]`) + .should('exist') + .click(); + + cy.get_open_dialog() + .get(`.frappe-control[data-fieldname="child_selection_area"]`) + .should('exist'); + + cy.get_open_dialog() + .get(`.dt-row-header`).should('contain', 'Assignment Rule'); + + cy.get_open_dialog() + .get(`.dt-row-header`).should('contain', 'Day'); + }); +}); \ No newline at end of file diff --git a/cypress/integration/navigation.js b/cypress/integration/navigation.js index 7e1426aa46..c4d0638f26 100644 --- a/cypress/integration/navigation.js +++ b/cypress/integration/navigation.js @@ -1,7 +1,6 @@ context('Navigation', () => { before(() => { cy.login(); - cy.visit('/app/website'); }); it('Navigate to route with hash in document name', () => { cy.insert_doc('ToDo', {'__newname': 'ABC#123', 'description': 'Test this', 'ignore_duplicate': true}); @@ -11,4 +10,16 @@ context('Navigation', () => { cy.go('back'); cy.title().should('eq', 'Website'); }); + + it.only('Navigate to previous page after login', () => { + cy.visit('/app/todo'); + cy.findByTitle('To Do').should('be.visible'); + cy.request('/api/method/logout'); + cy.reload(); + cy.get('.btn-primary').contains('Login').click(); + cy.location('pathname').should('eq', '/login'); + cy.login(); + cy.visit('/app'); + cy.location('pathname').should('eq', '/app/todo'); + }); }); diff --git a/cypress/integration/timeline.js b/cypress/integration/timeline.js index 6387485220..66ba0761c4 100644 --- a/cypress/integration/timeline.js +++ b/cypress/integration/timeline.js @@ -11,6 +11,7 @@ context('Timeline', () => { cy.visit('/app/todo'); cy.click_listview_primary_button('Add ToDo'); cy.findByRole('button', {name: 'Edit in full page'}).click(); + cy.findByTitle('New ToDo').should('be.visible'); cy.get('[data-fieldname="description"] .ql-editor').eq(0).type('Test ToDo', {force: true}); cy.wait(200); cy.findByRole('button', {name: 'Save'}).click(); diff --git a/cypress/integration/timeline_email.js b/cypress/integration/timeline_email.js index 82af24e822..dfe80e0019 100644 --- a/cypress/integration/timeline_email.js +++ b/cypress/integration/timeline_email.js @@ -5,14 +5,16 @@ context('Timeline Email', () => { cy.visit('/app/todo'); }); - it('Adding new ToDo, adding email and verifying timeline content for email attachment, deleting attachment and ToDo', () => { - //Adding new ToDo + it('Adding new ToDo', () => { cy.click_listview_primary_button('Add ToDo'); cy.get('.custom-actions:visible > .btn').contains("Edit in full page").click({delay: 500}); cy.fill_field("description", "Test ToDo", "Text Editor"); cy.wait(500); cy.get('.primary-action').contains('Save').click({force: true}); cy.wait(700); + }); + + it('Adding email and verifying timeline content for email attachment, deleting attachment and ToDo', () => { cy.visit('/app/todo'); cy.get('.list-row > .level-left > .list-subject').eq(0).click(); @@ -41,11 +43,13 @@ context('Timeline Email', () => { 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.wait(500); cy.get('.modal-footer:visible > .standard-actions > .btn-primary').contains('Yes').click(); //To check if the removed attachment is shown in the timeline content diff --git a/frappe/__init__.py b/frappe/__init__.py index 38904c68d0..64e445973f 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -235,12 +235,13 @@ def connect_replica(): from frappe.database import get_db user = local.conf.db_name password = local.conf.db_password + port = local.conf.replica_db_port if local.conf.different_credentials_for_replica: user = local.conf.replica_db_name password = local.conf.replica_db_password - local.replica_db = get_db(host=local.conf.replica_host, user=user, password=password) + local.replica_db = get_db(host=local.conf.replica_host, user=user, password=password, port=port) # swap db connections local.primary_db = local.db diff --git a/frappe/build.py b/frappe/build.py index dfbe20f31e..879d5ec432 100644 --- a/frappe/build.py +++ b/frappe/build.py @@ -1,10 +1,11 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE import os import re import json import shutil import subprocess +from subprocess import getoutput from io import StringIO from tempfile import mkdtemp, mktemp from distutils.spawn import find_executable @@ -17,6 +18,8 @@ import psutil from urllib.parse import urlparse from simple_chalk import green from semantic_version import Version +from requests import head +from requests.exceptions import HTTPError timestamps = {} @@ -24,6 +27,12 @@ app_paths = None sites_path = os.path.abspath(os.getcwd()) +class AssetsNotDownloadedError(Exception): + pass + +class AssetsDontExistError(HTTPError): + pass + def download_file(url, prefix): from requests import get @@ -70,81 +79,94 @@ def build_missing_files(): bundle(build_mode, apps="frappe") -def get_assets_link(frappe_head): - from subprocess import getoutput - from requests import head - +def get_assets_link(frappe_head) -> str: tag = getoutput( - r"cd ../apps/frappe && git show-ref --tags -d | grep %s | sed -e 's,.*" - r" refs/tags/,,' -e 's/\^{}//'" - % frappe_head - ) + r"cd ../apps/frappe && git show-ref --tags -d | grep %s | sed -e 's,.*" + r" refs/tags/,,' -e 's/\^{}//'" + % frappe_head + ) if tag: # if tag exists, download assets from github release - url = "https://github.com/frappe/frappe/releases/download/{0}/assets.tar.gz".format(tag) + url = f"https://github.com/frappe/frappe/releases/download/{tag}/assets.tar.gz" else: - url = "http://assets.frappeframework.com/{0}.tar.gz".format(frappe_head) + url = f"http://assets.frappeframework.com/{frappe_head}.tar.gz" if not head(url): - raise ValueError("URL {0} doesn't exist".format(url)) + reference = f"Release {tag}" if tag else f"Commit {frappe_head}" + raise AssetsDontExistError(f"Assets for {reference} don't exist") return url +def fetch_assets(url, frappe_head): + click.secho("Retrieving assets...", fg="yellow") + + prefix = mkdtemp(prefix="frappe-assets-", suffix=frappe_head) + assets_archive = download_file(url, prefix) + + if not assets_archive: + raise AssetsNotDownloadedError(f"Assets could not be retrived from {url}") + + print(f"\n{green('✔')} Downloaded Frappe assets from {url}") + + return assets_archive + + +def setup_assets(assets_archive): + import tarfile + directories_created = set() + + click.secho("\nExtracting assets...\n", fg="yellow") + with tarfile.open(assets_archive) as tar: + for file in tar: + if not file.isdir(): + dest = "." + file.name.replace("./frappe-bench/sites", "") + asset_directory = os.path.dirname(dest) + show = dest.replace("./assets/", "") + + if asset_directory not in directories_created: + if not os.path.exists(asset_directory): + os.makedirs(asset_directory, exist_ok=True) + directories_created.add(asset_directory) + + tar.makefile(file, dest) + print("{0} Restored {1}".format(green('✔'), show)) + + return directories_created + + def download_frappe_assets(verbose=True): """Downloads and sets up Frappe assets if they exist based on the current commit HEAD. Returns True if correctly setup else returns False. """ - from subprocess import getoutput - - assets_setup = False frappe_head = getoutput("cd ../apps/frappe && git rev-parse HEAD") - if frappe_head: + if not frappe_head: + return False + + try: + url = get_assets_link(frappe_head) + assets_archive = fetch_assets(url, frappe_head) + setup_assets(assets_archive) + build_missing_files() + return True + + except AssetsDontExistError as e: + click.secho(str(e), fg="yellow") + + except Exception as e: + # TODO: log traceback in bench.log + click.secho(str(e), fg="red") + + finally: try: - url = get_assets_link(frappe_head) - click.secho("Retrieving assets...", fg="yellow") - prefix = mkdtemp(prefix="frappe-assets-", suffix=frappe_head) - assets_archive = download_file(url, prefix) - print("\n{0} Downloaded Frappe assets from {1}".format(green('✔'), url)) - - if assets_archive: - import tarfile - directories_created = set() - - click.secho("\nExtracting assets...\n", fg="yellow") - with tarfile.open(assets_archive) as tar: - for file in tar: - if not file.isdir(): - dest = "." + file.name.replace("./frappe-bench/sites", "") - asset_directory = os.path.dirname(dest) - show = dest.replace("./assets/", "") - - if asset_directory not in directories_created: - if not os.path.exists(asset_directory): - os.makedirs(asset_directory, exist_ok=True) - directories_created.add(asset_directory) - - tar.makefile(file, dest) - print("{0} Restored {1}".format(green('✔'), show)) - - build_missing_files() - return True - else: - raise + shutil.rmtree(os.path.dirname(assets_archive)) except Exception: - # TODO: log traceback in bench.log - click.secho("An Error occurred while downloading assets...", fg="red") - assets_setup = False - finally: - try: - shutil.rmtree(os.path.dirname(assets_archive)) - except Exception: - pass - - return assets_setup + pass + + return False def symlink(target, link_name, overwrite=False): diff --git a/frappe/commands/__init__.py b/frappe/commands/__init__.py index 6eccdac4fb..82a71ce7b4 100644 --- a/frappe/commands/__init__.py +++ b/frappe/commands/__init__.py @@ -102,9 +102,24 @@ def get_commands(): from .site import commands as site_commands from .translate import commands as translate_commands from .utils import commands as utils_commands - from .redis import commands as redis_commands + from .redis_utils import commands as redis_commands + + clickable_link = ( + "\x1b]8;;https://frappeframework.com/docs\afrappeframework.com\x1b]8;;\a" + ) + all_commands = ( + scheduler_commands + + site_commands + + translate_commands + + utils_commands + + redis_commands + ) + + for command in all_commands: + if not command.help: + command.help = f"Refer to {clickable_link}" + + return all_commands - all_commands = scheduler_commands + site_commands + translate_commands + utils_commands + redis_commands - return list(set(all_commands)) commands = get_commands() diff --git a/frappe/commands/redis.py b/frappe/commands/redis_utils.py similarity index 97% rename from frappe/commands/redis.py rename to frappe/commands/redis_utils.py index 38a46c2142..3556050782 100644 --- a/frappe/commands/redis.py +++ b/frappe/commands/redis_utils.py @@ -3,7 +3,7 @@ import os import click import frappe -from frappe.utils.rq import RedisQueue +from frappe.utils.redis_queue import RedisQueue from frappe.installer import update_site_config @click.command('create-rq-users') diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 9098e31738..2bd3110481 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -67,6 +67,9 @@ def restore(context, sql_file_path, mariadb_root_username=None, mariadb_root_pas validate_database_sql ) + site = get_site(context) + frappe.init(site=site) + force = context.force or force decompressed_file_name = extract_sql_from_archive(sql_file_path) @@ -85,9 +88,6 @@ def restore(context, sql_file_path, mariadb_root_username=None, mariadb_root_pas # check if valid SQL file validate_database_sql(decompressed_file_name, _raise=not force) - site = get_site(context) - frappe.init(site=site) - # dont allow downgrading to older versions of frappe without force if not force and is_downgrade(decompressed_file_name, verbose=True): warn_message = ( @@ -474,7 +474,7 @@ def remove_from_installed_apps(context, app): @click.command('uninstall-app') @click.argument('app') -@click.option('--yes', '-y', help='To bypass confirmation prompt for uninstalling the app', is_flag=True, default=False, multiple=True) +@click.option('--yes', '-y', help='To bypass confirmation prompt for uninstalling the app', is_flag=True, default=False) @click.option('--dry-run', help='List all doctypes that will be deleted', is_flag=True, default=False) @click.option('--no-backup', help='Do not backup the site', is_flag=True, default=False) @click.option('--force', help='Force remove app from site', is_flag=True, default=False) @@ -738,6 +738,131 @@ def build_search_index(context): finally: frappe.destroy() +@click.command('trim-database') +@click.option('--dry-run', is_flag=True, default=False, help='Show what would be deleted') +@click.option('--format', '-f', default='text', type=click.Choice(['json', 'text']), help='Output format') +@click.option('--no-backup', is_flag=True, default=False, help='Do not backup the site') +@pass_context +def trim_database(context, dry_run, format, no_backup): + if not context.sites: + raise SiteNotSpecifiedError + + from frappe.utils.backups import scheduled_backup + + ALL_DATA = {} + + for site in context.sites: + frappe.init(site=site) + frappe.connect() + + TABLES_TO_DROP = [] + STANDARD_TABLES = get_standard_tables() + information_schema = frappe.qb.Schema("information_schema") + table_name = frappe.qb.Field("table_name").as_("name") + + queried_result = frappe.qb.from_( + information_schema.tables + ).select(table_name).where( + information_schema.tables.table_schema == frappe.conf.db_name + ).run() + + database_tables = [x[0] for x in queried_result] + doctype_tables = frappe.get_all("DocType", pluck="name") + + for x in database_tables: + doctype = x.lstrip("tab") + if not (doctype in doctype_tables or x.startswith("__") or x in STANDARD_TABLES): + TABLES_TO_DROP.append(x) + + if not TABLES_TO_DROP: + if format == "text": + click.secho(f"No ghost tables found in {frappe.local.site}...Great!", fg="green") + else: + if not (no_backup or dry_run): + if format == "text": + print(f"Backing Up Tables: {', '.join(TABLES_TO_DROP)}") + + odb = scheduled_backup( + ignore_conf=False, + include_doctypes=",".join(x.lstrip("tab") for x in TABLES_TO_DROP), + ignore_files=True, + force=True, + ) + if format == "text": + odb.print_summary() + print("\nTrimming Database") + + for table in TABLES_TO_DROP: + if format == "text": + print(f"* Dropping Table '{table}'...") + if not dry_run: + frappe.db.sql_ddl(f"drop table `{table}`") + + ALL_DATA[frappe.local.site] = TABLES_TO_DROP + frappe.destroy() + + if format == "json": + import json + print(json.dumps(ALL_DATA, indent=1)) + + +def get_standard_tables(): + import re + + tables = [] + sql_file = os.path.join( + "..", "apps", "frappe", "frappe", "database", frappe.conf.db_type, f'framework_{frappe.conf.db_type}.sql' + ) + content = open(sql_file).read().splitlines() + + for line in content: + table_found = re.search(r"""CREATE TABLE ("|`)(.*)?("|`) \(""", line) + if table_found: + tables.append(table_found.group(2)) + + return tables + +@click.command('trim-tables') +@click.option('--dry-run', is_flag=True, default=False, help='Show what would be deleted') +@click.option('--format', '-f', default='table', type=click.Choice(['json', 'table']), help='Output format') +@click.option('--no-backup', is_flag=True, default=False, help='Do not backup the site') +@pass_context +def trim_tables(context, dry_run, format, no_backup): + if not context.sites: + raise SiteNotSpecifiedError + + from frappe.model.meta import trim_tables + from frappe.utils.backups import scheduled_backup + + for site in context.sites: + frappe.init(site=site) + frappe.connect() + + if not (no_backup or dry_run): + click.secho(f"Taking backup for {frappe.local.site}", fg="green") + odb = scheduled_backup(ignore_files=False, force=True) + odb.print_summary() + + try: + trimmed_data = trim_tables(dry_run=dry_run, quiet=format == 'json') + + if format == 'table' and not dry_run: + click.secho(f"The following data have been removed from {frappe.local.site}", fg='green') + + handle_data(trimmed_data, format=format) + finally: + frappe.destroy() + +def handle_data(data: dict, format='json'): + if format == 'json': + import json + print(json.dumps({frappe.local.site: data}, indent=1, sort_keys=True)) + else: + from frappe.utils.commands import render_table + data = [["DocType", "Fields"]] + [[table, ", ".join(columns)] for table, columns in data.items()] + render_table(data) + + commands = [ add_system_manager, backup, @@ -766,5 +891,7 @@ commands = [ add_to_hosts, start_ngrok, build_search_index, - partial_restore + partial_restore, + trim_tables, + trim_database, ] diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index bef558c859..74af28e3ff 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -408,20 +408,47 @@ def bulk_rename(context, doctype, path): frappe.destroy() +@click.command('db-console') +@pass_context +def database(context): + """ + Enter into the Database console for given site. + """ + site = get_site(context) + if not site: + raise SiteNotSpecifiedError + frappe.init(site=site) + if not frappe.conf.db_type or frappe.conf.db_type == "mariadb": + _mariadb() + elif frappe.conf.db_type == "postgres": + _psql() + + @click.command('mariadb') @pass_context def mariadb(context): """ Enter into mariadb console for a given site. """ - import os - site = get_site(context) if not site: raise SiteNotSpecifiedError frappe.init(site=site) + _mariadb() + + +@click.command('postgres') +@pass_context +def postgres(context): + """ + Enter into postgres console for a given site. + """ + site = get_site(context) + frappe.init(site=site) + _psql() - # This is assuming you're within the bench instance. + +def _mariadb(): mysql = find_executable('mysql') os.execv(mysql, [ mysql, @@ -434,15 +461,7 @@ def mariadb(context): "-A"]) -@click.command('postgres') -@pass_context -def postgres(context): - """ - Enter into postgres console for a given site. - """ - site = get_site(context) - frappe.init(site=site) - # This is assuming you're within the bench instance. +def _psql(): psql = find_executable('psql') subprocess.run([ psql, '-d', frappe.conf.db_name]) @@ -525,6 +544,74 @@ def console(context, autoreload=False): terminal() +@click.command('transform-database', help="Change tables' internal settings changing engine and row formats") +@click.option('--table', required=True, help="Comma separated name of tables to convert. To convert all tables, pass 'all'") +@click.option('--engine', default=None, type=click.Choice(["InnoDB", "MyISAM"]), help="Choice of storage engine for said table(s)") +@click.option('--row_format', default=None, type=click.Choice(["DYNAMIC", "COMPACT", "REDUNDANT", "COMPRESSED"]), help="Set ROW_FORMAT parameter for said table(s)") +@click.option('--failfast', is_flag=True, default=False, help="Exit on first failure occurred") +@pass_context +def transform_database(context, table, engine, row_format, failfast): + "Transform site database through given parameters" + site = get_site(context) + check_table = [] + add_line = False + skipped = 0 + frappe.init(site=site) + + if frappe.conf.db_type and frappe.conf.db_type != "mariadb": + click.secho("This command only has support for MariaDB databases at this point", fg="yellow") + sys.exit(1) + + if not (engine or row_format): + click.secho("Values for `--engine` or `--row_format` must be set") + sys.exit(1) + + frappe.connect() + + if table == "all": + information_schema = frappe.qb.Schema("information_schema") + queried_tables = frappe.qb.from_( + information_schema.tables + ).select("table_name").where( + (information_schema.tables.row_format != row_format) + & (information_schema.tables.table_schema == frappe.conf.db_name) + ).run() + tables = [x[0] for x in queried_tables] + else: + tables = [x.strip() for x in table.split(",")] + + total = len(tables) + + for current, table in enumerate(tables): + values_to_set = "" + if engine: + values_to_set += f" ENGINE={engine}" + if row_format: + values_to_set += f" ROW_FORMAT={row_format}" + + try: + frappe.db.sql(f"ALTER TABLE `{table}`{values_to_set}") + update_progress_bar("Updating table schema", current - skipped, total) + add_line = True + + except Exception as e: + check_table.append([table, e.args]) + skipped += 1 + + if failfast: + break + + if add_line: + print() + + for errored_table in check_table: + table, err = errored_table + err_msg = f"{table}: ERROR {err[0]}: {err[1]}" + click.secho(err_msg, fg="yellow") + + frappe.destroy() + + @click.command('run-tests') @click.option('--app', help="For App") @click.option('--doctype', help="For DocType") @@ -814,6 +901,8 @@ commands = [ build, clear_cache, clear_website_cache, + database, + transform_database, jupyter, console, destroy_all_sessions, diff --git a/frappe/contacts/address_and_contact.py b/frappe/contacts/address_and_contact.py index 6b71ec50f9..79c3358665 100644 --- a/frappe/contacts/address_and_contact.py +++ b/frappe/contacts/address_and_contact.py @@ -178,4 +178,4 @@ def set_link_title(doc): for link in doc.links: if not link.link_title: linked_doc = frappe.get_doc(link.link_doctype, link.link_name) - link.link_title = linked_doc.get("title_field") or linked_doc.get("name") + link.link_title = linked_doc.get_title() or link.link_name diff --git a/frappe/contacts/doctype/address/address.py b/frappe/contacts/doctype/address/address.py index 5f0619d170..5d0ed18d5f 100644 --- a/frappe/contacts/doctype/address/address.py +++ b/frappe/contacts/doctype/address/address.py @@ -65,7 +65,7 @@ class Address(Document): def has_link(self, doctype, name): for link in self.links: - if link.link_doctype==doctype and link.link_name== name: + if link.link_doctype == doctype and link.link_name == name: return True def has_common_link(self, doc): diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index a1aa8408bf..dfb9ff2973 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -47,14 +47,14 @@ class Contact(Document): def get_link_for(self, link_doctype): '''Return the link name, if exists for the given link DocType''' for link in self.links: - if link.link_doctype==link_doctype: + if link.link_doctype == link_doctype: return link.link_name return None def has_link(self, doctype, name): for link in self.links: - if link.link_doctype==doctype and link.link_name== name: + if link.link_doctype == doctype and link.link_name == name: return True def has_common_link(self, doc): diff --git a/frappe/core/doctype/access_log/access_log.py b/frappe/core/doctype/access_log/access_log.py index d93da02d25..f631353d56 100644 --- a/frappe/core/doctype/access_log/access_log.py +++ b/frappe/core/doctype/access_log/access_log.py @@ -1,6 +1,7 @@ -# Copyright (c) 2019, Frappe Technologies and contributors +# Copyright (c) 2021, Frappe Technologies and contributors # License: MIT. See LICENSE import frappe +from tenacity import retry, retry_if_exception_type, stop_after_attempt from frappe.model.document import Document @@ -10,25 +11,40 @@ class AccessLog(Document): @frappe.whitelist() @frappe.write_only() -def make_access_log(doctype=None, document=None, method=None, file_type=None, - report_name=None, filters=None, page=None, columns=None): +@retry( + stop=stop_after_attempt(3), retry=retry_if_exception_type(frappe.DuplicateEntryError) +) +def make_access_log( + doctype=None, + document=None, + method=None, + file_type=None, + report_name=None, + filters=None, + page=None, + columns=None, +): user = frappe.session.user + in_request = frappe.request and frappe.request.method == "GET" - doc = frappe.get_doc({ - 'doctype': 'Access Log', - 'user': user, - 'export_from': doctype, - 'reference_document': document, - 'file_type': file_type, - 'report_name': report_name, - 'page': page, - 'method': method, - 'filters': frappe.utils.cstr(filters) if filters else None, - 'columns': columns - }) + doc = frappe.get_doc( + { + "doctype": "Access Log", + "user": user, + "export_from": doctype, + "reference_document": document, + "file_type": file_type, + "report_name": report_name, + "page": page, + "method": method, + "filters": frappe.utils.cstr(filters) if filters else None, + "columns": columns, + } + ) doc.insert(ignore_permissions=True) # `frappe.db.commit` added because insert doesnt `commit` when called in GET requests like `printview` - if frappe.request and frappe.request.method == 'GET': + # dont commit in test mode + if not frappe.flags.in_test or in_request: frappe.db.commit() diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index b240d29446..6910d615d3 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -1,544 +1,543 @@ { - "actions": [], - "autoname": "hash", - "creation": "2013-02-22 01:27:33", - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "label_and_type", - "label", - "fieldtype", - "fieldname", - "precision", - "length", - "non_negative", - "hide_days", - "hide_seconds", - "reqd", - "search_index", - "column_break_18", - "options", - "defaults_section", - "default", - "column_break_6", - "fetch_from", - "fetch_if_empty", - "visibility_section", - "hidden", - "bold", - "allow_in_quick_entry", - "translatable", - "print_hide", - "print_hide_if_no_value", - "report_hide", - "column_break_28", - "depends_on", - "collapsible", - "collapsible_depends_on", - "hide_border", - "list__search_settings_section", - "in_list_view", - "in_standard_filter", - "in_preview", - "column_break_35", - "in_filter", - "in_global_search", - "permissions", - "read_only", - "allow_on_submit", - "ignore_user_permissions", - "allow_bulk_edit", - "column_break_13", - "permlevel", - "ignore_xss_filter", - "constraints_section", - "unique", - "no_copy", - "set_only_once", - "remember_last_selected_value", - "column_break_38", - "mandatory_depends_on", - "read_only_depends_on", - "display", - "print_width", - "width", - "max_height", - "columns", - "column_break_22", - "description", - "oldfieldname", - "oldfieldtype" - ], - "fields": [ - { - "fieldname": "label_and_type", - "fieldtype": "Section Break" - }, - { - "bold": 1, - "fieldname": "label", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Label", - "oldfieldname": "label", - "oldfieldtype": "Data", - "print_width": "163", - "search_index": 1, - "width": "163" - }, - { - "bold": 1, - "default": "Data", - "fieldname": "fieldtype", - "fieldtype": "Select", - "in_list_view": 1, - "label": "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", - "reqd": 1, - "search_index": 1 - }, - { - "bold": 1, - "fieldname": "fieldname", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Name", - "oldfieldname": "fieldname", - "oldfieldtype": "Data", - "search_index": 1 - }, - { - "default": "0", - "depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)", - "fieldname": "reqd", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Mandatory", - "oldfieldname": "reqd", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "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", - "print_hide": 1 - }, - { - "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" - }, - { - "default": "0", - "fieldname": "search_index", - "fieldtype": "Check", - "label": "Index", - "oldfieldname": "search_index", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "in_list_view", - "fieldtype": "Check", - "label": "In List View", - "print_width": "70px", - "width": "70px" - }, - { - "default": "0", - "fieldname": "in_standard_filter", - "fieldtype": "Check", - "label": "In List 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", - "depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);", - "fieldname": "in_preview", - "fieldtype": "Check", - "label": "In Preview" - }, - { - "default": "0", - "fieldname": "allow_in_quick_entry", - "fieldtype": "Check", - "label": "Allow in Quick Entry" - }, - { - "default": "0", - "fieldname": "bold", - "fieldtype": "Check", - "label": "Bold" - }, - { - "default": "0", - "depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)", - "fieldname": "translatable", - "fieldtype": "Check", - "label": "Translatable" - }, - { - "default": "0", - "depends_on": "eval:doc.fieldtype===\"Section Break\"", - "fieldname": "collapsible", - "fieldtype": "Check", - "label": "Collapsible", - "length": 255 - }, - { - "depends_on": "eval:doc.fieldtype==\"Section Break\" && doc.collapsible", - "fieldname": "collapsible_depends_on", - "fieldtype": "Code", - "label": "Collapsible Depends On (JS)", - "max_height": "3rem", - "options": "JS" - }, - { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, - { - "description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.", - "fieldname": "options", - "fieldtype": "Small Text", - "in_list_view": 1, - "label": "Options", - "oldfieldname": "options", - "oldfieldtype": "Text" - }, - { - "fieldname": "default", - "fieldtype": "Small Text", - "label": "Default", - "max_height": "3rem", - "oldfieldname": "default", - "oldfieldtype": "Text" - }, - { - "fieldname": "fetch_from", - "fieldtype": "Small Text", - "label": "Fetch From" - }, - { - "default": "0", - "fieldname": "fetch_if_empty", - "fieldtype": "Check", - "label": "Fetch only if value is not set" - }, - { - "fieldname": "permissions", - "fieldtype": "Section Break", - "label": "Permissions" - }, - { - "fieldname": "depends_on", - "fieldtype": "Code", - "label": "Display Depends On (JS)", - "length": 255, - "max_height": "3rem", - "oldfieldname": "depends_on", - "oldfieldtype": "Data", - "options": "JS" - }, - { - "default": "0", - "fieldname": "hidden", - "fieldtype": "Check", - "label": "Hidden", - "oldfieldname": "hidden", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "read_only", - "fieldtype": "Check", - "label": "Read Only", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "unique", - "fieldtype": "Check", - "label": "Unique" - }, - { - "default": "0", - "fieldname": "set_only_once", - "fieldtype": "Check", - "label": "Set only once" - }, - { - "default": "0", - "depends_on": "eval: doc.fieldtype == \"Table\"", - "fieldname": "allow_bulk_edit", - "fieldtype": "Check", - "label": "Allow Bulk Edit" - }, - { - "fieldname": "column_break_13", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "permlevel", - "fieldtype": "Int", - "label": "Perm Level", - "oldfieldname": "permlevel", - "oldfieldtype": "Int", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "ignore_user_permissions", - "fieldtype": "Check", - "label": "Ignore User Permissions" - }, - { - "default": "0", - "depends_on": "eval: parent.is_submittable", - "fieldname": "allow_on_submit", - "fieldtype": "Check", - "label": "Allow on Submit", - "oldfieldname": "allow_on_submit", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "report_hide", - "fieldtype": "Check", - "label": "Report Hide", - "oldfieldname": "report_hide", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "depends_on": "eval:(doc.fieldtype == 'Link')", - "fieldname": "remember_last_selected_value", - "fieldtype": "Check", - "label": "Remember Last Selected Value" - }, - { - "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" - }, - { - "fieldname": "display", - "fieldtype": "Section Break", - "label": "Display" - }, - { - "default": "0", - "fieldname": "in_filter", - "fieldtype": "Check", - "label": "In Filter", - "oldfieldname": "in_filter", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "no_copy", - "fieldtype": "Check", - "label": "No Copy", - "oldfieldname": "no_copy", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "print_hide", - "fieldtype": "Check", - "label": "Print Hide", - "oldfieldname": "print_hide", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "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", - "label": "Print Width", - "length": 10 - }, - { - "fieldname": "width", - "fieldtype": "Data", - "label": "Width", - "length": 10, - "oldfieldname": "width", - "oldfieldtype": "Data", - "print_width": "50px", - "width": "50px" - }, - { - "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": "column_break_22", - "fieldtype": "Column Break" - }, - { - "fieldname": "description", - "fieldtype": "Small Text", - "in_list_view": 1, - "label": "Description", - "oldfieldname": "description", - "oldfieldtype": "Text", - "print_width": "300px", - "width": "300px" - }, - { - "fieldname": "oldfieldname", - "fieldtype": "Data", - "hidden": 1, - "oldfieldname": "oldfieldname", - "oldfieldtype": "Data" - }, - { - "fieldname": "oldfieldtype", - "fieldtype": "Data", - "hidden": 1, - "oldfieldname": "oldfieldtype", - "oldfieldtype": "Data" - }, - { - "fieldname": "mandatory_depends_on", - "fieldtype": "Code", - "label": "Mandatory Depends On (JS)", - "max_height": "3rem", - "options": "JS" - }, - { - "fieldname": "read_only_depends_on", - "fieldtype": "Code", - "label": "Read Only Depends On (JS)", - "max_height": "3rem", - "options": "JS" - }, - { - "fieldname": "column_break_38", - "fieldtype": "Column Break" - }, - { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Duration'", - "fieldname": "hide_days", - "fieldtype": "Check", - "label": "Hide Days" - }, - { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Duration'", - "fieldname": "hide_seconds", - "fieldtype": "Check", - "label": "Hide Seconds" - }, - { - "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": "column_break_18", - "fieldtype": "Column Break" - }, - { - "fieldname": "defaults_section", - "fieldtype": "Section Break", - "label": "Defaults", - "max_height": "2rem" - }, - { - "fieldname": "visibility_section", - "fieldtype": "Section Break", - "label": "Visibility" - }, - { - "fieldname": "column_break_28", - "fieldtype": "Column Break" - }, - { - "fieldname": "constraints_section", - "fieldtype": "Section Break", - "label": "Constraints" - }, - { - "fieldname": "max_height", - "fieldtype": "Data", - "label": "Max Height", - "length": 10 - }, - { - "fieldname": "list__search_settings_section", - "fieldtype": "Section Break", - "label": "List / Search Settings" - }, - { - "fieldname": "column_break_35", - "fieldtype": "Column Break" - } - ], - "idx": 1, - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-09-04 19:41:53.684094", - "modified_by": "Administrator", - "module": "Core", - "name": "DocField", - "naming_rule": "Random", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "ASC" + "actions": [], + "autoname": "hash", + "creation": "2013-02-22 01:27:33", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "label_and_type", + "label", + "fieldtype", + "fieldname", + "precision", + "length", + "non_negative", + "hide_days", + "hide_seconds", + "reqd", + "search_index", + "column_break_18", + "options", + "defaults_section", + "default", + "column_break_6", + "fetch_from", + "fetch_if_empty", + "visibility_section", + "hidden", + "bold", + "allow_in_quick_entry", + "translatable", + "print_hide", + "print_hide_if_no_value", + "report_hide", + "column_break_28", + "depends_on", + "collapsible", + "collapsible_depends_on", + "hide_border", + "list__search_settings_section", + "in_list_view", + "in_standard_filter", + "in_preview", + "column_break_35", + "in_filter", + "in_global_search", + "permissions", + "read_only", + "allow_on_submit", + "ignore_user_permissions", + "allow_bulk_edit", + "column_break_13", + "permlevel", + "ignore_xss_filter", + "constraints_section", + "unique", + "no_copy", + "set_only_once", + "remember_last_selected_value", + "column_break_38", + "mandatory_depends_on", + "read_only_depends_on", + "display", + "print_width", + "width", + "max_height", + "columns", + "column_break_22", + "description", + "oldfieldname", + "oldfieldtype" + ], + "fields": [{ + "fieldname": "label_and_type", + "fieldtype": "Section Break" + }, + { + "bold": 1, + "fieldname": "label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Label", + "oldfieldname": "label", + "oldfieldtype": "Data", + "print_width": "163", + "search_index": 1, + "width": "163" + }, + { + "bold": 1, + "default": "Data", + "fieldname": "fieldtype", + "fieldtype": "Select", + "in_list_view": 1, + "label": "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, + "search_index": 1 + }, + { + "bold": 1, + "fieldname": "fieldname", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Name", + "oldfieldname": "fieldname", + "oldfieldtype": "Data", + "search_index": 1 + }, + { + "default": "0", + "depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)", + "fieldname": "reqd", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Mandatory", + "oldfieldname": "reqd", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "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", + "print_hide": 1 + }, + { + "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" + }, + { + "default": "0", + "fieldname": "search_index", + "fieldtype": "Check", + "label": "Index", + "oldfieldname": "search_index", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "in_list_view", + "fieldtype": "Check", + "label": "In List View", + "print_width": "70px", + "width": "70px" + }, + { + "default": "0", + "fieldname": "in_standard_filter", + "fieldtype": "Check", + "label": "In List 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", + "depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);", + "fieldname": "in_preview", + "fieldtype": "Check", + "label": "In Preview" + }, + { + "default": "0", + "fieldname": "allow_in_quick_entry", + "fieldtype": "Check", + "label": "Allow in Quick Entry" + }, + { + "default": "0", + "fieldname": "bold", + "fieldtype": "Check", + "label": "Bold" + }, + { + "default": "0", + "depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)", + "fieldname": "translatable", + "fieldtype": "Check", + "label": "Translatable" + }, + { + "default": "0", + "depends_on": "eval:doc.fieldtype===\"Section Break\"", + "fieldname": "collapsible", + "fieldtype": "Check", + "label": "Collapsible", + "length": 255 + }, + { + "depends_on": "eval:doc.fieldtype==\"Section Break\" && doc.collapsible", + "fieldname": "collapsible_depends_on", + "fieldtype": "Code", + "label": "Collapsible Depends On (JS)", + "max_height": "3rem", + "options": "JS" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.", + "fieldname": "options", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Options", + "oldfieldname": "options", + "oldfieldtype": "Text" + }, + { + "fieldname": "default", + "fieldtype": "Small Text", + "label": "Default", + "max_height": "3rem", + "oldfieldname": "default", + "oldfieldtype": "Text" + }, + { + "fieldname": "fetch_from", + "fieldtype": "Small Text", + "label": "Fetch From" + }, + { + "default": "0", + "fieldname": "fetch_if_empty", + "fieldtype": "Check", + "label": "Fetch only if value is not set" + }, + { + "fieldname": "permissions", + "fieldtype": "Section Break", + "label": "Permissions" + }, + { + "fieldname": "depends_on", + "fieldtype": "Code", + "label": "Display Depends On (JS)", + "length": 255, + "max_height": "3rem", + "oldfieldname": "depends_on", + "oldfieldtype": "Data", + "options": "JS" + }, + { + "default": "0", + "fieldname": "hidden", + "fieldtype": "Check", + "label": "Hidden", + "oldfieldname": "hidden", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "read_only", + "fieldtype": "Check", + "label": "Read Only", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "unique", + "fieldtype": "Check", + "label": "Unique" + }, + { + "default": "0", + "fieldname": "set_only_once", + "fieldtype": "Check", + "label": "Set only once" + }, + { + "default": "0", + "depends_on": "eval: doc.fieldtype == \"Table\"", + "fieldname": "allow_bulk_edit", + "fieldtype": "Check", + "label": "Allow Bulk Edit" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "permlevel", + "fieldtype": "Int", + "label": "Perm Level", + "oldfieldname": "permlevel", + "oldfieldtype": "Int", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "ignore_user_permissions", + "fieldtype": "Check", + "label": "Ignore User Permissions" + }, + { + "default": "0", + "depends_on": "eval: parent.is_submittable", + "fieldname": "allow_on_submit", + "fieldtype": "Check", + "label": "Allow on Submit", + "oldfieldname": "allow_on_submit", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "report_hide", + "fieldtype": "Check", + "label": "Report Hide", + "oldfieldname": "report_hide", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "depends_on": "eval:(doc.fieldtype == 'Link')", + "fieldname": "remember_last_selected_value", + "fieldtype": "Check", + "label": "Remember Last Selected Value" + }, + { + "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" + }, + { + "fieldname": "display", + "fieldtype": "Section Break", + "label": "Display" + }, + { + "default": "0", + "fieldname": "in_filter", + "fieldtype": "Check", + "label": "In Filter", + "oldfieldname": "in_filter", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "no_copy", + "fieldtype": "Check", + "label": "No Copy", + "oldfieldname": "no_copy", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "print_hide", + "fieldtype": "Check", + "label": "Print Hide", + "oldfieldname": "print_hide", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "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", + "label": "Print Width", + "length": 10 + }, + { + "fieldname": "width", + "fieldtype": "Data", + "label": "Width", + "length": 10, + "oldfieldname": "width", + "oldfieldtype": "Data", + "print_width": "50px", + "width": "50px" + }, + { + "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": "column_break_22", + "fieldtype": "Column Break" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "print_width": "300px", + "width": "300px" + }, + { + "fieldname": "oldfieldname", + "fieldtype": "Data", + "hidden": 1, + "oldfieldname": "oldfieldname", + "oldfieldtype": "Data" + }, + { + "fieldname": "oldfieldtype", + "fieldtype": "Data", + "hidden": 1, + "oldfieldname": "oldfieldtype", + "oldfieldtype": "Data" + }, + { + "fieldname": "mandatory_depends_on", + "fieldtype": "Code", + "label": "Mandatory Depends On (JS)", + "max_height": "3rem", + "options": "JS" + }, + { + "fieldname": "read_only_depends_on", + "fieldtype": "Code", + "label": "Read Only Depends On (JS)", + "max_height": "3rem", + "options": "JS" + }, + { + "fieldname": "column_break_38", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval:doc.fieldtype=='Duration'", + "fieldname": "hide_days", + "fieldtype": "Check", + "label": "Hide Days" + }, + { + "default": "0", + "depends_on": "eval:doc.fieldtype=='Duration'", + "fieldname": "hide_seconds", + "fieldtype": "Check", + "label": "Hide Seconds" + }, + { + "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": "column_break_18", + "fieldtype": "Column Break" + }, + { + "fieldname": "defaults_section", + "fieldtype": "Section Break", + "label": "Defaults", + "max_height": "2rem" + }, + { + "fieldname": "visibility_section", + "fieldtype": "Section Break", + "label": "Visibility" + }, + { + "fieldname": "column_break_28", + "fieldtype": "Column Break" + }, + { + "fieldname": "constraints_section", + "fieldtype": "Section Break", + "label": "Constraints" + }, + { + "fieldname": "max_height", + "fieldtype": "Data", + "label": "Max Height", + "length": 10 + }, + { + "fieldname": "list__search_settings_section", + "fieldtype": "Section Break", + "label": "List / Search Settings" + }, + { + "fieldname": "column_break_35", + "fieldtype": "Column Break" + } + ], + "idx": 1, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-09-04 19:41:23.684094", + "modified_by": "Administrator", + "module": "Core", + "name": "DocField", + "naming_rule": "Random", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "ASC" } \ No newline at end of file diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 9bf21690fc..8f8a8ed287 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -274,6 +274,8 @@ class DocType(Document): d.fieldname = d.fieldname + '_section' elif d.fieldtype=='Column Break': d.fieldname = d.fieldname + '_column' + elif d.fieldtype=='Tab Break': + d.fieldname = d.fieldname + '_tab' else: d.fieldname = d.fieldtype.lower().replace(" ","_") + "_" + str(d.idx) else: diff --git a/frappe/core/doctype/document_naming_rule/document_naming_rule.json b/frappe/core/doctype/document_naming_rule/document_naming_rule.json index 4a88e3be6e..4e6f3f3fd1 100644 --- a/frappe/core/doctype/document_naming_rule/document_naming_rule.json +++ b/frappe/core/doctype/document_naming_rule/document_naming_rule.json @@ -41,6 +41,7 @@ "fieldname": "counter", "fieldtype": "Int", "label": "Counter", + "no_copy": 1, "read_only": 1 }, { @@ -79,7 +80,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-11-04 14:38:14.836056", + "modified": "2021-09-13 20:07:47.617615", "modified_by": "Administrator", "module": "Core", "name": "Document Naming Rule", diff --git a/frappe/core/doctype/sms_settings/sms_settings.json b/frappe/core/doctype/sms_settings/sms_settings.json index 073fb88bc7..d29949af45 100755 --- a/frappe/core/doctype/sms_settings/sms_settings.json +++ b/frappe/core/doctype/sms_settings/sms_settings.json @@ -1,238 +1,80 @@ { - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-01-10 16:34:24", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 0, + "actions": [], + "allow_copy": 1, + "creation": "2013-01-10 16:34:24", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "sms_gateway_url", + "message_parameter", + "receiver_parameter", + "static_parameters_section", + "parameters", + "use_post" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Eg. smsgateway.com/api/send_sms.cgi", - "fieldname": "sms_gateway_url", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "SMS Gateway URL", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "description": "Eg. smsgateway.com/api/send_sms.cgi", + "fieldname": "sms_gateway_url", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "SMS Gateway URL", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Enter url parameter for message", - "fieldname": "message_parameter", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Message Parameter", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "description": "Enter url parameter for message", + "fieldname": "message_parameter", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Message Parameter", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Enter url parameter for receiver nos", - "fieldname": "receiver_parameter", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Receiver Parameter", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "description": "Enter url parameter for receiver nos", + "fieldname": "receiver_parameter", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Receiver Parameter", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "static_parameters_section", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "static_parameters_section", + "fieldtype": "Column Break", "width": "50%" - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Enter static url parameters here (Eg. sender=ERPNext, username=ERPNext, password=1234 etc.)", - "fieldname": "parameters", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Static Parameters", - "length": 0, - "no_copy": 0, - "options": "SMS Parameter", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "description": "Enter static url parameters here (Eg. sender=ERPNext, username=ERPNext, password=1234 etc.)", + "fieldname": "parameters", + "fieldtype": "Table", + "label": "Static Parameters", + "options": "SMS Parameter" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "use_post", - "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": "Use POST", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "default": "0", + "fieldname": "use_post", + "fieldtype": "Check", + "label": "Use POST" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-cog", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2021-03-02 18:06:00.868688", - "modified_by": "Administrator", - "module": "Core", - "name": "SMS Settings", - "owner": "Administrator", + ], + "icon": "fa fa-cog", + "idx": 1, + "issingle": 1, + "links": [], + "modified": "2021-09-21 19:45:26.809793", + "modified_by": "Administrator", + "module": "Core", + "name": "SMS Settings", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "read": 1, + "role": "System Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "track_changes": 1, - "track_seen": 0 -} + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 147f4ddfee..e4b94cdbb6 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -788,7 +788,7 @@ def sign_up(email, full_name, redirect_to): return 2, _("Please ask your administrator to verify your sign-up") @frappe.whitelist(allow_guest=True) -@rate_limit(key='user', limit=get_password_reset_limit, seconds = 24*60*60, methods=['POST']) +@rate_limit(limit=get_password_reset_limit, seconds = 24*60*60, methods=['POST']) def reset_password(user): if user=="Administrator": return 'not allowed' diff --git a/frappe/core/doctype/version/version.css b/frappe/core/doctype/version/version.css deleted file mode 100644 index 769b352585..0000000000 --- a/frappe/core/doctype/version/version.css +++ /dev/null @@ -1,21 +0,0 @@ -.version-info { - overflow: auto; -} - -.version-info pre { - border: 0px; - margin: 0px; - background-color: inherit; -} - -.version-info .table { - background-color: inherit; -} - -.version-info .success { - background-color: #dff0d8 !important; -} - -.version-info .danger { - background-color: #f2dede !important; -} diff --git a/frappe/core/doctype/version/version.py b/frappe/core/doctype/version/version.py index 08d0456dff..fcb558650a 100644 --- a/frappe/core/doctype/version/version.py +++ b/frappe/core/doctype/version/version.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE -# License: MIT. See LICENSE - import frappe, json from frappe.model.document import Document diff --git a/frappe/core/page/background_jobs/background_jobs.py b/frappe/core/page/background_jobs/background_jobs.py index 5d9bb815da..4d9deca526 100644 --- a/frappe/core/page/background_jobs/background_jobs.py +++ b/frappe/core/page/background_jobs/background_jobs.py @@ -67,7 +67,8 @@ def get_info(show_failed=False) -> List[Dict]: fail_registry = queue.failed_job_registry for job_id in fail_registry.get_job_ids(): job = queue.fetch_job(job_id) - add_job(job, queue.name) + if job: + add_job(job, queue.name) return jobs diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index a8b1fb0e23..235f11aad8 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -1,460 +1,458 @@ { - "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", - "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:22.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", + "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 } \ No newline at end of file diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index 61fc4486bd..bf606701da 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -18,7 +18,7 @@ class CustomField(Document): if not self.fieldname: label = self.label if not label: - if self.fieldtype in ["Section Break", "Column Break"]: + if self.fieldtype in ["Section Break", "Column Break", "Tab Break"]: label = self.fieldtype + "_" + str(self.idx) else: frappe.throw(_("Label is mandatory")) diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index 0a456b1026..986b99a7af 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -82,7 +82,7 @@ "label": "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\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime", + "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\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nTab Break", "reqd": 1, "search_index": 1 }, @@ -428,7 +428,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-07-10 21:57:24.479749", + "modified": "2021-07-11 21:57:24.479749", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", diff --git a/frappe/custom/doctype/property_setter/property_setter.py b/frappe/custom/doctype/property_setter/property_setter.py index 7b95408060..d71b7b0021 100644 --- a/frappe/custom/doctype/property_setter/property_setter.py +++ b/frappe/custom/doctype/property_setter/property_setter.py @@ -34,7 +34,7 @@ class PropertySetter(Document): fields=['fieldname', 'label', 'fieldtype'], filters={ 'parent': dt, - 'fieldtype': ['not in', ('Section Break', 'Column Break', 'HTML', 'Read Only', 'Fold') + frappe.model.table_fields], + 'fieldtype': ['not in', ('Section Break', 'Column Break', 'Tab Break', 'HTML', 'Read Only', 'Fold') + frappe.model.table_fields], 'fieldname': ['!=', ''] }, order_by='label asc', diff --git a/frappe/data/sample_site_config.json b/frappe/data/sample_site_config.json deleted file mode 100644 index 715cd7b9fa..0000000000 --- a/frappe/data/sample_site_config.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "db_name": "testdb", - "db_password": "password", - "mute_emails": true, - - "limits": { - "emails": 1500, - "space": 0.157, - "expiry": "2016-07-25", - "users": 1 - }, - - "developer_mode": 1, - "auto_cache_clear": true, - "disable_website_cache": true, - "max_file_size": 1000000, - - "mail_server": "localhost", - "mail_login": null, - "mail_password": null, - "mail_port": 25, - "use_ssl": 0, - "auto_email_id": "hello@example.com", - - "google_analytics_id": "google_analytics_id", - "google_analytics_anonymize_ip": 1, - - "google_login": { - "client_id": "google_client_id", - "client_secret": "google_client_secret" - }, - "github_login": { - "client_id": "github_client_id", - "client_secret": "github_client_secret" - }, - "facebook_login": { - "client_id": "facebook_client_id", - "client_secret": "facebook_client_secret" - }, - - "celery_broker": "redis://localhost", - "celery_result_backend": null, - "scheduler_interval": 300, - "celery_queue_per_site": true -} diff --git a/frappe/database/database.py b/frappe/database/database.py index 84bfa76cd7..0ee11ea075 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -332,7 +332,7 @@ class Database(object): values[key] = value if isinstance(value, (list, tuple)): # value is a tuple like ("!=", 0) - _operator = value[0] + _operator = value[0].lower() values[key] = value[1] if isinstance(value[1], (tuple, list)): # value is a list in tuple ("in", ("A", "B")) @@ -919,13 +919,13 @@ class Database(object): WHERE table_name = 'tab{0}' AND column_name = '{1}' '''.format(doctype, column))[0][0] def has_index(self, table_name, index_name): - pass + raise NotImplementedError def add_index(self, doctype, fields, index_name=None): - pass + raise NotImplementedError def add_unique(self, doctype, fields, constraint_name=None): - pass + raise NotImplementedError @staticmethod def get_index_name(fields): @@ -951,7 +951,7 @@ class Database(object): def escape(s, percent=True): """Excape quotes and percent in given string.""" # implemented in specific class - pass + raise NotImplementedError @staticmethod def is_column_missing(e): diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index 71acefe17c..2f6d640743 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -22,11 +22,11 @@ class MariaDBDatabase(Database): def setup_type_map(self): self.db_type = 'mariadb' self.type_map = { - 'Currency': ('decimal', '18,6'), + 'Currency': ('decimal', '21,9'), 'Int': ('int', '11'), 'Long Int': ('bigint', '20'), - 'Float': ('decimal', '18,6'), - 'Percent': ('decimal', '18,6'), + 'Float': ('decimal', '21,9'), + 'Percent': ('decimal', '21,9'), 'Check': ('int', '1'), 'Small Text': ('text', ''), 'Long Text': ('longtext', ''), @@ -51,7 +51,7 @@ class MariaDBDatabase(Database): 'Color': ('varchar', self.VARCHAR_LEN), 'Barcode': ('longtext', ''), 'Geolocation': ('longtext', ''), - 'Duration': ('decimal', '18,6'), + 'Duration': ('decimal', '21,9'), 'Icon': ('varchar', self.VARCHAR_LEN) } @@ -135,8 +135,8 @@ class MariaDBDatabase(Database): table_name = get_table_name(doctype) return self.sql(f"DESC `{table_name}`") - def change_column_type(self, table: str, column: str, type: str) -> Union[List, Tuple]: - table_name = get_table_name(table) + def change_column_type(self, doctype: str, column: str, type: str) -> Union[List, Tuple]: + table_name = get_table_name(doctype) return self.sql(f"ALTER TABLE `{table_name}` MODIFY `{column}` {type} NOT NULL") # exception types @@ -195,7 +195,7 @@ class MariaDBDatabase(Database): `password` TEXT NOT NULL, `encrypted` INT(1) NOT NULL DEFAULT 0, PRIMARY KEY (`doctype`, `name`, `fieldname`) - ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci""") + ) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci""") def create_global_search_table(self): if not '__global_search' in self.get_tables(): diff --git a/frappe/database/mariadb/framework_mariadb.sql b/frappe/database/mariadb/framework_mariadb.sql index 777e036049..670fb71aa2 100644 --- a/frappe/database/mariadb/framework_mariadb.sql +++ b/frappe/database/mariadb/framework_mariadb.sql @@ -72,7 +72,7 @@ CREATE TABLE `tabDocField` ( KEY `label` (`label`), KEY `fieldtype` (`fieldtype`), KEY `fieldname` (`fieldname`) -) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- @@ -109,7 +109,7 @@ CREATE TABLE `tabDocPerm` ( `email` int(1) NOT NULL DEFAULT 1, PRIMARY KEY (`name`), KEY `parent` (`parent`) -) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- -- Table structure for table `tabDocType Action` @@ -133,7 +133,7 @@ CREATE TABLE `tabDocType Action` ( PRIMARY KEY (`name`), KEY `parent` (`parent`), KEY `modified` (`modified`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; -- -- Table structure for table `tabDocType Action` @@ -156,7 +156,7 @@ CREATE TABLE `tabDocType Link` ( PRIMARY KEY (`name`), KEY `parent` (`parent`), KEY `modified` (`modified`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; -- -- Table structure for table `tabDocType` @@ -228,7 +228,7 @@ CREATE TABLE `tabDocType` ( `sender_field` varchar(255) DEFAULT NULL, PRIMARY KEY (`name`), KEY `parent` (`parent`) -) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- -- Table structure for table `tabSeries` @@ -239,7 +239,7 @@ CREATE TABLE `tabSeries` ( `name` varchar(100), `current` int(10) NOT NULL DEFAULT 0, PRIMARY KEY(`name`) -) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- @@ -256,7 +256,7 @@ CREATE TABLE `tabSessions` ( `device` varchar(255) DEFAULT 'desktop', `status` varchar(20) DEFAULT NULL, KEY `sid` (`sid`) -) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- @@ -269,7 +269,7 @@ CREATE TABLE `tabSingles` ( `field` varchar(255) DEFAULT NULL, `value` text, KEY `singles_doctype_field_index` (`doctype`, `field`) -) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- -- Table structure for table `__Auth` @@ -283,7 +283,7 @@ CREATE TABLE `__Auth` ( `password` TEXT NOT NULL, `encrypted` INT(1) NOT NULL DEFAULT 0, PRIMARY KEY (`doctype`, `name`, `fieldname`) -) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- -- Table structure for table `tabFile` @@ -311,7 +311,7 @@ CREATE TABLE `tabFile` ( KEY `parent` (`parent`), KEY `attached_to_name` (`attached_to_name`), KEY `attached_to_doctype` (`attached_to_doctype`) -) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- -- Table structure for table `tabDefaultValue` @@ -334,4 +334,4 @@ CREATE TABLE `tabDefaultValue` ( PRIMARY KEY (`name`), KEY `parent` (`parent`), KEY `defaultvalue_parent_defkey_index` (`parent`,`defkey`) -) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/frappe/database/mariadb/schema.py b/frappe/database/mariadb/schema.py index b40af59286..5768a2f23d 100644 --- a/frappe/database/mariadb/schema.py +++ b/frappe/database/mariadb/schema.py @@ -4,18 +4,22 @@ from frappe.database.schema import DBTable class MariaDBTable(DBTable): def create(self): - add_text = '' + additional_definitions = "" + engine = self.meta.get("engine") or "InnoDB" + varchar_len = frappe.db.VARCHAR_LEN # columns column_defs = self.get_column_definitions() - if column_defs: add_text += ',\n'.join(column_defs) + ',\n' + if column_defs: + additional_definitions += ',\n'.join(column_defs) + ',\n' # index index_defs = self.get_index_definitions() - if index_defs: add_text += ',\n'.join(index_defs) + ',\n' + if index_defs: + additional_definitions += ',\n'.join(index_defs) + ',\n' # create table - frappe.db.sql("""create table `%s` ( + query = f"""create table `{self.table_name}` ( name varchar({varchar_len}) not null primary key, creation datetime(6), modified datetime(6), @@ -26,13 +30,15 @@ class MariaDBTable(DBTable): parentfield varchar({varchar_len}), parenttype varchar({varchar_len}), idx int(8) not null default '0', - %sindex parent(parent), + {additional_definitions} + index parent(parent), index modified(modified)) ENGINE={engine} - ROW_FORMAT=COMPRESSED + ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 - COLLATE=utf8mb4_unicode_ci""".format(varchar_len=frappe.db.VARCHAR_LEN, - engine=self.meta.get("engine") or 'InnoDB') % (self.table_name, add_text)) + COLLATE=utf8mb4_unicode_ci""" + + frappe.db.sql(query) def alter(self): for col in self.columns.values(): diff --git a/frappe/database/mariadb/setup_db.py b/frappe/database/mariadb/setup_db.py index 6be08c66bb..8088cc2331 100644 --- a/frappe/database/mariadb/setup_db.py +++ b/frappe/database/mariadb/setup_db.py @@ -34,25 +34,23 @@ def setup_database(force, source_sql, verbose, no_mariadb_socket=False): db_name = frappe.local.conf.db_name root_conn = get_root_connection(frappe.flags.root_login, frappe.flags.root_password) dbman = DbManager(root_conn) + dbman_kwargs = {} + if no_mariadb_socket: + dbman_kwargs["host"] = "%" + if force or (db_name not in dbman.get_database_list()): - dbman.delete_user(db_name) - if no_mariadb_socket: - dbman.delete_user(db_name, host="%") + dbman.delete_user(db_name, **dbman_kwargs) dbman.drop_database(db_name) else: raise Exception("Database %s already exists" % (db_name,)) - dbman.create_user(db_name, frappe.conf.db_password) - if no_mariadb_socket: - dbman.create_user(db_name, frappe.conf.db_password, host="%") + dbman.create_user(db_name, frappe.conf.db_password, **dbman_kwargs) if verbose: print("Created user %s" % db_name) dbman.create_database(db_name) if verbose: print("Created database %s" % db_name) - dbman.grant_all_privileges(db_name, db_name) - if no_mariadb_socket: - dbman.grant_all_privileges(db_name, db_name, host="%") + dbman.grant_all_privileges(db_name, db_name, **dbman_kwargs) dbman.flush_privileges() if verbose: print("Granted privileges to user %s and database %s" % (db_name, db_name)) diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index 264d3bbf14..bfa5515111 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -4,6 +4,7 @@ from typing import List, Tuple, Union import psycopg2 import psycopg2.extensions from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT +from psycopg2.errorcodes import STRING_DATA_RIGHT_TRUNCATION import frappe from frappe.database.database import Database @@ -31,11 +32,11 @@ class PostgresDatabase(Database): def setup_type_map(self): self.db_type = 'postgres' self.type_map = { - 'Currency': ('decimal', '18,6'), + 'Currency': ('decimal', '21,9'), 'Int': ('bigint', None), 'Long Int': ('bigint', None), - 'Float': ('decimal', '18,6'), - 'Percent': ('decimal', '18,6'), + 'Float': ('decimal', '21,9'), + 'Percent': ('decimal', '21,9'), 'Check': ('smallint', None), 'Small Text': ('text', ''), 'Long Text': ('text', ''), @@ -60,7 +61,7 @@ class PostgresDatabase(Database): 'Color': ('varchar', self.VARCHAR_LEN), 'Barcode': ('text', ''), 'Geolocation': ('text', ''), - 'Duration': ('decimal', '18,6'), + 'Duration': ('decimal', '21,9'), 'Icon': ('varchar', self.VARCHAR_LEN) } @@ -171,7 +172,7 @@ class PostgresDatabase(Database): @staticmethod def is_data_too_long(e): - return e.pgcode == '22001' + return e.pgcode == STRING_DATA_RIGHT_TRUNCATION def rename_table(self, old_name: str, new_name: str) -> Union[List, Tuple]: old_name = get_table_name(old_name) @@ -182,8 +183,8 @@ class PostgresDatabase(Database): table_name = get_table_name(doctype) return self.sql(f"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME = '{table_name}'") - def change_column_type(self, table: str, column: str, type: str) -> Union[List, Tuple]: - table_name = get_table_name(table) + def change_column_type(self, doctype: str, column: str, type: str) -> Union[List, Tuple]: + table_name = get_table_name(doctype) return self.sql(f'ALTER TABLE "{table_name}" ALTER COLUMN "{column}" TYPE {type}') def create_auth_table(self): diff --git a/frappe/database/schema.py b/frappe/database/schema.py index 31f11dbd5e..ce9fcb4147 100644 --- a/frappe/database/schema.py +++ b/frappe/database/schema.py @@ -303,6 +303,8 @@ def get_definition(fieldtype, precision=None, length=None): size = d[1] if d[1] else None if size: + # This check needs to exist for backward compatibility. + # Till V13, default size used for float, currency and percent are (18, 6). if fieldtype in ["Float", "Currency", "Percent"] and cint(precision) > 6: size = '21,9' diff --git a/frappe/desk/doctype/note/note.json b/frappe/desk/doctype/note/note.json index 8d476e83fe..69a9518ac4 100644 --- a/frappe/desk/doctype/note/note.json +++ b/frappe/desk/doctype/note/note.json @@ -1,322 +1,106 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 1, - "beta": 0, - "creation": "2013-05-24 13:41:00", - "custom": 0, - "description": "", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "public", - "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": "Public", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "depends_on": "public", - "fieldname": "notify_on_login", - "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": "Notify users with a popup when they log in", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "default": "0", - "depends_on": "notify_on_login", - "description": "If enabled, users will be notified every time they login. If not enabled, users will only be notified once.", - "fieldname": "notify_on_every_login", - "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": "Notify Users On Every Login", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.notify_on_login && doc.public", - "fieldname": "expire_notification_on", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Expire Notification On", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "description": "Help: To link to another record in the system, use \"#Form/Note/[Note Name]\" as the Link URL. (don't use \"http://\")", - "fieldname": "content", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Content", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "seen_by_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Seen By", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "seen_by", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Seen By Table", - "length": 0, - "no_copy": 0, - "options": "Note Seen By", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-file-text", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-09-21 15:15:44.909636", - "modified_by": "Administrator", - "module": "Desk", - "name": "Note", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "All", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 1, - "show_name_in_global_search": 0, - "sort_order": "ASC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 - } \ No newline at end of file + "actions": [], + "allow_rename": 1, + "creation": "2013-05-24 13:41:00", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "title", + "public", + "notify_on_login", + "notify_on_every_login", + "expire_notification_on", + "content", + "seen_by_section", + "seen_by" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1, + "reqd": 1 + }, + { + "bold": 1, + "default": "0", + "fieldname": "public", + "fieldtype": "Check", + "label": "Public", + "print_hide": 1 + }, + { + "bold": 1, + "default": "0", + "depends_on": "public", + "fieldname": "notify_on_login", + "fieldtype": "Check", + "label": "Notify users with a popup when they log in" + }, + { + "bold": 1, + "default": "0", + "depends_on": "notify_on_login", + "description": "If enabled, users will be notified every time they login. If not enabled, users will only be notified once.", + "fieldname": "notify_on_every_login", + "fieldtype": "Check", + "label": "Notify Users On Every Login" + }, + { + "depends_on": "eval:doc.notify_on_login && doc.public", + "fieldname": "expire_notification_on", + "fieldtype": "Date", + "label": "Expire Notification On", + "search_index": 1 + }, + { + "bold": 1, + "description": "Help: To link to another record in the system, use \"/app/note/[Note Name]\" as the Link URL. (don't use \"http://\")", + "fieldname": "content", + "fieldtype": "Text Editor", + "in_global_search": 1, + "label": "Content" + }, + { + "collapsible": 1, + "fieldname": "seen_by_section", + "fieldtype": "Section Break", + "label": "Seen By" + }, + { + "fieldname": "seen_by", + "fieldtype": "Table", + "label": "Seen By Table", + "options": "Note Seen By" + } + ], + "icon": "fa fa-file-text", + "idx": 1, + "links": [], + "modified": "2021-09-18 10:57:51.352643", + "modified_by": "Administrator", + "module": "Desk", + "name": "Note", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "All", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "ASC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/desk/doctype/system_console/system_console.js b/frappe/desk/doctype/system_console/system_console.js index 48dd2ba108..0fe3932671 100644 --- a/frappe/desk/doctype/system_console/system_console.js +++ b/frappe/desk/doctype/system_console/system_console.js @@ -10,15 +10,95 @@ frappe.ui.form.on('System Console', { description: __('Execute Console script'), ignore_inputs: true, }); + frm.set_value("type", "Python"); }, refresh: function(frm) { frm.disable_save(); frm.page.set_primary_action(__("Execute"), $btn => { - $btn.text(__('Executing...')); - return frm.execute_action("Execute").then(() => { - $btn.text(__('Execute')); - }); + $btn.text(__("Executing...")); + return frm + .execute_action("Execute") + .then(() => frm.trigger("render_sql_output")) + .finally(() => $btn.text(__("Execute"))); + }); + }, + + type: function(frm) { + if (frm.doc.type == "Python") { + frm.set_value("output", ""); + if (frm.sql_output) { + frm.sql_output.destroy(); + frm.get_field("sql_output").html(""); + } + } + }, + + render_sql_output: function(frm) { + if (frm.doc.type !== "SQL") return; + if (frm.sql_output) { + frm.sql_output.destroy(); + frm.get_field("sql_output").html(""); + } + + if (frm.doc.output.startsWith("Traceback")) { + return; + } + + let result = JSON.parse(frm.doc.output); + frm.set_value("output", `${result.length} ${result.length == 1 ? 'row' : 'rows'}`); + + if (result.length) { + let columns = Object.keys(result[0]); + frm.sql_output = new DataTable( + frm.get_field("sql_output").$wrapper.get(0), + { + columns, + data: result + } + ); + } + }, + + show_processlist: function(frm) { + if (frm.doc.show_processlist) { + // keep refreshing every 5 seconds + frm.events.refresh_processlist(frm); + frm.processlist_interval = setInterval(() => frm.events.refresh_processlist(frm), 5000); + } else { + if (frm.processlist_interval) { + + // end it + clearInterval(frm.processlist_interval); + frm.get_field("processlist").html(''); + } + } + }, + + refresh_processlist: function(frm) { + let timestamp = new Date(); + frappe.call('frappe.desk.doctype.system_console.system_console.show_processlist').then(r => { + let rows = ''; + for (let row of r.message) { + rows += `
Requested on: ${timestamp}
+Id + | Time + | State + | Info + | Progress + |
---|