diff --git a/.editorconfig b/.editorconfig index 24f122a8d4..d76f67cd7f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,6 +9,6 @@ trim_trailing_whitespace = true charset = utf-8 # python, js indentation settings -[{*.py,*.js}] +[{*.py,*.js,*.vue}] indent_style = tab indent_size = 4 diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 4faece896a..f02694846d 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -10,3 +10,6 @@ # Replace use of Class.extend with native JS class fe20515c23a3ac41f1092bf0eaf0a0a452ec2e85 + +# Updating license headers +34460265554242a8d05fb09f049033b1117e1a2b diff --git a/.github/helper/documentation.py b/.github/helper/documentation.py index 08d1d1aa9c..f8ee3fa10b 100644 --- a/.github/helper/documentation.py +++ b/.github/helper/documentation.py @@ -32,9 +32,9 @@ if __name__ == "__main__": if response.ok: payload = response.json() - title = payload.get("title", "").lower() - head_sha = payload.get("head", {}).get("sha") - body = payload.get("body", "").lower() + title = (payload.get("title") or "").lower() + head_sha = (payload.get("head") or {}).get("sha") + body = (payload.get("body") or "").lower() if title.startswith("feat") and head_sha and "no-docs" not in body: if docs_link_exists(body): diff --git a/.github/helper/install_dependencies.sh b/.github/helper/install_dependencies.sh index 9be8519d85..d16f5b62ad 100644 --- a/.github/helper/install_dependencies.sh +++ b/.github/helper/install_dependencies.sh @@ -2,11 +2,6 @@ set -e -# python "${GITHUB_WORKSPACE}/.github/helper/roulette.py" -# if [[ $? != 2 ]];then -# exit; -# fi - # install wkhtmltopdf wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz tar -xf /tmp/wkhtmltox.tar.xz -C /tmp diff --git a/.github/helper/roulette.py b/.github/helper/roulette.py index ea4f07b9f7..9831df7f30 100644 --- a/.github/helper/roulette.py +++ b/.github/helper/roulette.py @@ -1,56 +1,72 @@ -# if the script ends with exit code 0, then no tests are run further, else all tests are run +import json import os import re import shlex import subprocess import sys +import urllib.request +def get_files_list(pr_number, repo="frappe/frappe"): + req = urllib.request.Request(f"https://api.github.com/repos/{repo}/pulls/{pr_number}/files") + res = urllib.request.urlopen(req) + dump = json.loads(res.read().decode('utf8')) + return [change["filename"] for change in dump] + def get_output(command, shell=True): - print(command) - command = shlex.split(command) - return subprocess.check_output(command, shell=shell, encoding="utf8").strip() + print(command) + command = shlex.split(command) + return subprocess.check_output(command, shell=shell, encoding="utf8").strip() def is_py(file): - return file.endswith("py") + return file.endswith("py") + +def is_ci(file): + return ".github" in file -def is_js(file): - return file.endswith("js") +def is_frontend_code(file): + return file.lower().endswith((".css", ".scss", ".less", ".sass", ".styl", ".js", ".ts", ".vue")) def is_docs(file): - regex = re.compile(r'\.(md|png|jpg|jpeg)$|^.github|LICENSE') - return bool(regex.search(file)) + regex = re.compile(r'\.(md|png|jpg|jpeg|csv)$|^.github|LICENSE') + return bool(regex.search(file)) if __name__ == "__main__": - build_type = os.environ.get("TYPE") - before = os.environ.get("BEFORE") - after = os.environ.get("AFTER") - commit_range = before + '...' + after - print("Build Type: {}".format(build_type)) - print("Commit Range: {}".format(commit_range)) - - try: - files_changed = get_output("git diff --name-only {}".format(commit_range), shell=False) - except Exception: - sys.exit(2) - - if "fatal" not in files_changed: - files_list = files_changed.split() - only_docs_changed = len(list(filter(is_docs, files_list))) == len(files_list) - only_js_changed = len(list(filter(is_js, files_list))) == len(files_list) - only_py_changed = len(list(filter(is_py, files_list))) == len(files_list) - - if only_docs_changed: - print("Only docs were updated, stopping build process.") - sys.exit(0) - - if only_js_changed and build_type == "server": - print("Only JavaScript code was updated; Stopping Python build process.") - sys.exit(0) - - if only_py_changed and build_type == "ui": - print("Only Python code was updated, stopping Cypress build process.") - sys.exit(0) - - sys.exit(2) + files_list = sys.argv[1:] + build_type = os.environ.get("TYPE") + pr_number = os.environ.get("PR_NUMBER") + repo = os.environ.get("REPO_NAME") + + # this is a push build, run all builds + if not pr_number: + os.system('echo "::set-output name=build::strawberry"') + sys.exit(0) + + files_list = files_list or get_files_list(pr_number=pr_number, repo=repo) + + if not files_list: + print("No files' changes detected. Build is shutting") + sys.exit(0) + + ci_files_changed = any(f for f in files_list if is_ci(f)) + only_docs_changed = len(list(filter(is_docs, files_list))) == len(files_list) + only_frontend_code_changed = len(list(filter(is_frontend_code, files_list))) == len(files_list) + only_py_changed = len(list(filter(is_py, files_list))) == len(files_list) + + if ci_files_changed: + print("CI related files were updated, running all build processes.") + + elif only_docs_changed: + print("Only docs were updated, stopping build process.") + sys.exit(0) + + elif only_frontend_code_changed and build_type == "server": + print("Only Frontend code was updated; Stopping Python build process.") + sys.exit(0) + + elif only_py_changed and build_type == "ui": + print("Only Python code was updated, stopping Cypress build process.") + sys.exit(0) + + os.system('echo "::set-output name=build::strawberry"') diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml index e8627a01fb..0dd4cd51d8 100644 --- a/.github/workflows/patch-mariadb-tests.yml +++ b/.github/workflows/patch-mariadb-tests.yml @@ -2,6 +2,11 @@ name: Patch on: [pull_request, workflow_dispatch] + +concurrency: + group: patch-mariadb-develop-${{ github.event.number }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-18.04 @@ -26,10 +31,21 @@ jobs: with: python-version: 3.7 + - name: Check if build should be run + id: check-build + run: | + python "${GITHUB_WORKSPACE}/.github/helper/roulette.py" + env: + TYPE: "server" + PR_NUMBER: ${{ github.event.number }} + REPO_NAME: ${{ github.repository }} + - name: Add to Hosts + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts - name: Cache pip + if: ${{ steps.check-build.outputs.build == 'strawberry' }} uses: actions/cache@v2 with: path: ~/.cache/pip @@ -39,6 +55,7 @@ jobs: ${{ runner.os }}- - name: Cache node modules + if: ${{ steps.check-build.outputs.build == 'strawberry' }} uses: actions/cache@v2 env: cache-name: cache-node-modules @@ -51,10 +68,12 @@ jobs: ${{ runner.os }}- - name: Get yarn cache directory path + if: ${{ steps.check-build.outputs.build == 'strawberry' }} id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - uses: actions/cache@v2 + if: ${{ steps.check-build.outputs.build == 'strawberry' }} id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} @@ -63,6 +82,7 @@ jobs: ${{ runner.os }}-yarn- - name: Install Dependencies + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh env: BEFORE: ${{ env.GITHUB_EVENT_PATH.before }} @@ -70,12 +90,14 @@ jobs: TYPE: server - name: Install + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh env: DB: mariadb TYPE: server - name: Run Patch Tests + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: | cd ~/frappe-bench/ wget https://frappeframework.com/files/v10-frappe.sql.gz diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml index 2476102e3d..57a7fa304d 100644 --- a/.github/workflows/server-mariadb-tests.yml +++ b/.github/workflows/server-mariadb-tests.yml @@ -6,6 +6,11 @@ on: push: branches: [ develop ] +concurrency: + group: server-mariadb-develop-${{ github.event.number }} + cancel-in-progress: true + + jobs: test: runs-on: ubuntu-18.04 @@ -35,17 +40,29 @@ jobs: with: python-version: 3.7 + - name: Check if build should be run + id: check-build + run: | + python "${GITHUB_WORKSPACE}/.github/helper/roulette.py" + env: + TYPE: "server" + PR_NUMBER: ${{ github.event.number }} + REPO_NAME: ${{ github.repository }} + - uses: actions/setup-node@v2 + if: ${{ steps.check-build.outputs.build == 'strawberry' }} with: node-version: 14 check-latest: true - name: Add to Hosts + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: | echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts echo "127.0.0.1 test_site_producer" | sudo tee -a /etc/hosts - name: Cache pip + if: ${{ steps.check-build.outputs.build == 'strawberry' }} uses: actions/cache@v2 with: path: ~/.cache/pip @@ -55,6 +72,7 @@ jobs: ${{ runner.os }}- - name: Cache node modules + if: ${{ steps.check-build.outputs.build == 'strawberry' }} uses: actions/cache@v2 env: cache-name: cache-node-modules @@ -67,10 +85,12 @@ jobs: ${{ runner.os }}- - name: Get yarn cache directory path + if: ${{ steps.check-build.outputs.build == 'strawberry' }} id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - uses: actions/cache@v2 + if: ${{ steps.check-build.outputs.build == 'strawberry' }} id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} @@ -79,6 +99,7 @@ jobs: ${{ runner.os }}-yarn- - name: Install Dependencies + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh env: BEFORE: ${{ env.GITHUB_EVENT_PATH.before }} @@ -86,45 +107,23 @@ jobs: TYPE: server - name: Install + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh env: DB: mariadb TYPE: server - name: Run Tests + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator --with-coverage env: CI_BUILD_ID: ${{ github.run_id }} ORCHESTRATOR_URL: http://test-orchestrator.frappe.io - - name: Upload Coverage Data - run: | - cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} - cd ${GITHUB_WORKSPACE} - pip3 install coverage==5.5 - pip3 install coveralls==3.0.1 - coveralls - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} - COVERALLS_FLAG_NAME: run-${{ matrix.container }} - COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }} - COVERALLS_PARALLEL: true - - coveralls: - name: Coverage Wrap Up - needs: test - container: python:3-slim - runs-on: ubuntu-18.04 - steps: - - name: Clone - uses: actions/checkout@v2 - - - name: Coveralls Finished - run: | - cd ${GITHUB_WORKSPACE} - pip3 install coverage==5.5 - pip3 install coveralls==3.0.1 - coveralls --finish - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload coverage data + uses: codecov/codecov-action@v2 + with: + name: MariaDB + fail_ci_if_error: true + files: /home/runner/frappe-bench/sites/coverage.xml + verbose: true diff --git a/.github/workflows/server-postgres-tests.yml b/.github/workflows/server-postgres-tests.yml index 4325eebaad..57ac9c6c60 100644 --- a/.github/workflows/server-postgres-tests.yml +++ b/.github/workflows/server-postgres-tests.yml @@ -3,6 +3,12 @@ name: Server on: pull_request: workflow_dispatch: + push: + branches: [ develop ] + +concurrency: + group: server-postgres-develop-${{ github.event.number }} + cancel-in-progress: true jobs: test: @@ -37,17 +43,29 @@ jobs: with: python-version: 3.7 + - name: Check if build should be run + id: check-build + run: | + python "${GITHUB_WORKSPACE}/.github/helper/roulette.py" + env: + TYPE: "server" + PR_NUMBER: ${{ github.event.number }} + REPO_NAME: ${{ github.repository }} + - uses: actions/setup-node@v2 + if: ${{ steps.check-build.outputs.build == 'strawberry' }} with: node-version: '14' check-latest: true - name: Add to Hosts + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: | echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts echo "127.0.0.1 test_site_producer" | sudo tee -a /etc/hosts - name: Cache pip + if: ${{ steps.check-build.outputs.build == 'strawberry' }} uses: actions/cache@v2 with: path: ~/.cache/pip @@ -57,6 +75,7 @@ jobs: ${{ runner.os }}- - name: Cache node modules + if: ${{ steps.check-build.outputs.build == 'strawberry' }} uses: actions/cache@v2 env: cache-name: cache-node-modules @@ -69,10 +88,12 @@ jobs: ${{ runner.os }}- - name: Get yarn cache directory path + if: ${{ steps.check-build.outputs.build == 'strawberry' }} id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - uses: actions/cache@v2 + if: ${{ steps.check-build.outputs.build == 'strawberry' }} id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} @@ -81,6 +102,7 @@ jobs: ${{ runner.os }}-yarn- - name: Install Dependencies + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh env: BEFORE: ${{ env.GITHUB_EVENT_PATH.before }} @@ -88,13 +110,23 @@ jobs: TYPE: server - name: Install + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh env: DB: postgres TYPE: server - name: Run Tests - run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator + if: ${{ steps.check-build.outputs.build == 'strawberry' }} + run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator --with-coverage env: CI_BUILD_ID: ${{ github.run_id }} ORCHESTRATOR_URL: http://test-orchestrator.frappe.io + + - name: Upload coverage data + uses: codecov/codecov-action@v2 + with: + name: Postgres + fail_ci_if_error: true + files: /home/runner/frappe-bench/sites/coverage.xml + verbose: true diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index f342c0709e..2a55546ec4 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -6,6 +6,10 @@ on: push: branches: [ develop ] +concurrency: + group: ui-develop-${{ github.event.number }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-18.04 @@ -35,17 +39,29 @@ jobs: with: python-version: 3.7 + - name: Check if build should be run + id: check-build + run: | + python "${GITHUB_WORKSPACE}/.github/helper/roulette.py" + env: + TYPE: "ui" + PR_NUMBER: ${{ github.event.number }} + REPO_NAME: ${{ github.repository }} + - uses: actions/setup-node@v2 + if: ${{ steps.check-build.outputs.build == 'strawberry' }} with: node-version: 14 check-latest: true - name: Add to Hosts + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: | echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts echo "127.0.0.1 test_site_producer" | sudo tee -a /etc/hosts - name: Cache pip + if: ${{ steps.check-build.outputs.build == 'strawberry' }} uses: actions/cache@v2 with: path: ~/.cache/pip @@ -55,6 +71,7 @@ jobs: ${{ runner.os }}- - name: Cache node modules + if: ${{ steps.check-build.outputs.build == 'strawberry' }} uses: actions/cache@v2 env: cache-name: cache-node-modules @@ -67,10 +84,12 @@ jobs: ${{ runner.os }}- - name: Get yarn cache directory path + if: ${{ steps.check-build.outputs.build == 'strawberry' }} id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - uses: actions/cache@v2 + if: ${{ steps.check-build.outputs.build == 'strawberry' }} id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} @@ -79,6 +98,7 @@ jobs: ${{ runner.os }}-yarn- - name: Cache cypress binary + if: ${{ steps.check-build.outputs.build == 'strawberry' }} uses: actions/cache@v2 with: path: ~/.cache @@ -88,6 +108,7 @@ jobs: ${{ runner.os }}- - name: Install Dependencies + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh env: BEFORE: ${{ env.GITHUB_EVENT_PATH.before }} @@ -95,15 +116,18 @@ jobs: TYPE: ui - name: Install + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh env: DB: mariadb TYPE: ui - name: Site Setup + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: cd ~/frappe-bench/ && bench --site test_site execute frappe.utils.install.complete_setup_wizard - name: UI Tests + if: ${{ steps.check-build.outputs.build == 'strawberry' }} run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests frappe --headless --parallel --ci-build-id $GITHUB_RUN_ID env: CYPRESS_RECORD_KEY: 4a48f41c-11b3-425b-aa88-c58048fa69eb diff --git a/.mergify.yml b/.mergify.yml index c759c1e3ec..0bd9641d5b 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,4 +1,20 @@ pull_request_rules: + - name: Auto-close PRs on stable branch + conditions: + - and: + - and: + - author!=surajshetty3416 + - author!=gavindsouza + - or: + - base=version-13 + - base=version-12 + actions: + close: + comment: + message: | + @{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch. + https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch + - name: Automatic merge on CI success and review conditions: - status-success=Sider diff --git a/CODEOWNERS b/CODEOWNERS index 2dff157294..30cdb4d64d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -7,10 +7,13 @@ templates/ @surajshetty3416 www/ @surajshetty3416 integrations/ @leela -patches/ @surajshetty3416 +patches/ @surajshetty3416 @gavindsouza email/ @leela event_streaming/ @ruchamahabal data_import* @netchampfaris core/ @surajshetty3416 +database @gavindsouza +model @gavindsouza requirements.txt @gavindsouza commands/ @gavindsouza +workspace @shariquerik diff --git a/LICENSE b/LICENSE index 5dfc0fd5bd..6919960f8b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2016-2018 Frappe Technologies Pvt. Ltd. +Copyright (c) 2016-2021 Frappe Technologies Pvt. Ltd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 11343a632a..6c2804d843 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ - - + + diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000000..eb81252b61 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,9 @@ +codecov: + require_ci_to_pass: yes + status: + project: + default: + threshold: 0.5% +comment: + layout: "diff, flags, files" + require_changes: true diff --git a/cypress/integration/awesome_bar.js b/cypress/integration/awesome_bar.js index 3e12101532..fb09b384a8 100644 --- a/cypress/integration/awesome_bar.js +++ b/cypress/integration/awesome_bar.js @@ -10,9 +10,9 @@ context('Awesome Bar', () => { }); it('navigates to doctype list', () => { - cy.get('#navbar-search').type('todo', { delay: 200 }); - cy.get('#navbar-search + ul').should('be.visible'); - cy.get('#navbar-search').type('{downarrow}{enter}', { delay: 100 }); + cy.findByPlaceholderText('Search or type a command (Ctrl + G)').type('todo', { delay: 200 }); + cy.get('.awesomplete').findByRole('listbox').should('be.visible'); + cy.findByPlaceholderText('Search or type a command (Ctrl + G)').type('{downarrow}{enter}', { delay: 100 }); cy.get('.title-text').should('contain', 'To Do'); @@ -20,24 +20,24 @@ context('Awesome Bar', () => { }); it('find text in doctype list', () => { - cy.get('#navbar-search') + cy.findByPlaceholderText('Search or type a command (Ctrl + G)') .type('test in todo{downarrow}{enter}', { delay: 200 }); cy.get('.title-text').should('contain', 'To Do'); - cy.get('[data-original-title="Name"] > .input-with-feedback') + cy.findByPlaceholderText('Name') .should('have.value', '%test%'); }); it('navigates to new form', () => { - cy.get('#navbar-search') + cy.findByPlaceholderText('Search or type a command (Ctrl + G)') .type('new blog post{downarrow}{enter}', { delay: 200 }); cy.get('.title-text:visible').should('have.text', 'New Blog Post'); }); it('calculates math expressions', () => { - cy.get('#navbar-search') + cy.findByPlaceholderText('Search or type a command (Ctrl + G)') .type('55 + 32{downarrow}{enter}', { delay: 200 }); cy.get('.modal-title').should('contain', 'Result'); diff --git a/cypress/integration/control_barcode.js b/cypress/integration/control_barcode.js index 1df5e64f0e..5f1ab86d41 100644 --- a/cypress/integration/control_barcode.js +++ b/cypress/integration/control_barcode.js @@ -20,7 +20,7 @@ context('Control Barcode', () => { it('should generate barcode on setting a value', () => { get_dialog_with_barcode().as('dialog'); - cy.get('.frappe-control[data-fieldname=barcode] input') + cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox') .focus() .type('123456789') .blur(); @@ -37,11 +37,11 @@ context('Control Barcode', () => { it('should reset when input is cleared', () => { get_dialog_with_barcode().as('dialog'); - cy.get('.frappe-control[data-fieldname=barcode] input') + cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox') .focus() .type('123456789') .blur(); - cy.get('.frappe-control[data-fieldname=barcode] input') + cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox') .clear() .blur(); cy.get('.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]') diff --git a/cypress/integration/control_icon.js b/cypress/integration/control_icon.js index f92927f267..5c531a0823 100644 --- a/cypress/integration/control_icon.js +++ b/cypress/integration/control_icon.js @@ -17,17 +17,17 @@ context('Control Icon', () => { it('should set icon', () => { get_dialog_with_icon().as('dialog'); - cy.get('.frappe-control[data-fieldname=icon] input').first().click(); + cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').click(); cy.get('.icon-picker .icon-wrapper[id=active]').first().click(); - cy.get('.frappe-control[data-fieldname=icon] input').first().should('have.value', 'active'); + cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'active'); cy.get('@dialog').then(dialog => { let value = dialog.get_value('icon'); expect(value).to.equal('active'); }); cy.get('.icon-picker .icon-wrapper[id=resting]').first().click(); - cy.get('.frappe-control[data-fieldname=icon] input').first().should('have.value', 'resting'); + cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'resting'); cy.get('@dialog').then(dialog => { let value = dialog.get_value('icon'); expect(value).to.equal('resting'); @@ -36,14 +36,14 @@ context('Control Icon', () => { it('search for icon and clear search input', () => { let search_text = 'ed'; - cy.get('.icon-picker input[type=search]').first().click().type(search_text); + cy.get('.icon-picker').findByRole('searchbox').click().type(search_text); cy.get('.icon-section .icon-wrapper:not(.hidden)').then(i => { cy.get(`.icon-section .icon-wrapper[id*='${search_text}']`).then(icons => { expect(i.length).to.equal(icons.length); }); }); - cy.get('.icon-picker input[type=search]').clear().blur(); + cy.get('.icon-picker').findByRole('searchbox').clear().blur(); cy.get('.icon-section .icon-wrapper').should('not.have.class', 'hidden'); }); diff --git a/cypress/integration/control_link.js b/cypress/integration/control_link.js index 8f9257e9c4..7d44a71d06 100644 --- a/cypress/integration/control_link.js +++ b/cypress/integration/control_link.js @@ -35,7 +35,7 @@ context('Control Link', () => { cy.wait('@search_link'); cy.get('@input').type('todo for link', { delay: 200 }); cy.wait('@search_link'); - cy.get('.frappe-control[data-fieldname=link] ul').should('be.visible'); + cy.get('.frappe-control[data-fieldname=link]').findByRole('listbox').should('be.visible'); cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 }); cy.get('.frappe-control[data-fieldname=link] input').blur(); cy.get('@dialog').then(dialog => { @@ -71,7 +71,7 @@ context('Control Link', () => { cy.get('@input').type(todos[0]).blur(); cy.wait('@validate_link'); cy.get('@input').focus(); - cy.get('.frappe-control[data-fieldname=link] .link-btn') + cy.findByTitle('Open Link') .should('be.visible') .click(); cy.location('pathname').should('eq', `/app/todo/${todos[0]}`); diff --git a/cypress/integration/control_select.js b/cypress/integration/control_select.js index 0bc719b4a7..8e18d21260 100644 --- a/cypress/integration/control_select.js +++ b/cypress/integration/control_select.js @@ -24,8 +24,10 @@ context('Control Select', () => { cy.get('@control').get('.select-icon').should('exist'); cy.get('@control').get('.placeholder').should('have.css', 'display', 'block'); cy.get('@select').select('Option 1'); + cy.findByDisplayValue('Option 1').should('exist'); cy.get('@control').get('.placeholder').should('have.css', 'display', 'none'); cy.get('@select').invoke('val', ''); + cy.findByDisplayValue('Option 1').should('not.exist'); cy.get('@control').get('.placeholder').should('have.css', 'display', 'block'); diff --git a/cypress/integration/dashboard_links.js b/cypress/integration/dashboard_links.js new file mode 100644 index 0000000000..b77965ee1a --- /dev/null +++ b/cypress/integration/dashboard_links.js @@ -0,0 +1,63 @@ +context('Dashboard links', () => { + before(() => { + cy.visit('/login'); + cy.login(); + }); + + it('Adding a new contact, checking for the counter on the dashboard and deleting the created contact', () => { + cy.visit('/app/contact'); + cy.clear_filters(); + + cy.visit('/app/user'); + cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click(); + + //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('[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(); + + //To check if the counter for contact doc is "1" after adding the contact + cy.get('[data-doctype="Contact"] > .count').should('contain', '1'); + cy.get('[data-doctype="Contact"]').contains('Contact').click(); + + //Deleting the newly created contact + cy.visit('/app/contact'); + cy.get('.list-subject > .select-like > .list-row-checkbox').eq(0).click(); + cy.findByRole('button', {name: 'Actions'}).click(); + cy.get('.actions-btn-group [data-label="Delete"]').click(); + cy.findByRole('button', {name: 'Yes'}).click({delay: 700}); + + + //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('[data-doctype="Contact"]').should('contain', 'Contact'); + }); + + it('Report link in dashboard', () => { + cy.visit('/app/user'); + cy.visit('/app/user/Administrator'); + cy.get('[data-doctype="Contact"]').should('contain', 'Contact'); + cy.findByText('Connections'); + cy.window() + .its('cur_frm') + .then(cur_frm => { + cur_frm.dashboard.data.reports = [ + { + 'label': 'Reports', + 'items': ['Permitted Documents For User'] + } + ]; + cur_frm.dashboard.render_report_links(); + cy.get('[data-report="Permitted Documents For User"]').contains('Permitted Documents For User').click(); + cy.findByText('Permitted Documents For User'); + cy.findByPlaceholderText('User').should("have.value", "Administrator"); + }); + }); +}); diff --git a/cypress/integration/datetime_field_form_validation.js b/cypress/integration/datetime_field_form_validation.js new file mode 100644 index 0000000000..66fdde6863 --- /dev/null +++ b/cypress/integration/datetime_field_form_validation.js @@ -0,0 +1,19 @@ +context('Datetime Field Validation', () => { + before(() => { + cy.login(); + cy.visit('/app/communication'); + cy.window().its('frappe').then(frappe => { + frappe.call("frappe.tests.ui_test_helpers.create_communication_records"); + }); + }); + + // validating datetime field value when value is set from backend and get validated on form load. + it('datetime field form validation', () => { + cy.visit('/app/communication'); + cy.get('a[title="Test Form Communication 1"]').invoke('attr', 'data-name') + .then((name) => { + cy.visit(`/app/communication/${name}`); + cy.get('.indicator-pill').should('contain', 'Open').should('have.class', 'red'); + }); + }); +}); \ No newline at end of file diff --git a/cypress/integration/depends_on.js b/cypress/integration/depends_on.js index d33babb134..9aa6b5d89d 100644 --- a/cypress/integration/depends_on.js +++ b/cypress/integration/depends_on.js @@ -62,11 +62,11 @@ context('Depends On', () => { it('should set the field as mandatory depending on other fields value', () => { cy.new_form('Test Depends On'); cy.fill_field('test_field', 'Some Value'); - cy.get('button.primary-action').contains('Save').click(); + cy.findByRole('button', {name: 'Save'}).click(); cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('be.visible'); cy.hide_dialog(); cy.fill_field('test_field', 'Random value'); - cy.get('button.primary-action').contains('Save').click(); + cy.findByRole('button', {name: 'Save'}).click(); cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('not.be.visible'); }); it('should set the field as read only depending on other fields value', () => { @@ -84,7 +84,7 @@ context('Depends On', () => { cy.fill_field('dependant_field', 'Some Value'); //cy.fill_field('test_field', 'Some Other Value'); cy.get('.frappe-control[data-fieldname="child_test_depends_on_field"]').as('table'); - cy.get('@table').find('button.grid-add-row').click(); + cy.get('@table').findByRole('button', {name: 'Add Row'}).click(); cy.get('@table').find('[data-idx="1"]').as('row1'); cy.get('@row1').find('.btn-open-row').click(); cy.get('@row1').find('.form-in-grid').as('row1-form_in_grid'); diff --git a/cypress/integration/file_uploader.js b/cypress/integration/file_uploader.js index e1e232c058..3d4f92df3c 100644 --- a/cypress/integration/file_uploader.js +++ b/cypress/integration/file_uploader.js @@ -25,7 +25,7 @@ context('FileUploader', () => { cy.get_open_dialog().find('.file-name').should('contain', 'example.json'); cy.intercept('POST', '/api/method/upload_file').as('upload_file'); - cy.get_open_dialog().find('.btn-modal-primary').click(); + cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click(); cy.wait('@upload_file').its('response.statusCode').should('eq', 200); cy.get('.modal:visible').should('not.exist'); }); @@ -33,11 +33,11 @@ context('FileUploader', () => { it('should accept uploaded files', () => { open_upload_dialog(); - cy.get_open_dialog().find('.btn-file-upload div:contains("Library")').click(); - cy.get('.file-filter').type('example.json'); - cy.get_open_dialog().find('.tree-label:contains("example.json")').first().click(); + cy.get_open_dialog().findByRole('button', {name: 'Library'}).click(); + cy.findByPlaceholderText('Search by filename or extension').type('example.json'); + cy.get_open_dialog().findAllByText('example.json').first().click(); cy.intercept('POST', '/api/method/upload_file').as('upload_file'); - cy.get_open_dialog().find('.btn-primary').click(); + cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click(); cy.wait('@upload_file').its('response.body.message') .should('have.property', 'file_name', 'example.json'); cy.get('.modal:visible').should('not.exist'); @@ -46,10 +46,12 @@ context('FileUploader', () => { it('should accept web links', () => { open_upload_dialog(); - cy.get_open_dialog().find('.btn-file-upload div:contains("Link")').click(); - cy.get_open_dialog().find('.file-web-link input').type('https://github.com', { delay: 100, force: true }); + cy.get_open_dialog().findByRole('button', {name: 'Link'}).click(); + cy.get_open_dialog() + .findByPlaceholderText('Attach a web link') + .type('https://github.com', { delay: 100, force: true }); cy.intercept('POST', '/api/method/upload_file').as('upload_file'); - cy.get_open_dialog().find('.btn-primary').click(); + cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click(); cy.wait('@upload_file').its('response.body.message') .should('have.property', 'file_url', 'https://github.com'); cy.get('.modal:visible').should('not.exist'); @@ -62,15 +64,14 @@ context('FileUploader', () => { subjectType: 'drag-n-drop', }); - cy.get_open_dialog().find('.file-name').should('contain', 'sample_image.jpg'); + cy.get_open_dialog().findAllByText('sample_image.jpg').should('exist'); cy.get_open_dialog().find('.btn-crop').first().click(); - cy.get_open_dialog().find('.image-cropper-actions > .btn-primary').should('contain', 'Crop'); - cy.get_open_dialog().find('.image-cropper-actions > .btn-primary').click(); - cy.get_open_dialog().find('.optimize-checkbox').first().should('contain', 'Optimize'); - cy.get_open_dialog().find('.optimize-checkbox').first().click(); + cy.get_open_dialog().findByRole('button', {name: 'Crop'}).click(); + cy.get_open_dialog().findAllByRole('checkbox', {name: 'Optimize'}).should('exist'); + cy.get_open_dialog().findAllByLabelText('Optimize').first().click(); cy.intercept('POST', '/api/method/upload_file').as('upload_file'); - cy.get_open_dialog().find('.btn-modal-primary').click(); + cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click(); cy.wait('@upload_file').its('response.statusCode').should('eq', 200); cy.get('.modal:visible').should('not.exist'); }); diff --git a/cypress/integration/folder_navigation.js b/cypress/integration/folder_navigation.js new file mode 100644 index 0000000000..1b7c02d98c --- /dev/null +++ b/cypress/integration/folder_navigation.js @@ -0,0 +1,79 @@ +context('Folder Navigation', () => { + before(() => { + cy.visit('/login'); + cy.login(); + cy.visit('/app/file'); + }); + + it('Adding Folders', () => { + //Adding filter to go into the home folder + cy.get('.filter-selector > .btn').findByText('1 filter').click(); + cy.findByRole('button', {name: 'Clear Filters'}).click(); + cy.get('.filter-action-buttons > .text-muted').findByText('+ Add a Filter').click(); + cy.get('.fieldname-select-area > .awesomplete > .form-control').type('Fol{enter}'); + cy.get('.filter-field > .form-group > .link-field > .awesomplete > .input-with-feedback').type('Home{enter}'); + cy.get('.filter-action-buttons > div > .btn-primary').findByText('Apply Filters').click(); + + //Adding folder (Test Folder) + cy.get('.menu-btn-group > .btn').click(); + cy.get('.menu-btn-group [data-label="New Folder"]').click(); + cy.get('form > [data-fieldname="value"]').type('Test Folder'); + cy.findByRole('button', {name: 'Create'}).click(); + }); + + it('Navigating the nested folders, checking if the URL formed is correct, checking if the added content in the child folder is correct', () => { + //Navigating inside the Attachments folder + cy.get('[title="Attachments"] > span').click(); + + //To check if the URL formed after visiting the attachments folder is correct + cy.location('pathname').should('eq', '/app/file/view/home/Attachments'); + cy.visit('/app/file/view/home/Attachments'); + + //Adding folder inside the attachments folder + cy.get('.menu-btn-group > .btn').click(); + cy.get('.menu-btn-group [data-label="New Folder"]').click(); + cy.get('form > [data-fieldname="value"]').type('Test Folder'); + cy.findByRole('button', {name: 'Create'}).click(); + + //Navigating inside the added folder in the Attachments folder + cy.get('[title="Test Folder"] > span').click(); + + //To check if the URL is correct after visiting the Test Folder + cy.location('pathname').should('eq', '/app/file/view/home/Attachments/Test%20Folder'); + cy.visit('/app/file/view/home/Attachments/Test%20Folder'); + + //Adding a file inside the Test Folder + cy.findByRole('button', {name: 'Add File'}).eq(0).click({force: true}); + cy.get('.file-uploader').findByText('Link').click(); + cy.get('.input-group > .form-control').type('https://wallpaperplay.com/walls/full/8/2/b/72402.jpg'); + cy.findByRole('button', {name: 'Upload'}).click(); + + //To check if the added file is present in the Test Folder + cy.get('span.level-item > span').should('contain', 'Test Folder'); + cy.get('.list-row-container').eq(0).should('contain.text', '72402.jpg'); + cy.get('.list-row-checkbox').eq(0).click(); + + //Deleting the added file from the Test folder + cy.findByRole('button', {name: 'Actions'}).click(); + cy.get('.actions-btn-group [data-label="Delete"]').click(); + cy.wait(700); + cy.findByRole('button', {name: 'Yes'}).click(); + cy.wait(700); + + //Deleting the Test Folder + cy.visit('/app/file/view/home/Attachments'); + cy.get('.list-row-checkbox').eq(0).click(); + cy.findByRole('button', {name: 'Actions'}).click(); + cy.get('.actions-btn-group [data-label="Delete"]').click(); + cy.findByRole('button', {name: 'Yes'}).click(); + }); + + 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.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 909955c1df..d20750b1d5 100644 --- a/cypress/integration/form.js +++ b/cypress/integration/form.js @@ -26,7 +26,7 @@ context('Form', () => { cy.visit('/app/contact'); cy.add_filter(); cy.get('.filter-field .input-with-feedback.form-control').type('123', { force: true }); - cy.get('.filter-popover .apply-filters').click({ force: true }); + cy.findByRole('button', {name: 'Apply Filters'}).click({ force: true }); cy.visit('/app/contact/Test Form Contact 3'); cy.get('.prev-doc').should('be.visible').click(); cy.get('.msgprint-dialog .modal-body').contains('No further records').should('be.visible'); diff --git a/cypress/integration/form_tour.js b/cypress/integration/form_tour.js index d12be63f3b..ab7ada9034 100644 --- a/cypress/integration/form_tour.js +++ b/cypress/integration/form_tour.js @@ -9,7 +9,7 @@ context('Form Tour', () => { const open_test_form_tour = () => { cy.visit('/app/form-tour/Test Form Tour'); - cy.get('button[data-label="Show%20Tour"]').should('be.visible').and('contain', 'Show Tour').as('show_tour'); + cy.findByRole('button', {name: 'Show Tour'}).should('be.visible').as('show_tour'); cy.get('@show_tour').click(); cy.wait(500); cy.url().should('include', '/app/contact'); @@ -20,10 +20,10 @@ context('Form Tour', () => { it('navigates a form tour', () => { open_test_form_tour(); - cy.get('#driver-popover-item').should('be.visible'); + cy.get('.frappe-driver').should('be.visible'); cy.get('.frappe-control[data-fieldname="first_name"]').as('first_name'); cy.get('@first_name').should('have.class', 'driver-highlighted-element'); - cy.get('.driver-next-btn').as('next_btn'); + cy.get('.frappe-driver').findByRole('button', {name: 'Next'}).as('next_btn'); // next btn shouldn't move to next step, if first name is not entered cy.get('@next_btn').click(); @@ -39,7 +39,7 @@ context('Form Tour', () => { // assert field is highlighted cy.get('.frappe-control[data-fieldname="last_name"]').as('last_name'); cy.get('@last_name').should('have.class', 'driver-highlighted-element'); - + // after filling the field, next step should be highlighted cy.fill_field('last_name', 'Test Last Name', 'Data'); cy.wait(500); @@ -49,12 +49,12 @@ context('Form Tour', () => { // assert field is highlighted cy.get('.frappe-control[data-fieldname="phone_nos"]').as('phone_nos'); cy.get('@phone_nos').should('have.class', 'driver-highlighted-element'); - + // move to next step cy.wait(500); cy.get('@next_btn').click(); cy.wait(500); - + // assert add row btn is highlighted cy.get('@phone_nos').find('.grid-add-row').as('add_row'); cy.get('@add_row').should('have.class', 'driver-highlighted-element'); @@ -68,21 +68,21 @@ context('Form Tour', () => { cy.get('.grid-row-open .frappe-control[data-fieldname="phone"]').as('phone'); cy.get('@phone').should('have.class', 'driver-highlighted-element'); // enter value in a table field - cy.fill_table_field('phone_nos', '1', 'phone', '1234567890'); + let field = cy.fill_table_field('phone_nos', '1', 'phone', '1234567890'); + field.blur(); // move to collapse row step cy.wait(500); - cy.get('@next_btn').click(); + cy.get('.driver-popover-title').contains('Test Title 4').siblings().get('@next_btn').click(); cy.wait(500); - // collapse row cy.get('.grid-row-open .grid-collapse-row').click(); cy.wait(500); - + // assert save btn is highlighted cy.get('.primary-action').should('have.class', 'driver-highlighted-element'); - cy.get('@next_btn').should('contain', 'Save'); + cy.wait(500); + cy.get('.frappe-driver').findByRole('button', {name: 'Save'}).should('be.visible'); }); }); - \ No newline at end of file diff --git a/cypress/integration/grid_pagination.js b/cypress/integration/grid_pagination.js index 8f6b79c1f4..c07230d2b8 100644 --- a/cypress/integration/grid_pagination.js +++ b/cypress/integration/grid_pagination.js @@ -30,12 +30,12 @@ context('Grid Pagination', () => { it('adds and deletes rows and changes page', () => { cy.visit('/app/contact/Test Contact'); cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table'); - cy.get('@table').find('button.grid-add-row').click(); + cy.get('@table').findByRole('button', {name: 'Add Row'}).click(); cy.get('@table').find('.grid-body .row-index').should('contain', 1001); cy.get('@table').find('.current-page-number').should('contain', '21'); cy.get('@table').find('.total-page-number').should('contain', '21'); cy.get('@table').find('.grid-body .grid-row .grid-row-check').click({ force: true }); - cy.get('@table').find('button.grid-remove-rows').click(); + cy.get('@table').findByRole('button', {name: 'Delete'}).click(); cy.get('@table').find('.grid-body .row-index').last().should('contain', 1000); cy.get('@table').find('.current-page-number').should('contain', '20'); cy.get('@table').find('.total-page-number').should('contain', '20'); diff --git a/cypress/integration/list_view_settings.js b/cypress/integration/list_view_settings.js index 52512b911e..61d4b8aae5 100644 --- a/cypress/integration/list_view_settings.js +++ b/cypress/integration/list_view_settings.js @@ -17,9 +17,9 @@ context('List View Settings', () => { cy.get('.dropdown-menu li').filter(':visible').contains('List Settings').click(); cy.get('.modal-dialog').should('contain', 'DocType Settings'); - cy.get('input[data-fieldname="disable_count"]').check({ force: true }); - cy.get('input[data-fieldname="disable_sidebar_stats"]').check({ force: true }); - cy.get('button').filter(':visible').contains('Save').click(); + cy.findByLabelText('Disable Count').check({ force: true }); + cy.findByLabelText('Disable Sidebar Stats').check({ force: true }); + cy.findByRole('button', {name: 'Save'}).click(); cy.reload({ force: true }); @@ -29,8 +29,8 @@ context('List View Settings', () => { cy.get('.menu-btn-group button').click({ force: true }); cy.get('.dropdown-menu li').filter(':visible').contains('List Settings').click(); cy.get('.modal-dialog').should('contain', 'DocType Settings'); - cy.get('input[data-fieldname="disable_count"]').uncheck({ force: true }); - cy.get('input[data-fieldname="disable_sidebar_stats"]').uncheck({ force: true }); - cy.get('button').filter(':visible').contains('Save').click(); + cy.findByLabelText('Disable Count').uncheck({ force: true }); + cy.findByLabelText('Disable Sidebar Stats').uncheck({ force: true }); + cy.findByRole('button', {name: 'Save'}).click(); }); }); diff --git a/cypress/integration/login.js b/cypress/integration/login.js index 6b109dd18d..98739bb4c9 100644 --- a/cypress/integration/login.js +++ b/cypress/integration/login.js @@ -11,13 +11,13 @@ context('Login', () => { it('validates password', () => { cy.get('#login_email').type('Administrator'); - cy.get('.btn-login:visible').click(); + cy.findByRole('button', {name: 'Login'}).click(); cy.location('pathname').should('eq', '/login'); }); it('validates email', () => { cy.get('#login_password').type('qwe'); - cy.get('.btn-login:visible').click(); + cy.findByRole('button', {name: 'Login'}).click(); cy.location('pathname').should('eq', '/login'); }); @@ -25,8 +25,8 @@ context('Login', () => { cy.get('#login_email').type('Administrator'); cy.get('#login_password').type('qwer'); - cy.get('.btn-login:visible').click(); - cy.get('.btn-login:visible').contains('Invalid Login. Try again.'); + cy.findByRole('button', {name: 'Login'}).click(); + cy.findByRole('button', {name: 'Invalid Login. Try again.'}).should('exist'); cy.location('pathname').should('eq', '/login'); }); @@ -34,7 +34,7 @@ context('Login', () => { cy.get('#login_email').type('Administrator'); cy.get('#login_password').type(Cypress.config('adminPassword')); - cy.get('.btn-login:visible').click(); + cy.findByRole('button', {name: 'Login'}).click(); cy.location('pathname').should('eq', '/app'); cy.window().its('frappe.session.user').should('eq', 'Administrator'); }); @@ -60,7 +60,7 @@ context('Login', () => { cy.get('#login_email').type('Administrator'); cy.get('#login_password').type(Cypress.config('adminPassword')); - cy.get('.btn-login:visible').click(); + cy.findByRole('button', {name: 'Login'}).click(); // verify redirected location and url params after login cy.url().should('include', '/me?' + payload.toString().replace('+', '%20')); diff --git a/cypress/integration/recorder.js b/cypress/integration/recorder.js index 5b7692d8ff..7a62b2e6d9 100644 --- a/cypress/integration/recorder.js +++ b/cypress/integration/recorder.js @@ -16,24 +16,24 @@ context('Recorder', () => { it('Navigate to Recorder', () => { cy.visit('/app'); cy.awesomebar('recorder'); - cy.get('h3').should('contain', 'Recorder'); + cy.findByTitle('Recorder').should('exist'); cy.url().should('include', '/recorder/detail'); }); it('Recorder Empty State', () => { - cy.get('.title-text').should('contain', 'Recorder'); + cy.findByTitle('Recorder').should('exist'); cy.get('.indicator-pill').should('contain', 'Inactive').should('have.class', 'red'); - cy.get('.primary-action').should('contain', 'Start'); - cy.get('.btn-secondary').should('contain', 'Clear'); + cy.findByRole('button', {name: 'Start'}).should('exist'); + cy.findByRole('button', {name: 'Clear'}).should('exist'); cy.get('.msg-box').should('contain', 'Inactive'); - cy.get('.msg-box .btn-primary').should('contain', 'Start Recording'); + cy.findByRole('button', {name: 'Start Recording'}).should('exist'); }); it('Recorder Start', () => { - cy.get('.primary-action').should('contain', 'Start').click(); + cy.findByRole('button', {name: 'Start'}).click(); cy.get('.indicator-pill').should('contain', 'Active').should('have.class', 'green'); cy.get('.msg-box').should('contain', 'No Requests'); @@ -46,12 +46,12 @@ context('Recorder', () => { cy.get('.list-count').should('contain', '20 of '); cy.visit('/app/recorder'); - cy.get('.title-text').should('contain', 'Recorder'); + cy.findByTitle('Recorder').should('exist'); cy.get('.result-list').should('contain', '/api/method/frappe.desk.reportview.get'); }); it('Recorder View Request', () => { - cy.get('.primary-action').should('contain', 'Start').click(); + cy.findByRole('button', {name: 'Start'}).click(); cy.visit('/app/List/DocType/List'); cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh'); diff --git a/cypress/integration/report_view.js b/cypress/integration/report_view.js index ea76246ae2..e762eebea1 100644 --- a/cypress/integration/report_view.js +++ b/cypress/integration/report_view.js @@ -23,7 +23,7 @@ context('Report View', () => { let cell = cy.get('.dt-row-0 > .dt-cell--col-4'); // select the cell cell.dblclick(); - cell.find('input[data-fieldname="enabled"]').check({ force: true }); + cell.findByRole('checkbox').check({ force: true }); cy.get('.dt-row-0 > .dt-cell--col-5').click(); cy.wait('@value-update'); cy.get('@doc').then(doc => { diff --git a/cypress/integration/timeline.js b/cypress/integration/timeline.js index c7bbe29e5a..7a8f3a159b 100644 --- a/cypress/integration/timeline.js +++ b/cypress/integration/timeline.js @@ -10,26 +10,26 @@ context('Timeline', () => { it('Adding new ToDo, adding new comment, verifying comment addition & deletion and deleting ToDo', () => { //Adding new ToDo cy.click_listview_primary_button('Add ToDo'); - cy.get('.modal-footer > .custom-actions > .btn').contains('Edit in full page').click(); - cy.get('.row > .section-body > .form-column > form > .frappe-control > .form-group > .control-input-wrapper > .control-input > .ql-container > .ql-editor').eq(0).type('Test ToDo', {force: true}); + cy.findByRole('button', {name: 'Edit in full page'}).click(); + cy.get('[data-fieldname="description"] .ql-editor').eq(0).type('Test ToDo', {force: true}); cy.wait(200); - cy.get('#page-ToDo > .page-head > .container > .row > .col > .standard-actions > .primary-action').contains('Save').click(); + cy.findByRole('button', {name: 'Save'}).click(); cy.wait(700); cy.visit('/app/todo'); - cy.get('.list-row > .level-left > .list-subject > .level-item.ellipsis > .ellipsis').eq(0).click(); + cy.get('.level-item.ellipsis').eq(0).click(); //To check if the comment box is initially empty and tying some text into it - cy.get('.comment-input-container > .frappe-control > .ql-container > .ql-editor').should('contain', '').type('Testing Timeline'); + cy.get('[data-fieldname="comment"] .ql-editor').should('contain', '').type('Testing Timeline'); //Adding new comment - cy.get('.comment-input-wrapper > .btn').contains('Comment').click(); + cy.findByRole('button', {name: 'Comment'}).click(); //To check if the commented text is visible in the timeline content cy.get('.timeline-content').should('contain', 'Testing Timeline'); //Editing comment cy.click_timeline_action_btn(0); - cy.get('.timeline-content > .timeline-message-box > .comment-edit-box > .frappe-control > .ql-container > .ql-editor').first().type(' 123'); + cy.get('.timeline-content [data-fieldname="comment"] .ql-editor').first().type(' 123'); cy.click_timeline_action_btn(0); //To check if the edited comment text is visible in timeline content @@ -37,20 +37,20 @@ context('Timeline', () => { //Discarding comment cy.click_timeline_action_btn(0); - cy.get('.actions > .btn').eq(1).first().click(); + cy.findByRole('button', {name: 'Dismiss'}).click(); //To check if after discarding the timeline content is same as previous cy.get('.timeline-content').should('contain', 'Testing Timeline 123'); //Deleting the added comment cy.get('.actions > .btn > .icon').first().click(); - cy.get('.modal-footer > .standard-actions > .btn-primary').contains('Yes').click(); + cy.findByRole('button', {name: 'Yes'}).click(); cy.click_modal_primary_button('Yes'); //Deleting the added ToDo - cy.get('#page-ToDo > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .btn').click({force: true}); - cy.get('.menu-btn-group > .dropdown-menu > li > .grey-link').eq(17).click({force: true}); - cy.get('.modal.show > .modal-dialog > .modal-content > .modal-footer > .standard-actions > .btn-primary').contains('Yes').click({force: true}); + cy.get('.menu-btn-group button').eq(1).click(); + cy.get('.menu-btn-group [data-label="Delete"]').click(); + cy.findByRole('button', {name: 'Yes'}).click(); }); it('Timeline should have submit and cancel activity information', () => { @@ -64,31 +64,31 @@ context('Timeline', () => { //Adding a new entry for the created custom doctype cy.fill_field('title', 'Test'); - cy.get('.modal-footer > .standard-actions > .btn-primary').contains('Save').click(); - cy.get('.modal-footer > .standard-actions > .btn-primary').contains('Submit').click(); + cy.findByRole('button', {name: 'Save'}).click(); + cy.findByRole('button', {name: 'Submit'}).click(); cy.visit('/app/custom-submittable-doctype'); cy.get('.list-subject > .bold > .ellipsis').eq(0).click(); //To check if the submission of the documemt is visible in the timeline content cy.get('.timeline-content').should('contain', 'Administrator submitted this document'); - cy.get('.page-actions > .standard-actions > .btn-secondary').contains('Cancel').click({delay: 900}); - cy.get('.modal-footer > .standard-actions > .btn-primary').contains('Yes').click(); - + cy.findByRole('button', {name: 'Cancel'}).click({delay: 900}); + cy.findByRole('button', {name: 'Yes'}).click(); + //To check if the cancellation of the documemt is visible in the timeline content cy.get('.timeline-content').should('contain', 'Administrator cancelled this document'); //Deleting the document cy.visit('/app/custom-submittable-doctype'); cy.get('.list-subject > .select-like > .list-row-checkbox').eq(0).click(); - cy.get('.page-actions > .standard-actions > .actions-btn-group > .btn').contains('Actions').click(); + cy.findByRole('button', {name: 'Actions'}).click(); cy.get('.actions-btn-group > .dropdown-menu > li > .grey-link').eq(7).click(); cy.click_modal_primary_button('Yes', {force: true, delay: 700}); //Deleting the custom doctype cy.visit('/app/doctype'); cy.get('.list-subject > .select-like > .list-row-checkbox').eq(0).click(); - cy.get('.page-actions > .standard-actions > .actions-btn-group > .btn').contains('Actions').click(); - cy.get('.actions-btn-group > .dropdown-menu > li > .grey-link').eq(5).click(); + cy.findByRole('button', {name: 'Actions'}).click(); + cy.get('.actions-btn-group [data-label="Delete"]').click(); cy.click_modal_primary_button('Yes'); }); }); \ No newline at end of file diff --git a/cypress/integration/timeline_email.js b/cypress/integration/timeline_email.js index e5b3ebeb7c..82af24e822 100644 --- a/cypress/integration/timeline_email.js +++ b/cypress/integration/timeline_email.js @@ -8,14 +8,13 @@ context('Timeline Email', () => { it('Adding new ToDo, adding email and verifying timeline content for email attachment, deleting attachment and ToDo', () => { //Adding new ToDo cy.click_listview_primary_button('Add ToDo'); - cy.get('.custom-actions > .btn').trigger('click', {delay: 500}); - cy.get('.row > .section-body > .form-column > form > .frappe-control > .form-group > .control-input-wrapper > .control-input > .ql-container > .ql-editor').eq(0).type('Test ToDo', {force: true}); + cy.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.click_listview_primary_button('Save'); cy.get('.primary-action').contains('Save').click({force: true}); cy.wait(700); cy.visit('/app/todo'); - cy.get('.list-row > .level-left > .list-subject > .level-item.ellipsis > .ellipsis').eq(0).click(); + cy.get('.list-row > .level-left > .list-subject').eq(0).click(); //Creating a new email cy.get('.timeline-actions > .btn').click(); @@ -47,7 +46,7 @@ context('Timeline Email', () => { //Removing the added attachment cy.get('.attachment-row > .data-pill > .remove-btn > .icon').click(); - cy.get('.modal-dialog > .modal-content > .modal-footer > .standard-actions > .btn-primary').contains('Yes').click(); + cy.get('.modal-footer:visible > .standard-actions > .btn-primary').contains('Yes').click(); //To check if the removed attachment is shown in the timeline content cy.get('.timeline-content').should('contain', 'Removed 72402.jpg'); @@ -55,17 +54,17 @@ context('Timeline Email', () => { //To check if the discard button functionality in email is working correctly cy.get('.timeline-actions > .btn').click(); - cy.fill_field('recipients', 'test@example.com', 'MultiSelect'); + cy.fill_field('recipients', 'test@example.com', 'MultiSelect'); cy.get('.modal-footer > .standard-actions > .btn-secondary').contains('Discard').click(); cy.wait(500); cy.get('.timeline-actions > .btn').click(); cy.wait(500); cy.get_field('recipients', 'MultiSelect').should('have.text', ''); - cy.get('.modal.show > .modal-dialog > .modal-content > .modal-header > .modal-actions > .btn-modal-close > .icon').click(); + cy.get('.modal-header:visible > .modal-actions > .btn-modal-close > .icon').click(); //Deleting the added ToDo - cy.get('#page-ToDo > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .btn').click(); - cy.get('.menu-btn-group > .dropdown-menu > li > .grey-link').eq(17).click(); - cy.get('.modal.show > .modal-dialog > .modal-content > .modal-footer > .standard-actions > .btn-primary').click(); + cy.get('.menu-btn-group:visible > .btn').click(); + cy.get('.menu-btn-group:visible > .dropdown-menu > li > .dropdown-item').contains('Delete').click(); + cy.get('.modal-footer:visible > .standard-actions > .btn-primary').click(); }); }); diff --git a/cypress/integration/workspace.js b/cypress/integration/workspace.js new file mode 100644 index 0000000000..65586366e6 --- /dev/null +++ b/cypress/integration/workspace.js @@ -0,0 +1,90 @@ +context('Workspace 2.0', () => { + before(() => { + cy.visit('/login'); + cy.login(); + cy.visit('/app/website'); + }); + + it('Navigate to page from sidebar', () => { + cy.visit('/app/build'); + cy.get('.codex-editor__redactor .ce-block'); + cy.get('.sidebar-item-container[item-name="Settings"]').first().click(); + cy.location('pathname').should('eq', '/app/settings'); + }); + + it('Create Private Page', () => { + cy.get('.codex-editor__redactor .ce-block'); + cy.get('.custom-actions button[data-label="Create%20Workspace"]').click(); + cy.fill_field('title', 'Test Private Page', 'Data'); + cy.fill_field('icon', 'edit', 'Icon'); + cy.get_open_dialog().find('.modal-header').click(); + cy.get_open_dialog().find('.btn-primary').click(); + + // check if sidebar item is added in pubic section + cy.get('.sidebar-item-container[item-name="Test Private Page"]').should('have.attr', 'item-public', '0'); + + cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click(); + cy.wait(300); + cy.get('.sidebar-item-container[item-name="Test Private Page"]').should('have.attr', 'item-public', '0'); + + cy.wait(500); + cy.get('.codex-editor__redactor .ce-block'); + cy.get('.standard-actions .btn-secondary[data-label=Edit]').click(); + }); + + it('Add New Block', () => { + cy.get('.codex-editor__redactor .ce-block'); + cy.get('.custom-actions .inner-group-button[data-label="Add%20Block"]').click(); + cy.get('.custom-actions .inner-group-button .dropdown-menu .block-menu-item-label').contains('Heading').click(); + cy.get(":focus").type('Header'); + cy.get(".ce-block:last").find('.ce-header').should('exist'); + + cy.get('.custom-actions .inner-group-button[data-label="Add%20Block"]').click(); + cy.get('.custom-actions .inner-group-button .dropdown-menu .block-menu-item-label').contains('Text').click(); + cy.get(":focus").type('Paragraph text'); + cy.get(".ce-block:last").find('.ce-paragraph').should('exist'); + }); + + it('Delete A Block', () => { + cy.get(".ce-block:last").find('.delete-paragraph').click(); + cy.get(".ce-block:last").find('.ce-paragraph').should('not.exist'); + }); + + it('Shrink and Expand A Block', () => { + cy.get(".ce-block:last").find('.tune-btn').click(); + cy.get('.ce-settings--opened .ce-shrink-button').click(); + cy.get(".ce-block:last").should('have.class', 'col-11'); + cy.get('.ce-settings--opened .ce-shrink-button').click(); + cy.get(".ce-block:last").should('have.class', 'col-10'); + cy.get('.ce-settings--opened .ce-shrink-button').click(); + cy.get(".ce-block:last").should('have.class', 'col-9'); + cy.get('.ce-settings--opened .ce-expand-button').click(); + cy.get(".ce-block:last").should('have.class', 'col-10'); + cy.get('.ce-settings--opened .ce-expand-button').click(); + cy.get(".ce-block:last").should('have.class', 'col-11'); + cy.get('.ce-settings--opened .ce-expand-button').click(); + cy.get(".ce-block:last").should('have.class', 'col-12'); + }); + + it('Change Header Text Size', () => { + cy.get('.ce-settings--opened .cdx-settings-button[data-level="3"]').click(); + cy.get(".ce-block:last").find('.widget-head h3').should('exist'); + cy.get('.ce-settings--opened .cdx-settings-button[data-level="4"]').click(); + cy.get(".ce-block:last").find('.widget-head h4').should('exist'); + + cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click(); + }); + + it('Delete Private Page', () => { + cy.get('.codex-editor__redactor .ce-block'); + cy.get('.standard-actions .btn-secondary[data-label=Edit]').click(); + + cy.get('.sidebar-item-container[item-name="Test Private Page"]').find('.sidebar-item-control .delete-page').click(); + cy.wait(300); + cy.get('.modal-footer > .standard-actions > .btn-modal-primary:visible').first().click(); + cy.get('.standard-actions .btn-primary[data-label="Save Customizations"]').click(); + cy.get('.codex-editor__redactor .ce-block'); + cy.get('.sidebar-item-container[item-name="Test Private Page"]').should('not.exist'); + }); + +}); \ No newline at end of file diff --git a/cypress/support/commands.js b/cypress/support/commands.js index a81ba60fb0..c941652487 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -1,4 +1,5 @@ import 'cypress-file-upload'; +import '@testing-library/cypress/add-commands'; // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite diff --git a/frappe/__init__.py b/frappe/__init__.py index 6d79cbd760..7c6005a350 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE """ Frappe - Low Code Open Source Framework in Python and JS @@ -140,7 +140,11 @@ lang = local("lang") if typing.TYPE_CHECKING: from frappe.database.mariadb.database import MariaDBDatabase from frappe.database.postgres.database import PostgresDatabase + from pypika import Query + db: typing.Union[MariaDBDatabase, PostgresDatabase] + qb: Query + # end: static analysis hack def init(site, sites_path=None, new_site=False): diff --git a/frappe/api.py b/frappe/api.py index 636c6b2888..b061761d10 100644 --- a/frappe/api.py +++ b/frappe/api.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import base64 import binascii import json diff --git a/frappe/app.py b/frappe/app.py index 920628dda4..8e1534e7ef 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import os import logging diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py index ef579aca01..a3e27d4da5 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/automation/doctype/assignment_rule/test_assignment_rule.py b/frappe/automation/doctype/assignment_rule/test_assignment_rule.py index e287b83965..1c9e177f94 100644 --- a/frappe/automation/doctype/assignment_rule/test_assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/test_assignment_rule.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest from frappe.utils import random_string @@ -76,7 +76,7 @@ class TestAutoAssign(unittest.TestCase): # clear 5 assignments for first user # can't do a limit in "delete" since postgres does not support it for d in frappe.get_all('ToDo', dict(reference_type = 'Note', owner = 'test@example.com'), limit=5): - frappe.db.sql("delete from tabToDo where name = %s", d.name) + frappe.db.delete("ToDo", {"name": d.name}) # add 5 more assignments for i in range(5): @@ -177,7 +177,7 @@ class TestAutoAssign(unittest.TestCase): ), 'owner'), 'test@example.com') def check_assignment_rule_scheduling(self): - frappe.db.sql("DELETE FROM `tabAssignment Rule`") + frappe.db.delete("Assignment Rule") days_1 = [dict(day = 'Sunday'), dict(day = 'Monday'), dict(day = 'Tuesday')] @@ -204,7 +204,7 @@ class TestAutoAssign(unittest.TestCase): ), 'owner'), ['test3@example.com']) def test_assignment_rule_condition(self): - frappe.db.sql("DELETE FROM `tabAssignment Rule`") + frappe.db.delete("Assignment Rule") # Add expiry_date custom field from frappe.custom.doctype.custom_field.custom_field import create_custom_field @@ -253,7 +253,7 @@ class TestAutoAssign(unittest.TestCase): assignment_rule.delete() def clear_assignments(): - frappe.db.sql("delete from tabToDo where reference_type = 'Note'") + frappe.db.delete("ToDo", {"reference_type": "Note"}) def get_assignment_rule(days, assign=None): frappe.delete_doc_if_exists('Assignment Rule', 'For Note 1') diff --git a/frappe/automation/doctype/assignment_rule_day/assignment_rule_day.py b/frappe/automation/doctype/assignment_rule_day/assignment_rule_day.py index c734495c39..836ae3d453 100644 --- a/frappe/automation/doctype/assignment_rule_day/assignment_rule_day.py +++ b/frappe/automation/doctype/assignment_rule_day/assignment_rule_day.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/automation/doctype/assignment_rule_user/assignment_rule_user.py b/frappe/automation/doctype/assignment_rule_user/assignment_rule_user.py index 4d65efd5c1..1bb8953a7a 100644 --- a/frappe/automation/doctype/assignment_rule_user/assignment_rule_user.py +++ b/frappe/automation/doctype/assignment_rule_user/assignment_rule_user.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.js b/frappe/automation/doctype/auto_repeat/auto_repeat.js index 896a10dfe0..80f2255f47 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.js +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.js @@ -30,7 +30,7 @@ frappe.ui.form.on('Auto Repeat', { refresh: function(frm) { // auto repeat message if (frm.is_new()) { - let customize_form_link = `${__('Customize Form')}`; + let customize_form_link = `${__('Customize Form')}`; frm.dashboard.set_headline(__('To configure Auto Repeat, enable "Allow Auto Repeat" from {0}.', [customize_form_link])); } diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py index d2afda1553..5ab6c86c00 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/automation/doctype/auto_repeat/test_auto_repeat.py b/frappe/automation/doctype/auto_repeat/test_auto_repeat.py index 567c1161af..30a0310a92 100644 --- a/frappe/automation/doctype/auto_repeat/test_auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/test_auto_repeat.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest import frappe diff --git a/frappe/automation/doctype/auto_repeat_day/auto_repeat_day.py b/frappe/automation/doctype/auto_repeat_day/auto_repeat_day.py index 8af3284cde..54fc0d14e9 100644 --- a/frappe/automation/doctype/auto_repeat_day/auto_repeat_day.py +++ b/frappe/automation/doctype/auto_repeat_day/auto_repeat_day.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/automation/doctype/milestone/milestone.py b/frappe/automation/doctype/milestone/milestone.py index 6ea6d7544a..eff65571fd 100644 --- a/frappe/automation/doctype/milestone/milestone.py +++ b/frappe/automation/doctype/milestone/milestone.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/automation/doctype/milestone/test_milestone.py b/frappe/automation/doctype/milestone/test_milestone.py index 175c56e552..f8fb910072 100644 --- a/frappe/automation/doctype/milestone/test_milestone.py +++ b/frappe/automation/doctype/milestone/test_milestone.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE #import frappe import unittest diff --git a/frappe/automation/doctype/milestone_tracker/milestone_tracker.py b/frappe/automation/doctype/milestone_tracker/milestone_tracker.py index 125cad7fa8..042e7b0391 100644 --- a/frappe/automation/doctype/milestone_tracker/milestone_tracker.py +++ b/frappe/automation/doctype/milestone_tracker/milestone_tracker.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py b/frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py index 21b2779018..f4d5f00d83 100644 --- a/frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py +++ b/frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import frappe.cache_manager import unittest class TestMilestoneTracker(unittest.TestCase): def test_milestone(self): - frappe.db.sql('delete from `tabMilestone Tracker`') + frappe.db.delete("Milestone Tracker") frappe.cache().delete_key('milestone_tracker_map') @@ -44,5 +44,5 @@ class TestMilestoneTracker(unittest.TestCase): self.assertEqual(milestones[0].value, 'Closed') # cleanup - frappe.db.sql('delete from tabMilestone') + frappe.db.delete("Milestone") milestone_tracker.delete() \ No newline at end of file diff --git a/frappe/automation/workspace/tools/tools.json b/frappe/automation/workspace/tools/tools.json index 4a0835657b..f556be1c07 100644 --- a/frappe/automation/workspace/tools/tools.json +++ b/frappe/automation/workspace/tools/tools.json @@ -1,22 +1,27 @@ { - "category": "Administration", + "category": "", "charts": [], + "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"ToDo\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Note\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"File\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Assignment Rule\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Auto Repeat\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Email\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Automation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Event Streaming\", \"col\": 4}}]", "creation": "2020-03-02 14:53:24.980279", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "tool", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "Tools", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Tools", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -25,6 +30,7 @@ "hidden": 0, "is_query_report": 0, "label": "To Do", + "link_count": 0, "link_to": "ToDo", "link_type": "DocType", "onboard": 1, @@ -35,6 +41,7 @@ "hidden": 0, "is_query_report": 0, "label": "Calendar", + "link_count": 0, "link_to": "Event", "link_type": "DocType", "onboard": 1, @@ -45,6 +52,7 @@ "hidden": 0, "is_query_report": 0, "label": "Note", + "link_count": 0, "link_to": "Note", "link_type": "DocType", "onboard": 1, @@ -55,6 +63,7 @@ "hidden": 0, "is_query_report": 0, "label": "Files", + "link_count": 0, "link_to": "File", "link_type": "DocType", "onboard": 0, @@ -65,6 +74,7 @@ "hidden": 0, "is_query_report": 0, "label": "Activity", + "link_count": 0, "link_to": "activity", "link_type": "Page", "onboard": 0, @@ -74,6 +84,7 @@ "hidden": 0, "is_query_report": 0, "label": "Email", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -82,6 +93,7 @@ "hidden": 0, "is_query_report": 0, "label": "Newsletter", + "link_count": 0, "link_to": "Newsletter", "link_type": "DocType", "onboard": 1, @@ -92,6 +104,7 @@ "hidden": 0, "is_query_report": 0, "label": "Email Group", + "link_count": 0, "link_to": "Email Group", "link_type": "DocType", "onboard": 0, @@ -101,6 +114,7 @@ "hidden": 0, "is_query_report": 0, "label": "Automation", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -109,6 +123,7 @@ "hidden": 0, "is_query_report": 0, "label": "Assignment Rule", + "link_count": 0, "link_to": "Assignment Rule", "link_type": "DocType", "onboard": 0, @@ -119,6 +134,7 @@ "hidden": 0, "is_query_report": 0, "label": "Milestone", + "link_count": 0, "link_to": "Milestone", "link_type": "DocType", "onboard": 0, @@ -129,6 +145,7 @@ "hidden": 0, "is_query_report": 0, "label": "Auto Repeat", + "link_count": 0, "link_to": "Auto Repeat", "link_type": "DocType", "onboard": 0, @@ -138,6 +155,7 @@ "hidden": 0, "is_query_report": 0, "label": "Event Streaming", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -146,6 +164,7 @@ "hidden": 0, "is_query_report": 0, "label": "Event Producer", + "link_count": 0, "link_to": "Event Producer", "link_type": "DocType", "onboard": 0, @@ -156,6 +175,7 @@ "hidden": 0, "is_query_report": 0, "label": "Event Consumer", + "link_count": 0, "link_to": "Event Consumer", "link_type": "DocType", "onboard": 0, @@ -166,6 +186,7 @@ "hidden": 0, "is_query_report": 0, "label": "Event Update Log", + "link_count": 0, "link_to": "Event Update Log", "link_type": "DocType", "onboard": 0, @@ -176,6 +197,7 @@ "hidden": 0, "is_query_report": 0, "label": "Event Sync Log", + "link_count": 0, "link_to": "Event Sync Log", "link_type": "DocType", "onboard": 0, @@ -186,19 +208,26 @@ "hidden": 0, "is_query_report": 0, "label": "Document Type Mapping", + "link_count": 0, "link_to": "Document Type Mapping", "link_type": "DocType", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:39.950350", + "modified": "2021-08-05 12:16:02.839180", "modified_by": "Administrator", "module": "Automation", "name": "Tools", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 26, "shortcuts": [ { "label": "ToDo", @@ -225,5 +254,6 @@ "link_to": "Auto Repeat", "type": "DocType" } - ] + ], + "title": "Tools" } \ No newline at end of file diff --git a/frappe/boot.py b/frappe/boot.py index 0589e32ac8..cf2b914436 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE """ bootstrap client session """ @@ -105,8 +105,8 @@ def load_conf_settings(bootinfo): if key in conf: bootinfo[key] = conf.get(key) def load_desktop_data(bootinfo): - from frappe.desk.desktop import get_desk_sidebar_items - bootinfo.allowed_workspaces = get_desk_sidebar_items() + from frappe.desk.desktop import get_wspace_sidebar_items + bootinfo.allowed_workspaces = get_wspace_sidebar_items().get('pages') bootinfo.module_page_map = get_controller("Workspace").get_module_page_map() bootinfo.dashboards = frappe.get_all("Dashboard") diff --git a/frappe/build.py b/frappe/build.py index ed19574cfd..dfbe20f31e 100644 --- a/frappe/build.py +++ b/frappe/build.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import os import re import json diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index 2ee3b46b7c..0df8878da4 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -1,5 +1,5 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe, json from frappe.model.document import Document diff --git a/frappe/chat/doctype/chat_token/chat_token.py b/frappe/chat/doctype/chat_token/chat_token.py index 63d69a58be..0be51b6081 100644 --- a/frappe/chat/doctype/chat_token/chat_token.py +++ b/frappe/chat/doctype/chat_token/chat_token.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/client.py b/frappe/client.py index 66c457e893..21d10e8271 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe import _ import frappe.model diff --git a/frappe/commands/__init__.py b/frappe/commands/__init__.py index 9ed333d034..6eccdac4fb 100644 --- a/frappe/commands/__init__.py +++ b/frappe/commands/__init__.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import sys import click diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index f2395ae490..b0151106db 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -486,15 +486,26 @@ frappe.db.connect() @click.command('console') +@click.option( + '--autoreload', + is_flag=True, + help="Reload changes to code automatically" +) @pass_context -def console(context): +def console(context, autoreload=False): "Start ipython console for a site" site = get_site(context) frappe.init(site=site) frappe.connect() frappe.local.lang = frappe.db.get_default("lang") - import IPython + from IPython.terminal.embed import InteractiveShellEmbed + + terminal = InteractiveShellEmbed() + if autoreload: + terminal.extension_manager.load_extension("autoreload") + terminal.run_line_magic("autoreload", "2") + all_apps = frappe.get_installed_apps() failed_to_import = [] @@ -509,7 +520,9 @@ def console(context): if failed_to_import: print("\nFailed to import:\n{}".format(", ".join(failed_to_import))) - IPython.embed(display_banner="", header="", colors="neutral") + terminal.colors = "neutral" + terminal.display_banner = False + terminal() @click.command('run-tests') @@ -524,7 +537,7 @@ def console(context): @click.option('--skip-test-records', is_flag=True, default=False, help="Don't create test records") @click.option('--skip-before-tests', is_flag=True, default=False, help="Don't run before tests hook") @click.option('--junit-xml-output', help="Destination file path for junit xml report") -@click.option('--failfast', is_flag=True, default=False) +@click.option('--failfast', is_flag=True, default=False, help="Stop the test run on the first error or failure") @pass_context def run_tests(context, app=None, module=None, doctype=None, test=(), profile=False, coverage=False, junit_xml_output=False, ui_tests = False, doctype_list_path=None, @@ -589,24 +602,26 @@ def run_ui_tests(context, app, headless=False, parallel=True, ci_build_id=None): admin_password = frappe.get_conf(site).admin_password # override baseUrl using env variable - site_env = 'CYPRESS_baseUrl={}'.format(site_url) - password_env = 'CYPRESS_adminPassword={}'.format(admin_password) if admin_password else '' + site_env = f'CYPRESS_baseUrl={site_url}' + password_env = f'CYPRESS_adminPassword={admin_password}' if admin_password else '' os.chdir(app_base_path) node_bin = subprocess.getoutput("npm bin") - cypress_path = "{0}/cypress".format(node_bin) - plugin_path = "{0}/../cypress-file-upload".format(node_bin) + cypress_path = f"{node_bin}/cypress" + plugin_path = f"{node_bin}/../cypress-file-upload" + testing_library_path = f"{node_bin}/../@testing-library" # check if cypress in path...if not, install it. if not ( os.path.exists(cypress_path) and os.path.exists(plugin_path) + and os.path.exists(testing_library_path) and cint(subprocess.getoutput("npm view cypress version")[:1]) >= 6 ): # install cypress click.secho("Installing Cypress...", fg="yellow") - frappe.commands.popen("yarn add cypress@^6 cypress-file-upload@^5 --no-lockfile") + frappe.commands.popen("yarn add cypress@^6 cypress-file-upload@^5 @testing-library/cypress@^8 --no-lockfile") # run for headless mode run_or_open = 'run --browser firefox --record' if headless else 'open' @@ -617,7 +632,7 @@ def run_ui_tests(context, app, headless=False, parallel=True, ci_build_id=None): formatted_command += ' --parallel' if ci_build_id: - formatted_command += ' --ci-build-id {}'.format(ci_build_id) + formatted_command += f' --ci-build-id {ci_build_id}' click.secho("Running Cypress...", fg="yellow") frappe.commands.popen(formatted_command, cwd=app_base_path, raise_err=True) diff --git a/frappe/contacts/address_and_contact.py b/frappe/contacts/address_and_contact.py index 77305168c1..6b71ec50f9 100644 --- a/frappe/contacts/address_and_contact.py +++ b/frappe/contacts/address_and_contact.py @@ -1,5 +1,5 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# License: MIT. See LICENSE import frappe diff --git a/frappe/contacts/doctype/address/address.py b/frappe/contacts/doctype/address/address.py index 755bc63064..5f0619d170 100644 --- a/frappe/contacts/doctype/address/address.py +++ b/frappe/contacts/doctype/address/address.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/contacts/doctype/address/test_address.py b/frappe/contacts/doctype/address/test_address.py index ed61b6f0ee..dd6cd1ca83 100644 --- a/frappe/contacts/doctype/address/test_address.py +++ b/frappe/contacts/doctype/address/test_address.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe, unittest from frappe.contacts.doctype.address.address import get_address_display diff --git a/frappe/contacts/doctype/address_template/address_template.py b/frappe/contacts/doctype/address_template/address_template.py index 2d69a792ab..005f414303 100644 --- a/frappe/contacts/doctype/address_template/address_template.py +++ b/frappe/contacts/doctype/address_template/address_template.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/contacts/doctype/address_template/test_address_template.py b/frappe/contacts/doctype/address_template/test_address_template.py index 6b519a3bb7..b86623b548 100644 --- a/frappe/contacts/doctype/address_template/test_address_template.py +++ b/frappe/contacts/doctype/address_template/test_address_template.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe, unittest class TestAddressTemplate(unittest.TestCase): diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index d1dd1f1010..a1aa8408bf 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -1,5 +1,5 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# License: MIT. See LICENSE import frappe from frappe.utils import cstr, has_gravatar from frappe import _ diff --git a/frappe/contacts/doctype/contact/test_contact.py b/frappe/contacts/doctype/contact/test_contact.py index 6c6089edeb..1170ba843a 100644 --- a/frappe/contacts/doctype/contact/test_contact.py +++ b/frappe/contacts/doctype/contact/test_contact.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/contacts/doctype/contact_email/contact_email.py b/frappe/contacts/doctype/contact_email/contact_email.py index 5fc2fef316..58d37376b8 100644 --- a/frappe/contacts/doctype/contact_email/contact_email.py +++ b/frappe/contacts/doctype/contact_email/contact_email.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/contacts/doctype/contact_phone/contact_phone.py b/frappe/contacts/doctype/contact_phone/contact_phone.py index 63f5f73cf1..ed7d3b9911 100644 --- a/frappe/contacts/doctype/contact_phone/contact_phone.py +++ b/frappe/contacts/doctype/contact_phone/contact_phone.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/contacts/doctype/gender/gender.py b/frappe/contacts/doctype/gender/gender.py index 319800de7e..b4efcb64b9 100644 --- a/frappe/contacts/doctype/gender/gender.py +++ b/frappe/contacts/doctype/gender/gender.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from frappe.model.document import Document diff --git a/frappe/contacts/doctype/gender/test_gender.py b/frappe/contacts/doctype/gender/test_gender.py index 071ed47df0..8549cc2130 100644 --- a/frappe/contacts/doctype/gender/test_gender.py +++ b/frappe/contacts/doctype/gender/test_gender.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest class TestGender(unittest.TestCase): diff --git a/frappe/contacts/doctype/salutation/salutation.py b/frappe/contacts/doctype/salutation/salutation.py index d79ad66845..380af6de28 100644 --- a/frappe/contacts/doctype/salutation/salutation.py +++ b/frappe/contacts/doctype/salutation/salutation.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from frappe.model.document import Document diff --git a/frappe/contacts/doctype/salutation/test_salutation.py b/frappe/contacts/doctype/salutation/test_salutation.py index e2e9075855..59333fb61e 100644 --- a/frappe/contacts/doctype/salutation/test_salutation.py +++ b/frappe/contacts/doctype/salutation/test_salutation.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest class TestSalutation(unittest.TestCase): diff --git a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py index bf48b6b185..671e1c6bc8 100644 --- a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py +++ b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py @@ -1,5 +1,5 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/core/__init__.py b/frappe/core/__init__.py index f064a66c17..98029dd956 100644 --- a/frappe/core/__init__.py +++ b/frappe/core/__init__.py @@ -1,2 +1,2 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/core/doctype/__init__.py b/frappe/core/doctype/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/core/doctype/__init__.py +++ b/frappe/core/doctype/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/core/doctype/access_log/access_log.py b/frappe/core/doctype/access_log/access_log.py index 2ea014f981..0f5776ce2f 100644 --- a/frappe/core/doctype/access_log/access_log.py +++ b/frappe/core/doctype/access_log/access_log.py @@ -1,5 +1,5 @@ # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document @@ -29,4 +29,5 @@ def make_access_log(doctype=None, document=None, method=None, file_type=None, doc.insert(ignore_permissions=True) # `frappe.db.commit` added because insert doesnt `commit` when called in GET requests like `printview` - frappe.db.commit() + if frappe.request and frappe.request.method == 'GET': + frappe.db.commit() diff --git a/frappe/core/doctype/access_log/test_access_log.py b/frappe/core/doctype/access_log/test_access_log.py index 9830507423..42878d0eb4 100644 --- a/frappe/core/doctype/access_log/test_access_log.py +++ b/frappe/core/doctype/access_log/test_access_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # imports - standard imports import unittest diff --git a/frappe/core/doctype/activity_log/activity_log.py b/frappe/core/doctype/activity_log/activity_log.py index efec0dc217..183a1c264c 100644 --- a/frappe/core/doctype/activity_log/activity_log.py +++ b/frappe/core/doctype/activity_log/activity_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from frappe import _ from frappe.utils import get_fullname, now diff --git a/frappe/core/doctype/activity_log/feed.py b/frappe/core/doctype/activity_log/feed.py index 19d7b77184..358272ac63 100644 --- a/frappe/core/doctype/activity_log/feed.py +++ b/frappe/core/doctype/activity_log/feed.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: See license.txt +# License: MIT. See LICENSE import frappe import frappe.permissions diff --git a/frappe/core/doctype/activity_log/test_activity_log.py b/frappe/core/doctype/activity_log/test_activity_log.py index ed7b70cca1..87d3538cc7 100644 --- a/frappe/core/doctype/activity_log/test_activity_log.py +++ b/frappe/core/doctype/activity_log/test_activity_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest import time diff --git a/frappe/core/doctype/block_module/block_module.py b/frappe/core/doctype/block_module/block_module.py index d9723f9170..cc6c222a04 100644 --- a/frappe/core/doctype/block_module/block_module.py +++ b/frappe/core/doctype/block_module/block_module.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py index 2706ab1c30..e28d350d04 100644 --- a/frappe/core/doctype/comment/comment.py +++ b/frappe/core/doctype/comment/comment.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _ import json diff --git a/frappe/core/doctype/comment/test_comment.py b/frappe/core/doctype/comment/test_comment.py index 13db92e7a8..99bd19c106 100644 --- a/frappe/core/doctype/comment/test_comment.py +++ b/frappe/core/doctype/comment/test_comment.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe, json import unittest @@ -30,7 +30,7 @@ class TestComment(unittest.TestCase): from frappe.website.doctype.blog_post.test_blog_post import make_test_blog test_blog = make_test_blog() - frappe.db.sql("delete from `tabComment` where reference_doctype = 'Blog Post'") + frappe.db.delete("Comment", {"reference_doctype": "Blog Post"}) from frappe.templates.includes.comments.comments import add_comment add_comment('Good comment with 10 chars', 'test@test.com', 'Good Tester', @@ -41,7 +41,7 @@ class TestComment(unittest.TestCase): reference_name = test_blog.name ))[0].published, 1) - frappe.db.sql("delete from `tabComment` where reference_doctype = 'Blog Post'") + frappe.db.delete("Comment", {"reference_doctype": "Blog Post"}) add_comment('pleez vizits my site http://mysite.com', 'test@test.com', 'bad commentor', 'Blog Post', test_blog.name, test_blog.route) diff --git a/frappe/core/doctype/communication/__init__.py b/frappe/core/doctype/communication/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/core/doctype/communication/__init__.py +++ b/frappe/core/doctype/communication/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 17b1290776..66bb3909da 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE from collections import Counter import frappe diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index 7ffbe6781d..4d22075b78 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import json diff --git a/frappe/core/doctype/communication/test_communication.py b/frappe/core/doctype/communication/test_communication.py index d50a4db88a..b0c8e1fcee 100644 --- a/frappe/core/doctype/communication/test_communication.py +++ b/frappe/core/doctype/communication/test_communication.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest from urllib.parse import quote diff --git a/frappe/core/doctype/communication_link/communication_link.py b/frappe/core/doctype/communication_link/communication_link.py index d3307d1d32..a895ad3df5 100644 --- a/frappe/core/doctype/communication_link/communication_link.py +++ b/frappe/core/doctype/communication_link/communication_link.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/custom_docperm/custom_docperm.py b/frappe/core/doctype/custom_docperm/custom_docperm.py index 225f5db79b..1790344776 100644 --- a/frappe/core/doctype/custom_docperm/custom_docperm.py +++ b/frappe/core/doctype/custom_docperm/custom_docperm.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/custom_docperm/test_custom_docperm.py b/frappe/core/doctype/custom_docperm/test_custom_docperm.py index 6e0c82d1db..422b711e5b 100644 --- a/frappe/core/doctype/custom_docperm/test_custom_docperm.py +++ b/frappe/core/doctype/custom_docperm/test_custom_docperm.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/custom_role/custom_role.py b/frappe/core/doctype/custom_role/custom_role.py index 89e478dd38..c6630baf6d 100644 --- a/frappe/core/doctype/custom_role/custom_role.py +++ b/frappe/core/doctype/custom_role/custom_role.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/custom_role/test_custom_role.py b/frappe/core/doctype/custom_role/test_custom_role.py index 0ad77524fa..21511a7408 100644 --- a/frappe/core/doctype/custom_role/test_custom_role.py +++ b/frappe/core/doctype/custom_role/test_custom_role.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/data_export/data_export.py b/frappe/core/doctype/data_export/data_export.py index c376b25230..46fe3570a1 100644 --- a/frappe/core/doctype/data_export/data_export.py +++ b/frappe/core/doctype/data_export/data_export.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from frappe.model.document import Document diff --git a/frappe/core/doctype/data_export/exporter.py b/frappe/core/doctype/data_export/exporter.py index ffd828bfdb..7c660c7180 100644 --- a/frappe/core/doctype/data_export/exporter.py +++ b/frappe/core/doctype/data_export/exporter.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/core/doctype/data_import/data_import.py b/frappe/core/doctype/data_import/data_import.py index 50469eeb4d..5935ddc4ba 100644 --- a/frappe/core/doctype/data_import/data_import.py +++ b/frappe/core/doctype/data_import/data_import.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import os diff --git a/frappe/core/doctype/data_import/exporter.py b/frappe/core/doctype/data_import/exporter.py index acaa294a6f..684328a4c7 100644 --- a/frappe/core/doctype/data_import/exporter.py +++ b/frappe/core/doctype/data_import/exporter.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import typing diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index bb922f1f5d..ef2bb02398 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -1,5 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import os import io diff --git a/frappe/core/doctype/data_import/test_data_import.py b/frappe/core/doctype/data_import/test_data_import.py index c9366a97ba..c0e4f50d6d 100644 --- a/frappe/core/doctype/data_import/test_data_import.py +++ b/frappe/core/doctype/data_import/test_data_import.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/core/doctype/data_import/test_exporter.py b/frappe/core/doctype/data_import/test_exporter.py index dfe9926906..cb9461451f 100644 --- a/frappe/core/doctype/data_import/test_exporter.py +++ b/frappe/core/doctype/data_import/test_exporter.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest import frappe from frappe.core.doctype.data_import.exporter import Exporter diff --git a/frappe/core/doctype/data_import/test_importer.py b/frappe/core/doctype/data_import/test_importer.py index 7a4d185d8f..e1bc0e7ca5 100644 --- a/frappe/core/doctype/data_import/test_importer.py +++ b/frappe/core/doctype/data_import/test_importer.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest import frappe from frappe.core.doctype.data_import.importer import Importer diff --git a/frappe/core/doctype/defaultvalue/__init__.py b/frappe/core/doctype/defaultvalue/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/core/doctype/defaultvalue/__init__.py +++ b/frappe/core/doctype/defaultvalue/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/core/doctype/defaultvalue/defaultvalue.py b/frappe/core/doctype/defaultvalue/defaultvalue.py index 0ae088ee96..1d597c7fc4 100644 --- a/frappe/core/doctype/defaultvalue/defaultvalue.py +++ b/frappe/core/doctype/defaultvalue/defaultvalue.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index f4109c8197..b398ec5410 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe import json diff --git a/frappe/core/doctype/deleted_document/test_deleted_document.py b/frappe/core/doctype/deleted_document/test_deleted_document.py index d9dc2bb2d1..fb2376de90 100644 --- a/frappe/core/doctype/deleted_document/test_deleted_document.py +++ b/frappe/core/doctype/deleted_document/test_deleted_document.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/docfield/__init__.py b/frappe/core/doctype/docfield/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/core/doctype/docfield/__init__.py +++ b/frappe/core/doctype/docfield/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index ce62adc8be..b240d29446 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -18,46 +18,53 @@ "hide_seconds", "reqd", "search_index", - "in_list_view", - "in_standard_filter", - "in_global_search", - "in_preview", - "allow_in_quick_entry", - "bold", - "translatable", - "collapsible", - "collapsible_depends_on", - "column_break_6", + "column_break_18", "options", + "defaults_section", "default", + "column_break_6", "fetch_from", "fetch_if_empty", - "permissions", - "depends_on", + "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", - "unique", - "set_only_once", + "allow_on_submit", + "ignore_user_permissions", "allow_bulk_edit", "column_break_13", "permlevel", - "ignore_user_permissions", - "allow_on_submit", - "report_hide", - "remember_last_selected_value", "ignore_xss_filter", - "hide_border", - "property_depends_on_section", - "mandatory_depends_on", + "constraints_section", + "unique", + "no_copy", + "set_only_once", + "remember_last_selected_value", "column_break_38", + "mandatory_depends_on", "read_only_depends_on", "display", - "in_filter", - "no_copy", - "print_hide", - "print_hide_if_no_value", "print_width", "width", + "max_height", "columns", "column_break_22", "description", @@ -153,7 +160,7 @@ "default": "0", "fieldname": "in_standard_filter", "fieldtype": "Check", - "label": "In Standard Filter" + "label": "In List Filter" }, { "default": "0", @@ -197,10 +204,11 @@ "length": 255 }, { - "depends_on": "eval:doc.fieldtype==\"Section Break\"", + "depends_on": "eval:doc.fieldtype==\"Section Break\" && doc.collapsible", "fieldname": "collapsible_depends_on", "fieldtype": "Code", - "label": "Collapsible Depends On", + "label": "Collapsible Depends On (JS)", + "max_height": "3rem", "options": "JS" }, { @@ -220,6 +228,7 @@ "fieldname": "default", "fieldtype": "Small Text", "label": "Default", + "max_height": "3rem", "oldfieldname": "default", "oldfieldtype": "Text" }, @@ -230,10 +239,9 @@ }, { "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" + "label": "Fetch only if value is not set" }, { "fieldname": "permissions", @@ -243,8 +251,9 @@ { "fieldname": "depends_on", "fieldtype": "Code", - "label": "Display Depends On", + "label": "Display Depends On (JS)", "length": 255, + "max_height": "3rem", "oldfieldname": "depends_on", "oldfieldtype": "Data", "options": "JS" @@ -275,10 +284,9 @@ }, { "default": "0", - "description": "Do not allow user to change after set the first time", "fieldname": "set_only_once", "fieldtype": "Check", - "label": "Set Only Once" + "label": "Set only once" }, { "default": "0", @@ -303,7 +311,6 @@ }, { "default": "0", - "description": "User permissions should not apply for this Link", "fieldname": "ignore_user_permissions", "fieldtype": "Check", "label": "Ignore User Permissions" @@ -388,12 +395,14 @@ { "fieldname": "print_width", "fieldtype": "Data", - "label": "Print Width" + "label": "Print Width", + "length": 10 }, { "fieldname": "width", "fieldtype": "Data", "label": "Width", + "length": 10, "oldfieldname": "width", "oldfieldtype": "Data", "print_width": "50px", @@ -436,20 +445,17 @@ { "fieldname": "mandatory_depends_on", "fieldtype": "Code", - "label": "Mandatory Depends On", + "label": "Mandatory Depends On (JS)", + "max_height": "3rem", "options": "JS" }, { "fieldname": "read_only_depends_on", "fieldtype": "Code", - "label": "Read Only Depends On", + "label": "Read Only Depends On (JS)", + "max_height": "3rem", "options": "JS" }, - { - "fieldname": "property_depends_on_section", - "fieldtype": "Section Break", - "label": "Property Depends On" - }, { "fieldname": "column_break_38", "fieldtype": "Column Break" @@ -481,16 +487,56 @@ "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-07-10 21:56:04.167745", + "modified": "2021-09-04 19:41:53.684094", "modified_by": "Administrator", "module": "Core", "name": "DocField", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/frappe/core/doctype/docfield/docfield.py b/frappe/core/doctype/docfield/docfield.py index 175cba3c7c..4dd49631ae 100644 --- a/frappe/core/doctype/docfield/docfield.py +++ b/frappe/core/doctype/docfield/docfield.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/docperm/__init__.py b/frappe/core/doctype/docperm/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/core/doctype/docperm/__init__.py +++ b/frappe/core/doctype/docperm/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/core/doctype/docperm/docperm.py b/frappe/core/doctype/docperm/docperm.py index 9732cde920..4751816dc5 100644 --- a/frappe/core/doctype/docperm/docperm.py +++ b/frappe/core/doctype/docperm/docperm.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/core/doctype/docshare/docshare.py b/frappe/core/doctype/docshare/docshare.py index 2d7b6b9e48..6320fba60b 100644 --- a/frappe/core/doctype/docshare/docshare.py +++ b/frappe/core/doctype/docshare/docshare.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/docshare/test_docshare.py b/frappe/core/doctype/docshare/test_docshare.py index 6551dabbea..cbdaa8ebaf 100644 --- a/frappe/core/doctype/docshare/test_docshare.py +++ b/frappe/core/doctype/docshare/test_docshare.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import frappe.share diff --git a/frappe/core/doctype/doctype/__init__.py b/frappe/core/doctype/doctype/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/core/doctype/doctype/__init__.py +++ b/frappe/core/doctype/doctype/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js index 3f1b5bb7ad..262a6efd90 100644 --- a/frappe/core/doctype/doctype/doctype.js +++ b/frappe/core/doctype/doctype/doctype.js @@ -61,9 +61,73 @@ frappe.ui.form.on('DocType', { __('In Grid View') : __('In List View'); frm.events.autoname(frm); + frm.events.set_naming_rule_description(frm); + }, + + naming_rule: function(frm) { + // set the "autoname" property based on naming_rule + if (frm.doc.naming_rule && !frm.__from_autoname) { + + // flag to avoid recursion + frm.__from_naming_rule = true; + + if (frm.doc.naming_rule=='Set by user') { + frm.set_value('autoname', 'Prompt'); + } else if (frm.doc.naming_rule=='By fieldname') { + frm.set_value('autoname', 'field:'); + } else if (frm.doc.naming_rule=='By "Naming Series" field') { + frm.set_value('autoname', 'naming_series:'); + } else if (frm.doc.naming_rule=='Expression') { + frm.set_value('autoname', 'format:'); + } else if (frm.doc.naming_rule=='Expression (old style)') { + // pass + } else if (frm.doc.naming_rule=='Random') { + frm.set_value('autoname', 'hash'); + } + setTimeout(() =>frm.__from_naming_rule = false, 500); + + frm.events.set_naming_rule_description(frm); + } + + }, + + set_naming_rule_description(frm) { + let naming_rule_description = { + 'Set by user': '', + 'By fieldname': 'Format: field:[fieldname]. Valid fieldname must exist', + 'By "Naming Series" field': 'Format: naming_series:[fieldname]. Fieldname called naming_series must exist', + 'Expression': 'Format: format:EXAMPLE-{MM}morewords{fieldname1}-{fieldname2}-{#####} - Replace all braced words (fieldnames, date words (DD, MM, YY), series) with their value. Outside braces, any characters can be used.', + 'Expression (old style)': 'Format: EXAMPLE-.##### Series by prefix (separated by a dot)', + 'Random': '', + 'By script': '' + }; + + if (frm.doc.naming_rule) { + frm.get_field('autoname').set_description(naming_rule_description[frm.doc.naming_rule]); + } }, autoname: function(frm) { + // set naming_rule based on autoname (for old doctypes where its not been set) + if (frm.doc.autoname && !frm.doc.naming_rule && !frm.__from_naming_rule) { + // flag to avoid recursion + frm.__from_autoname = true; + if (frm.doc.autoname.toLowerCase() === 'prompt') { + frm.set_value('naming_rule', 'Set by user'); + } else if (frm.doc.autoname.startsWith('field:')) { + frm.set_value('naming_rule', 'By fieldname'); + } else if (frm.doc.autoname.startsWith('naming_series:')) { + frm.set_value('naming_rule', 'By "Naming Series" field'); + } else if (frm.doc.autoname.startsWith('format:')) { + frm.set_value('naming_rule', 'Expression'); + } else if (frm.doc.autoname.toLowerCase() === 'hash') { + frm.set_value('naming_rule', 'Random'); + } else { + frm.set_value('naming_rule', 'Expression (old style)'); + } + setTimeout(() => frm.__from_autoname = false, 500); + } + frm.set_df_property('fields', 'reqd', frm.doc.autoname !== 'Prompt'); } }); diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 6a427f71e1..18435f8873 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -26,8 +26,10 @@ "fields_section_break", "fields", "sb1", + "naming_rule", "autoname", "name_case", + "allow_rename", "column_break_15", "description", "documentation", @@ -39,7 +41,6 @@ "column_break_23", "hide_toolbar", "allow_copy", - "allow_rename", "allow_import", "allow_events_in_timeline", "allow_auto_repeat", @@ -276,7 +277,7 @@ "oldfieldtype": "Check" }, { - "default": "0", + "default": "1", "fieldname": "allow_rename", "fieldtype": "Check", "label": "Allow Rename", @@ -554,6 +555,13 @@ "fieldname": "website_search_field", "fieldtype": "Data", "label": "Website Search Field" + }, + { + "fieldname": "naming_rule", + "fieldtype": "Select", + "label": "Naming Rule", + "length": 40, + "options": "\nSet by user\nBy fieldname\nBy \"Naming Series\" field\nExpression\nExpression (old style)\nRandom\nBy script" } ], "icon": "fa fa-bolt", @@ -635,7 +643,7 @@ "link_fieldname": "reference_doctype" } ], - "modified": "2021-06-17 23:31:44.974199", + "modified": "2021-09-05 15:39:13.233403", "modified_by": "Administrator", "module": "Core", "name": "DocType", diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index a7f4dd9def..9bf21690fc 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE # imports - standard imports import re, copy, os, shutil @@ -493,6 +493,9 @@ class DocType(Document): # retain order of 'fields' table and change order in 'field_order' docdict["field_order"] = [f.fieldname for f in self.fields] + if self.custom: + return + path = get_file_path(self.module, "DocType", self.name) if os.path.exists(path): try: @@ -719,20 +722,20 @@ def validate_links_table_fieldnames(meta): for index, link in enumerate(meta.links): link_meta = frappe.get_meta(link.link_doctype) if not link_meta.get_field(link.link_fieldname): - message = _("Row #{0}: Could not find field {1} in {2} DocType").format(index+1, frappe.bold(link.link_fieldname), frappe.bold(link.link_doctype)) + message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format(index+1, frappe.bold(link.link_fieldname), frappe.bold(link.link_doctype)) frappe.throw(message, InvalidFieldNameError, _("Invalid Fieldname")) if link.is_child_table and not meta.get_field(link.table_fieldname): - message = _("Row #{0}: Could not find field {1} in {2} DocType").format(index+1, frappe.bold(link.table_fieldname), frappe.bold(meta.name)) + message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format(index+1, frappe.bold(link.table_fieldname), frappe.bold(meta.name)) frappe.throw(message, frappe.ValidationError, _("Invalid Table Fieldname")) if link.is_child_table: if not link.parent_doctype: - message = _("Row #{0}: Parent DocType is mandatory for internal links").format(index+1) + message = _("Document Links Row #{0}: Parent DocType is mandatory for internal links").format(index+1) frappe.throw(message, frappe.ValidationError, _("Parent Missing")) if not link.table_fieldname: - message = _("Row #{0}: Table Fieldname is mandatory for internal links").format(index+1) + message = _("Document Links Row #{0}: Table Fieldname is mandatory for internal links").format(index+1) frappe.throw(message, frappe.ValidationError, _("Table Fieldname Missing")) def validate_fields_for_doctype(doctype): @@ -1027,6 +1030,9 @@ def validate_fields(meta): frappe.throw(_('Option {0} for field {1} is not a child table') .format(frappe.bold(doctype), frappe.bold(docfield.fieldname)), title=_("Invalid Option")) + def check_max_height(docfield): + if getattr(docfield, 'max_height', None) and (docfield.max_height[-2:] not in ('px', 'em')): + frappe.throw('Max for {} height must be in px, em, rem'.format(frappe.bold(docfield.fieldname))) fields = meta.get("fields") fieldname_list = [d.fieldname for d in fields] @@ -1060,6 +1066,7 @@ def validate_fields(meta): scrub_options_in_select(d) scrub_fetch_from(d) validate_data_field_type(d) + check_max_height(d) check_fold(fields) check_search_fields(meta, fields) @@ -1216,8 +1223,14 @@ def make_module_and_roles(doc, perm_fieldname="permissions"): if ("tabModule Def" in frappe.db.get_tables() and not frappe.db.exists("Module Def", doc.module)): m = frappe.get_doc({"doctype": "Module Def", "module_name": doc.module}) - m.app_name = frappe.local.module_app[frappe.scrub(doc.module)] + if frappe.scrub(doc.module) in frappe.local.module_app: + m.app_name = frappe.local.module_app[frappe.scrub(doc.module)] + else: + m.app_name = 'frappe' m.flags.ignore_mandatory = m.flags.ignore_permissions = True + if frappe.flags.package: + m.package = frappe.flags.package.name + m.custom = 1 m.insert() default_roles = ["Administrator", "Guest", "All"] diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 9aaaf5a1ac..4362a52c34 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest from frappe.core.doctype.doctype.doctype import (UniqueFieldnameError, diff --git a/frappe/core/doctype/doctype_action/doctype_action.py b/frappe/core/doctype/doctype_action/doctype_action.py index 203b06ec1b..807d1bf0b1 100644 --- a/frappe/core/doctype/doctype_action/doctype_action.py +++ b/frappe/core/doctype/doctype_action/doctype_action.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/doctype_link/doctype_link.py b/frappe/core/doctype/doctype_link/doctype_link.py index 07e0efdace..ca2c4efa16 100644 --- a/frappe/core/doctype/doctype_link/doctype_link.py +++ b/frappe/core/doctype/doctype_link/doctype_link.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/document_naming_rule/document_naming_rule.py b/frappe/core/doctype/document_naming_rule/document_naming_rule.py index 10099bd19a..8013f9df6f 100644 --- a/frappe/core/doctype/document_naming_rule/document_naming_rule.py +++ b/frappe/core/doctype/document_naming_rule/document_naming_rule.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/document_naming_rule/test_document_naming_rule.py b/frappe/core/doctype/document_naming_rule/test_document_naming_rule.py index 2206d173d7..50f1386758 100644 --- a/frappe/core/doctype/document_naming_rule/test_document_naming_rule.py +++ b/frappe/core/doctype/document_naming_rule/test_document_naming_rule.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/document_naming_rule_condition/document_naming_rule_condition.py b/frappe/core/doctype/document_naming_rule_condition/document_naming_rule_condition.py index dfca052d95..4706492cea 100644 --- a/frappe/core/doctype/document_naming_rule_condition/document_naming_rule_condition.py +++ b/frappe/core/doctype/document_naming_rule_condition/document_naming_rule_condition.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/document_naming_rule_condition/test_document_naming_rule_condition.py b/frappe/core/doctype/document_naming_rule_condition/test_document_naming_rule_condition.py index 643e963bd7..3d0565234c 100644 --- a/frappe/core/doctype/document_naming_rule_condition/test_document_naming_rule_condition.py +++ b/frappe/core/doctype/document_naming_rule_condition/test_document_naming_rule_condition.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/core/doctype/domain/domain.py b/frappe/core/doctype/domain/domain.py index bbd20f3b70..ebd6e3ac9e 100644 --- a/frappe/core/doctype/domain/domain.py +++ b/frappe/core/doctype/domain/domain.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/core/doctype/domain/test_domain.py b/frappe/core/doctype/domain/test_domain.py index c2686a7566..d7924ebc90 100644 --- a/frappe/core/doctype/domain/test_domain.py +++ b/frappe/core/doctype/domain/test_domain.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/domain_settings/domain_settings.py b/frappe/core/doctype/domain_settings/domain_settings.py index a8c7c6a747..276411c2ab 100644 --- a/frappe/core/doctype/domain_settings/domain_settings.py +++ b/frappe/core/doctype/domain_settings/domain_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/dynamic_link/dynamic_link.py b/frappe/core/doctype/dynamic_link/dynamic_link.py index a7adb9ae72..c0502824c6 100644 --- a/frappe/core/doctype/dynamic_link/dynamic_link.py +++ b/frappe/core/doctype/dynamic_link/dynamic_link.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/error_log/error_log.py b/frappe/core/doctype/error_log/error_log.py index 3d66253b08..39c307520f 100644 --- a/frappe/core/doctype/error_log/error_log.py +++ b/frappe/core/doctype/error_log/error_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/error_log/test_error_log.py b/frappe/core/doctype/error_log/test_error_log.py index d7444ab2a7..54a41cd4a9 100644 --- a/frappe/core/doctype/error_log/test_error_log.py +++ b/frappe/core/doctype/error_log/test_error_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/error_snapshot/error_snapshot.py b/frappe/core/doctype/error_snapshot/error_snapshot.py index 247a796a6b..85143b5aa6 100644 --- a/frappe/core/doctype/error_snapshot/error_snapshot.py +++ b/frappe/core/doctype/error_snapshot/error_snapshot.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/error_snapshot/test_error_snapshot.py b/frappe/core/doctype/error_snapshot/test_error_snapshot.py index 135136294a..86928db9cc 100644 --- a/frappe/core/doctype/error_snapshot/test_error_snapshot.py +++ b/frappe/core/doctype/error_snapshot/test_error_snapshot.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/feedback/feedback.json b/frappe/core/doctype/feedback/feedback.json index cf8a180e27..b77e7a6677 100644 --- a/frappe/core/doctype/feedback/feedback.json +++ b/frappe/core/doctype/feedback/feedback.json @@ -8,8 +8,8 @@ "reference_doctype", "reference_name", "column_break_3", - "email", "rating", + "ip_address", "section_break_6", "feedback" ], @@ -18,12 +18,6 @@ "fieldname": "column_break_3", "fieldtype": "Column Break" }, - { - "fieldname": "email", - "fieldtype": "Data", - "label": "Email", - "reqd": 1 - }, { "fieldname": "rating", "fieldtype": "Float", @@ -56,11 +50,18 @@ "label": "Reference Name", "options": "reference_doctype", "reqd": 1 + }, + { + "fieldname": "ip_address", + "fieldtype": "Data", + "hidden": 1, + "label": "IP Address", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-06-14 15:11:26.005805", + "modified": "2021-06-23 12:45:42.045696", "modified_by": "Administrator", "module": "Core", "name": "Feedback", diff --git a/frappe/core/doctype/feedback/feedback.py b/frappe/core/doctype/feedback/feedback.py index 655bed6eb1..3704ee66e0 100644 --- a/frappe/core/doctype/feedback/feedback.py +++ b/frappe/core/doctype/feedback/feedback.py @@ -1,5 +1,5 @@ # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/feedback/test_feedback.py b/frappe/core/doctype/feedback/test_feedback.py index 702f9d8ac1..7a722ae0d1 100644 --- a/frappe/core/doctype/feedback/test_feedback.py +++ b/frappe/core/doctype/feedback/test_feedback.py @@ -1,5 +1,5 @@ # Copyright (c) 2021, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest @@ -9,19 +9,19 @@ class TestFeedback(unittest.TestCase): from frappe.website.doctype.blog_post.test_blog_post import make_test_blog test_blog = make_test_blog() - frappe.db.sql("delete from `tabFeedback` where reference_doctype = 'Blog Post'") + frappe.db.delete("Feedback", {"reference_doctype": "Blog Post"}) from frappe.templates.includes.feedback.feedback import add_feedback, update_feedback - feedback = add_feedback('Blog Post', test_blog.name, 5, 'New feedback','test@test.com') + feedback = add_feedback('Blog Post', test_blog.name, 5, 'New feedback') self.assertEqual(feedback.feedback, 'New feedback') self.assertEqual(feedback.rating, 5) - updated_feedback = update_feedback('Blog Post', test_blog.name, 6, 'Updated feedback', 'test@test.com') + updated_feedback = update_feedback('Blog Post', test_blog.name, 6, 'Updated feedback') self.assertEqual(updated_feedback.feedback, 'Updated feedback') self.assertEqual(updated_feedback.rating, 6) - frappe.db.sql("delete from `tabFeedback` where reference_doctype = 'Blog Post'") + frappe.db.delete("Feedback", {"reference_doctype": "Blog Post"}) test_blog.delete() \ No newline at end of file diff --git a/frappe/core/doctype/file/file.js b/frappe/core/doctype/file/file.js index bc0cc17553..d40328d3cd 100644 --- a/frappe/core/doctype/file/file.js +++ b/frappe/core/doctype/file/file.js @@ -29,15 +29,8 @@ frappe.ui.form.on("File", "refresh", function(frm) { if (is_optimizable) { frm.add_custom_button(__("Optimize"), function() { frappe.show_alert(__("Optimizing image...")); - frappe.call({ - method: "frappe.core.doctype.file.file.optimize_saved_image", - args: { - doc_name: frm.doc.name, - }, - callback: function() { - frappe.show_alert(__("Image optimized")); - frappe.set_route("List", "File"); - } + frm.call("optimize_file").then(() => { + frappe.show_alert(__("Image optimized")); }); }); } diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index e79b2bd761..d9ecd85533 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE """ record of files @@ -21,11 +21,11 @@ import zipfile import requests import requests.exceptions from PIL import Image, ImageFile, ImageOps -from io import StringIO +from io import BytesIO from urllib.parse import quote, unquote import frappe -from frappe import _, conf +from frappe import _, conf, safe_decode from frappe.model.document import Document from frappe.utils import call_hook_method, cint, cstr, encode, get_files_path, get_hook_method, random_string, strip from frappe.utils.image import strip_exif_data, optimize_image @@ -254,11 +254,11 @@ class File(Document): return file_name = self.file_url.split('/')[-1] try: - with open(get_files_path(file_name, is_private=self.is_private), "rb") as f: + file_path = get_files_path(file_name, is_private=self.is_private) + with open(file_path, "rb") as f: self.content_hash = get_content_hash(f.read()) except IOError: - frappe.msgprint(_("File {0} does not exist").format(self.file_url)) - raise + frappe.throw(_("File {0} does not exist").format(file_path)) def on_trash(self): if self.is_home_folder or self.is_attachments_folder: @@ -270,16 +270,12 @@ class File(Document): def make_thumbnail(self, set_as_thumbnail=True, width=300, height=300, suffix="small", crop=False): if self.file_url: - if self.file_url.startswith("/files"): - try: + try: + if self.file_url.startswith(("/files", "/private/files")): image, filename, extn = get_local_image(self.file_url) - except IOError: - return - - else: - try: + else: image, filename, extn = get_web_image(self.file_url) - except (requests.exceptions.HTTPError, requests.exceptions.SSLError, IOError, TypeError): + except (requests.exceptions.HTTPError, requests.exceptions.SSLError, IOError, TypeError): return size = width, height @@ -289,16 +285,13 @@ class File(Document): image.thumbnail(size, Image.ANTIALIAS) thumbnail_url = filename + "_" + suffix + "." + extn - path = os.path.abspath(frappe.get_site_path("public", thumbnail_url.lstrip("/"))) try: image.save(path) - if set_as_thumbnail: self.db_set("thumbnail_url", thumbnail_url) - self.db_set("thumbnail_url", thumbnail_url) except IOError: frappe.msgprint(_("Unable to write file format for {0}").format(path)) return @@ -321,17 +314,23 @@ class File(Document): self.delete_file_data_content(only_thumbnail=True) def on_rollback(self): - self.flags.on_rollback = True - self.on_trash() + # if original_content flag is set, this rollback should revert the file to its original state + if self.flags.original_content: + file_path = self.get_full_path() + with open(file_path, "wb+") as f: + f.write(self.flags.original_content) + + # following condition is only executed when an insert has been rolledback + else: + self.flags.on_rollback = True + self.on_trash() def unzip(self): '''Unzip current file and replace it by its children''' - if not ".zip" in self.file_name: - frappe.msgprint(_("Not a zip file")) - return + if not self.file_url.endswith(".zip"): + frappe.throw(_("{0} is not a zip file").format(self.file_name)) - zip_path = frappe.get_site_path(self.file_url.strip('/')) - base_url = os.path.dirname(self.file_url) + zip_path = self.get_full_path() files = [] with zipfile.ZipFile(zip_path) as z: @@ -359,10 +358,6 @@ class File(Document): return files - def get_file_url(self): - data = frappe.db.get_value("File", self.file_data_name, ["file_name", "file_url"], as_dict=True) - return data.file_url or data.file_name - def exists_on_disk(self): exists = os.path.exists(self.get_full_path()) return exists @@ -431,47 +426,6 @@ class File(Document): return get_files_path(self.file_name, is_private=self.is_private) - def get_file_doc(self): - '''returns File object (Document) from given parameters or form_dict''' - r = frappe.form_dict - - if self.file_url is None: self.file_url = r.file_url - if self.file_name is None: self.file_name = r.file_name - if self.attached_to_doctype is None: self.attached_to_doctype = r.doctype - if self.attached_to_name is None: self.attached_to_name = r.docname - if self.attached_to_field is None: self.attached_to_field = r.docfield - if self.folder is None: self.folder = r.folder - if self.is_private is None: self.is_private = r.is_private - - if r.filedata: - file_doc = self.save_uploaded() - - elif r.file_url: - file_doc = self.save() - - return file_doc - - - def save_uploaded(self): - self.content = self.get_uploaded_content() - if self.content: - return self.save() - else: - raise Exception - - def get_uploaded_content(self): - # should not be unicode when reading a file, hence using frappe.form - if 'filedata' in frappe.form_dict: - if "," in frappe.form_dict.filedata: - frappe.form_dict.filedata = frappe.form_dict.filedata.rsplit(",", 1)[1] - frappe.uploaded_content = base64.b64decode(frappe.form_dict.filedata) - return frappe.uploaded_content - elif self.content: - return self.content - frappe.msgprint(_('No file attached')) - return None - - def save_file(self, content=None, decode=False, ignore_existing_file_check=False): file_exists = False self.content = content @@ -539,14 +493,6 @@ class File(Document): 'file_url': self.file_url } - def get_file_data_from_hash(self): - for name in frappe.db.sql_list("select name from `tabFile` where content_hash=%s and is_private=%s", - (self.content_hash, self.is_private)): - b = frappe.get_doc('File', name) - return {k: b.get(k) for k in frappe.get_hooks()['write_file_keys']} - return False - - def check_max_file_size(self): max_file_size = get_max_file_size() file_size = len(self.content) @@ -594,6 +540,35 @@ class File(Document): if self.file_url: self.is_private = cint(self.file_url.startswith('/private')) + @frappe.whitelist() + def optimize_file(self): + if self.is_folder: + raise TypeError('Folders cannot be optimized') + + content_type = mimetypes.guess_type(self.file_name)[0] + is_local_image = content_type.startswith('image/') and self.file_size > 0 + is_svg = content_type == 'image/svg+xml' + + if not is_local_image: + raise NotImplementedError('Only local image files can be optimized') + + if is_svg: + raise TypeError('Optimization of SVG images is not supported') + + content = self.get_content() + file_path = self.get_full_path() + optimized_content = optimize_image(content, content_type) + + with open(file_path, 'wb+') as f: + f.write(optimized_content) + + self.file_size = len(optimized_content) + self.content_hash = get_content_hash(optimized_content) + # if rolledback, revert back to original + self.flags.original_content = content + frappe.local.rollback_observers.append(self) + self.save() + def on_doctype_update(): frappe.db.add_index("File", ["attached_to_doctype", "attached_to_name"]) @@ -621,7 +596,8 @@ def create_new_folder(file_name, folder): file.file_name = file_name file.is_folder = 1 file.folder = folder - file.insert() + file.insert(ignore_if_duplicate=True) + return file @frappe.whitelist() def move_file(file_list, new_parent, old_parent): @@ -672,7 +648,7 @@ def get_local_image(file_url): try: image = Image.open(file_path) except IOError: - frappe.msgprint(_("Unable to read file format for {0}").format(file_url), raise_exception=True) + frappe.throw(_("Unable to read file format for {0}").format(file_url)) content = None @@ -704,7 +680,7 @@ def get_web_image(file_url): raise try: - image = Image.open(StringIO(frappe.safe_decode(r.content))) + image = Image.open(BytesIO(r.content)) except Exception as e: frappe.msgprint(_("Image link '{0}' is not valid").format(file_url), raise_exception=e) @@ -740,48 +716,12 @@ def delete_file(path): os.remove(path) -def remove_file(fid=None, attached_to_doctype=None, attached_to_name=None, from_delete=False, delete_permanently=False): - """Remove file and File entry""" - file_name = None - if not (attached_to_doctype and attached_to_name): - attached = frappe.db.get_value("File", fid, - ["attached_to_doctype", "attached_to_name", "file_name"]) - if attached: - attached_to_doctype, attached_to_name, file_name = attached - - ignore_permissions, comment = False, None - if attached_to_doctype and attached_to_name and not from_delete: - doc = frappe.get_doc(attached_to_doctype, attached_to_name) - ignore_permissions = doc.has_permission("write") or False - if frappe.flags.in_web_form: - ignore_permissions = True - if not file_name: - file_name = frappe.db.get_value("File", fid, "file_name") - comment = doc.add_comment("Attachment Removed", _("Removed {0}").format(file_name)) - frappe.delete_doc("File", fid, ignore_permissions=ignore_permissions, delete_permanently=delete_permanently) - - return comment def get_max_file_size(): return cint(conf.get('max_file_size')) or 10485760 -def remove_all(dt, dn, from_delete=False, delete_permanently=False): - """remove all files in a transaction""" - try: - for fid in frappe.db.sql_list("""select name from `tabFile` where - attached_to_doctype=%s and attached_to_name=%s""", (dt, dn)): - if from_delete: - # If deleting a doc, directly delete files - frappe.delete_doc("File", fid, ignore_permissions=True, delete_permanently=delete_permanently) - else: - # Removes file and adds a comment in the document it is attached to - remove_file(fid=fid, attached_to_doctype=dt, attached_to_name=dn, - from_delete=from_delete, delete_permanently=delete_permanently) - except Exception as e: - if e.args[0]!=1054: raise # (temp till for patched) - def has_permission(doc, ptype=None, user=None): has_access = False @@ -827,6 +767,7 @@ def remove_file_by_url(file_url, doctype=None, name=None): fid = frappe.db.get_value("File", {"file_url": file_url}) if fid: + from frappe.utils.file_manager import remove_file return remove_file(fid=fid) @@ -886,15 +827,13 @@ def extract_images_from_html(doc, content): if b"," in content: content = content.split(b",")[1] content = base64.b64decode(content) - + content = optimize_image(content, mtype) if "filename=" in headers: filename = headers.split("filename=")[-1] + filename = safe_decode(filename).split(";")[0] - # decode filename - if not isinstance(filename, str): - filename = str(filename, 'utf-8') else: filename = get_random_filename(content_type=mtype) @@ -922,12 +861,9 @@ def extract_images_from_html(doc, content): return content -def get_random_filename(extn=None, content_type=None): - if extn: - if not extn.startswith("."): - extn = "." + extn - - elif content_type: +def get_random_filename(content_type=None): + extn = None + if content_type: extn = mimetypes.guess_extension(content_type) return random_string(7) + (extn or "") @@ -938,24 +874,8 @@ def unzip_file(name): '''Unzip the given file and make file records for each of the extracted files''' file_obj = frappe.get_doc('File', name) files = file_obj.unzip() - return len(files) + return files -@frappe.whitelist() -def optimize_saved_image(doc_name): - file_doc = frappe.get_doc('File', doc_name) - content = file_doc.get_content() - content_type = mimetypes.guess_type(file_doc.file_name)[0] - - optimized_content = optimize_image(content, content_type) - - file_path = get_files_path(is_private=file_doc.is_private) - file_path = os.path.join(file_path.encode('utf-8'), file_doc.file_name.encode('utf-8')) - with open(file_path, 'wb+') as f: - f.write(optimized_content) - - file_doc.file_size = len(optimized_content) - file_doc.content_hash = get_content_hash(optimized_content) - file_doc.save() @frappe.whitelist() def get_attached_images(doctype, names): @@ -979,13 +899,6 @@ def get_attached_images(doctype, names): return out -@frappe.whitelist() -def validate_filename(filename): - from frappe.utils import now_datetime - timestamp = now_datetime().strftime(" %Y-%m-%d %H:%M:%S") - fname = get_file_name(filename, timestamp) - return fname - @frappe.whitelist() def get_files_in_folder(folder, start=0, page_length=20): start = cint(start) diff --git a/frappe/core/doctype/file/test_file.py b/frappe/core/doctype/file/test_file.py index 649010c468..4538ffb6bb 100644 --- a/frappe/core/doctype/file/test_file.py +++ b/frappe/core/doctype/file/test_file.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import base64 +import json import frappe import os import unittest from frappe import _ -from frappe.core.doctype.file.file import move_file, get_files_in_folder +from frappe.core.doctype.file.file import get_attached_images, move_file, get_files_in_folder, unzip_file from frappe.utils import get_files_path # test_records = frappe.get_test_records('File') @@ -365,6 +366,81 @@ class TestFile(unittest.TestCase): file1.file_url = '/private/files/parent_dir2.txt' file1.save() + def test_file_url_validation(self): + test_file = frappe.get_doc({ + "doctype": "File", + "file_name": 'logo', + "file_url": 'https://frappe.io/files/frappe.png' + }) + + self.assertIsNone(test_file.validate()) + + # bad path + test_file.file_url = "/usr/bin/man" + self.assertRaisesRegex(frappe.exceptions.ValidationError, "URL must start with http:// or https://", test_file.validate) + + test_file.file_url = None + test_file.file_name = "/usr/bin/man" + self.assertRaisesRegex(frappe.exceptions.ValidationError, "There is some problem with the file url", test_file.validate) + + test_file.file_url = None + test_file.file_name = "_file" + self.assertRaisesRegex(IOError, "does not exist", test_file.validate) + + test_file.file_url = None + test_file.file_name = "/private/files/_file" + self.assertRaisesRegex(IOError, "does not exist", test_file.validate) + + def test_make_thumbnail(self): + # test web image + test_file = frappe.get_doc({ + "doctype": "File", + "file_name": 'logo', + "file_url": frappe.utils.get_url('/_test/assets/image.jpg'), + }).insert(ignore_permissions=True) + + test_file.make_thumbnail() + self.assertEquals(test_file.thumbnail_url, '/files/image_small.jpg') + + # test local image + test_file.db_set('thumbnail_url', None) + test_file.reload() + test_file.file_url = "/files/image_small.jpg" + test_file.make_thumbnail(suffix="xs", crop=True) + self.assertEquals(test_file.thumbnail_url, '/files/image_small_xs.jpg') + + frappe.clear_messages() + test_file.db_set('thumbnail_url', None) + test_file.reload() + test_file.file_url = frappe.utils.get_url('unknown.jpg') + test_file.make_thumbnail(suffix="xs") + self.assertEqual(json.loads(frappe.message_log[0]), {"message": f"File '{frappe.utils.get_url('unknown.jpg')}' not found"}) + self.assertEquals(test_file.thumbnail_url, None) + + def test_file_unzip(self): + file_path = frappe.get_app_path('frappe', 'www/_test/assets/file.zip') + public_file_path = frappe.get_site_path('public', 'files') + try: + import shutil + shutil.copy(file_path, public_file_path) + except Exception: + pass + + test_file = frappe.get_doc({ + "doctype": "File", + "file_url": '/files/file.zip', + }).insert(ignore_permissions=True) + + self.assertListEqual([file.file_name for file in unzip_file(test_file.name)], + ['css_asset.css', 'image.jpg', 'js_asset.min.js']) + + test_file = frappe.get_doc({ + "doctype": "File", + "file_url": frappe.utils.get_url('/_test/assets/image.jpg'), + }).insert(ignore_permissions=True) + self.assertRaisesRegex(frappe.exceptions.ValidationError, 'not a zip file', test_file.unzip) + + class TestAttachment(unittest.TestCase): test_doctype = 'Test For Attachment' @@ -469,3 +545,93 @@ class TestAttachmentsAccess(unittest.TestCase): frappe.set_user('Administrator') frappe.db.rollback() + + +class TestFileUtils(unittest.TestCase): + def test_extract_images_from_doc(self): + # with filename in data URI + todo = frappe.get_doc({ + "doctype": "ToDo", + "description": 'Test ' + }).insert() + self.assertTrue(frappe.db.exists("File", {"attached_to_name": todo.name})) + self.assertIn('', todo.description) + self.assertListEqual(get_attached_images('ToDo', [todo.name])[todo.name], ['/files/pix.png']) + + # without filename in data URI + todo = frappe.get_doc({ + "doctype": "ToDo", + "description": 'Test ' + }).insert() + filename = frappe.db.exists("File", {"attached_to_name": todo.name}) + self.assertIn(f' { frm.set_df_property('app_name', 'options', JSON.parse(r)); + if (!frm.doc.app_name) { + frm.set_value('app_name', 'frappe'); + } }); } }); diff --git a/frappe/core/doctype/module_def/module_def.json b/frappe/core/doctype/module_def/module_def.json index 4de046bbb6..7ddc55fce5 100644 --- a/frappe/core/doctype/module_def/module_def.json +++ b/frappe/core/doctype/module_def/module_def.json @@ -8,6 +8,7 @@ "field_order": [ "module_name", "custom", + "package", "app_name", "restrict_to_domain" ], @@ -23,6 +24,7 @@ "unique": 1 }, { + "depends_on": "eval:!doc.custom", "fieldname": "app_name", "fieldtype": "Select", "in_list_view": 1, @@ -41,24 +43,84 @@ "fieldname": "custom", "fieldtype": "Check", "label": "Custom" + }, + { + "depends_on": "custom", + "fieldname": "package", + "fieldtype": "Link", + "label": "Package", + "options": "Package" } ], "icon": "fa fa-sitemap", "idx": 1, "links": [ { + "group": "DocType", "link_doctype": "DocType", "link_fieldname": "module" }, { + "group": "DocType", + "link_doctype": "Client Script", + "link_fieldname": "module" + }, + { + "group": "DocType", + "link_doctype": "Server Script", + "link_fieldname": "module" + }, + { + "group": "Website", + "link_doctype": "Web Page", + "link_fieldname": "module" + }, + { + "group": "Website", + "link_doctype": "Web Template", + "link_fieldname": "module" + }, + { + "group": "Website", + "link_doctype": "Website Theme", + "link_fieldname": "module" + }, + { + "group": "Website", + "link_doctype": "Web Form", + "link_fieldname": "module" + }, + { + "group": "Customization", "link_doctype": "Workspace", "link_fieldname": "module" + }, + { + "group": "Customization", + "link_doctype": "Custom Field", + "link_fieldname": "module" + }, + { + "group": "Customization", + "link_doctype": "Property Setter", + "link_fieldname": "module" + }, + { + "group": "Customization", + "link_doctype": "Print Format", + "link_fieldname": "module" + }, + { + "group": "Customization", + "link_doctype": "Notification", + "link_fieldname": "module" } ], - "modified": "2021-06-02 13:04:53.118716", + "modified": "2021-09-05 21:58:40.253909", "modified_by": "Administrator", "module": "Core", "name": "Module Def", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ { diff --git a/frappe/core/doctype/module_def/module_def.py b/frappe/core/doctype/module_def/module_def.py index 68025c83bb..6b420430b8 100644 --- a/frappe/core/doctype/module_def/module_def.py +++ b/frappe/core/doctype/module_def/module_def.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe, os, json diff --git a/frappe/core/doctype/module_def/test_module_def.py b/frappe/core/doctype/module_def/test_module_def.py index 3a3ceb4b57..69a114d765 100644 --- a/frappe/core/doctype/module_def/test_module_def.py +++ b/frappe/core/doctype/module_def/test_module_def.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/module_profile/module_profile.py b/frappe/core/doctype/module_profile/module_profile.py index 373e5078d0..930c3879b6 100644 --- a/frappe/core/doctype/module_profile/module_profile.py +++ b/frappe/core/doctype/module_profile/module_profile.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from frappe.model.document import Document diff --git a/frappe/core/doctype/module_profile/test_module_profile.py b/frappe/core/doctype/module_profile/test_module_profile.py index e0d9c13371..e676767db6 100644 --- a/frappe/core/doctype/module_profile/test_module_profile.py +++ b/frappe/core/doctype/module_profile/test_module_profile.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/navbar_item/navbar_item.py b/frappe/core/doctype/navbar_item/navbar_item.py index a8fa611374..d4952a75f2 100644 --- a/frappe/core/doctype/navbar_item/navbar_item.py +++ b/frappe/core/doctype/navbar_item/navbar_item.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/navbar_item/test_navbar_item.py b/frappe/core/doctype/navbar_item/test_navbar_item.py index 85852a45e8..bb4b2a837a 100644 --- a/frappe/core/doctype/navbar_item/test_navbar_item.py +++ b/frappe/core/doctype/navbar_item/test_navbar_item.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/core/doctype/navbar_settings/navbar_settings.py b/frappe/core/doctype/navbar_settings/navbar_settings.py index 60aec67a00..fd8db31d10 100644 --- a/frappe/core/doctype/navbar_settings/navbar_settings.py +++ b/frappe/core/doctype/navbar_settings/navbar_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/navbar_settings/test_navbar_settings.py b/frappe/core/doctype/navbar_settings/test_navbar_settings.py index 4d1ee72815..01497d9035 100644 --- a/frappe/core/doctype/navbar_settings/test_navbar_settings.py +++ b/frappe/core/doctype/navbar_settings/test_navbar_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/core/doctype/package/__init__.py b/frappe/core/doctype/package/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/package/licenses/GNU Affero General Public License.md b/frappe/core/doctype/package/licenses/GNU Affero General Public License.md new file mode 100644 index 0000000000..c7f159aed8 --- /dev/null +++ b/frappe/core/doctype/package/licenses/GNU Affero General Public License.md @@ -0,0 +1,614 @@ +### GNU AFFERO GENERAL PUBLIC LICENSE + +Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +### Preamble + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains +free software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing +under this license. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS + +#### 0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public +License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +#### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +#### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +#### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +#### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 + to "keep intact all notices". +- c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +#### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. +- d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +#### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or +- e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +#### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +#### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +#### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +#### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +#### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +#### 13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your +version supports such interaction) an opportunity to receive the +Corresponding Source of your version by providing access to the +Corresponding Source from a network server at no charge, through some +standard or customary means of facilitating copying of software. This +Corresponding Source shall include the Corresponding Source for any +work covered by version 3 of the GNU General Public License that is +incorporated pursuant to the following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Affero General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever +published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +#### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +#### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +#### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. diff --git a/frappe/core/doctype/package/licenses/GNU General Public License.md b/frappe/core/doctype/package/licenses/GNU General Public License.md new file mode 100644 index 0000000000..c4580f2eb6 --- /dev/null +++ b/frappe/core/doctype/package/licenses/GNU General Public License.md @@ -0,0 +1,617 @@ +### GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +### Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom +to share and change all versions of a program--to make sure it remains +free software for all its users. We, the Free Software Foundation, use +the GNU General Public License for most of our software; it applies +also to any other work released this way by its authors. You can apply +it to your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you +have certain responsibilities if you distribute copies of the +software, or if you modify it: responsibilities to respect the freedom +of others. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + +Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + +Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the +manufacturer can do so. This is fundamentally incompatible with the +aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for +individuals to use, which is precisely where it is most unacceptable. +Therefore, we have designed this version of the GPL to prohibit the +practice for those products. If such problems arise substantially in +other domains, we stand ready to extend this provision to those +domains in future versions of the GPL, as needed to protect the +freedom of users. + +Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish +to avoid the special danger that patents applied to a free program +could make it effectively proprietary. To prevent this, the GPL +assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and +modification follow. + +### TERMS AND CONDITIONS + +#### 0. Definitions. + +"This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +#### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +#### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +#### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +#### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 + to "keep intact all notices". +- c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +#### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. +- d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +#### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or +- e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +#### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +#### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +#### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +#### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +#### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +#### 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU General Public +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that numbered version or +of any later version published by the Free Software Foundation. If the +Program does not specify a version number of the GNU General Public +License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU General Public License can be used, that proxy's public +statement of acceptance of a version permanently authorizes you to +choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +#### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +#### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +#### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. diff --git a/frappe/core/doctype/package/licenses/MIT License.md b/frappe/core/doctype/package/licenses/MIT License.md new file mode 100644 index 0000000000..c038ee76ae --- /dev/null +++ b/frappe/core/doctype/package/licenses/MIT License.md @@ -0,0 +1,17 @@ +### MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies +or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/frappe/core/doctype/package/package.js b/frappe/core/doctype/package/package.js new file mode 100644 index 0000000000..90e2eed1e3 --- /dev/null +++ b/frappe/core/doctype/package/package.js @@ -0,0 +1,17 @@ +// Copyright (c) 2021, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Package', { + validate: function(frm) { + if (!frm.doc.package_name) { + frm.set_value('package_name', frm.doc.name.toLowerCase().replace(' ', '-')); + } + }, + + license_type: function(frm) { + frappe.call('frappe.core.doctype.package.package.get_license_text', + {'license_type': frm.doc.license_type}).then(r => { + frm.set_value('license', r.message); + }); + } +}); diff --git a/frappe/core/doctype/package/package.json b/frappe/core/doctype/package/package.json new file mode 100644 index 0000000000..285e17a5bb --- /dev/null +++ b/frappe/core/doctype/package/package.json @@ -0,0 +1,76 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "Prompt", + "creation": "2021-09-04 11:54:35.155687", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "package_name", + "readme", + "license_type", + "license" + ], + "fields": [ + { + "fieldname": "readme", + "fieldtype": "Markdown Editor", + "label": "Readme" + }, + { + "fieldname": "package_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Package Name", + "reqd": 1 + }, + { + "fieldname": "license_type", + "fieldtype": "Select", + "label": "License Type", + "options": "\nMIT License\nGNU General Public License\nGNU Affero General Public License" + }, + { + "fieldname": "license", + "fieldtype": "Markdown Editor", + "label": "License" + } + ], + "index_web_pages_for_search": 1, + "links": [ + { + "group": "Modules", + "link_doctype": "Module Def", + "link_fieldname": "package" + }, + { + "group": "Release", + "link_doctype": "Package Release", + "link_fieldname": "package" + } + ], + "modified": "2021-09-05 13:15:01.130982", + "modified_by": "Administrator", + "module": "Core", + "name": "Package", + "naming_rule": "Set by user", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/core/doctype/package/package.py b/frappe/core/doctype/package/package.py new file mode 100644 index 0000000000..aa9735c061 --- /dev/null +++ b/frappe/core/doctype/package/package.py @@ -0,0 +1,18 @@ +# Copyright (c) 2021, Frappe Technologies and contributors +# For license information, please see license.txt + +import frappe +import os +from frappe.model.document import Document + +class Package(Document): + def validate(self): + if not self.package_name: + self.package_name = self.name.lower().replace(' ', '-') + +@frappe.whitelist() +def get_license_text(license_type): + with open(os.path.join(os.path.dirname(__file__), 'licenses', + license_type + '.md'), 'r') as textfile: + return textfile.read() + diff --git a/frappe/core/doctype/package/test_package.py b/frappe/core/doctype/package/test_package.py new file mode 100644 index 0000000000..3fb8d48274 --- /dev/null +++ b/frappe/core/doctype/package/test_package.py @@ -0,0 +1,89 @@ +# Copyright (c) 2021, Frappe Technologies and Contributors +# See license.txt + +import frappe +import os +import json +import unittest + +class TestPackage(unittest.TestCase): + def test_package_release(self): + make_test_package() + make_test_module() + make_test_doctype() + make_test_server_script() + make_test_web_page() + + # make release + frappe.get_doc(dict( + doctype = 'Package Release', + package = 'Test Package', + publish = 1 + )).insert() + + self.assertTrue(os.path.exists(frappe.get_site_path('packages', 'test-package'))) + self.assertTrue(os.path.exists(frappe.get_site_path('packages', 'test-package', 'test_module_for_package'))) + self.assertTrue(os.path.exists(frappe.get_site_path('packages', 'test-package', 'test_module_for_package', 'doctype', 'test_doctype_for_package'))) + with open(frappe.get_site_path('packages', 'test-package', 'test_module_for_package', + 'doctype', 'test_doctype_for_package', 'test_doctype_for_package.json')) as f: + doctype = json.loads(f.read()) + self.assertEqual(doctype['doctype'], 'DocType') + self.assertEqual(doctype['name'], 'Test DocType for Package') + self.assertEqual(doctype['fields'][0]['fieldname'], 'test_field') + + +def make_test_package(): + if not frappe.db.exists('Package', 'Test Package'): + frappe.get_doc(dict( + doctype = 'Package', + name = 'Test Package', + package_name = 'test-package', + readme = '# Test Package' + )).insert() + +def make_test_module(): + if not frappe.db.exists('Module Def', 'Test Module for Package'): + frappe.get_doc(dict( + doctype = 'Module Def', + module_name = 'Test Module for Package', + custom = 1, + app_name = 'frappe', + package = 'Test Package' + )).insert() + +def make_test_doctype(): + if not frappe.db.exists('DocType', 'Test DocType for Package'): + frappe.get_doc(dict( + doctype = 'DocType', + name = 'Test DocType for Package', + custom = 1, + module = 'Test Module for Package', + autoname = 'Prompt', + fields = [dict( + fieldname = 'test_field', + fieldtype = 'Data', + label = 'Test Field' + )] + )).insert() + +def make_test_server_script(): + if not frappe.db.exists('Server Script', 'Test Script for Package'): + frappe.get_doc(dict( + doctype = 'Server Script', + name = 'Test Script for Package', + module = 'Test Module for Package', + script_type = 'DocType Event', + reference_doctype = 'Test DocType for Package', + doctype_event = 'Before Save', + script = 'frappe.msgprint("Test")' + )).insert() + +def make_test_web_page(): + if not frappe.db.exists('Web Page', 'test-web-page-for-package'): + frappe.get_doc(dict( + doctype = "Web Page", + module = 'Test Module for Package', + main_section = "Some content", + published = 1, + title = "Test Web Page for Package" + )).insert() diff --git a/frappe/core/doctype/package_import/__init__.py b/frappe/core/doctype/package_import/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/package_import/package_import.js b/frappe/core/doctype/package_import/package_import.js new file mode 100644 index 0000000000..c01a6266cc --- /dev/null +++ b/frappe/core/doctype/package_import/package_import.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Package Import', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/core/doctype/package_import/package_import.json b/frappe/core/doctype/package_import/package_import.json new file mode 100644 index 0000000000..f3c6168f8d --- /dev/null +++ b/frappe/core/doctype/package_import/package_import.json @@ -0,0 +1,65 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "format:Package Import at {creation}", + "creation": "2021-09-05 16:36:46.680094", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "attach_package", + "activate", + "force", + "log" + ], + "fields": [ + { + "fieldname": "attach_package", + "fieldtype": "Attach", + "label": "Attach Package" + }, + { + "default": "0", + "fieldname": "activate", + "fieldtype": "Check", + "label": "Activate" + }, + { + "fieldname": "log", + "fieldtype": "Code", + "label": "Log", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "force", + "fieldtype": "Check", + "label": "Force" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-09-05 21:30:04.796090", + "modified_by": "Administrator", + "module": "Core", + "name": "Package Import", + "naming_rule": "Expression", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/core/doctype/package_import/package_import.py b/frappe/core/doctype/package_import/package_import.py new file mode 100644 index 0000000000..f4a2d666dd --- /dev/null +++ b/frappe/core/doctype/package_import/package_import.py @@ -0,0 +1,58 @@ +# Copyright (c) 2021, Frappe Technologies and contributors +# For license information, please see license.txt + +import frappe +import os +import json +import subprocess +from frappe.model.document import Document +from frappe.desk.form.load import get_attachments +from frappe.model.sync import get_doc_files +from frappe.modules.import_file import import_file_by_path, import_doc + +class PackageImport(Document): + def validate(self): + if self.activate: + self.import_package() + + def import_package(self): + attachment = get_attachments(self.doctype, self.name) + + if not attachment: + frappe.throw(frappe._('Please attach the package')) + + attachment = attachment[0] + + # get package_name from file (package_name-0.0.0.tar.gz) + package_name = attachment.file_name.split('.')[0].rsplit('-', 1)[0] + if not os.path.exists(frappe.get_site_path('packages')): + os.makedirs(frappe.get_site_path('packages')) + + # extract + subprocess.check_output(['tar', 'xzf', + frappe.get_site_path(attachment.file_url.strip('/')), '-C', + frappe.get_site_path('packages')]) + + package_path = frappe.get_site_path('packages', package_name) + + # import Package + with open(os.path.join(package_path, package_name + '.json'), 'r') as packagefile: + doc_dict = json.loads(packagefile.read()) + + frappe.flags.package = import_doc(doc_dict) + + # collect modules + files = [] + log = [] + for module in os.listdir(package_path): + module_path = os.path.join(package_path, module) + if os.path.isdir(module_path): + get_doc_files(files, module_path) + + # import files + for file in files: + import_file_by_path(file, force=self.force, ignore_version=True, + for_sync=True) + log.append('Imported {}'.format(file)) + + self.log = '\n'.join(log) diff --git a/frappe/core/doctype/package_import/test_package_import.py b/frappe/core/doctype/package_import/test_package_import.py new file mode 100644 index 0000000000..04628fed93 --- /dev/null +++ b/frappe/core/doctype/package_import/test_package_import.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies and Contributors +# See license.txt + +# import frappe +import unittest + +class TestPackageImport(unittest.TestCase): + pass diff --git a/frappe/core/doctype/package_release/__init__.py b/frappe/core/doctype/package_release/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/package_release/package_release.js b/frappe/core/doctype/package_release/package_release.js new file mode 100644 index 0000000000..9eabe36839 --- /dev/null +++ b/frappe/core/doctype/package_release/package_release.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Package Release', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/core/doctype/package_release/package_release.json b/frappe/core/doctype/package_release/package_release.json new file mode 100644 index 0000000000..b651d699c4 --- /dev/null +++ b/frappe/core/doctype/package_release/package_release.json @@ -0,0 +1,95 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2021-09-05 12:59:01.932327", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "package", + "publish", + "path", + "column_break_3", + "major", + "minor", + "patch", + "section_break_7", + "release_notes" + ], + "fields": [ + { + "fieldname": "package", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Package", + "options": "Package", + "reqd": 1 + }, + { + "fieldname": "major", + "fieldtype": "Int", + "label": "Major" + }, + { + "fieldname": "minor", + "fieldtype": "Int", + "label": "Minor" + }, + { + "fieldname": "patch", + "fieldtype": "Int", + "label": "Patch", + "no_copy": 1 + }, + { + "fieldname": "path", + "fieldtype": "Small Text", + "label": "Path", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "fieldname": "release_notes", + "fieldtype": "Markdown Editor", + "label": "Release Notes" + }, + { + "default": "0", + "fieldname": "publish", + "fieldtype": "Check", + "label": "Publish" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-09-05 16:04:32.860988", + "modified_by": "Administrator", + "module": "Core", + "name": "Package Release", + "naming_rule": "By script", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/core/doctype/package_release/package_release.py b/frappe/core/doctype/package_release/package_release.py new file mode 100644 index 0000000000..1fb8796882 --- /dev/null +++ b/frappe/core/doctype/package_release/package_release.py @@ -0,0 +1,81 @@ +# Copyright (c) 2021, Frappe Technologies and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document +from frappe.modules.export_file import export_doc +import os +import subprocess + +class PackageRelease(Document): + def set_version(self): + # set the next patch release by default + if not self.major: + self.major = frappe.db.max('Package Release', 'major', dict(package=self.package)) + if not self.minor: + self.minor = frappe.db.max('Package Release', 'minor', dict(package=self.package)) + if not self.patch: + self.patch = frappe.db.max('Package Release', 'patch', dict(package=self.package)) + 1 + + def autoname(self): + self.set_version() + self.name = '{}-{}.{}.{}'.format( + frappe.db.get_value('Package', self.package, 'package_name'), + self.major, self.minor, self.patch) + + def validate(self): + if self.publish: + self.export_files() + + def export_files(self): + '''Export all the documents in this package to site/packages folder''' + package = frappe.get_doc('Package', self.package) + + self.export_modules() + self.export_package_files(package) + self.make_tarfile(package) + + def export_modules(self): + for m in frappe.db.get_all('Module Def', dict(package=self.package)): + module = frappe.get_doc('Module Def', m.name) + for l in module.meta.links: + if l.link_doctype == 'Module Def': + continue + # all documents of the type in the module + for d in frappe.get_all(l.link_doctype, dict(module=m.name)): + export_doc(frappe.get_doc(l.link_doctype, d.name)) + + def export_package_files(self, package): + # write readme + with open(frappe.get_site_path('packages', package.package_name, 'README.md'), 'w') as readme: + readme.write(package.readme) + + # write license + if package.license: + with open(frappe.get_site_path('packages', package.package_name, 'LICENSE.md'), 'w') as license: + license.write(package.license) + + # write package.json as `frappe_package.json` + with open(frappe.get_site_path('packages', package.package_name, package.package_name + '.json'), 'w') as packagefile: + packagefile.write(frappe.as_json(package.as_dict(no_nulls=True))) + + def make_tarfile(self, package): + # make tarfile + filename = '{}.tar.gz'.format(self.name) + subprocess.check_output(['tar', 'czf', filename, package.package_name], + cwd=frappe.get_site_path('packages')) + + # move file + subprocess.check_output(['mv', frappe.get_site_path('packages', filename), + frappe.get_site_path('public', 'files')]) + + # make attachment + file = frappe.get_doc(dict( + doctype = 'File', + file_url = '/' + os.path.join('files', filename), + attached_to_doctype = self.doctype, + attached_to_name = self.name + )) + + file.flags.ignore_duplicate_entry_error = True + file.insert() diff --git a/frappe/core/doctype/package_release/test_package_release.py b/frappe/core/doctype/package_release/test_package_release.py new file mode 100644 index 0000000000..6a15e8625b --- /dev/null +++ b/frappe/core/doctype/package_release/test_package_release.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies and Contributors +# See license.txt + +# import frappe +import unittest + +class TestPackageRelease(unittest.TestCase): + pass diff --git a/frappe/core/doctype/page/__init__.py b/frappe/core/doctype/page/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/core/doctype/page/__init__.py +++ b/frappe/core/doctype/page/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/core/doctype/page/page.py b/frappe/core/doctype/page/page.py index 5aa18cd9e3..894e180bb1 100644 --- a/frappe/core/doctype/page/page.py +++ b/frappe/core/doctype/page/page.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import os diff --git a/frappe/core/doctype/page/test_page.py b/frappe/core/doctype/page/test_page.py index 18b4aea2c8..7db32497a8 100644 --- a/frappe/core/doctype/page/test_page.py +++ b/frappe/core/doctype/page/test_page.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/patch_log/__init__.py b/frappe/core/doctype/patch_log/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/core/doctype/patch_log/__init__.py +++ b/frappe/core/doctype/patch_log/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/core/doctype/patch_log/patch_log.py b/frappe/core/doctype/patch_log/patch_log.py index cc66955eb8..9a5da24e37 100644 --- a/frappe/core/doctype/patch_log/patch_log.py +++ b/frappe/core/doctype/patch_log/patch_log.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/core/doctype/patch_log/test_patch_log.py b/frappe/core/doctype/patch_log/test_patch_log.py index d0690ecee0..df1ca16b22 100644 --- a/frappe/core/doctype/patch_log/test_patch_log.py +++ b/frappe/core/doctype/patch_log/test_patch_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/payment_gateway/payment_gateway.py b/frappe/core/doctype/payment_gateway/payment_gateway.py index 1459635b01..d0fa550ea1 100644 --- a/frappe/core/doctype/payment_gateway/payment_gateway.py +++ b/frappe/core/doctype/payment_gateway/payment_gateway.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/payment_gateway/test_payment_gateway.py b/frappe/core/doctype/payment_gateway/test_payment_gateway.py index 66f899bd27..e2ad081cfa 100644 --- a/frappe/core/doctype/payment_gateway/test_payment_gateway.py +++ b/frappe/core/doctype/payment_gateway/test_payment_gateway.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index c68bb6a4f1..2d1b026572 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import json @@ -11,8 +11,6 @@ from frappe.desk.query_report import generate_report_result from frappe.model.document import Document from frappe.utils import gzip_compress, gzip_decompress from frappe.utils.background_jobs import enqueue -from frappe.core.doctype.file.file import remove_all - class PreparedReport(Document): def before_insert(self): diff --git a/frappe/core/doctype/prepared_report/test_prepared_report.py b/frappe/core/doctype/prepared_report/test_prepared_report.py index ef324dd01a..5b12990f64 100644 --- a/frappe/core/doctype/prepared_report/test_prepared_report.py +++ b/frappe/core/doctype/prepared_report/test_prepared_report.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest import json diff --git a/frappe/core/doctype/report/__init__.py b/frappe/core/doctype/report/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/core/doctype/report/__init__.py +++ b/frappe/core/doctype/report/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/core/doctype/report/boilerplate/controller.py b/frappe/core/doctype/report/boilerplate/controller.py index b8e9cb7467..ccf732a405 100644 --- a/frappe/core/doctype/report/boilerplate/controller.py +++ b/frappe/core/doctype/report/boilerplate/controller.py @@ -1,5 +1,5 @@ # Copyright (c) 2013, {app_publisher} and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index a5c61fa436..6a54314667 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import json, datetime from frappe import _, scrub diff --git a/frappe/core/doctype/report/test_report.py b/frappe/core/doctype/report/test_report.py index 9c953db1f0..36e3b09254 100644 --- a/frappe/core/doctype/report/test_report.py +++ b/frappe/core/doctype/report/test_report.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe, json, os import unittest diff --git a/frappe/core/doctype/report_column/report_column.py b/frappe/core/doctype/report_column/report_column.py index f9078d820d..3b2c1e130b 100644 --- a/frappe/core/doctype/report_column/report_column.py +++ b/frappe/core/doctype/report_column/report_column.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/report_filter/report_filter.py b/frappe/core/doctype/report_filter/report_filter.py index ccdcc0eb6f..b325985308 100644 --- a/frappe/core/doctype/report_filter/report_filter.py +++ b/frappe/core/doctype/report_filter/report_filter.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/role/__init__.py b/frappe/core/doctype/role/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/core/doctype/role/__init__.py +++ b/frappe/core/doctype/role/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/core/doctype/role/role.py b/frappe/core/doctype/role/role.py index 28b444e1e7..f4fa855ea1 100644 --- a/frappe/core/doctype/role/role.py +++ b/frappe/core/doctype/role/role.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/core/doctype/role/test_role.py b/frappe/core/doctype/role/test_role.py index 471f6cac43..1671f9a9c8 100644 --- a/frappe/core/doctype/role/test_role.py +++ b/frappe/core/doctype/role/test_role.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py b/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py index 59f34a1483..cd9a6dc0fa 100644 --- a/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py +++ b/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.core.doctype.report.report import is_prepared_report_disabled diff --git a/frappe/core/doctype/role_profile/role_profile.py b/frappe/core/doctype/role_profile/role_profile.py index 0f58da5b5e..cb0a43d68f 100644 --- a/frappe/core/doctype/role_profile/role_profile.py +++ b/frappe/core/doctype/role_profile/role_profile.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from frappe.model.document import Document +import frappe class RoleProfile(Document): def autoname(self): @@ -11,5 +12,9 @@ class RoleProfile(Document): def on_update(self): """ Changes in role_profile reflected across all its user """ - from frappe.core.doctype.user.user import update_roles - update_roles(self.name) + users = frappe.get_all('User', filters={'role_profile_name': self.name}) + roles = [role.role for role in self.roles] + for d in users: + user = frappe.get_doc('User', d) + user.set('roles', []) + user.add_roles(*roles) diff --git a/frappe/core/doctype/role_profile/test_role_profile.py b/frappe/core/doctype/role_profile/test_role_profile.py index 53e0a1b043..b208a186de 100644 --- a/frappe/core/doctype/role_profile/test_role_profile.py +++ b/frappe/core/doctype/role_profile/test_role_profile.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest @@ -8,6 +8,7 @@ test_dependencies = ['Role'] class TestRoleProfile(unittest.TestCase): def test_make_new_role_profile(self): + frappe.delete_doc_if_exists('Role Profile', 'Test 1', force=1) new_role_profile = frappe.get_doc(dict(doctype='Role Profile', role_profile='Test 1')).insert() self.assertEqual(new_role_profile.role_profile, 'Test 1') @@ -19,7 +20,25 @@ class TestRoleProfile(unittest.TestCase): new_role_profile.save() self.assertEqual(new_role_profile.roles[0].role, '_Test Role 2') + # user with a role profile + random_user = frappe.mock("email") + random_user_name = frappe.mock("name") + + random_user = frappe.get_doc({ + "doctype": "User", + "email": random_user, + "enabled": 1, + "first_name": random_user_name, + "new_password": "Eastern_43A1W", + "role_profile_name": 'Test 1' + }).insert(ignore_permissions=True, ignore_if_duplicate=True) + self.assertListEqual([role.role for role in random_user.roles], [role.role for role in new_role_profile.roles]) + # clear roles new_role_profile.roles = [] new_role_profile.save() self.assertEqual(new_role_profile.roles, []) + + # user roles with the role profile should also be updated + random_user.reload() + self.assertListEqual(random_user.roles, []) \ No newline at end of file diff --git a/frappe/core/doctype/scheduled_job_log/scheduled_job_log.py b/frappe/core/doctype/scheduled_job_log/scheduled_job_log.py index 7f54a3b6ae..bd5c15bc31 100644 --- a/frappe/core/doctype/scheduled_job_log/scheduled_job_log.py +++ b/frappe/core/doctype/scheduled_job_log/scheduled_job_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/scheduled_job_log/test_scheduled_job_log.py b/frappe/core/doctype/scheduled_job_log/test_scheduled_job_log.py index 85471d0d71..9957f6c34c 100644 --- a/frappe/core/doctype/scheduled_job_log/test_scheduled_job_log.py +++ b/frappe/core/doctype/scheduled_job_log/test_scheduled_job_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py index b6515b1e79..1a795bab82 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -1,5 +1,5 @@ # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import json from datetime import datetime diff --git a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py index a071cfe9a9..dc3353b176 100644 --- a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest from frappe.utils import get_datetime diff --git a/frappe/core/doctype/server_script/server_script.json b/frappe/core/doctype/server_script/server_script.json index b7e49673f8..520c0008c5 100644 --- a/frappe/core/doctype/server_script/server_script.json +++ b/frappe/core/doctype/server_script/server_script.json @@ -13,6 +13,7 @@ "api_method", "allow_guest", "column_break_3", + "module", "disabled", "section_break_8", "script", @@ -93,6 +94,12 @@ "label": "Event Frequency", "mandatory_depends_on": "eval:doc.script_type == \"Scheduler Event\"", "options": "All\nHourly\nDaily\nWeekly\nMonthly\nYearly\nHourly Long\nDaily Long\nWeekly Long\nMonthly Long" + }, + { + "fieldname": "module", + "fieldtype": "Link", + "label": "Module (for export)", + "options": "Module Def" } ], "index_web_pages_for_search": 1, @@ -102,7 +109,7 @@ "link_fieldname": "server_script" } ], - "modified": "2021-02-18 12:36:19.803425", + "modified": "2021-09-04 12:02:43.671240", "modified_by": "Administrator", "module": "Core", "name": "Server Script", diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index ebd581ce87..79fe7a9140 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import ast from types import FunctionType, MethodType, ModuleType @@ -27,6 +27,11 @@ class ServerScript(Document): for job in self.scheduled_jobs: frappe.delete_doc("Scheduled Job Type", job.name) + def get_code_fields(self): + return { + 'script': 'py' + } + @property def scheduled_jobs(self) -> List[Dict[str, str]]: return frappe.get_all( diff --git a/frappe/core/doctype/server_script/test_server_script.py b/frappe/core/doctype/server_script/test_server_script.py index ed6b5539c5..6c028ff136 100644 --- a/frappe/core/doctype/server_script/test_server_script.py +++ b/frappe/core/doctype/server_script/test_server_script.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest import requests diff --git a/frappe/core/doctype/session_default/session_default.py b/frappe/core/doctype/session_default/session_default.py index 70ff103111..9470a1bb38 100644 --- a/frappe/core/doctype/session_default/session_default.py +++ b/frappe/core/doctype/session_default/session_default.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/session_default_settings/session_default_settings.py b/frappe/core/doctype/session_default_settings/session_default_settings.py index 25f7522c86..52c917223e 100644 --- a/frappe/core/doctype/session_default_settings/session_default_settings.py +++ b/frappe/core/doctype/session_default_settings/session_default_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/core/doctype/session_default_settings/test_session_default_settings.py b/frappe/core/doctype/session_default_settings/test_session_default_settings.py index 7d20015b66..7a7e971aed 100644 --- a/frappe/core/doctype/session_default_settings/test_session_default_settings.py +++ b/frappe/core/doctype/session_default_settings/test_session_default_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest from frappe.core.doctype.session_default_settings.session_default_settings import set_session_default_values, clear_session_defaults diff --git a/frappe/core/doctype/sms_parameter/sms_parameter.py b/frappe/core/doctype/sms_parameter/sms_parameter.py index d1fb1c53db..fb8466eac6 100644 --- a/frappe/core/doctype/sms_parameter/sms_parameter.py +++ b/frappe/core/doctype/sms_parameter/sms_parameter.py @@ -1,5 +1,5 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# License: MIT. See LICENSE import frappe diff --git a/frappe/core/doctype/sms_settings/sms_settings.py b/frappe/core/doctype/sms_settings/sms_settings.py index 58a0ff08f6..f15ba7e4f6 100644 --- a/frappe/core/doctype/sms_settings/sms_settings.py +++ b/frappe/core/doctype/sms_settings/sms_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# License: MIT. See LICENSE import frappe diff --git a/frappe/core/doctype/sms_settings/test_sms_settings.py b/frappe/core/doctype/sms_settings/test_sms_settings.py index 862f5e3965..b3be912f9e 100644 --- a/frappe/core/doctype/sms_settings/test_sms_settings.py +++ b/frappe/core/doctype/sms_settings/test_sms_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/success_action/success_action.py b/frappe/core/doctype/success_action/success_action.py index 4ebd3d250b..afb3a87485 100644 --- a/frappe/core/doctype/success_action/success_action.py +++ b/frappe/core/doctype/success_action/success_action.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from frappe.model.document import Document diff --git a/frappe/core/doctype/system_settings/system_settings.py b/frappe/core/doctype/system_settings/system_settings.py index 466914569f..1ae8e9e79e 100644 --- a/frappe/core/doctype/system_settings/system_settings.py +++ b/frappe/core/doctype/system_settings/system_settings.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/core/doctype/system_settings/test_system_settings.py b/frappe/core/doctype/system_settings/test_system_settings.py index a65c602abe..f95e26b793 100644 --- a/frappe/core/doctype/system_settings/test_system_settings.py +++ b/frappe/core/doctype/system_settings/test_system_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/test/test.py b/frappe/core/doctype/test/test.py index 98e36e6a30..4cb088c117 100644 --- a/frappe/core/doctype/test/test.py +++ b/frappe/core/doctype/test/test.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/test/test_test.py b/frappe/core/doctype/test/test_test.py index d8ca975d63..d8508b8651 100644 --- a/frappe/core/doctype/test/test_test.py +++ b/frappe/core/doctype/test/test_test.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/core/doctype/transaction_log/test_transaction_log.py b/frappe/core/doctype/transaction_log/test_transaction_log.py index 0d9b9353d0..c332a82f65 100644 --- a/frappe/core/doctype/transaction_log/test_transaction_log.py +++ b/frappe/core/doctype/transaction_log/test_transaction_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest import hashlib diff --git a/frappe/core/doctype/transaction_log/transaction_log.py b/frappe/core/doctype/transaction_log/transaction_log.py index 58d0b3d176..bb94642f48 100644 --- a/frappe/core/doctype/transaction_log/transaction_log.py +++ b/frappe/core/doctype/transaction_log/transaction_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/core/doctype/translation/test_translation.py b/frappe/core/doctype/translation/test_translation.py index ae1293b38f..982d9bf976 100644 --- a/frappe/core/doctype/translation/test_translation.py +++ b/frappe/core/doctype/translation/test_translation.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest @@ -8,7 +8,7 @@ from frappe import _ class TestTranslation(unittest.TestCase): def setUp(self): - frappe.db.sql('delete from tabTranslation') + frappe.db.delete("Translation") def tearDown(self): frappe.local.lang = 'en' diff --git a/frappe/core/doctype/translation/translation.py b/frappe/core/doctype/translation/translation.py index b1f4642791..a01552903c 100644 --- a/frappe/core/doctype/translation/translation.py +++ b/frappe/core/doctype/translation/translation.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/user/test_records.json b/frappe/core/doctype/user/test_records.json index f9033d4660..21fe3ff69d 100644 --- a/frappe/core/doctype/user/test_records.json +++ b/frappe/core/doctype/user/test_records.json @@ -70,5 +70,19 @@ "role": "System Manager" } ] - } + }, + { + "doctype": "User", + "email": "testpassword@example.com", + "enabled": 1, + "first_name": "_Test", + "new_password": "Eastern_43A1W", + "roles": [ + { + "doctype": "Has Role", + "parentfield": "roles", + "role": "System Manager" + } + ] + } ] diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py index 392128834d..e47846958a 100644 --- a/frappe/core/doctype/user/test_user.py +++ b/frappe/core/doctype/user/test_user.py @@ -1,16 +1,18 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt -import frappe, unittest, uuid - +# License: MIT. See LICENSE +import json +import unittest +from unittest.mock import patch + +import frappe +import frappe.exceptions +from frappe.core.doctype.user.user import (extract_mentions, reset_password, + sign_up, test_password_strength, update_password, verify_password) +from frappe.frappeclient import FrappeClient from frappe.model.delete_doc import delete_doc -from frappe.utils.data import today, add_to_date -from frappe import _dict from frappe.utils import get_url -from frappe.core.doctype.user.user import get_total_users -from frappe.core.doctype.user.user import MaxUsersReachedError, test_password_strength -from frappe.core.doctype.user.user import extract_mentions -from frappe.frappeclient import FrappeClient +user_module = frappe.core.doctype.user.user test_records = frappe.get_test_records('User') class TestUser(unittest.TestCase): @@ -23,7 +25,7 @@ class TestUser(unittest.TestCase): def test_user_type(self): new_user = frappe.get_doc(dict(doctype='User', email='test-for-type@example.com', - first_name='Tester')).insert() + first_name='Tester')).insert(ignore_if_duplicate=True) self.assertEqual(new_user.user_type, 'Website User') # social login userid for frappe @@ -52,7 +54,7 @@ class TestUser(unittest.TestCase): def test_delete(self): frappe.get_doc("User", "test@example.com").add_roles("_Test Role 2") self.assertRaises(frappe.LinkExistsError, delete_doc, "Role", "_Test Role 2") - frappe.db.sql("""delete from `tabHas Role` where role='_Test Role 2'""") + frappe.db.delete("Has Role", {"role": "_Test Role 2"}) delete_doc("Role","_Test Role 2") if frappe.db.exists("User", "_test@example.com"): @@ -119,40 +121,9 @@ class TestUser(unittest.TestCase): # system manager now added by Administrator self.assertTrue("System Manager" in [d.role for d in me.get("roles")]) - # def test_deny_multiple_sessions(self): - # from frappe.installer import update_site_config - # clear_limit('users') - # - # # allow one session - # user = frappe.get_doc('User', 'test@example.com') - # user.simultaneous_sessions = 1 - # user.new_password = 'Eastern_43A1W' - # user.save() - # - # def test_request(conn): - # value = conn.get_value('User', 'first_name', {'name': 'test@example.com'}) - # self.assertTrue('first_name' in value) - # - # from frappe.frappeclient import FrappeClient - # update_site_config('deny_multiple_sessions', 0) - # - # conn1 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False) - # test_request(conn1) - # - # conn2 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False) - # test_request(conn2) - # - # update_site_config('deny_multiple_sessions', 1) - # conn3 = FrappeClient(get_url(), "test@example.com", "Eastern_43A1W", verify=False) - # test_request(conn3) - # - # # first connection should fail - # test_request(conn1) - - def test_delete_user(self): new_user = frappe.get_doc(dict(doctype='User', email='test-for-delete@example.com', - first_name='Tester Delete User')).insert() + first_name='Tester Delete User')).insert(ignore_if_duplicate=True) self.assertEqual(new_user.user_type, 'Website User') # role with desk access @@ -174,7 +145,7 @@ class TestUser(unittest.TestCase): self.assertFalse(frappe.db.exists('User', new_user.name)) def test_password_strength(self): - # Test Password without Password Strenth Policy + # Test Password without Password Strength Policy frappe.db.set_value("System Settings", "System Settings", "enable_password_policy", 0) # password policy is disabled, test_password_strength should be ignored @@ -193,6 +164,17 @@ class TestUser(unittest.TestCase): result = test_password_strength("Eastern_43A1W") self.assertEqual(result['feedback']['password_policy_validation_passed'], True) + + # test password strength while saving user with new password + user = frappe.get_doc("User", "test@example.com") + frappe.flags.in_test = False + user.new_password = "password" + self.assertRaisesRegex(frappe.exceptions.ValidationError, "Invalid Password", user.save) + user.reload() + user.new_password = "Eastern_43A1W" + user.save() + frappe.flags.in_test = True + def test_comment_mentions(self): comment = ''' @@ -227,6 +209,7 @@ class TestUser(unittest.TestCase): self.assertEqual(extract_mentions(comment)[0], "test_user@example.com") self.assertEqual(extract_mentions(comment)[1], "test.again@example1.com") + frappe.delete_doc("User Group", "Team") doc = frappe.get_doc({ 'doctype': 'User Group', 'name': 'Team', @@ -236,14 +219,18 @@ class TestUser(unittest.TestCase): 'user': 'test1@example.com' }] }) - doc.insert(ignore_if_duplicate=True) + + doc.insert() comment = '''
Testing comment for @Team - + and + + @Unknown Team + please check
''' @@ -267,32 +254,125 @@ class TestUser(unittest.TestCase): self.assertEqual(res1.status_code, 200) self.assertEqual(res2.status_code, 417) - # def test_user_rollback(self): - # """ - # FIXME: This is failing with PR #12693 as Rollback can't happen if notifications sent on user creation. - # Make sure that notifications disabled. - # """ - # frappe.db.commit() - # frappe.db.begin() - # user_id = str(uuid.uuid4()) - # email = f'{user_id}@example.com' - # try: - # frappe.flags.in_import = True # disable throttling - # frappe.get_doc(dict( - # doctype='User', - # email=email, - # first_name=user_id, - # )).insert() - # finally: - # frappe.flags.in_import = False - - # # Check user has been added - # self.assertIsNotNone(frappe.db.get("User", {"email": email})) - - # # Check that rollback works - # frappe.db.rollback() - # self.assertIsNone(frappe.db.get("User", {"email": email})) + def test_user_rename(self): + old_name = "test_user_rename@example.com" + new_name = "test_user_rename_new@example.com" + user = frappe.get_doc({ + "doctype": "User", + "email": old_name, + "enabled": 1, + "first_name": "_Test", + "new_password": "Eastern_43A1W", + "roles": [ + { + "doctype": "Has Role", + "parentfield": "roles", + "role": "System Manager" + }] + }).insert(ignore_permissions=True, ignore_if_duplicate=True) + + frappe.rename_doc('User', user.name, new_name) + self.assertTrue(frappe.db.exists("Notification Settings", new_name)) + + frappe.delete_doc("User", new_name) + + def test_signup(self): + import frappe.website.utils + random_user = frappe.mock('email') + random_user_name = frappe.mock('name') + # disabled signup + with patch.object(user_module, "is_signup_disabled", return_value=True): + self.assertRaisesRegex(frappe.exceptions.ValidationError, "Sign Up is disabled", + sign_up, random_user, random_user_name, "/signup") + + self.assertTupleEqual(sign_up(random_user, random_user_name, "/welcome"), (1, "Please check your email for verification")) + self.assertEqual(frappe.cache().hget('redirect_after_login', random_user), "/welcome") + + # re-register + self.assertTupleEqual(sign_up(random_user, random_user_name, "/welcome"), (0, "Already Registered")) + + # disabled user + user = frappe.get_doc("User", random_user) + user.enabled = 0 + user.save() + + self.assertTupleEqual(sign_up(random_user, random_user_name, "/welcome"), (0, "Registered but disabled")) + + # throttle user creation + with patch.object(user_module.frappe.db, "get_creation_count", return_value=301): + self.assertRaisesRegex(frappe.exceptions.ValidationError, "Throttled", + sign_up, frappe.mock('email'), random_user_name, "/signup") + + + def test_reset_password(self): + from frappe.auth import CookieManager, LoginManager + from frappe.utils import set_request + old_password = "Eastern_43A1W" + new_password = "easy_password" + + set_request(path="/random") + frappe.local.cookie_manager = CookieManager() + frappe.local.login_manager = LoginManager() + + frappe.set_user("testpassword@example.com") + test_user = frappe.get_doc("User", "testpassword@example.com") + test_user.reset_password() + self.assertEqual(update_password(new_password, key=test_user.reset_password_key), "/app") + self.assertEqual(update_password(new_password, key="wrong_key"), "The Link specified has either been used before or Invalid") + + # password verification should fail with old password + self.assertRaises(frappe.exceptions.AuthenticationError, verify_password, old_password) + verify_password(new_password) + + # reset password + update_password(old_password, old_password=new_password) + + self.assertRaisesRegex(frappe.exceptions.ValidationError, "Invalid key type", update_password, "test", 1, ['like', '%']) + + password_strength_response = { + "feedback": { + "password_policy_validation_passed": False, + "suggestions": ["Fix password"] + } + } + + # password strength failure test + with patch.object(user_module, "test_password_strength", return_value=password_strength_response): + self.assertRaisesRegex(frappe.exceptions.ValidationError, "Fix password", update_password, new_password, 0, test_user.reset_password_key) + + + # test redirect URL for website users + frappe.set_user("test2@example.com") + self.assertEqual(update_password(new_password, old_password=old_password), "/") + # reset password + update_password(old_password, old_password=new_password) + + # test API endpoint + with patch.object(user_module.frappe, 'sendmail') as sendmail: + frappe.clear_messages() + test_user = frappe.get_doc("User", "test2@example.com") + self.assertEqual(reset_password(user="test2@example.com"), None) + test_user.reload() + self.assertEqual(update_password(new_password, key=test_user.reset_password_key), "/") + update_password(old_password, old_password=new_password) + self.assertEqual(json.loads(frappe.message_log[0]), {"message": "Password reset instructions have been sent to your email"}) + sendmail.assert_called_once() + self.assertEqual(sendmail.call_args[1]["recipients"], "test2@example.com") + + self.assertEqual(reset_password(user="test2@example.com"), None) + self.assertEqual(reset_password(user="Administrator"), "not allowed") + self.assertEqual(reset_password(user="random"), "not found") + + def test_user_onload_modules(self): + from frappe.config import get_modules_from_all_apps + from frappe.desk.form.load import getdoc + frappe.response.docs = [] + getdoc("User", "Administrator") + doc = frappe.response.docs[0] + self.assertListEqual(doc.get("__onload").get('all_modules', []), + [m.get("module_name") for m in get_modules_from_all_apps()]) + def delete_contact(user): - frappe.db.sql("DELETE FROM `tabContact` WHERE `email_id`= %s", user) - frappe.db.sql("DELETE FROM `tabContact Email` WHERE `email_id`= %s", user) + frappe.db.delete("Contact", {"email_id": user}) + frappe.db.delete("Contact Email", {"email_id": user}) diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index 8c5b89c5fc..96726d875c 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -166,7 +166,7 @@ frappe.ui.form.on('User', { frm.add_custom_button(__("Reset OTP Secret"), function() { frappe.call({ - method: "frappe.core.doctype.user.user.reset_otp_secret", + method: "frappe.twofactor.reset_otp_secret", args: { "user": frm.doc.name } diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 5d799f8ee9..147f4ddfee 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE from bs4 import BeautifulSoup import frappe import frappe.share @@ -15,17 +15,11 @@ from frappe.desk.doctype.notification_settings.notification_settings import crea from frappe.utils.user import get_system_managers from frappe.website.utils import is_signup_disabled from frappe.rate_limiter import rate_limit -from frappe.utils.background_jobs import enqueue from frappe.core.doctype.user_type.user_type import user_linked_with_permission_on_doctype STANDARD_USERS = ("Guest", "Administrator") - -class MaxUsersReachedError(frappe.ValidationError): - pass - - class User(Document): __new_password = None @@ -56,8 +50,6 @@ class User(Document): frappe.cache().delete_key('enabled_users') def validate(self): - self.check_demo() - # clear new password self.__new_password = self.new_password self.new_password = "" @@ -137,10 +129,6 @@ class User(Document): """Returns true if current user is the session user""" return self.name == frappe.session.user - def check_demo(self): - if frappe.session.user == 'demo@erpnext.com': - frappe.throw(_('Cannot change user details in demo. Please signup for a new account at https://erpnext.com'), title=_('Not Allowed')) - def set_full_name(self): self.full_name = " ".join(filter(None, [self.first_name, self.last_name])) @@ -398,7 +386,6 @@ class User(Document): def before_rename(self, old_name, new_name, merge=False): - self.check_demo() frappe.clear_cache(user=old_name) self.validate_rename(old_name, new_name) @@ -718,85 +705,6 @@ def get_email_awaiting(user): where parent = %(user)s""",{"user":user}) return False -@frappe.whitelist(allow_guest=False) -def set_email_password(email_account, user, password): - account = frappe.get_doc("Email Account", email_account) - if account.awaiting_password: - account.awaiting_password = 0 - account.password = password - try: - account.save(ignore_permissions=True) - except Exception: - frappe.db.rollback() - return False - - return True - -def setup_user_email_inbox(email_account, awaiting_password, email_id, enable_outgoing): - """ setup email inbox for user """ - def add_user_email(user): - user = frappe.get_doc("User", user) - row = user.append("user_emails", {}) - - row.email_id = email_id - row.email_account = email_account - row.awaiting_password = awaiting_password or 0 - row.enable_outgoing = enable_outgoing or 0 - - user.save(ignore_permissions=True) - - udpate_user_email_settings = False - if not all([email_account, email_id]): - return - - user_names = frappe.db.get_values("User", { "email": email_id }, as_dict=True) - if not user_names: - return - - for user in user_names: - user_name = user.get("name") - - # check if inbox is alreay configured - user_inbox = frappe.db.get_value("User Email", { - "email_account": email_account, - "parent": user_name - }, ["name"]) or None - - if not user_inbox: - add_user_email(user_name) - else: - # update awaiting password for email account - udpate_user_email_settings = True - - if udpate_user_email_settings: - frappe.db.sql("""UPDATE `tabUser Email` SET awaiting_password = %(awaiting_password)s, - enable_outgoing = %(enable_outgoing)s WHERE email_account = %(email_account)s""", { - "email_account": email_account, - "enable_outgoing": enable_outgoing, - "awaiting_password": awaiting_password or 0 - }) - else: - users = " and ".join([frappe.bold(user.get("name")) for user in user_names]) - frappe.msgprint(_("Enabled email inbox for user {0}").format(users)) - - ask_pass_update() - -def remove_user_email_inbox(email_account): - """ remove user email inbox settings if email account is deleted """ - if not email_account: - return - - users = frappe.get_all("User Email", filters={ - "email_account": email_account - }, fields=["parent as name"]) - - for user in users: - doc = frappe.get_doc("User", user.get("name")) - to_remove = [ row for row in doc.user_emails if row.email_account == email_account ] - [ doc.remove(row) for row in to_remove ] - - doc.save(ignore_permissions=True) - def ask_pass_update(): # update the sys defaults as to awaiting users from frappe.utils import set_default @@ -809,24 +717,19 @@ def ask_pass_update(): def _get_user_for_update_password(key, old_password): # verify old password + result = frappe._dict() if key: - user = frappe.db.get_value("User", {"reset_password_key": key}) - if not user: - return { - 'message': _("The Link specified has either been used before or Invalid") - } + result.user = frappe.db.get_value("User", {"reset_password_key": key}) + if not result.user: + result.message = _("The Link specified has either been used before or Invalid") elif old_password: # verify old password frappe.local.login_manager.check_password(frappe.session.user, old_password) user = frappe.session.user + result.user = user - else: - return - - return { - 'user': user - } + return result def reset_user_data(user): user_doc = frappe.get_doc("User", user) @@ -848,14 +751,12 @@ def sign_up(email, full_name, redirect_to): user = frappe.db.get("User", {"email": email}) if user: - if user.disabled: - return 0, _("Registered but disabled") - else: + if user.enabled: return 0, _("Already Registered") + else: + return 0, _("Registered but disabled") else: - if frappe.db.sql("""select count(*) from tabUser where - HOUR(TIMEDIFF(CURRENT_TIMESTAMP, TIMESTAMP(modified)))=1""")[0][0] > 300: - + if frappe.db.get_creation_count('User', 60) > 300: frappe.respond_as_web_page(_('Temporarily Disabled'), _('Too many users signed up recently, so the registration is disabled. Please try back in an hour'), http_status_code=429) @@ -1048,91 +949,6 @@ def update_gravatar(name): if gravatar: frappe.db.set_value('User', name, 'user_image', gravatar) -@frappe.whitelist(allow_guest=True) -def send_token_via_sms(tmp_id,phone_no=None,user=None): - try: - from frappe.core.doctype.sms_settings.sms_settings import send_request - except: - return False - - if not frappe.cache().ttl(tmp_id + '_token'): - return False - ss = frappe.get_doc('SMS Settings', 'SMS Settings') - if not ss.sms_gateway_url: - return False - - token = frappe.cache().get(tmp_id + '_token') - args = {ss.message_parameter: 'verification code is {}'.format(token)} - - for d in ss.get("parameters"): - args[d.parameter] = d.value - - if user: - user_phone = frappe.db.get_value('User', user, ['phone','mobile_no'], as_dict=1) - usr_phone = user_phone.mobile_no or user_phone.phone - if not usr_phone: - return False - else: - if phone_no: - usr_phone = phone_no - else: - return False - - args[ss.receiver_parameter] = usr_phone - status = send_request(ss.sms_gateway_url, args, use_post=ss.use_post) - - if 200 <= status < 300: - frappe.cache().delete(tmp_id + '_token') - return True - else: - return False - -@frappe.whitelist(allow_guest=True) -def send_token_via_email(tmp_id,token=None): - import pyotp - - user = frappe.cache().get(tmp_id + '_user') - count = token or frappe.cache().get(tmp_id + '_token') - - if ((not user) or (user == 'None') or (not count)): - return False - user_email = frappe.db.get_value('User',user, 'email') - if not user_email: - return False - - otpsecret = frappe.cache().get(tmp_id + '_otp_secret') - hotp = pyotp.HOTP(otpsecret) - - frappe.sendmail( - recipients=user_email, - sender=None, - subject="Verification Code", - template="verification_code", - args=dict(code=hotp.at(int(count))), - delayed=False, - retry=3 - ) - - return True - -@frappe.whitelist(allow_guest=True) -def reset_otp_secret(user): - otp_issuer = frappe.db.get_value('System Settings', 'System Settings', 'otp_issuer_name') - user_email = frappe.db.get_value('User',user, 'email') - if frappe.session.user in ["Administrator", user] : - frappe.defaults.clear_default(user + '_otplogin') - frappe.defaults.clear_default(user + '_otpsecret') - email_args = { - 'recipients':user_email, 'sender':None, 'subject':'OTP Secret Reset - {}'.format(otp_issuer or "Frappe Framework"), - 'message':'

Your OTP secret on {} has been reset. If you did not perform this reset and did not request it, please contact your System Administrator immediately.

'.format(otp_issuer or "Frappe Framework"), - 'delayed':False, - 'retry':3 - } - enqueue(method=frappe.sendmail, queue='short', timeout=300, event=None, is_async=True, job_name=None, now=False, **email_args) - return frappe.msgprint(_("OTP Secret has been reset. Re-registration will be required on next login.")) - else: - return frappe.throw(_("OTP secret can only be reset by the Administrator.")) - def throttle_user_creation(): if frappe.flags.in_import: return @@ -1150,15 +966,6 @@ def get_module_profile(module_profile): module_profile = frappe.get_doc('Module Profile', {'module_profile_name': module_profile}) return module_profile.get('block_modules') -def update_roles(role_profile): - users = frappe.get_all('User', filters={'role_profile_name': role_profile}) - role_profile = frappe.get_doc('Role Profile', role_profile) - roles = [role.role for role in role_profile.roles] - for d in users: - user = frappe.get_doc('User', d) - user.set('roles', []) - user.add_roles(*roles) - def create_contact(user, ignore_links=False, ignore_mandatory=False): from frappe.contacts.doctype.contact.contact import get_contact_name if user.name in ["Administrator", "Guest"]: return @@ -1217,18 +1024,18 @@ def generate_keys(user): :param user: str """ - if "System Manager" in frappe.get_roles(): - user_details = frappe.get_doc("User", user) - api_secret = frappe.generate_hash(length=15) - # if api key is not set generate api key - if not user_details.api_key: - api_key = frappe.generate_hash(length=15) - user_details.api_key = api_key - user_details.api_secret = api_secret - user_details.save() - - return {"api_secret": api_secret} - frappe.throw(frappe._("Not Permitted"), frappe.PermissionError) + frappe.only_for("System Manager") + user_details = frappe.get_doc("User", user) + api_secret = frappe.generate_hash(length=15) + # if api key is not set generate api key + if not user_details.api_key: + api_key = frappe.generate_hash(length=15) + user_details.api_key = api_key + user_details.api_secret = api_secret + user_details.save() + + return {"api_secret": api_secret} + @frappe.whitelist() def switch_theme(theme): diff --git a/frappe/core/doctype/user_document_type/user_document_type.py b/frappe/core/doctype/user_document_type/user_document_type.py index 48dbf87b3d..a14d735e6a 100644 --- a/frappe/core/doctype/user_document_type/user_document_type.py +++ b/frappe/core/doctype/user_document_type/user_document_type.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/user_email/user_email.py b/frappe/core/doctype/user_email/user_email.py index 729aa03444..daad083577 100644 --- a/frappe/core/doctype/user_email/user_email.py +++ b/frappe/core/doctype/user_email/user_email.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/user_group/test_user_group.py b/frappe/core/doctype/user_group/test_user_group.py index 2f89d032e1..b5d642ae9c 100644 --- a/frappe/core/doctype/user_group/test_user_group.py +++ b/frappe/core/doctype/user_group/test_user_group.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/core/doctype/user_group/user_group.py b/frappe/core/doctype/user_group/user_group.py index 178775d407..05ff71e353 100644 --- a/frappe/core/doctype/user_group/user_group.py +++ b/frappe/core/doctype/user_group/user_group.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/user_group_member/test_user_group_member.py b/frappe/core/doctype/user_group_member/test_user_group_member.py index 8dbaed9e65..6d4650a3d0 100644 --- a/frappe/core/doctype/user_group_member/test_user_group_member.py +++ b/frappe/core/doctype/user_group_member/test_user_group_member.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/core/doctype/user_group_member/user_group_member.py b/frappe/core/doctype/user_group_member/user_group_member.py index f85ddc3209..69718d8d91 100644 --- a/frappe/core/doctype/user_group_member/user_group_member.py +++ b/frappe/core/doctype/user_group_member/user_group_member.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py index 5201ffef8d..66ffd48822 100644 --- a/frappe/core/doctype/user_permission/user_permission.py +++ b/frappe/core/doctype/user_permission/user_permission.py @@ -1,5 +1,5 @@ # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe, json from frappe.model.document import Document diff --git a/frappe/core/doctype/user_select_document_type/user_select_document_type.py b/frappe/core/doctype/user_select_document_type/user_select_document_type.py index 13e3f0d351..18a21931e5 100644 --- a/frappe/core/doctype/user_select_document_type/user_select_document_type.py +++ b/frappe/core/doctype/user_select_document_type/user_select_document_type.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/user_social_login/user_social_login.py b/frappe/core/doctype/user_social_login/user_social_login.py index 4a34006d2b..80c0c89383 100644 --- a/frappe/core/doctype/user_social_login/user_social_login.py +++ b/frappe/core/doctype/user_social_login/user_social_login.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from frappe.model.document import Document diff --git a/frappe/core/doctype/user_type/test_user_type.py b/frappe/core/doctype/user_type/test_user_type.py index 1c47f02bbb..7080e1830b 100644 --- a/frappe/core/doctype/user_type/test_user_type.py +++ b/frappe/core/doctype/user_type/test_user_type.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/core/doctype/user_type/user_type.py b/frappe/core/doctype/user_type/user_type.py index 82ffb090f1..2e5254b622 100644 --- a/frappe/core/doctype/user_type/user_type.py +++ b/frappe/core/doctype/user_type/user_type.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/core/doctype/user_type_module/user_type_module.py b/frappe/core/doctype/user_type_module/user_type_module.py index 9afbcd294d..d25479f869 100644 --- a/frappe/core/doctype/user_type_module/user_type_module.py +++ b/frappe/core/doctype/user_type_module/user_type_module.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/version/test_version.py b/frappe/core/doctype/version/test_version.py index f6c099c4ea..608dc9f0ab 100644 --- a/frappe/core/doctype/version/test_version.py +++ b/frappe/core/doctype/version/test_version.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest, copy from frappe.test_runner import make_test_objects diff --git a/frappe/core/doctype/version/version.py b/frappe/core/doctype/version/version.py index a1bd851346..08d0456dff 100644 --- a/frappe/core/doctype/version/version.py +++ b/frappe/core/doctype/version/version.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe, json diff --git a/frappe/core/doctype/view_log/test_view_log.py b/frappe/core/doctype/view_log/test_view_log.py index 025f3d8ad9..efa9538fbf 100644 --- a/frappe/core/doctype/view_log/test_view_log.py +++ b/frappe/core/doctype/view_log/test_view_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/core/doctype/view_log/view_log.py b/frappe/core/doctype/view_log/view_log.py index 242250be8b..fbbd6e1154 100644 --- a/frappe/core/doctype/view_log/view_log.py +++ b/frappe/core/doctype/view_log/view_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/core/notifications.py b/frappe/core/notifications.py index 707de43f28..b43d424df5 100644 --- a/frappe/core/notifications.py +++ b/frappe/core/notifications.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/core/page/__init__.py b/frappe/core/page/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/core/page/__init__.py +++ b/frappe/core/page/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/core/page/background_jobs/background_jobs.py b/frappe/core/page/background_jobs/background_jobs.py index 1f3555e351..5d9bb815da 100644 --- a/frappe/core/page/background_jobs/background_jobs.py +++ b/frappe/core/page/background_jobs/background_jobs.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import json from typing import TYPE_CHECKING, Dict, List diff --git a/frappe/core/page/permission_manager/__init__.py b/frappe/core/page/permission_manager/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/core/page/permission_manager/__init__.py +++ b/frappe/core/page/permission_manager/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/core/page/permission_manager/permission_manager.py b/frappe/core/page/permission_manager/permission_manager.py index 2a99283dda..08642c599e 100644 --- a/frappe/core/page/permission_manager/permission_manager.py +++ b/frappe/core/page/permission_manager/permission_manager.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/core/report/__init__.py b/frappe/core/report/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/core/report/__init__.py +++ b/frappe/core/report/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py index 13602ca777..535d354250 100644 --- a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py +++ b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe import _, throw diff --git a/frappe/core/report/transaction_log_report/transaction_log_report.py b/frappe/core/report/transaction_log_report/transaction_log_report.py index ff8d8345d6..0a74ece322 100644 --- a/frappe/core/report/transaction_log_report/transaction_log_report.py +++ b/frappe/core/report/transaction_log_report/transaction_log_report.py @@ -1,5 +1,5 @@ # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe import hashlib diff --git a/frappe/core/utils.py b/frappe/core/utils.py index 9b8ee3a326..d4690cae89 100644 --- a/frappe/core/utils.py +++ b/frappe/core/utils.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/core/workspace/build/build.json b/frappe/core/workspace/build/build.json index aefda698b1..8536c807d2 100644 --- a/frappe/core/workspace/build/build.json +++ b/frappe/core/workspace/build/build.json @@ -1,24 +1,28 @@ { "cards_label": "Elements", - "category": "Modules", + "category": "", "charts": [], + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"DocType\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Workspace\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Report\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Elements\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Modules\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Models\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Views\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Scripting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Packages\",\"col\":4}}]", "creation": "2021-01-02 10:51:16.579957", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "tool", "idx": 0, "is_default": 0, - "is_standard": 1, + "is_standard": 0, "label": "Build", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Modules", + "link_count": 0, "link_type": "DocType", "onboard": 0, "only_for": "", @@ -28,6 +32,7 @@ "hidden": 0, "is_query_report": 0, "label": "Module Def", + "link_count": 0, "link_to": "Module Def", "link_type": "DocType", "onboard": 0, @@ -38,6 +43,7 @@ "hidden": 0, "is_query_report": 0, "label": "Workspace", + "link_count": 0, "link_to": "Workspace", "link_type": "DocType", "onboard": 0, @@ -48,6 +54,7 @@ "hidden": 0, "is_query_report": 0, "label": "Module Onboarding", + "link_count": 0, "link_to": "Module Onboarding", "link_type": "DocType", "onboard": 0, @@ -58,6 +65,7 @@ "hidden": 0, "is_query_report": 0, "label": "Block Module", + "link_count": 0, "link_to": "Block Module", "link_type": "DocType", "onboard": 0, @@ -68,6 +76,7 @@ "hidden": 0, "is_query_report": 0, "label": "Models", + "link_count": 0, "link_type": "DocType", "onboard": 0, "only_for": "", @@ -77,6 +86,7 @@ "hidden": 0, "is_query_report": 0, "label": "DocType", + "link_count": 0, "link_to": "DocType", "link_type": "DocType", "onboard": 0, @@ -87,6 +97,7 @@ "hidden": 0, "is_query_report": 0, "label": "Workflow", + "link_count": 0, "link_to": "Workflow", "link_type": "DocType", "onboard": 0, @@ -97,6 +108,7 @@ "hidden": 0, "is_query_report": 0, "label": "Views", + "link_count": 0, "link_type": "DocType", "onboard": 0, "only_for": "", @@ -106,6 +118,7 @@ "hidden": 0, "is_query_report": 0, "label": "Report", + "link_count": 0, "link_to": "Report", "link_type": "DocType", "onboard": 0, @@ -116,6 +129,7 @@ "hidden": 0, "is_query_report": 0, "label": "Print Format", + "link_count": 0, "link_to": "Print Format", "link_type": "DocType", "onboard": 0, @@ -126,6 +140,7 @@ "hidden": 0, "is_query_report": 0, "label": "Workspace", + "link_count": 0, "link_to": "Workspace", "link_type": "DocType", "onboard": 0, @@ -136,6 +151,7 @@ "hidden": 0, "is_query_report": 0, "label": "Dashboard", + "link_count": 0, "link_to": "Dashboard", "link_type": "DocType", "onboard": 0, @@ -146,6 +162,7 @@ "hidden": 0, "is_query_report": 0, "label": "Scripting", + "link_count": 0, "link_type": "DocType", "onboard": 0, "only_for": "", @@ -155,6 +172,7 @@ "hidden": 0, "is_query_report": 0, "label": "Server Script", + "link_count": 0, "link_to": "Server Script", "link_type": "DocType", "onboard": 0, @@ -165,6 +183,7 @@ "hidden": 0, "is_query_report": 0, "label": "Client Script", + "link_count": 0, "link_to": "Client Script", "link_type": "DocType", "onboard": 0, @@ -175,20 +194,55 @@ "hidden": 0, "is_query_report": 0, "label": "Scheduled Job Type", + "link_count": 0, "link_to": "Scheduled Job Type", "link_type": "DocType", "onboard": 0, "only_for": "", "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Packages", + "link_count": 2, + "onboard": 0, + "type": "Card Break" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Package", + "link_count": 0, + "link_to": "Package", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Package Import", + "link_count": 0, + "link_to": "Package Import", + "link_type": "DocType", + "onboard": 0, + "type": "Link" } ], - "modified": "2021-02-04 13:48:48.493146", + "modified": "2021-09-05 21:14:52.384815", "modified_by": "Administrator", "module": "Core", "name": "Build", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 5, "shortcuts": [ { "doc_view": "", @@ -208,5 +262,6 @@ "link_to": "Report", "type": "DocType" } - ] + ], + "title": "Build" } \ No newline at end of file diff --git a/frappe/core/workspace/settings/settings.json b/frappe/core/workspace/settings/settings.json index fb26b73cfc..93a6c81c90 100644 --- a/frappe/core/workspace/settings/settings.json +++ b/frappe/core/workspace/settings/settings.json @@ -1,22 +1,27 @@ { - "category": "Modules", + "category": "", "charts": [], + "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Settings\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"System Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Print Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Website Settings\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Data\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Email / Notifications\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Website\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Core\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Printing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Workflow\", \"col\": 4}}]", "creation": "2020-03-02 15:09:40.527211", "developer_mode_only": 0, - "disable_user_customization": 1, + "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "setting", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "Settings", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Data", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -25,6 +30,7 @@ "hidden": 0, "is_query_report": 0, "label": "Import Data", + "link_count": 0, "link_to": "Data Import", "link_type": "DocType", "onboard": 0, @@ -35,6 +41,7 @@ "hidden": 0, "is_query_report": 0, "label": "Export Data", + "link_count": 0, "link_to": "Data Export", "link_type": "DocType", "onboard": 0, @@ -45,6 +52,7 @@ "hidden": 0, "is_query_report": 0, "label": "Bulk Update", + "link_count": 0, "link_to": "Bulk Update", "link_type": "DocType", "onboard": 0, @@ -55,6 +63,7 @@ "hidden": 0, "is_query_report": 0, "label": "Download Backups", + "link_count": 0, "link_to": "backups", "link_type": "Page", "onboard": 0, @@ -65,6 +74,7 @@ "hidden": 0, "is_query_report": 0, "label": "Deleted Documents", + "link_count": 0, "link_to": "Deleted Document", "link_type": "DocType", "onboard": 0, @@ -74,6 +84,7 @@ "hidden": 0, "is_query_report": 0, "label": "Email / Notifications", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -82,6 +93,7 @@ "hidden": 0, "is_query_report": 0, "label": "Email Account", + "link_count": 0, "link_to": "Email Account", "link_type": "DocType", "onboard": 0, @@ -92,6 +104,7 @@ "hidden": 0, "is_query_report": 0, "label": "Email Domain", + "link_count": 0, "link_to": "Email Domain", "link_type": "DocType", "onboard": 0, @@ -102,6 +115,7 @@ "hidden": 0, "is_query_report": 0, "label": "Notification", + "link_count": 0, "link_to": "Notification", "link_type": "DocType", "onboard": 0, @@ -112,6 +126,7 @@ "hidden": 0, "is_query_report": 0, "label": "Email Template", + "link_count": 0, "link_to": "Email Template", "link_type": "DocType", "onboard": 0, @@ -122,6 +137,7 @@ "hidden": 0, "is_query_report": 0, "label": "Auto Email Report", + "link_count": 0, "link_to": "Auto Email Report", "link_type": "DocType", "onboard": 0, @@ -132,6 +148,7 @@ "hidden": 0, "is_query_report": 0, "label": "Newsletter", + "link_count": 0, "link_to": "Newsletter", "link_type": "DocType", "onboard": 0, @@ -142,6 +159,7 @@ "hidden": 0, "is_query_report": 0, "label": "Notification Settings", + "link_count": 0, "link_to": "Notification Settings", "link_type": "DocType", "onboard": 0, @@ -151,6 +169,7 @@ "hidden": 0, "is_query_report": 0, "label": "Website", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -159,6 +178,7 @@ "hidden": 0, "is_query_report": 0, "label": "Website Settings", + "link_count": 0, "link_to": "Website Settings", "link_type": "DocType", "onboard": 1, @@ -169,6 +189,7 @@ "hidden": 0, "is_query_report": 0, "label": "Website Theme", + "link_count": 0, "link_to": "Website Theme", "link_type": "DocType", "onboard": 1, @@ -179,6 +200,7 @@ "hidden": 0, "is_query_report": 0, "label": "Website Script", + "link_count": 0, "link_to": "Website Script", "link_type": "DocType", "onboard": 0, @@ -189,6 +211,7 @@ "hidden": 0, "is_query_report": 0, "label": "About Us Settings", + "link_count": 0, "link_to": "About Us Settings", "link_type": "DocType", "onboard": 0, @@ -199,6 +222,7 @@ "hidden": 0, "is_query_report": 0, "label": "Contact Us Settings", + "link_count": 0, "link_to": "Contact Us Settings", "link_type": "DocType", "onboard": 0, @@ -208,6 +232,7 @@ "hidden": 0, "is_query_report": 0, "label": "Core", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -216,6 +241,7 @@ "hidden": 0, "is_query_report": 0, "label": "System Settings", + "link_count": 0, "link_to": "System Settings", "link_type": "DocType", "onboard": 0, @@ -226,6 +252,7 @@ "hidden": 0, "is_query_report": 0, "label": "Error Log", + "link_count": 0, "link_to": "Error Log", "link_type": "DocType", "onboard": 0, @@ -236,6 +263,7 @@ "hidden": 0, "is_query_report": 0, "label": "Error Snapshot", + "link_count": 0, "link_to": "Error Snapshot", "link_type": "DocType", "onboard": 0, @@ -246,6 +274,7 @@ "hidden": 0, "is_query_report": 0, "label": "Domain Settings", + "link_count": 0, "link_to": "Domain Settings", "link_type": "DocType", "onboard": 0, @@ -255,6 +284,7 @@ "hidden": 0, "is_query_report": 0, "label": "Printing", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -263,6 +293,7 @@ "hidden": 0, "is_query_report": 0, "label": "Print Format Builder", + "link_count": 0, "link_to": "print-format-builder", "link_type": "Page", "onboard": 0, @@ -273,6 +304,7 @@ "hidden": 0, "is_query_report": 0, "label": "Print Settings", + "link_count": 0, "link_to": "Print Settings", "link_type": "DocType", "onboard": 0, @@ -283,6 +315,7 @@ "hidden": 0, "is_query_report": 0, "label": "Print Format", + "link_count": 0, "link_to": "Print Format", "link_type": "DocType", "onboard": 0, @@ -293,6 +326,7 @@ "hidden": 0, "is_query_report": 0, "label": "Print Style", + "link_count": 0, "link_to": "Print Style", "link_type": "DocType", "onboard": 0, @@ -302,6 +336,7 @@ "hidden": 0, "is_query_report": 0, "label": "Workflow", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -310,6 +345,7 @@ "hidden": 0, "is_query_report": 0, "label": "Workflow", + "link_count": 0, "link_to": "Workflow", "link_type": "DocType", "onboard": 0, @@ -320,6 +356,7 @@ "hidden": 0, "is_query_report": 0, "label": "Workflow State", + "link_count": 0, "link_to": "Workflow State", "link_type": "DocType", "onboard": 0, @@ -330,19 +367,26 @@ "hidden": 0, "is_query_report": 0, "label": "Workflow Action", + "link_count": 0, "link_to": "Workflow Action", "link_type": "DocType", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:40.235323", + "modified": "2021-08-05 12:16:03.456173", "modified_by": "Administrator", "module": "Core", "name": "Settings", + "onboarding": "", "owner": "Administrator", - "pin_to_bottom": 1, + "parent_page": "", + "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 29, "shortcuts": [ { "icon": "setting", @@ -363,5 +407,6 @@ "type": "DocType" } ], - "shortcuts_label": "Settings" + "shortcuts_label": "Settings", + "title": "Settings" } \ No newline at end of file diff --git a/frappe/core/workspace/users/users.json b/frappe/core/workspace/users/users.json index ba82461b57..09a835ea2c 100644 --- a/frappe/core/workspace/users/users.json +++ b/frappe/core/workspace/users/users.json @@ -1,23 +1,27 @@ { - "category": "Administration", + "category": "", "charts": [], + "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"User\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Role\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Permission Manager\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"User Profile\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"User Type\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Users\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Logs\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Permissions\", \"col\": 4}}]", "creation": "2020-03-02 15:12:16.754449", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "users", "idx": 0, "is_default": 0, - "is_standard": 1, + "is_standard": 0, "label": "Users", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Users", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -26,6 +30,7 @@ "hidden": 0, "is_query_report": 0, "label": "User", + "link_count": 0, "link_to": "User", "link_type": "DocType", "onboard": 0, @@ -36,6 +41,7 @@ "hidden": 0, "is_query_report": 0, "label": "Role", + "link_count": 0, "link_to": "Role", "link_type": "DocType", "onboard": 0, @@ -46,6 +52,7 @@ "hidden": 0, "is_query_report": 0, "label": "Role Profile", + "link_count": 0, "link_to": "Role Profile", "link_type": "DocType", "onboard": 0, @@ -55,6 +62,7 @@ "hidden": 0, "is_query_report": 0, "label": "Logs", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -63,6 +71,7 @@ "hidden": 0, "is_query_report": 0, "label": "Activity Log", + "link_count": 0, "link_to": "Activity Log", "link_type": "DocType", "onboard": 0, @@ -73,6 +82,7 @@ "hidden": 0, "is_query_report": 0, "label": "Access Log", + "link_count": 0, "link_to": "Access Log", "link_type": "DocType", "onboard": 0, @@ -82,6 +92,7 @@ "hidden": 0, "is_query_report": 0, "label": "Permissions", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -90,6 +101,7 @@ "hidden": 0, "is_query_report": 0, "label": "Role Permissions Manager", + "link_count": 0, "link_to": "permission-manager", "link_type": "Page", "onboard": 0, @@ -100,6 +112,7 @@ "hidden": 0, "is_query_report": 0, "label": "User Permissions", + "link_count": 0, "link_to": "User Permission", "link_type": "DocType", "onboard": 0, @@ -110,6 +123,7 @@ "hidden": 0, "is_query_report": 0, "label": "Role Permission for Page and Report", + "link_count": 0, "link_to": "Role Permission for Page and Report", "link_type": "DocType", "onboard": 0, @@ -120,6 +134,7 @@ "hidden": 0, "is_query_report": 1, "label": "Permitted Documents For User", + "link_count": 0, "link_to": "Permitted Documents For User", "link_type": "Report", "onboard": 0, @@ -130,19 +145,26 @@ "hidden": 0, "is_query_report": 0, "label": "Document Share Report", + "link_count": 0, "link_to": "Document Share Report", "link_type": "Report", "onboard": 0, "type": "Link" } ], - "modified": "2021-03-25 23:02:34.582569", + "modified": "2021-08-05 12:16:03.010204", "modified_by": "Administrator", "module": "Core", "name": "Users", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 27, "shortcuts": [ { "label": "User", @@ -170,5 +192,6 @@ "link_to": "User Type", "type": "DocType" } - ] + ], + "title": "Users" } \ No newline at end of file diff --git a/frappe/coverage.py b/frappe/coverage.py index 33f945be40..1969cae141 100644 --- a/frappe/coverage.py +++ b/frappe/coverage.py @@ -58,4 +58,5 @@ class CodeCoverage(): def __exit__(self, exc_type, exc_value, traceback): if self.with_coverage: self.coverage.stop() - self.coverage.save() \ No newline at end of file + self.coverage.save() + self.coverage.xml_report() \ No newline at end of file diff --git a/frappe/custom/doctype/client_script/__init__.py b/frappe/custom/doctype/client_script/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/custom/doctype/client_script/__init__.py +++ b/frappe/custom/doctype/client_script/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/custom/doctype/client_script/client_script.json b/frappe/custom/doctype/client_script/client_script.json index db02d8d4bc..50f6bf3cc4 100644 --- a/frappe/custom/doctype/client_script/client_script.json +++ b/frappe/custom/doctype/client_script/client_script.json @@ -9,7 +9,10 @@ "field_order": [ "dt", "view", + "column_break_3", + "module", "enabled", + "section_break_6", "script", "sample" ], @@ -53,13 +56,27 @@ "label": "Apply To", "options": "List\nForm", "set_only_once": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "module", + "fieldtype": "Link", + "label": "Module (for export)", + "options": "Module Def" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" } ], "icon": "fa fa-glass", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2021-03-16 20:33:51.400191", + "modified": "2021-09-04 12:03:27.029815", "modified_by": "Administrator", "module": "Custom", "name": "Client Script", diff --git a/frappe/custom/doctype/client_script/client_script.py b/frappe/custom/doctype/client_script/client_script.py index 9c098fe8c9..fd6bc9accd 100644 --- a/frappe/custom/doctype/client_script/client_script.py +++ b/frappe/custom/doctype/client_script/client_script.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/custom/doctype/client_script/test_client_script.py b/frappe/custom/doctype/client_script/test_client_script.py index b8358468b9..4887956001 100644 --- a/frappe/custom/doctype/client_script/test_client_script.py +++ b/frappe/custom/doctype/client_script/test_client_script.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/custom/doctype/custom_field/__init__.py b/frappe/custom/doctype/custom_field/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/custom/doctype/custom_field/__init__.py +++ b/frappe/custom/doctype/custom_field/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 55a7ec5963..a8b1fb0e23 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -8,6 +8,7 @@ "engine": "InnoDB", "field_order": [ "dt", + "module", "label", "label_help", "fieldname", @@ -411,13 +412,19 @@ "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-07-12 05:54:13.042319", + "modified": "2021-09-04 12:45:22.810120", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index e266455f7a..61fc4486bd 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import json diff --git a/frappe/custom/doctype/custom_field/test_custom_field.py b/frappe/custom/doctype/custom_field/test_custom_field.py index 3196b66ee8..9633f0eb8a 100644 --- a/frappe/custom/doctype/custom_field/test_custom_field.py +++ b/frappe/custom/doctype/custom_field/test_custom_field.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/custom/doctype/customize_form/__init__.py b/frappe/custom/doctype/customize_form/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/custom/doctype/customize_form/__init__.py +++ b/frappe/custom/doctype/customize_form/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 8de194fb00..94f25a41aa 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -193,6 +193,16 @@ class CustomizeForm(Document): if prop == "fieldtype": self.validate_fieldtype_change(df, meta_df[0].get(prop), df.get(prop)) + elif prop == "length": + old_value_length = cint(meta_df[0].get(prop)) + new_value_length = cint(df.get(prop)) + + if new_value_length and (old_value_length > new_value_length): + self.check_length_for_fieldtypes.append({'df': df, 'old_value': meta_df[0].get(prop)}) + self.validate_fieldtype_length() + else: + self.flags.update_db = True + elif prop == "allow_on_submit" and df.get(prop): if not frappe.db.get_value("DocField", {"parent": self.doc_type, "fieldname": df.fieldname}, "allow_on_submit"): diff --git a/frappe/custom/doctype/customize_form/test_customize_form.py b/frappe/custom/doctype/customize_form/test_customize_form.py index aef95cd676..8a287b17e8 100644 --- a/frappe/custom/doctype/customize_form/test_customize_form.py +++ b/frappe/custom/doctype/customize_form/test_customize_form.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe, unittest, json from frappe.test_runner import make_test_records_for_doctype @@ -188,6 +188,26 @@ class TestCustomizeForm(unittest.TestCase): def test_core_doctype_customization(self): self.assertRaises(frappe.ValidationError, self.get_customize_form, 'User') + def test_save_customization_length_field_property(self): + # Using Notification Log doctype as it doesn't have any other custom fields + d = self.get_customize_form("Notification Log") + + document_name = d.get("fields", {"fieldname": "document_name"})[0] + document_name.length = 255 + d.run_method("save_customization") + + self.assertEqual(frappe.db.get_value("Property Setter", + {"doc_type": "Notification Log", "property": "length", "field_name": "document_name"}, "value"), '255') + + self.assertTrue(d.flags.update_db) + + length = frappe.db.sql("""SELECT character_maximum_length + FROM information_schema.columns + WHERE table_name = 'tabNotification Log' + AND column_name = 'document_name'""")[0][0] + + self.assertEqual(length, 255) + def test_custom_link(self): try: # create a dummy doctype linked to Event diff --git a/frappe/custom/doctype/customize_form_field/__init__.py b/frappe/custom/doctype/customize_form_field/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/custom/doctype/customize_form_field/__init__.py +++ b/frappe/custom/doctype/customize_form_field/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.py b/frappe/custom/doctype/customize_form_field/customize_form_field.py index f288e70754..67563cf048 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.py +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/custom/doctype/doctype_layout/doctype_layout.py b/frappe/custom/doctype/doctype_layout/doctype_layout.py index 0dc320353d..fa285ddb62 100644 --- a/frappe/custom/doctype/doctype_layout/doctype_layout.py +++ b/frappe/custom/doctype/doctype_layout/doctype_layout.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from frappe.model.document import Document diff --git a/frappe/custom/doctype/doctype_layout/test_doctype_layout.py b/frappe/custom/doctype/doctype_layout/test_doctype_layout.py index dcde3c00a4..a63dd7ee16 100644 --- a/frappe/custom/doctype/doctype_layout/test_doctype_layout.py +++ b/frappe/custom/doctype/doctype_layout/test_doctype_layout.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.py b/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.py index c1e963602f..3f8487b659 100644 --- a/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.py +++ b/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/custom/doctype/property_setter/__init__.py b/frappe/custom/doctype/property_setter/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/custom/doctype/property_setter/__init__.py +++ b/frappe/custom/doctype/property_setter/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/custom/doctype/property_setter/property_setter.json b/frappe/custom/doctype/property_setter/property_setter.json index b318d92c5a..fcb36637fe 100644 --- a/frappe/custom/doctype/property_setter/property_setter.json +++ b/frappe/custom/doctype/property_setter/property_setter.json @@ -13,6 +13,8 @@ "field_name", "row_name", "column_break0", + "module", + "section_break_9", "property", "property_type", "value", @@ -91,13 +93,23 @@ "fieldname": "row_name", "fieldtype": "Data", "label": "Row Name" + }, + { + "fieldname": "module", + "fieldtype": "Link", + "label": "Module (for export)", + "options": "Module Def" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break" } ], "icon": "fa fa-glass", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-09-24 14:42:38.599684", + "modified": "2021-09-04 12:46:17.860769", "modified_by": "Administrator", "module": "Custom", "name": "Property Setter", diff --git a/frappe/custom/doctype/property_setter/property_setter.py b/frappe/custom/doctype/property_setter/property_setter.py index 2a6c06b70a..7b95408060 100644 --- a/frappe/custom/doctype/property_setter/property_setter.py +++ b/frappe/custom/doctype/property_setter/property_setter.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/custom/doctype/property_setter/test_property_setter.py b/frappe/custom/doctype/property_setter/test_property_setter.py index 4d4de66d51..1bbbe59a0f 100644 --- a/frappe/custom/doctype/property_setter/test_property_setter.py +++ b/frappe/custom/doctype/property_setter/test_property_setter.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/custom/doctype/test_rename_new/test_rename_new.py b/frappe/custom/doctype/test_rename_new/test_rename_new.py index 32d2396b2b..fc4ab97cfe 100644 --- a/frappe/custom/doctype/test_rename_new/test_rename_new.py +++ b/frappe/custom/doctype/test_rename_new/test_rename_new.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/custom/doctype/test_rename_new/test_test_rename_new.py b/frappe/custom/doctype/test_rename_new/test_test_rename_new.py index b3ea4818de..03202669ed 100644 --- a/frappe/custom/doctype/test_rename_new/test_test_rename_new.py +++ b/frappe/custom/doctype/test_rename_new/test_test_rename_new.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/custom/workspace/customization/customization.json b/frappe/custom/workspace/customization/customization.json index cdc3b73366..136b1a57eb 100644 --- a/frappe/custom/workspace/customization/customization.json +++ b/frappe/custom/workspace/customization/customization.json @@ -1,23 +1,27 @@ { - "category": "Administration", + "category": "", "charts": [], + "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Customize Form\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Custom Role\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Client Script\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Server Script\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Dashboards\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Form Customization\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other\", \"col\": 4}}]", "creation": "2020-03-02 15:15:03.839594", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "customization", "idx": 0, "is_default": 0, - "is_standard": 1, + "is_standard": 0, "label": "Customization", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Dashboards", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -26,6 +30,7 @@ "hidden": 0, "is_query_report": 0, "label": "Dashboard", + "link_count": 0, "link_to": "Dashboard", "link_type": "DocType", "onboard": 0, @@ -36,6 +41,7 @@ "hidden": 0, "is_query_report": 0, "label": "Dashboard Chart", + "link_count": 0, "link_to": "Dashboard Chart", "link_type": "DocType", "onboard": 0, @@ -46,6 +52,7 @@ "hidden": 0, "is_query_report": 0, "label": "Dashboard Chart Source", + "link_count": 0, "link_to": "Dashboard Chart Source", "link_type": "DocType", "onboard": 0, @@ -55,6 +62,7 @@ "hidden": 0, "is_query_report": 0, "label": "Form Customization", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -63,6 +71,7 @@ "hidden": 0, "is_query_report": 0, "label": "Customize Form", + "link_count": 0, "link_to": "Customize Form", "link_type": "DocType", "onboard": 0, @@ -73,6 +82,7 @@ "hidden": 0, "is_query_report": 0, "label": "Custom Field", + "link_count": 0, "link_to": "Custom Field", "link_type": "DocType", "onboard": 0, @@ -83,6 +93,7 @@ "hidden": 0, "is_query_report": 0, "label": "Client Script", + "link_count": 0, "link_to": "Client Script", "link_type": "DocType", "onboard": 0, @@ -93,6 +104,7 @@ "hidden": 0, "is_query_report": 0, "label": "DocType", + "link_count": 0, "link_to": "DocType", "link_type": "DocType", "onboard": 0, @@ -102,6 +114,7 @@ "hidden": 0, "is_query_report": 0, "label": "Other", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -110,19 +123,26 @@ "hidden": 0, "is_query_report": 0, "label": "Custom Translations", + "link_count": 0, "link_to": "Translation", "link_type": "DocType", "onboard": 0, "type": "Link" } ], - "modified": "2021-02-04 13:50:35.750463", + "modified": "2021-08-05 12:15:57.486112", "modified_by": "Administrator", "module": "Custom", "name": "Customization", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 8, "shortcuts": [ { "label": "Customize Form", @@ -145,5 +165,6 @@ "link_to": "Server Script", "type": "DocType" } - ] + ], + "title": "Customization" } \ No newline at end of file diff --git a/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.py b/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.py index d1137f2e67..2e4e4d45b3 100644 --- a/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.py +++ b/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe, os from frappe.model.document import Document diff --git a/frappe/data_migration/doctype/data_migration_connector/test_data_migration_connector.py b/frappe/data_migration/doctype/data_migration_connector/test_data_migration_connector.py index fd45f86ec1..ffc96c8266 100644 --- a/frappe/data_migration/doctype/data_migration_connector/test_data_migration_connector.py +++ b/frappe/data_migration/doctype/data_migration_connector/test_data_migration_connector.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest class TestDataMigrationConnector(unittest.TestCase): diff --git a/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py index 5cb20ba56c..46d33eaca9 100644 --- a/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py +++ b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/data_migration/doctype/data_migration_mapping/test_data_migration_mapping.py b/frappe/data_migration/doctype/data_migration_mapping/test_data_migration_mapping.py index df11fc0522..b1040aaa58 100644 --- a/frappe/data_migration/doctype/data_migration_mapping/test_data_migration_mapping.py +++ b/frappe/data_migration/doctype/data_migration_mapping/test_data_migration_mapping.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest class TestDataMigrationMapping(unittest.TestCase): diff --git a/frappe/data_migration/doctype/data_migration_mapping_detail/data_migration_mapping_detail.py b/frappe/data_migration/doctype/data_migration_mapping_detail/data_migration_mapping_detail.py index 6d3ef50937..ce46f60f67 100644 --- a/frappe/data_migration/doctype/data_migration_mapping_detail/data_migration_mapping_detail.py +++ b/frappe/data_migration/doctype/data_migration_mapping_detail/data_migration_mapping_detail.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from frappe.model.document import Document diff --git a/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.py b/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.py index a8d0e40a4c..94ed77e2ec 100644 --- a/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.py +++ b/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.modules import get_module_path, scrub_dt_dn diff --git a/frappe/data_migration/doctype/data_migration_plan/test_data_migration_plan.py b/frappe/data_migration/doctype/data_migration_plan/test_data_migration_plan.py index 14c585a82d..649f7db903 100644 --- a/frappe/data_migration/doctype/data_migration_plan/test_data_migration_plan.py +++ b/frappe/data_migration/doctype/data_migration_plan/test_data_migration_plan.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest class TestDataMigrationPlan(unittest.TestCase): diff --git a/frappe/data_migration/doctype/data_migration_plan_mapping/data_migration_plan_mapping.py b/frappe/data_migration/doctype/data_migration_plan_mapping/data_migration_plan_mapping.py index ba4cf28eb8..7939a68d97 100644 --- a/frappe/data_migration/doctype/data_migration_plan_mapping/data_migration_plan_mapping.py +++ b/frappe/data_migration/doctype/data_migration_plan_mapping/data_migration_plan_mapping.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from frappe.model.document import Document diff --git a/frappe/data_migration/doctype/data_migration_run/data_migration_run.py b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py index c35af5827b..deb14baf27 100644 --- a/frappe/data_migration/doctype/data_migration_run/data_migration_run.py +++ b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe, json, math from frappe.model.document import Document diff --git a/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py b/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py index ef7b70dca2..485f86a7f9 100644 --- a/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py +++ b/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe, unittest class TestDataMigrationRun(unittest.TestCase): diff --git a/frappe/database/__init__.py b/frappe/database/__init__.py index a899bec3d1..b0e3183d4f 100644 --- a/frappe/database/__init__.py +++ b/frappe/database/__init__.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE # Database Module # -------------------- diff --git a/frappe/database/database.py b/frappe/database/database.py index d6ecf0795d..84bfa76cd7 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE # Database Module # -------------------- @@ -14,7 +14,7 @@ import frappe.model.meta from frappe import _ from time import time -from frappe.utils import now, getdate, cast_fieldtype, get_datetime, get_table_name +from frappe.utils import now, getdate, cast, get_datetime, get_table_name from frappe.model.utils.link_count import flush_local_link_count @@ -516,7 +516,6 @@ class Database(object): FROM `tabSingles` WHERE doctype = %s """, doctype) - # result = _cast_result(doctype, result) dict_ = frappe._dict(result) @@ -557,7 +556,7 @@ class Database(object): if not df: frappe.throw(_('Invalid field name: {0}').format(frappe.bold(fieldname)), self.InvalidColumnName) - val = cast_fieldtype(df.fieldtype, val) + val = cast(df.fieldtype, val) self.value_cache[doctype][fieldname] = val @@ -840,6 +839,30 @@ class Database(object): return count + def sum(self, dt, fieldname, filters=None): + return self._get_aggregation('SUM', dt, fieldname, filters) + + def avg(self, dt, fieldname, filters=None): + return self._get_aggregation('AVG', dt, fieldname, filters) + + def min(self, dt, fieldname, filters=None): + return self._get_aggregation('MIN', dt, fieldname, filters) + + def max(self, dt, fieldname, filters=None): + return self._get_aggregation('MAX', dt, fieldname, filters) + + def _get_aggregation(self, function, dt, fieldname, filters=None): + if not self.has_column(dt, fieldname): + frappe.throw(frappe._('Invalid column'), self.InvalidColumnName) + + query = f'SELECT {function}({fieldname}) AS value FROM `tab{dt}`' + values = () + if filters: + conditions, values = self.build_conditions(filters) + query = f"{query} WHERE {conditions}" + + return self.sql(query, values)[0][0] or 0 + @staticmethod def format_date(date): return getdate(date).strftime("%Y-%m-%d") @@ -1052,19 +1075,3 @@ def enqueue_jobs_after_commit(): q.enqueue_call(execute_job, timeout=job.get("timeout"), kwargs=job.get("queue_args")) frappe.flags.enqueue_after_commit = [] - -# Helpers -def _cast_result(doctype, result): - batch = [ ] - - try: - for field, value in result: - df = frappe.get_meta(doctype).get_field(field) - if df: - value = cast_fieldtype(df.fieldtype, value) - - batch.append(tuple([field, value])) - except frappe.exceptions.DoesNotExistError: - return result - - return tuple(batch) diff --git a/frappe/database/mariadb/framework_mariadb.sql b/frappe/database/mariadb/framework_mariadb.sql index f8841e9417..777e036049 100644 --- a/frappe/database/mariadb/framework_mariadb.sql +++ b/frappe/database/mariadb/framework_mariadb.sql @@ -61,6 +61,7 @@ CREATE TABLE `tabDocField` ( `in_preview` int(1) NOT NULL DEFAULT 0, `read_only` int(1) NOT NULL DEFAULT 0, `precision` varchar(255) DEFAULT NULL, + `max_height` varchar(10) DEFAULT NULL, `length` int(11) NOT NULL DEFAULT 0, `translatable` int(1) NOT NULL DEFAULT 0, `hide_border` int(1) NOT NULL DEFAULT 0, @@ -183,6 +184,7 @@ CREATE TABLE `tabDocType` ( `restrict_to_domain` varchar(255) DEFAULT NULL, `app` varchar(255) DEFAULT NULL, `autoname` varchar(255) DEFAULT NULL, + `naming_rule` varchar(40) DEFAULT NULL, `name_case` varchar(255) DEFAULT NULL, `title_field` varchar(255) DEFAULT NULL, `image_field` varchar(255) DEFAULT NULL, diff --git a/frappe/database/postgres/framework_postgres.sql b/frappe/database/postgres/framework_postgres.sql index a4e94aa326..868f98fc98 100644 --- a/frappe/database/postgres/framework_postgres.sql +++ b/frappe/database/postgres/framework_postgres.sql @@ -61,6 +61,7 @@ CREATE TABLE "tabDocField" ( "in_preview" smallint NOT NULL DEFAULT 0, "read_only" smallint NOT NULL DEFAULT 0, "precision" varchar(255) DEFAULT NULL, + "max_height" varchar(10) DEFAULT NULL, "length" bigint NOT NULL DEFAULT 0, "translatable" smallint NOT NULL DEFAULT 0, "hide_border" smallint NOT NULL DEFAULT 0, @@ -188,6 +189,7 @@ CREATE TABLE "tabDocType" ( "restrict_to_domain" varchar(255) DEFAULT NULL, "app" varchar(255) DEFAULT NULL, "autoname" varchar(255) DEFAULT NULL, + "naming_rule" varchar(40) DEFAULT NULL, "name_case" varchar(255) DEFAULT NULL, "title_field" varchar(255) DEFAULT NULL, "image_field" varchar(255) DEFAULT NULL, diff --git a/frappe/defaults.py b/frappe/defaults.py index d4c338388d..75feabc332 100644 --- a/frappe/defaults.py +++ b/frappe/defaults.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe.desk.notifications import clear_notifications diff --git a/frappe/desk/__init__.py b/frappe/desk/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/desk/__init__.py +++ b/frappe/desk/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/desk/calendar.py b/frappe/desk/calendar.py index 273b2654bf..66e6dd8434 100644 --- a/frappe/desk/calendar.py +++ b/frappe/desk/calendar.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/desk/desk_page.py b/frappe/desk/desk_page.py index d373dbda0e..a01008280c 100644 --- a/frappe/desk/desk_page.py +++ b/frappe/desk/desk_page.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe.translate import send_translations diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index ca53e6cba4..b9b01d0a74 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -1,11 +1,12 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE # Author - Shivam Mishra import frappe from json import loads, dumps from frappe import _, DoesNotExistError, ValidationError, _dict from frappe.boot import get_allowed_pages, get_allowed_reports +from frappe.core.doctype.custom_role.custom_role import get_custom_allowed_roles from functools import wraps from frappe.cache_manager import ( build_domain_restriced_doctype_cache, @@ -27,18 +28,21 @@ def handle_not_exist(fn): class Workspace: - def __init__(self, page_name, minimal=False): - self.page_name = page_name + def __init__(self, page, minimal=False): + self.page_name = page.get('name') + self.page_title = page.get('title') + self.public_page = page.get('public') self.extended_links = [] self.extended_charts = [] self.extended_shortcuts = [] + self.workspace_manager = "Workspace Manager" in frappe.get_roles() self.user = frappe.get_user() self.allowed_modules = self.get_cached('user_allowed_modules', self.get_allowed_modules) - self.doc = self.get_page_for_user() + self.doc = frappe.get_cached_doc("Workspace", self.page_name) - if self.doc.module and self.doc.module not in self.allowed_modules: + if self.doc and self.doc.module and self.doc.module not in self.allowed_modules and not self.workspace_manager: raise frappe.PermissionError self.can_read = self.get_cached('user_perm_can_read', self.get_can_read_items) @@ -47,16 +51,17 @@ class Workspace: self.allowed_reports = get_allowed_reports(cache=True) if not minimal: - self.onboarding_doc = self.get_onboarding_doc() - self.onboarding = None + if self.doc.content: + self.onboarding_list = [x['data']['onboarding_name'] for x in loads(self.doc.content) if x['type'] == 'onboarding'] + self.onboardings = [] self.table_counts = get_table_with_counts() self.restricted_doctypes = frappe.cache().get_value("domain_restricted_doctypes") or build_domain_restriced_doctype_cache() self.restricted_pages = frappe.cache().get_value("domain_restricted_pages") or build_domain_restriced_page_cache() def is_page_allowed(self): - cards = self.doc.get_link_groups() + get_custom_reports_and_doctypes(self.doc.module) + self.extended_links - shortcuts = self.doc.shortcuts + self.extended_shortcuts + cards = self.doc.get_link_groups() + get_custom_reports_and_doctypes(self.doc.module) + shortcuts = self.doc.shortcuts for section in cards: links = loads(section.get('links')) if isinstance(section.get('links'), str) else section.get('links') @@ -74,8 +79,28 @@ class Workspace: if self.is_item_allowed(item.link_to, item.type) and _in_active_domains(item): return True + if not shortcuts and not self.doc.links: + return True + return False + def is_permitted(self): + """Returns true if Has Role is not set or the user is allowed.""" + from frappe.utils import has_common + + allowed = [d.role for d in frappe.get_all("Has Role", fields=["role"], filters={"parent": self.doc.name})] + + custom_roles = get_custom_allowed_roles('page', self.doc.name) + allowed.extend(custom_roles) + + if not allowed: + return True + + roles = frappe.get_roles() + + if has_common(roles, allowed): + return True + def get_cached(self, cache_key, fallback_fn): _cache = frappe.cache() @@ -101,39 +126,18 @@ class Workspace: return self.user.allow_modules - def get_page_for_user(self): - filters = { - 'extends': self.page_name, - 'for_user': frappe.session.user - } - user_pages = frappe.get_all("Workspace", filters=filters, limit=1) - if user_pages: - return frappe.get_cached_doc("Workspace", user_pages[0]) - - filters = { - 'extends_another_page': 1, - 'extends': self.page_name, - 'is_default': 1 - } - default_page = frappe.get_all("Workspace", filters=filters, limit=1) - if default_page: - return frappe.get_cached_doc("Workspace", default_page[0]) - - self.get_pages_to_extend() - return frappe.get_cached_doc("Workspace", self.page_name) - - def get_onboarding_doc(self): + def get_onboarding_doc(self, onboarding): # Check if onboarding is enabled if not frappe.get_system_settings("enable_onboarding"): return None - if not self.doc.onboarding: + if not self.onboarding_list: return None - if frappe.db.get_value("Module Onboarding", self.doc.onboarding, "is_complete"): + if frappe.db.get_value("Module Onboarding", onboarding, "is_complete"): return None - doc = frappe.get_doc("Module Onboarding", self.doc.onboarding) + doc = frappe.get_doc("Module Onboarding", onboarding) # Check if user is allowed allowed_roles = set(doc.get_allowed_roles()) @@ -197,14 +201,9 @@ class Workspace: 'items': self.get_shortcuts() } - if self.onboarding_doc: - self.onboarding = { - 'label': _(self.onboarding_doc.title), - 'subtitle': _(self.onboarding_doc.subtitle), - 'success': _(self.onboarding_doc.success_message), - 'docs_url': self.onboarding_doc.documentation_url, - 'items': self.get_onboarding_steps() - } + self.onboardings = { + 'items': self.get_onboardings() + } def _doctype_contains_a_record(self, name): exists = self.table_counts.get(name, False) @@ -333,9 +332,26 @@ class Workspace: return items @handle_not_exist - def get_onboarding_steps(self): + def get_onboardings(self): + if self.onboarding_list: + for onboarding in self.onboarding_list: + onboarding_doc = self.get_onboarding_doc(onboarding) + if onboarding_doc: + item = { + 'label': _(onboarding), + 'title': _(onboarding_doc.title), + 'subtitle': _(onboarding_doc.subtitle), + 'success': _(onboarding_doc.success_message), + 'docs_url': onboarding_doc.documentation_url, + 'items': self.get_onboarding_steps(onboarding_doc) + } + self.onboardings.append(item) + return self.onboardings + + @handle_not_exist + def get_onboarding_steps(self, onboarding_doc): steps = [] - for doc in self.onboarding_doc.get_steps(): + for doc in onboarding_doc.get_steps(): step = doc.as_dict().copy() step.label = _(doc.title) if step.action == "Create Entry": @@ -352,19 +368,19 @@ def get_desktop_page(page): on desk. Args: - page (string): page name + page (json): page data Returns: dict: dictionary of cards, charts and shortcuts to be displayed on website """ try: - wspace = Workspace(page) + wspace = Workspace(loads(page)) wspace.build_workspace() return { 'charts': wspace.charts, 'shortcuts': wspace.shortcuts, 'cards': wspace.cards, - 'onboarding': wspace.onboarding, + 'onboardings': wspace.onboardings, 'allow_customization': not wspace.doc.disable_user_customization } except DoesNotExistError: @@ -372,39 +388,45 @@ def get_desktop_page(page): return {} @frappe.whitelist() -def get_desk_sidebar_items(): +def get_wspace_sidebar_items(): """Get list of sidebar items for desk""" + has_access = "Workspace Manager" in frappe.get_roles() # don't get domain restricted pages blocked_modules = frappe.get_doc('User', frappe.session.user).get_blocked_modules() + blocked_modules.append('Dummy Module') filters = { 'restrict_to_domain': ['in', frappe.get_active_domains()], - 'extends_another_page': 0, - 'for_user': '', 'module': ['not in', blocked_modules] } - if not frappe.local.conf.developer_mode: - filters['developer_mode_only'] = '0' + if has_access: + filters = [] - # pages sorted based on pinned to top and then by name - order_by = "pin_to_top desc, pin_to_bottom asc, name asc" - all_pages = frappe.get_all("Workspace", fields=["name", "category", "icon", "module"], - filters=filters, order_by=order_by, ignore_permissions=True) + # pages sorted based on sequence id + order_by = "sequence_id asc" + fields = ["name", "title", "for_user", "parent_page", "content", "public", "module", "icon"] + all_pages = frappe.get_all("Workspace", fields=fields, filters=filters, order_by=order_by, ignore_permissions=True) pages = [] + private_pages = [] # Filter Page based on Permission for page in all_pages: try: - wspace = Workspace(page.get('name'), True) - if wspace.is_page_allowed(): - pages.append(page) + wspace = Workspace(page) + if wspace.is_permitted() and wspace.is_page_allowed() or has_access: + if page.public: + pages.append(page) + elif page.for_user == frappe.session.user: + private_pages.append(page) page['label'] = _(page.get('name')) except frappe.PermissionError: pass + if private_pages: + pages.extend(private_pages) - return pages + return {'pages': pages, 'has_access': has_access} def get_table_with_counts(): counts = frappe.cache().get_value("information_schema:counts") @@ -471,7 +493,7 @@ def get_custom_workspace_for_user(page): """ filters = { 'extends': page, - 'for_user': frappe.session.user + 'for_user': frappe.session.user, } pages = frappe.get_list("Workspace", filters=filters) if pages: @@ -481,7 +503,6 @@ def get_custom_workspace_for_user(page): doc.for_user = frappe.session.user return doc - @frappe.whitelist() def save_customization(page, config): """Save customizations as a separate doctype in Workspace per user @@ -540,6 +561,80 @@ def save_customization(page, config): return True +def save_new_widget(doc, page, blocks, new_widgets): + + widgets = _dict(loads(new_widgets)) + + if widgets.chart: + doc.charts.extend(new_widget(widgets.chart, "Workspace Chart", "charts")) + if widgets.shortcut: + doc.shortcuts.extend(new_widget(widgets.shortcut, "Workspace Shortcut", "shortcuts")) + if widgets.card: + doc.build_links_table_from_card(widgets.card) + + # remove duplicate and unwanted widgets + if widgets: + clean_up(doc, blocks) + + try: + doc.save(ignore_permissions=True) + except (ValidationError, TypeError) as e: + # Create a json string to log + json_config = dumps(widgets, sort_keys=True, indent=4) + + # Error log body + log = \ + """ + page: {0} + config: {1} + exception: {2} + """.format(page, json_config, e) + frappe.log_error(log, _("Could not save customization")) + return False + + return True +def clean_up(original_page, blocks): + page_widgets = {} + + for wid in ['shortcut', 'card', 'chart']: + # get list of widget's name from blocks + page_widgets[wid] = [x['data'][wid + '_name'] for x in loads(blocks) if x['type'] == wid] + + # shortcut & chart cleanup + for wid in ['shortcut', 'chart']: + updated_widgets = [] + original_page.get(wid+'s').reverse() + + for w in original_page.get(wid+'s'): + if w.label in page_widgets[wid] and w.label not in [x.label for x in updated_widgets]: + updated_widgets.append(w) + original_page.set(wid+'s', updated_widgets) + + # card cleanup + for i, v in enumerate(original_page.links): + if v.type == 'Card Break' and v.label not in page_widgets['card']: + del original_page.links[i : i+v.link_count+1] + +def new_widget(config, doctype, parentfield): + if not config: + return [] + prepare_widget_list = [] + for idx, widget in enumerate(config): + # Some cleanup + widget.pop("name", None) + + # New Doc + doc = frappe.new_doc(doctype) + doc.update(widget) + + # Manually Set IDX + doc.idx = idx + 1 + + # Set Parent Field + doc.parentfield = parentfield + + prepare_widget_list.append(doc) + return prepare_widget_list def prepare_widget(config, doctype, parentfield): """Create widget child table entries with parent details diff --git a/frappe/desk/doctype/bulk_update/bulk_update.py b/frappe/desk/doctype/bulk_update/bulk_update.py index 469ee839f1..b512ca175c 100644 --- a/frappe/desk/doctype/bulk_update/bulk_update.py +++ b/frappe/desk/doctype/bulk_update/bulk_update.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/calendar_view/calendar_view.py b/frappe/desk/doctype/calendar_view/calendar_view.py index 3a986f3273..11612f5587 100644 --- a/frappe/desk/doctype/calendar_view/calendar_view.py +++ b/frappe/desk/doctype/calendar_view/calendar_view.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from frappe.model.document import Document diff --git a/frappe/desk/doctype/console_log/console_log.py b/frappe/desk/doctype/console_log/console_log.py index 5d0f1cfa93..e0b552ebfd 100644 --- a/frappe/desk/doctype/console_log/console_log.py +++ b/frappe/desk/doctype/console_log/console_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/console_log/test_console_log.py b/frappe/desk/doctype/console_log/test_console_log.py index 3bb1605204..c41b9d68c8 100644 --- a/frappe/desk/doctype/console_log/test_console_log.py +++ b/frappe/desk/doctype/console_log/test_console_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/desk/doctype/dashboard/dashboard.py b/frappe/desk/doctype/dashboard/dashboard.py index 1d333609db..0dfd458a37 100644 --- a/frappe/desk/doctype/dashboard/dashboard.py +++ b/frappe/desk/doctype/dashboard/dashboard.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from frappe.model.document import Document from frappe.modules.export_file import export_to_files diff --git a/frappe/desk/doctype/dashboard/test_dashboard.py b/frappe/desk/doctype/dashboard/test_dashboard.py index dd1bc31d86..15c132c027 100644 --- a/frappe/desk/doctype/dashboard/test_dashboard.py +++ b/frappe/desk/doctype/dashboard/test_dashboard.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest class TestDashboard(unittest.TestCase): diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index db5964e7b2..598b5e0b2b 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py index 9f10522b12..5562f2fc92 100644 --- a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest, frappe from frappe.utils import getdate, formatdate, get_last_day from frappe.utils.dateutils import get_period_ending, get_period diff --git a/frappe/desk/doctype/dashboard_chart_field/dashboard_chart_field.py b/frappe/desk/doctype/dashboard_chart_field/dashboard_chart_field.py index 7d6f66daa2..8b2fba2e58 100644 --- a/frappe/desk/doctype/dashboard_chart_field/dashboard_chart_field.py +++ b/frappe/desk/doctype/dashboard_chart_field/dashboard_chart_field.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/dashboard_chart_link/dashboard_chart_link.py b/frappe/desk/doctype/dashboard_chart_link/dashboard_chart_link.py index 359801a303..87d095d5d1 100644 --- a/frappe/desk/doctype/dashboard_chart_link/dashboard_chart_link.py +++ b/frappe/desk/doctype/dashboard_chart_link/dashboard_chart_link.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/dashboard_chart_source/dashboard_chart_source.py b/frappe/desk/doctype/dashboard_chart_source/dashboard_chart_source.py index 791dbc563b..71ded32837 100644 --- a/frappe/desk/doctype/dashboard_chart_source/dashboard_chart_source.py +++ b/frappe/desk/doctype/dashboard_chart_source/dashboard_chart_source.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe, os from frappe import _ diff --git a/frappe/desk/doctype/dashboard_chart_source/test_dashboard_chart_source.py b/frappe/desk/doctype/dashboard_chart_source/test_dashboard_chart_source.py index 53fe127dfb..6d6773d52e 100644 --- a/frappe/desk/doctype/dashboard_chart_source/test_dashboard_chart_source.py +++ b/frappe/desk/doctype/dashboard_chart_source/test_dashboard_chart_source.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest class TestDashboardChartSource(unittest.TestCase): diff --git a/frappe/desk/doctype/dashboard_settings/dashboard_settings.py b/frappe/desk/doctype/dashboard_settings/dashboard_settings.py index df61c52114..2f29b3e989 100644 --- a/frappe/desk/doctype/dashboard_settings/dashboard_settings.py +++ b/frappe/desk/doctype/dashboard_settings/dashboard_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/desktop_icon/desktop_icon.py b/frappe/desk/doctype/desktop_icon/desktop_icon.py index 28c5a670cb..194b0d0ca4 100644 --- a/frappe/desk/doctype/desktop_icon/desktop_icon.py +++ b/frappe/desk/doctype/desktop_icon/desktop_icon.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/desk/doctype/event/__init__.py b/frappe/desk/doctype/event/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/desk/doctype/event/__init__.py +++ b/frappe/desk/doctype/event/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py index e7e7be530b..d4c185e56f 100644 --- a/frappe/desk/doctype/event/event.py +++ b/frappe/desk/doctype/event/event.py @@ -1,5 +1,5 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/desk/doctype/event/test_event.py b/frappe/desk/doctype/event/test_event.py index 77211946a9..6b7f6ee471 100644 --- a/frappe/desk/doctype/event/test_event.py +++ b/frappe/desk/doctype/event/test_event.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE """Use blog post test to test user permissions logic""" import frappe @@ -14,7 +14,7 @@ test_records = frappe.get_test_records('Event') class TestEvent(unittest.TestCase): def setUp(self): - frappe.db.sql('delete from tabEvent') + frappe.db.delete("Event") make_test_objects('Event', reset=True) self.test_records = frappe.get_test_records('Event') diff --git a/frappe/desk/doctype/event_participants/event_participants.py b/frappe/desk/doctype/event_participants/event_participants.py index ca4fae9930..b834ba3a82 100644 --- a/frappe/desk/doctype/event_participants/event_participants.py +++ b/frappe/desk/doctype/event_participants/event_participants.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from frappe.model.document import Document class EventParticipants(Document): diff --git a/frappe/desk/doctype/form_tour/form_tour.py b/frappe/desk/doctype/form_tour/form_tour.py index dbc667ce28..82d47224dd 100644 --- a/frappe/desk/doctype/form_tour/form_tour.py +++ b/frappe/desk/doctype/form_tour/form_tour.py @@ -1,5 +1,5 @@ # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document @@ -25,7 +25,7 @@ class FormTour(Document): def on_update(self): if frappe.conf.developer_mode and self.is_standard: export_to_files([['Form Tour', self.name]], self.module) - + def before_export(self, doc): for d in doc.steps: d.field = "" diff --git a/frappe/desk/doctype/form_tour/test_form_tour.py b/frappe/desk/doctype/form_tour/test_form_tour.py index a4a796ce41..3670cbc218 100644 --- a/frappe/desk/doctype/form_tour/test_form_tour.py +++ b/frappe/desk/doctype/form_tour/test_form_tour.py @@ -1,5 +1,5 @@ # Copyright (c) 2021, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/desk/doctype/form_tour_step/form_tour_step.py b/frappe/desk/doctype/form_tour_step/form_tour_step.py index 0df5665c63..bbc8edea08 100644 --- a/frappe/desk/doctype/form_tour_step/form_tour_step.py +++ b/frappe/desk/doctype/form_tour_step/form_tour_step.py @@ -1,5 +1,5 @@ # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/global_search_doctype/global_search_doctype.py b/frappe/desk/doctype/global_search_doctype/global_search_doctype.py index de8a48af01..30a31f959f 100644 --- a/frappe/desk/doctype/global_search_doctype/global_search_doctype.py +++ b/frappe/desk/doctype/global_search_doctype/global_search_doctype.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/global_search_settings/global_search_settings.py b/frappe/desk/doctype/global_search_settings/global_search_settings.py index 9112349c1b..9ffe9aaf06 100644 --- a/frappe/desk/doctype/global_search_settings/global_search_settings.py +++ b/frappe/desk/doctype/global_search_settings/global_search_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/kanban_board/kanban_board.py b/frappe/desk/doctype/kanban_board/kanban_board.py index 5100727f43..155a925fcf 100644 --- a/frappe/desk/doctype/kanban_board/kanban_board.py +++ b/frappe/desk/doctype/kanban_board/kanban_board.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe import json diff --git a/frappe/desk/doctype/kanban_board/test_kanban_board.py b/frappe/desk/doctype/kanban_board/test_kanban_board.py index f9503d736a..f00446141a 100644 --- a/frappe/desk/doctype/kanban_board/test_kanban_board.py +++ b/frappe/desk/doctype/kanban_board/test_kanban_board.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/desk/doctype/kanban_board_column/kanban_board_column.py b/frappe/desk/doctype/kanban_board_column/kanban_board_column.py index aebba3351c..d919fd6aed 100644 --- a/frappe/desk/doctype/kanban_board_column/kanban_board_column.py +++ b/frappe/desk/doctype/kanban_board_column/kanban_board_column.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/list_filter/list_filter.py b/frappe/desk/doctype/list_filter/list_filter.py index 2467ae40a4..d2b01d301e 100644 --- a/frappe/desk/doctype/list_filter/list_filter.py +++ b/frappe/desk/doctype/list_filter/list_filter.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe, json from frappe.model.document import Document diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index f4a288b7ba..78b56fe7d5 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/list_view_settings/test_list_view_settings.py b/frappe/desk/doctype/list_view_settings/test_list_view_settings.py index 00010d7604..85872dd36e 100644 --- a/frappe/desk/doctype/list_view_settings/test_list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/test_list_view_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/desk/doctype/module_onboarding/module_onboarding.py b/frappe/desk/doctype/module_onboarding/module_onboarding.py index 6f01e0fd8d..aa268c792c 100644 --- a/frappe/desk/doctype/module_onboarding/module_onboarding.py +++ b/frappe/desk/doctype/module_onboarding/module_onboarding.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/module_onboarding/test_module_onboarding.py b/frappe/desk/doctype/module_onboarding/test_module_onboarding.py index 39184401a1..42f472abc1 100644 --- a/frappe/desk/doctype/module_onboarding/test_module_onboarding.py +++ b/frappe/desk/doctype/module_onboarding/test_module_onboarding.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/desk/doctype/note/note.py b/frappe/desk/doctype/note/note.py index 790f9a514c..ae7af07cd9 100644 --- a/frappe/desk/doctype/note/note.py +++ b/frappe/desk/doctype/note/note.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: See license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/note/test_note.py b/frappe/desk/doctype/note/test_note.py index 1bb1730357..ac2116c38a 100644 --- a/frappe/desk/doctype/note/test_note.py +++ b/frappe/desk/doctype/note/test_note.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest @@ -8,9 +8,9 @@ test_records = frappe.get_test_records('Note') class TestNote(unittest.TestCase): def insert_note(self): - frappe.db.sql('delete from tabVersion') - frappe.db.sql('delete from tabNote') - frappe.db.sql('delete from `tabNote Seen By`') + frappe.db.delete("Version") + frappe.db.delete("Note") + frappe.db.delete("Note Seen By") return frappe.get_doc(dict(doctype='Note', title='test note', content='test note content')).insert() diff --git a/frappe/desk/doctype/note_seen_by/note_seen_by.py b/frappe/desk/doctype/note_seen_by/note_seen_by.py index cec4628b20..01bee05a9f 100644 --- a/frappe/desk/doctype/note_seen_by/note_seen_by.py +++ b/frappe/desk/doctype/note_seen_by/note_seen_by.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index d7d7f68b74..12e628ada2 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/desk/doctype/notification_log/test_notification_log.py b/frappe/desk/doctype/notification_log/test_notification_log.py index af4dee8df3..4c415a860c 100644 --- a/frappe/desk/doctype/notification_log/test_notification_log.py +++ b/frappe/desk/doctype/notification_log/test_notification_log.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe +from frappe.core.doctype.user.user import get_system_users from frappe.desk.form.assign_to import add as assign_task import unittest @@ -54,7 +55,4 @@ def get_todo(): return frappe.get_cached_doc('ToDo', res[0].name) def get_user(): - users = frappe.db.get_all('User', - filters={'name': ('not in', ['Administrator', 'Guest'])}, - fields='name', limit=1) - return users[0].name + return get_system_users(limit=1)[0] diff --git a/frappe/desk/doctype/notification_settings/notification_settings.py b/frappe/desk/doctype/notification_settings/notification_settings.py index eb3a16435f..cf6bb2d78d 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.py +++ b/frappe/desk/doctype/notification_settings/notification_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/notification_subscribed_document/notification_subscribed_document.py b/frappe/desk/doctype/notification_subscribed_document/notification_subscribed_document.py index 6931e77754..1fdba22779 100644 --- a/frappe/desk/doctype/notification_subscribed_document/notification_subscribed_document.py +++ b/frappe/desk/doctype/notification_subscribed_document/notification_subscribed_document.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py index d8d5fe0953..5662523a9d 100644 --- a/frappe/desk/doctype/number_card/number_card.py +++ b/frappe/desk/doctype/number_card/number_card.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/number_card/test_number_card.py b/frappe/desk/doctype/number_card/test_number_card.py index c395f5f915..cc92e63341 100644 --- a/frappe/desk/doctype/number_card/test_number_card.py +++ b/frappe/desk/doctype/number_card/test_number_card.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/desk/doctype/number_card_link/number_card_link.py b/frappe/desk/doctype/number_card_link/number_card_link.py index 6c16f45f4b..0b55ae6dcd 100644 --- a/frappe/desk/doctype/number_card_link/number_card_link.py +++ b/frappe/desk/doctype/number_card_link/number_card_link.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/onboarding_permission/onboarding_permission.py b/frappe/desk/doctype/onboarding_permission/onboarding_permission.py index 40d3dc33b1..a0e87c3067 100644 --- a/frappe/desk/doctype/onboarding_permission/onboarding_permission.py +++ b/frappe/desk/doctype/onboarding_permission/onboarding_permission.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/onboarding_permission/test_onboarding_permission.py b/frappe/desk/doctype/onboarding_permission/test_onboarding_permission.py index 80b166de0a..c13fb29678 100644 --- a/frappe/desk/doctype/onboarding_permission/test_onboarding_permission.py +++ b/frappe/desk/doctype/onboarding_permission/test_onboarding_permission.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/desk/doctype/onboarding_step/onboarding_step.py b/frappe/desk/doctype/onboarding_step/onboarding_step.py index 10bd8926ce..45e0ca34fd 100644 --- a/frappe/desk/doctype/onboarding_step/onboarding_step.py +++ b/frappe/desk/doctype/onboarding_step/onboarding_step.py @@ -1,11 +1,27 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE -# import frappe +import frappe +from frappe import _ +import json from frappe.model.document import Document class OnboardingStep(Document): def before_export(self, doc): doc.is_complete = 0 doc.is_skipped = 0 + + +@frappe.whitelist() +def get_onboarding_steps(ob_steps): + steps = [] + for s in json.loads(ob_steps): + doc = frappe.get_doc('Onboarding Step', s.get('step')) + step = doc.as_dict().copy() + step.label = _(doc.title) + if step.action == "Create Entry": + step.is_submittable = frappe.db.get_value("DocType", step.reference_document, 'is_submittable', cache=True) + steps.append(step) + + return steps diff --git a/frappe/desk/doctype/onboarding_step/test_onboarding_step.py b/frappe/desk/doctype/onboarding_step/test_onboarding_step.py index 2425577478..b0651da4da 100644 --- a/frappe/desk/doctype/onboarding_step/test_onboarding_step.py +++ b/frappe/desk/doctype/onboarding_step/test_onboarding_step.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/desk/doctype/onboarding_step_map/onboarding_step_map.py b/frappe/desk/doctype/onboarding_step_map/onboarding_step_map.py index c79244c4ad..7c20e220db 100644 --- a/frappe/desk/doctype/onboarding_step_map/onboarding_step_map.py +++ b/frappe/desk/doctype/onboarding_step_map/onboarding_step_map.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/route_history/route_history.py b/frappe/desk/doctype/route_history/route_history.py index 95872440c7..01184fcc3a 100644 --- a/frappe/desk/doctype/route_history/route_history.py +++ b/frappe/desk/doctype/route_history/route_history.py @@ -1,5 +1,5 @@ # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/system_console/system_console.js b/frappe/desk/doctype/system_console/system_console.js index c7eac39490..48dd2ba108 100644 --- a/frappe/desk/doctype/system_console/system_console.js +++ b/frappe/desk/doctype/system_console/system_console.js @@ -5,7 +5,7 @@ frappe.ui.form.on('System Console', { onload: function(frm) { frappe.ui.keys.add_shortcut({ shortcut: 'shift+enter', - action: () => frm.execute_action('Execute'), + action: () => frm.page.btn_primary.trigger('click'), page: frm.page, description: __('Execute Console script'), ignore_inputs: true, @@ -14,8 +14,11 @@ frappe.ui.form.on('System Console', { refresh: function(frm) { frm.disable_save(); - frm.page.set_primary_action(__("Execute"), () => { - frm.execute_action('Execute'); + frm.page.set_primary_action(__("Execute"), $btn => { + $btn.text(__('Executing...')); + return frm.execute_action("Execute").then(() => { + $btn.text(__('Execute')); + }); }); } }); diff --git a/frappe/desk/doctype/system_console/system_console.py b/frappe/desk/doctype/system_console/system_console.py index e2b5656bc0..f7f31cc3ba 100644 --- a/frappe/desk/doctype/system_console/system_console.py +++ b/frappe/desk/doctype/system_console/system_console.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import json diff --git a/frappe/desk/doctype/system_console/test_system_console.py b/frappe/desk/doctype/system_console/test_system_console.py index 743c2d6dde..fa7c577faa 100644 --- a/frappe/desk/doctype/system_console/test_system_console.py +++ b/frappe/desk/doctype/system_console/test_system_console.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index 2341d721e2..44bb780681 100644 --- a/frappe/desk/doctype/tag/tag.py +++ b/frappe/desk/doctype/tag/tag.py @@ -1,5 +1,5 @@ # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/tag/test_tag.py b/frappe/desk/doctype/tag/test_tag.py index 6eb7219c26..b9c6e0b744 100644 --- a/frappe/desk/doctype/tag/test_tag.py +++ b/frappe/desk/doctype/tag/test_tag.py @@ -6,7 +6,7 @@ from frappe.desk.doctype.tag.tag import add_tag class TestTag(unittest.TestCase): def setUp(self) -> None: - frappe.db.sql("DELETE from `tabTag`") + frappe.db.delete("Tag") frappe.db.sql("UPDATE `tabDocType` set _user_tags=''") def test_tag_count_query(self): diff --git a/frappe/desk/doctype/tag_link/tag_link.py b/frappe/desk/doctype/tag_link/tag_link.py index 4c5149f42c..d07894989d 100644 --- a/frappe/desk/doctype/tag_link/tag_link.py +++ b/frappe/desk/doctype/tag_link/tag_link.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/tag_link/test_tag_link.py b/frappe/desk/doctype/tag_link/test_tag_link.py index 297ee3cc96..fa6a22903f 100644 --- a/frappe/desk/doctype/tag_link/test_tag_link.py +++ b/frappe/desk/doctype/tag_link/test_tag_link.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/desk/doctype/todo/__init__.py b/frappe/desk/doctype/todo/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/desk/doctype/todo/__init__.py +++ b/frappe/desk/doctype/todo/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/desk/doctype/todo/test_todo.py b/frappe/desk/doctype/todo/test_todo.py index b38e4a059a..34d3cee191 100644 --- a/frappe/desk/doctype/todo/test_todo.py +++ b/frappe/desk/doctype/todo/test_todo.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest from frappe.model.db_query import DatabaseQuery @@ -14,7 +14,7 @@ class TestToDo(unittest.TestCase): todo = frappe.get_doc(dict(doctype='ToDo', description='test todo', assigned_by='Administrator')).insert() - frappe.db.sql('delete from `tabDeleted Document`') + frappe.db.delete("Deleted Document") todo.delete() deleted = frappe.get_doc('Deleted Document', dict(deleted_doctype=todo.doctype, deleted_name=todo.name)) @@ -27,7 +27,7 @@ class TestToDo(unittest.TestCase): frappe.db.get_value('User', todo.assigned_by, 'full_name')) def test_fetch_setup(self): - frappe.db.sql('delete from tabToDo') + frappe.db.delete("ToDo") todo_meta = frappe.get_doc('DocType', 'ToDo') todo_meta.get('fields', dict(fieldname='assigned_by_full_name'))[0].fetch_from = '' @@ -104,8 +104,8 @@ class TestToDo(unittest.TestCase): clear_permissions_cache('ToDo') frappe.db.rollback() -def test_fetch_if_empty(self): - frappe.db.sql('delete from tabToDo') + def test_fetch_if_empty(self): + frappe.db.delete("ToDo") # Allow user changes todo_meta = frappe.get_doc('DocType', 'ToDo') @@ -122,9 +122,8 @@ def test_fetch_if_empty(self): self.assertEqual(todo.assigned_by_full_name, 'Admin') # Overwrite user changes - todo_meta = frappe.get_doc('DocType', 'ToDo') - todo_meta.get('fields', dict(fieldname='assigned_by_full_name'))[0].fetch_if_empty = 0 - todo_meta.save() + todo.meta.get('fields', dict(fieldname='assigned_by_full_name'))[0].fetch_if_empty = 0 + todo.meta.save() todo.reload() todo.save() diff --git a/frappe/desk/doctype/todo/todo.py b/frappe/desk/doctype/todo/todo.py index 09297b4e5e..6f3f4160e6 100644 --- a/frappe/desk/doctype/todo/todo.py +++ b/frappe/desk/doctype/todo/todo.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import json @@ -29,8 +29,15 @@ class ToDo(Document): else: # NOTE the previous value is only available in validate method if self.get_db_value("status") != self.status: + if self.owner == frappe.session.user: + removal_message = frappe._("{0} removed their assignment.").format( + get_fullname(frappe.session.user)) + else: + removal_message = frappe._("Assignment of {0} removed by {1}").format( + get_fullname(self.owner), get_fullname(frappe.session.user)) + self._assignment = { - "text": frappe._("Assignment closed by {0}").format(get_fullname(frappe.session.user)), + "text": removal_message, "comment_type": "Assignment Completed" } diff --git a/frappe/desk/doctype/workspace/test_workspace.py b/frappe/desk/doctype/workspace/test_workspace.py index 619b3608eb..6c16e69afe 100644 --- a/frappe/desk/doctype/workspace/test_workspace.py +++ b/frappe/desk/doctype/workspace/test_workspace.py @@ -1,8 +1,95 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt -# import frappe +# License: MIT. See LICENSE +import frappe import unittest - class TestWorkspace(unittest.TestCase): - pass + def setUp(self): + create_module("Test Module") + + def tearDown(self): + frappe.db.delete("Workspace", {"module": "Test Module"}) + frappe.db.delete("DocType", {"module": "Test Module"}) + frappe.delete_doc("Module Def", "Test Module") + + # TODO: FIX ME - flaky test!!! + # def test_workspace_with_cards_specific_to_a_country(self): + # workspace = create_workspace() + # insert_card(workspace, "Card Label 1", "DocType 1", "DocType 2", "France") + # insert_card(workspace, "Card Label 2", "DocType A", "DocType B") + + # workspace.insert(ignore_if_duplicate = True) + + # cards = workspace.get_link_groups() + + # if frappe.get_system_settings('country') == "France": + # self.assertEqual(len(cards), 2) + # else: + # self.assertEqual(len(cards), 1) + +def create_module(module_name): + module = frappe.get_doc({ + "doctype": "Module Def", + "module_name": module_name, + "app_name": "frappe" + }) + module.insert(ignore_if_duplicate = True) + + return module + +def create_workspace(**args): + workspace = frappe.new_doc("Workspace") + args = frappe._dict(args) + + workspace.name = args.name or "Test Workspace" + workspace.label = args.label or "Test Workspace" + workspace.category = args.category or "Modules" + workspace.is_standard = args.is_standard or 1 + workspace.module = "Test Module" + + return workspace + +def insert_card(workspace, card_label, doctype1, doctype2, country=None): + workspace.append("links", { + "type": "Card Break", + "label": card_label, + "only_for": country + }) + + create_doctype(doctype1, "Test Module") + workspace.append("links", { + "type": "Link", + "label": doctype1, + "only_for": country, + "link_type": "DocType", + "link_to": doctype1 + }) + + create_doctype(doctype2, "Test Module") + workspace.append("links", { + "type": "Link", + "label": doctype2, + "only_for": country, + "link_type": "DocType", + "link_to": doctype2 + }) + +def create_doctype(doctype_name, module): + frappe.get_doc({ + 'doctype': 'DocType', + 'name': doctype_name, + 'module': module, + 'custom': 1, + 'autoname': 'field:title', + 'fields': [ + {'label': 'Title', 'fieldname': 'title', 'reqd': 1, 'fieldtype': 'Data'}, + {'label': 'Description', 'fieldname': 'description', 'fieldtype': 'Small Text'}, + {'label': 'Date', 'fieldname': 'date', 'fieldtype': 'Date'}, + {'label': 'Duration', 'fieldname': 'duration', 'fieldtype': 'Duration'}, + {'label': 'Number', 'fieldname': 'number', 'fieldtype': 'Int'}, + {'label': 'Number', 'fieldname': 'another_number', 'fieldtype': 'Int'} + ], + 'permissions': [ + {'role': 'System Manager'} + ] + }).insert(ignore_if_duplicate = True) diff --git a/frappe/desk/doctype/workspace/workspace.json b/frappe/desk/doctype/workspace/workspace.json index 386267b699..1e111b8d12 100644 --- a/frappe/desk/doctype/workspace/workspace.json +++ b/frappe/desk/doctype/workspace/workspace.json @@ -8,8 +8,11 @@ "engine": "InnoDB", "field_order": [ "label", + "title", + "sequence_id", "for_user", "extends", + "parent_page", "module", "category", "icon", @@ -24,6 +27,8 @@ "pin_to_top", "pin_to_bottom", "hide_custom", + "public", + "content", "section_break_2", "charts_label", "charts", @@ -32,13 +37,16 @@ "shortcuts", "section_break_18", "cards_label", - "links" + "links", + "roles_section", + "roles" ], "fields": [ { "fieldname": "label", "fieldtype": "Data", "label": "Name", + "reqd": 1, "unique": 1 }, { @@ -199,7 +207,7 @@ }, { "fieldname": "icon", - "fieldtype": "Data", + "fieldtype": "Icon", "label": "Icon" }, { @@ -209,16 +217,56 @@ "options": "Workspace Link" }, { - "default": "0", - "depends_on": "extends_another_page", - "description": "Sets the current page as default for all users", - "fieldname": "is_default", - "fieldtype": "Check", - "label": "Is Default" - } + "default": "0", + "depends_on": "extends_another_page", + "description": "Sets the current page as default for all users", + "fieldname": "is_default", + "fieldtype": "Check", + "label": "Is Default" + }, + { + "default": "0", + "fieldname": "public", + "fieldtype": "Check", + "label": "Public" + }, + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "parent_page", + "fieldtype": "Data", + "label": "Parent Page" + }, + { + "default": "[]", + "fieldname": "content", + "fieldtype": "Long Text", + "hidden": 1, + "label": "Content" + }, + { + "fieldname": "sequence_id", + "fieldtype": "Int", + "label": "Sequence Id" + }, + { + "fieldname": "roles", + "fieldtype": "Table", + "label": "Roles", + "options": "Has Role" + }, + { + "fieldname": "roles_section", + "fieldtype": "Section Break", + "label": "Roles" + } ], "links": [], - "modified": "2021-01-21 12:09:36.156614", + "modified": "2021-08-30 18:47:18.227154", "modified_by": "Administrator", "module": "Desk", "name": "Workspace", @@ -232,7 +280,7 @@ "print": 1, "read": 1, "report": 1, - "role": "System Manager", + "role": "Workspace Manager", "share": 1, "write": 1 }, @@ -248,4 +296,4 @@ ], "sort_field": "modified", "sort_order": "DESC" -} +} \ No newline at end of file diff --git a/frappe/desk/doctype/workspace/workspace.py b/frappe/desk/doctype/workspace/workspace.py index 41b0227f2a..25dd9b26d2 100644 --- a/frappe/desk/doctype/workspace/workspace.py +++ b/frappe/desk/doctype/workspace/workspace.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _ from frappe.modules.export_file import export_to_files from frappe.model.document import Document +from frappe.desk.desktop import save_new_widget from frappe.desk.utils import validate_route_conflict from json import loads @@ -16,6 +17,12 @@ class Workspace(Document): frappe.throw(_("You need to be in developer mode to edit this document")) validate_route_conflict(self.doctype, self.name) + try: + if not isinstance(loads(self.content), list): + raise + except Exception: + frappe.throw(_("Content data shoud be a list")) + duplicate_exists = frappe.db.exists("Workspace", { "name": ["!=", self.name], 'is_default': 1, 'extends': self.extends }) @@ -27,7 +34,7 @@ class Workspace(Document): if disable_saving_as_standard(): return - if frappe.conf.developer_mode and self.is_standard: + if frappe.conf.developer_mode and self.module and self.public: export_to_files(record_list=[['Workspace', self.name]], record_module=self.module) @staticmethod @@ -55,7 +62,7 @@ class Workspace(Document): for link in self.links: link = link.as_dict() if link.type == "Card Break": - if card_links and (not current_card.only_for or current_card.only_for == frappe.get_system_settings('country')): + if card_links and (not current_card.get('only_for') or current_card.get('only_for') == frappe.get_system_settings('country')): current_card['links'] = card_links cards.append(current_card) @@ -98,6 +105,37 @@ class Workspace(Document): "is_query_report": link.get('is_query_report') }) + def build_links_table_from_card(self, config): + + for idx, card in enumerate(config): + links = loads(card.get('links')) + + # remove duplicate before adding + for idx, link in enumerate(self.links): + if link.label == card.get('label') and link.type == 'Card Break': + del self.links[idx : idx + link.link_count + 1] + + self.append('links', { + "label": card.get('label'), + "type": "Card Break", + "icon": card.get('icon'), + "hidden": card.get('hidden') or False, + "link_count": card.get('link_count'), + "idx": 1 if not self.links else self.links[-1].idx + 1 + }) + + for link in links: + self.append('links', { + "label": link.get('label'), + "type": "Link", + "link_type": link.get('link_type'), + "link_to": link.get('link_to'), + "onboard": link.get('onboard'), + "only_for": link.get('only_for'), + "dependencies": link.get('dependencies'), + "is_query_report": link.get('is_query_report'), + "idx": self.links[-1].idx + 1 + }) def disable_saving_as_standard(): return frappe.flags.in_install or \ @@ -123,3 +161,84 @@ def get_link_type(key): def get_report_type(report): report_type = frappe.get_value("Report", report, "report_type") return report_type in ["Query Report", "Script Report", "Custom Report"] + + +@frappe.whitelist() +def save_page(title, icon, parent, public, sb_public_items, sb_private_items, deleted_pages, new_widgets, blocks, save): + save = frappe.parse_json(save) + public = frappe.parse_json(public) + if save: + doc = frappe.new_doc('Workspace') + doc.title = title + doc.icon = icon + doc.content = blocks + doc.parent_page = parent + + if public: + doc.label = title + doc.public = 1 + else: + doc.label = title + "-" + frappe.session.user + doc.for_user = frappe.session.user + doc.save(ignore_permissions=True) + else: + if public: + filters = { + 'public': public, + 'label': title + } + else: + filters = { + 'for_user': frappe.session.user, + 'label': title + "-" + frappe.session.user + } + pages = frappe.get_list("Workspace", filters=filters) + if pages: + doc = frappe.get_doc("Workspace", pages[0]) + + doc.content = blocks + doc.save(ignore_permissions=True) + + if loads(new_widgets): + save_new_widget(doc, title, blocks, new_widgets) + + if loads(sb_public_items) or loads(sb_private_items): + sort_pages(loads(sb_public_items), loads(sb_private_items)) + + if loads(deleted_pages): + return delete_pages(loads(deleted_pages)) + + return {"name": title, "public": public} + +def delete_pages(deleted_pages): + for page in deleted_pages: + if page.get("public") and "Workspace Manager" not in frappe.get_roles(): + return {"name": page.get("title"), "public": 1} + + if frappe.db.exists("Workspace", page.get("name")): + frappe.get_doc("Workspace", page.get("name")).delete(ignore_permissions=True) + + return {"name": "Home", "public": 1} + +def sort_pages(sb_public_items, sb_private_items): + wspace_public_pages = get_page_list(['name', 'title'], {'public': 1}) + wspace_private_pages = get_page_list(['name', 'title'], {'for_user': frappe.session.user}) + + if sb_private_items: + sort_page(wspace_private_pages, sb_private_items) + + if sb_public_items and "Workspace Manager" in frappe.get_roles(): + sort_page(wspace_public_pages, sb_public_items) + +def sort_page(wspace_pages, pages): + for seq, d in enumerate(pages): + for page in wspace_pages: + if page.title == d.get('title'): + doc = frappe.get_doc('Workspace', page.name) + doc.sequence_id = seq + 1 + doc.parent_page = d.get('parent_page') or "" + doc.save(ignore_permissions=True) + break + +def get_page_list(fields, filters): + return frappe.get_list("Workspace", fields=fields, filters=filters, order_by='sequence_id asc') diff --git a/frappe/desk/doctype/workspace_chart/workspace_chart.py b/frappe/desk/doctype/workspace_chart/workspace_chart.py index 6ec7abfd3c..a3b66d99ab 100644 --- a/frappe/desk/doctype/workspace_chart/workspace_chart.py +++ b/frappe/desk/doctype/workspace_chart/workspace_chart.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/workspace_link/workspace_link.json b/frappe/desk/doctype/workspace_link/workspace_link.json index 53dadad83d..a7b217be9e 100644 --- a/frappe/desk/doctype/workspace_link/workspace_link.json +++ b/frappe/desk/doctype/workspace_link/workspace_link.json @@ -8,15 +8,16 @@ "type", "label", "icon", - "only_for", "hidden", "link_details_section", "link_type", "link_to", "column_break_7", "dependencies", + "only_for", "onboard", - "is_query_report" + "is_query_report", + "link_count" ], "fields": [ { @@ -99,12 +100,19 @@ "fieldname": "is_query_report", "fieldtype": "Check", "label": "Is Query Report" + }, + { + "depends_on": "eval:doc.type == \"Card Break\"", + "fieldname": "link_count", + "fieldtype": "Int", + "hidden": 1, + "label": "Link Count" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-05-13 13:10:18.128512", + "modified": "2021-06-01 11:23:28.990593", "modified_by": "Administrator", "module": "Desk", "name": "Workspace Link", diff --git a/frappe/desk/doctype/workspace_link/workspace_link.py b/frappe/desk/doctype/workspace_link/workspace_link.py index d6ccc5306a..72256ba490 100644 --- a/frappe/desk/doctype/workspace_link/workspace_link.py +++ b/frappe/desk/doctype/workspace_link/workspace_link.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.py b/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.py index 83b446e454..1dad4cca05 100644 --- a/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.py +++ b/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/desk/form/__init__.py b/frappe/desk/form/__init__.py index 0e57cb68c3..eb5ba62e5c 100644 --- a/frappe/desk/form/__init__.py +++ b/frappe/desk/form/__init__.py @@ -1,3 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 3eda291d1e..bf77170eeb 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE """assign/unassign to ToDo""" diff --git a/frappe/desk/form/document_follow.py b/frappe/desk/form/document_follow.py index 7f65f76a58..14970092d0 100644 --- a/frappe/desk/form/document_follow.py +++ b/frappe/desk/form/document_follow.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import frappe.utils diff --git a/frappe/desk/form/linked_with.py b/frappe/desk/form/linked_with.py index ae48b7fc6b..3aff3877d6 100644 --- a/frappe/desk/form/linked_with.py +++ b/frappe/desk/form/linked_with.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import json from collections import defaultdict diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index a62bfd01d0..c86efbcefd 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe, json import frappe.utils diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py index cf3606e785..b91dd3d481 100644 --- a/frappe/desk/form/meta.py +++ b/frappe/desk/form/meta.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import io import os diff --git a/frappe/desk/form/save.py b/frappe/desk/form/save.py index a7a4b829d8..b580e2c769 100644 --- a/frappe/desk/form/save.py +++ b/frappe/desk/form/save.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe, json from frappe.desk.form.load import run_onload diff --git a/frappe/desk/form/test_form.py b/frappe/desk/form/test_form.py index f3c4132777..86c3aba29a 100644 --- a/frappe/desk/form/test_form.py +++ b/frappe/desk/form/test_form.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe, unittest diff --git a/frappe/desk/form/utils.py b/frappe/desk/form/utils.py index bfceee6ea2..a4dcee4ab3 100644 --- a/frappe/desk/form/utils.py +++ b/frappe/desk/form/utils.py @@ -1,11 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe, json import frappe.desk.form.meta import frappe.desk.form.load from frappe.desk.form.document_follow import follow_document -from frappe.utils.file_manager import extract_images_from_html +from frappe.core.doctype.file.file import extract_images_from_html from frappe import _ diff --git a/frappe/desk/gantt.py b/frappe/desk/gantt.py index 7f0889c751..58ef3b836e 100644 --- a/frappe/desk/gantt.py +++ b/frappe/desk/gantt.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe, json diff --git a/frappe/desk/like.py b/frappe/desk/like.py index d44d58a761..4480ed8a1e 100644 --- a/frappe/desk/like.py +++ b/frappe/desk/like.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE """Allow adding of likes to documents""" diff --git a/frappe/desk/listview.py b/frappe/desk/listview.py index d2c84d36bf..f079205cb0 100644 --- a/frappe/desk/listview.py +++ b/frappe/desk/listview.py @@ -1,5 +1,5 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe @frappe.whitelist(allow_guest=True) diff --git a/frappe/desk/moduleview.py b/frappe/desk/moduleview.py index 021698ac92..e2e2c4c155 100644 --- a/frappe/desk/moduleview.py +++ b/frappe/desk/moduleview.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import json diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index c84027928e..2a4567ab4f 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe.desk.doctype.notification_settings.notification_settings import get_subscribed_documents diff --git a/frappe/desk/page/activity/activity.py b/frappe/desk/page/activity/activity.py index 3abc8e0ea5..71130f2304 100644 --- a/frappe/desk/page/activity/activity.py +++ b/frappe/desk/page/activity/activity.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: See license.txt +# License: MIT. See LICENSE import frappe from frappe.utils import cint diff --git a/frappe/desk/page/leaderboard/leaderboard.py b/frappe/desk/page/leaderboard/leaderboard.py index 9469096f50..ad22eb9199 100644 --- a/frappe/desk/page/leaderboard/leaderboard.py +++ b/frappe/desk/page/leaderboard/leaderboard.py @@ -1,5 +1,5 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe @frappe.whitelist() diff --git a/frappe/desk/page/setup_wizard/install_fixtures.py b/frappe/desk/page/setup_wizard/install_fixtures.py index 06301cdeaf..1ef83f7ba0 100644 --- a/frappe/desk/page/setup_wizard/install_fixtures.py +++ b/frappe/desk/page/setup_wizard/install_fixtures.py @@ -1,5 +1,5 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index 5edb44e182..c729c1d78b 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: See license.txt +# License: MIT. See LICENSE import frappe, json, os from frappe.utils import strip, cint diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index b42c9c89a0..9a37d16d0a 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import os @@ -391,7 +391,7 @@ def handle_duration_fieldtype_values(result, columns): return result -def build_xlsx_data(columns, data, visible_idx, include_indentation): +def build_xlsx_data(columns, data, visible_idx, include_indentation, ignore_visible_idx=False): result = [[]] column_widths = [] @@ -407,7 +407,7 @@ def build_xlsx_data(columns, data, visible_idx, include_indentation): # build table from result for row_idx, row in enumerate(data.result): # only pick up rows that are visible in the report - if row_idx in visible_idx: + if ignore_visible_idx or row_idx in visible_idx: row_data = [] if isinstance(row, dict): for col_idx, column in enumerate(data.columns): diff --git a/frappe/desk/report/todo/todo.py b/frappe/desk/report/todo/todo.py index 6bd22b843e..b1e49bc95d 100644 --- a/frappe/desk/report/todo/todo.py +++ b/frappe/desk/report/todo/todo.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/desk/report_dump.py b/frappe/desk/report_dump.py index b2d3ca3443..f57ed97fa5 100644 --- a/frappe/desk/report_dump.py +++ b/frappe/desk/report_dump.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 1dbc52eb5b..d758ebb590 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE """build query for doclistview and return results""" diff --git a/frappe/desk/search.py b/frappe/desk/search.py index f9b65fc98e..db88e6ec52 100644 --- a/frappe/desk/search.py +++ b/frappe/desk/search.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE # Search import frappe, json @@ -265,6 +265,7 @@ def get_users_for_mentions(): 'name': ['not in', ('Administrator', 'Guest')], 'allowed_in_mentions': True, 'user_type': 'System User', + 'enabled': True, }) def get_user_groups(): diff --git a/frappe/desk/treeview.py b/frappe/desk/treeview.py index 66acde4cb2..704e5d8ed6 100644 --- a/frappe/desk/treeview.py +++ b/frappe/desk/treeview.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/desk/utils.py b/frappe/desk/utils.py index 01b47ac106..5908277386 100644 --- a/frappe/desk/utils.py +++ b/frappe/desk/utils.py @@ -1,5 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/email/__init__.py b/frappe/email/__init__.py index 3fb539398a..79dec977b7 100644 --- a/frappe/email/__init__.py +++ b/frappe/email/__init__.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe.desk.reportview import build_match_conditions diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.py b/frappe/email/doctype/auto_email_report/auto_email_report.py index f30279e308..37089d58df 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.py +++ b/frappe/email/doctype/auto_email_report/auto_email_report.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import calendar from datetime import timedelta @@ -13,6 +13,7 @@ from frappe.utils import (format_time, get_link_to_form, get_url_to_report, from frappe.model.naming import append_number_if_name_exists from frappe.utils.csvutils import to_csv from frappe.utils.xlsxutils import make_xlsx +from frappe.desk.query_report import build_xlsx_data max_reports_per_user = frappe.local.conf.max_reports_per_user or 3 @@ -99,13 +100,21 @@ class AutoEmailReport(Document): return self.get_html_table(columns, data) elif self.format == 'XLSX': - spreadsheet_data = self.get_spreadsheet_data(columns, data) - xlsx_file = make_xlsx(spreadsheet_data, "Auto Email Report") + report_data = frappe._dict() + report_data['columns'] = columns + report_data['result'] = data + + xlsx_data, column_widths = build_xlsx_data(columns, report_data, [], 1, ignore_visible_idx=True) + xlsx_file = make_xlsx(xlsx_data, "Auto Email Report", column_widths=column_widths) return xlsx_file.getvalue() elif self.format == 'CSV': - spreadsheet_data = self.get_spreadsheet_data(columns, data) - return to_csv(spreadsheet_data) + report_data = frappe._dict() + report_data['columns'] = columns + report_data['result'] = data + + xlsx_data, column_widths = build_xlsx_data(columns, report_data, [], 1, ignore_visible_idx=True) + return to_csv(xlsx_data) else: frappe.throw(_('Invalid Output Format')) @@ -126,18 +135,6 @@ class AutoEmailReport(Document): 'edit_report_settings': get_link_to_form('Auto Email Report', self.name) }) - @staticmethod - def get_spreadsheet_data(columns, data): - out = [[_(df.label) for df in columns], ] - for row in data: - new_row = [] - out.append(new_row) - for df in columns: - if df.fieldname not in row: continue - new_row.append(frappe.format(row[df.fieldname], df, row)) - - return out - def get_file_name(self): return "{0}.{1}".format(self.report.replace(" ", "-").replace("/", "-"), self.format.lower()) diff --git a/frappe/email/doctype/auto_email_report/test_auto_email_report.py b/frappe/email/doctype/auto_email_report/test_auto_email_report.py index 211a141ec0..559adfbe1a 100644 --- a/frappe/email/doctype/auto_email_report/test_auto_email_report.py +++ b/frappe/email/doctype/auto_email_report/test_auto_email_report.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import json import unittest diff --git a/frappe/email/doctype/document_follow/document_follow.py b/frappe/email/doctype/document_follow/document_follow.py index a04f8ef4c2..97f8237736 100644 --- a/frappe/email/doctype/document_follow/document_follow.py +++ b/frappe/email/doctype/document_follow/document_follow.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from frappe.model.document import Document diff --git a/frappe/email/doctype/document_follow/test_document_follow.py b/frappe/email/doctype/document_follow/test_document_follow.py index 456c0931f8..050add65e9 100644 --- a/frappe/email/doctype/document_follow/test_document_follow.py +++ b/frappe/email/doctype/document_follow/test_document_follow.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest import frappe.desk.form.document_follow as document_follow diff --git a/frappe/email/doctype/email_account/email_account.js b/frappe/email/doctype/email_account/email_account.js index 83896e0af7..277bf43eb6 100644 --- a/frappe/email/doctype/email_account/email_account.js +++ b/frappe/email/doctype/email_account/email_account.js @@ -151,18 +151,6 @@ frappe.ui.form.on("Email Account", { callback: function (r) { if (r.message) { frm.events.set_domain_fields(frm, r.message); - } else { - frm.set_value("domain", ""); - frappe.confirm(__('Email Domain not configured for this account, Create one?'), - function () { - frappe.model.with_doctype("Email Domain", function() { - frappe.route_options = { email_id: frm.doc.email_id }; - frappe.route_flags.return_to_email_account = 1; - var doc = frappe.model.get_new_doc("Email Domain"); - frappe.set_route("Form", "Email Domain", doc.name); - }); - } - ); } } }); diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json index 6d811b801f..8cfd75d839 100644 --- a/frappe/email/doctype/email_account/email_account.json +++ b/frappe/email/doctype/email_account/email_account.json @@ -7,30 +7,34 @@ "document_type": "Setup", "engine": "InnoDB", "field_order": [ + "account_section", "email_id", - "login_id_is_different", - "login_id", - "password", - "awaiting_password", - "ascii_encode_password", "email_account_name", - "email_settings", + "column_break_3", "domain", "service", + "authentication_column", + "password", + "awaiting_password", + "ascii_encode_password", + "column_break_10", + "login_id_is_different", + "login_id", "mailbox_settings", "enable_incoming", + "default_incoming", "use_imap", - "email_server", "use_ssl", - "append_emails_to_sent_folder", + "email_server", "incoming_port", + "column_break_18", "attachment_limit", - "append_to", - "default_incoming", "email_sync_option", "initial_sync_count", - "create_contact", "section_break_12", + "append_emails_to_sent_folder", + "append_to", + "create_contact", "enable_automatic_linking", "section_break_13", "notify_if_unreplied", @@ -42,6 +46,7 @@ "use_tls", "use_ssl_for_outgoing", "smtp_port", + "column_break_38", "default_outgoing", "always_use_account_email_id_as_sender", "always_use_account_name_as_sender_name", @@ -80,7 +85,7 @@ "fieldtype": "Check", "hide_days": 1, "hide_seconds": 1, - "label": "Use Different Email Login ID" + "label": "Use different login" }, { "depends_on": "login_id_is_different", @@ -122,12 +127,6 @@ "label": "Email Account Name", "unique": 1 }, - { - "fieldname": "email_settings", - "fieldtype": "Section Break", - "hide_days": 1, - "hide_seconds": 1 - }, { "depends_on": "eval:!doc.service", "fieldname": "domain", @@ -136,7 +135,7 @@ "hide_seconds": 1, "in_list_view": 1, "in_standard_filter": 1, - "label": "Domain", + "label": "Domain (optional)", "options": "Email Domain" }, { @@ -145,18 +144,18 @@ "fieldtype": "Select", "hide_days": 1, "hide_seconds": 1, - "label": "Service", + "label": "Service (optional)", "options": "\nGMail\nSendgrid\nSparkPost\nYahoo Mail\nOutlook.com\nYandex.Mail" }, { "fieldname": "mailbox_settings", "fieldtype": "Section Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "label": "Incoming (POP/IMAP) Settings" }, { "default": "0", - "description": "Check this to pull emails from your mailbox", "fieldname": "enable_incoming", "fieldtype": "Check", "hide_days": 1, @@ -237,6 +236,7 @@ }, { "default": "250", + "depends_on": "eval: doc.enable_incoming", "description": "Total number of emails to sync in initial sync process ", "fieldname": "initial_sync_count", "fieldtype": "Select", @@ -248,7 +248,7 @@ { "depends_on": "enable_incoming", "fieldname": "section_break_13", - "fieldtype": "Section Break", + "fieldtype": "Column Break", "hide_days": 1, "hide_seconds": 1 }, @@ -282,7 +282,8 @@ "fieldname": "outgoing_mail_settings", "fieldtype": "Section Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "label": "Outgoing (SMTP) Settings" }, { "default": "0", @@ -336,22 +337,20 @@ { "default": "0", "depends_on": "enable_outgoing", - "description": "Uses the Email Address mentioned in this Account as the Sender for all emails sent using this Account. ", "fieldname": "always_use_account_email_id_as_sender", "fieldtype": "Check", "hide_days": 1, "hide_seconds": 1, - "label": "Always use Account's Email Address as Sender" + "label": "Always use this email address as sender address" }, { "default": "0", "depends_on": "enable_outgoing", - "description": "Uses the Email Address Name mentioned in this Account as the Sender's Name for all emails sent using this Account.", "fieldname": "always_use_account_name_as_sender_name", "fieldtype": "Check", "hide_days": 1, "hide_seconds": 1, - "label": "Always use Account's Name as Sender's Name" + "label": "Always use this name as sender name" }, { "default": "1", @@ -379,10 +378,13 @@ "label": "Disable SMTP server authentication" }, { + "collapsible": 1, + "collapsible_depends_on": "add_signature", "fieldname": "signature_section", "fieldtype": "Section Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "label": "Signature" }, { "default": "0", @@ -401,10 +403,13 @@ "label": "Signature" }, { + "collapsible": 1, + "collapsible_depends_on": "enable_auto_reply", "fieldname": "auto_reply", "fieldtype": "Section Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "label": "Auto Reply" }, { "default": "0", @@ -424,17 +429,20 @@ "label": "Auto Reply Message" }, { + "collapsible": 1, + "collapsible_depends_on": "eval:frappe.utils.html2text(doc.footer || '')!=''", "fieldname": "set_footer", "fieldtype": "Section Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "label": "Footer" }, { "fieldname": "footer", "fieldtype": "Text Editor", "hide_days": 1, "hide_seconds": 1, - "label": "Footer" + "label": "Footer Content" }, { "fieldname": "uidvalidity", @@ -477,7 +485,8 @@ "fieldname": "section_break_12", "fieldtype": "Section Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "label": "Document Linking" }, { "default": "0", @@ -527,12 +536,38 @@ "fieldname": "brand_logo", "fieldtype": "Attach Image", "label": "Brand Logo" + }, + { + "fieldname": "authentication_column", + "fieldtype": "Section Break", + "label": "Authentication" + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_38", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "account_section", + "fieldtype": "Section Break", + "label": "Account" } ], "icon": "fa fa-inbox", "index_web_pages_for_search": 1, "links": [], - "modified": "2021-01-21 10:05:24.820597", + "modified": "2021-08-31 15:23:25.714366", "modified_by": "Administrator", "module": "Email", "name": "Email Account", diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index ecd59f42bb..d90c56d90d 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import email.utils import functools import imaplib @@ -137,8 +137,6 @@ class EmailAccount(Document): def on_update(self): """Check there is only one default of each type.""" - from frappe.core.doctype.user.user import setup_user_email_inbox - self.check_automatic_linking_email_account() self.there_must_be_only_one_default() setup_user_email_inbox(email_account=self.name, awaiting_password=self.awaiting_password, @@ -532,8 +530,6 @@ class EmailAccount(Document): def on_trash(self): """Clear communications where email account is linked""" - from frappe.core.doctype.user.user import remove_user_email_inbox - frappe.db.sql("update `tabCommunication` set email_account='' where email_account=%s", self.name) remove_user_email_inbox(email_account=self.name) @@ -724,3 +720,84 @@ def get_max_email_uid(email_account): else: max_uid = cint(result[0].get("uid", 0)) + 1 return max_uid + + +def setup_user_email_inbox(email_account, awaiting_password, email_id, enable_outgoing): + """ setup email inbox for user """ + from frappe.core.doctype.user.user import ask_pass_update + + def add_user_email(user): + user = frappe.get_doc("User", user) + row = user.append("user_emails", {}) + + row.email_id = email_id + row.email_account = email_account + row.awaiting_password = awaiting_password or 0 + row.enable_outgoing = enable_outgoing or 0 + + user.save(ignore_permissions=True) + + update_user_email_settings = False + if not all([email_account, email_id]): + return + + user_names = frappe.db.get_values("User", {"email": email_id}, as_dict=True) + if not user_names: + return + + for user in user_names: + user_name = user.get("name") + + # check if inbox is alreay configured + user_inbox = frappe.db.get_value("User Email", { + "email_account": email_account, + "parent": user_name + }, ["name"]) or None + + if not user_inbox: + add_user_email(user_name) + else: + # update awaiting password for email account + update_user_email_settings = True + + if update_user_email_settings: + frappe.db.sql("""UPDATE `tabUser Email` SET awaiting_password = %(awaiting_password)s, + enable_outgoing = %(enable_outgoing)s WHERE email_account = %(email_account)s""", { + "email_account": email_account, + "enable_outgoing": enable_outgoing, + "awaiting_password": awaiting_password or 0 + }) + else: + users = " and ".join([frappe.bold(user.get("name")) for user in user_names]) + frappe.msgprint(_("Enabled email inbox for user {0}").format(users)) + ask_pass_update() + +def remove_user_email_inbox(email_account): + """ remove user email inbox settings if email account is deleted """ + if not email_account: + return + + users = frappe.get_all("User Email", filters={ + "email_account": email_account + }, fields=["parent as name"]) + + for user in users: + doc = frappe.get_doc("User", user.get("name")) + to_remove = [row for row in doc.user_emails if row.email_account == email_account] + [doc.remove(row) for row in to_remove] + + doc.save(ignore_permissions=True) + +@frappe.whitelist(allow_guest=False) +def set_email_password(email_account, user, password): + account = frappe.get_doc("Email Account", email_account) + if account.awaiting_password: + account.awaiting_password = 0 + account.password = password + try: + account.save(ignore_permissions=True) + except Exception: + frappe.db.rollback() + return False + + return True \ No newline at end of file diff --git a/frappe/email/doctype/email_account/test_email_account.py b/frappe/email/doctype/email_account/test_email_account.py index 35cacac45a..21dc4b84c4 100644 --- a/frappe/email/doctype/email_account/test_email_account.py +++ b/frappe/email/doctype/email_account/test_email_account.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import os import email @@ -34,8 +34,8 @@ class TestEmailAccount(unittest.TestCase): def setUp(self): frappe.flags.mute_emails = False frappe.flags.sent_mail = None - frappe.db.sql('delete from `tabEmail Queue`') - frappe.db.sql('delete from `tabUnhandled Email`') + frappe.db.delete("Email Queue") + frappe.db.delete("Unhandled Email") def get_test_mail(self, fname): with open(os.path.join(os.path.dirname(__file__), "test_mails", fname), "r") as f: @@ -60,7 +60,7 @@ class TestEmailAccount(unittest.TestCase): comm = frappe.get_doc("Communication", {"sender": "test_sender@example.com"}) comm.db_set("creation", datetime.now() - timedelta(seconds = 30 * 60)) - frappe.db.sql("DELETE FROM `tabEmail Queue`") + frappe.db.delete("Email Queue") notify_unreplied() self.assertTrue(frappe.db.get_value("Email Queue", {"reference_doctype": comm.reference_doctype, "reference_name": comm.reference_name, "status":"Not Sent"})) @@ -183,7 +183,7 @@ class TestEmailAccount(unittest.TestCase): def test_threading_by_message_id(self): cleanup() - frappe.db.sql("""delete from `tabEmail Queue`""") + frappe.db.delete("Email Queue") # reference document for testing event = frappe.get_doc(dict(doctype='Event', subject='test-message')).insert() @@ -242,8 +242,8 @@ class TestInboundMail(unittest.TestCase): def setUp(self): cleanup() - frappe.db.sql('delete from `tabEmail Queue`') - frappe.db.sql('delete from `tabToDo`') + frappe.db.delete("Email Queue") + frappe.db.delete("ToDo") def get_test_mail(self, fname): with open(os.path.join(os.path.dirname(__file__), "test_mails", fname), "r") as f: diff --git a/frappe/email/doctype/email_domain/email_domain.py b/frappe/email/doctype/email_domain/email_domain.py index 0856549eb7..1611d32351 100644 --- a/frappe/email/doctype/email_domain/email_domain.py +++ b/frappe/email/doctype/email_domain/email_domain.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/email/doctype/email_domain/test_email_domain.py b/frappe/email/doctype/email_domain/test_email_domain.py index 8607151ca8..1064c7684a 100644 --- a/frappe/email/doctype/email_domain/test_email_domain.py +++ b/frappe/email/doctype/email_domain/test_email_domain.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest from frappe.test_runner import make_test_objects diff --git a/frappe/email/doctype/email_flag_queue/email_flag_queue.py b/frappe/email/doctype/email_flag_queue/email_flag_queue.py index 9bb30f08b2..886cf3c24b 100644 --- a/frappe/email/doctype/email_flag_queue/email_flag_queue.py +++ b/frappe/email/doctype/email_flag_queue/email_flag_queue.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/email/doctype/email_flag_queue/test_email_flag_queue.py b/frappe/email/doctype/email_flag_queue/test_email_flag_queue.py index d09b823ce6..b0e17b3b85 100644 --- a/frappe/email/doctype/email_flag_queue/test_email_flag_queue.py +++ b/frappe/email/doctype/email_flag_queue/test_email_flag_queue.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/email/doctype/email_group/email_group.py b/frappe/email/doctype/email_group/email_group.py index 2679353edf..ad52d9a9ec 100755 --- a/frappe/email/doctype/email_group/email_group.py +++ b/frappe/email/doctype/email_group/email_group.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/email/doctype/email_group/test_email_group.py b/frappe/email/doctype/email_group/test_email_group.py index 3e894118df..06341c128e 100644 --- a/frappe/email/doctype/email_group/test_email_group.py +++ b/frappe/email/doctype/email_group/test_email_group.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/email/doctype/email_group_member/email_group_member.py b/frappe/email/doctype/email_group_member/email_group_member.py index 1f9303b83e..a9fd26f710 100644 --- a/frappe/email/doctype/email_group_member/email_group_member.py +++ b/frappe/email/doctype/email_group_member/email_group_member.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/email/doctype/email_group_member/test_email_group_member.py b/frappe/email/doctype/email_group_member/test_email_group_member.py index 829d686400..de006dccb9 100644 --- a/frappe/email/doctype/email_group_member/test_email_group_member.py +++ b/frappe/email/doctype/email_group_member/test_email_group_member.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py index e1e332f978..4489a68cac 100644 --- a/frappe/email/doctype/email_queue/email_queue.py +++ b/frappe/email/doctype/email_queue/email_queue.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import traceback import json diff --git a/frappe/email/doctype/email_queue/test_email_queue.py b/frappe/email/doctype/email_queue/test_email_queue.py index b76d6347b9..8ebcb68a38 100644 --- a/frappe/email/doctype/email_queue/test_email_queue.py +++ b/frappe/email/doctype/email_queue/test_email_queue.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py b/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py index 055bdb3fc1..95b8593c4c 100644 --- a/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py +++ b/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/email/doctype/email_rule/email_rule.py b/frappe/email/doctype/email_rule/email_rule.py index 9807724ef1..b2a4be5421 100644 --- a/frappe/email/doctype/email_rule/email_rule.py +++ b/frappe/email/doctype/email_rule/email_rule.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/email/doctype/email_rule/test_email_rule.py b/frappe/email/doctype/email_rule/test_email_rule.py index b2213f7405..eef5448e57 100644 --- a/frappe/email/doctype/email_rule/test_email_rule.py +++ b/frappe/email/doctype/email_rule/test_email_rule.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/email/doctype/email_template/email_template.py b/frappe/email/doctype/email_template/email_template.py index 4711451fd2..c51c46d72d 100644 --- a/frappe/email/doctype/email_template/email_template.py +++ b/frappe/email/doctype/email_template/email_template.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe, json from frappe.model.document import Document diff --git a/frappe/email/doctype/email_template/test_email_template.py b/frappe/email/doctype/email_template/test_email_template.py index 5a9ee969c6..a92ee9f9c3 100644 --- a/frappe/email/doctype/email_template/test_email_template.py +++ b/frappe/email/doctype/email_template/test_email_template.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest class TestEmailTemplate(unittest.TestCase): diff --git a/frappe/email/doctype/email_unsubscribe/email_unsubscribe.py b/frappe/email/doctype/email_unsubscribe/email_unsubscribe.py index 6c47d8c538..d2ee828a55 100644 --- a/frappe/email/doctype/email_unsubscribe/email_unsubscribe.py +++ b/frappe/email/doctype/email_unsubscribe/email_unsubscribe.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/email/doctype/email_unsubscribe/test_email_unsubscribe.py b/frappe/email/doctype/email_unsubscribe/test_email_unsubscribe.py index 602840fe3b..fdea802fdf 100644 --- a/frappe/email/doctype/email_unsubscribe/test_email_unsubscribe.py +++ b/frappe/email/doctype/email_unsubscribe/test_email_unsubscribe.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/email/doctype/newsletter/exceptions.py b/frappe/email/doctype/newsletter/exceptions.py new file mode 100644 index 0000000000..a6c688dbe8 --- /dev/null +++ b/frappe/email/doctype/newsletter/exceptions.py @@ -0,0 +1,13 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See LICENSE + +from frappe.exceptions import ValidationError + +class NewsletterAlreadySentError(ValidationError): + pass + +class NoRecipientFoundError(ValidationError): + pass + +class NewsletterNotSavedError(ValidationError): + pass diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 97d77549b7..a118240488 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -1,241 +1,323 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See LICENSE + +from typing import Dict, List import frappe import frappe.utils -from frappe import throw, _ + +from frappe import _ from frappe.website.website_generator import WebsiteGenerator from frappe.utils.verified_command import get_signed_params, verify_request from frappe.email.doctype.email_group.email_group import add_subscribers -from frappe.utils import parse_addr, now_datetime, markdown, validate_email_address + +from .exceptions import NewsletterAlreadySentError, NoRecipientFoundError, NewsletterNotSavedError + class Newsletter(WebsiteGenerator): def onload(self): - if self.email_sent: - self.get("__onload").status_count = dict(frappe.db.sql("""select status, count(name) - from `tabEmail Queue` where reference_doctype=%s and reference_name=%s - group by status""", (self.doctype, self.name))) or None + self.setup_newsletter_status() def validate(self): - self.route = "newsletters/" + self.name - if self.send_from: - validate_email_address(self.send_from, True) + self.route = f"newsletters/{self.name}" + self.validate_sender_address() + self.validate_recipient_address() + + @property + def newsletter_recipients(self) -> List[str]: + if getattr(self, "_recipients", None) is None: + self._recipients = self.get_recipients() + return self._recipients @frappe.whitelist() - def test_send(self, doctype="Lead"): - self.recipients = frappe.utils.split_emails(self.test_email_id) - self.queue_all(test_email=True) + def test_send(self): + test_emails = frappe.utils.split_emails(self.test_email_id) + self.queue_all(test_emails=test_emails) frappe.msgprint(_("Test email sent to {0}").format(self.test_email_id)) @frappe.whitelist() def send_emails(self): """send emails to leads and customers""" + self.queue_all() + frappe.msgprint(_("Email queued to {0} recipients").format(len(self.newsletter_recipients))) + + def setup_newsletter_status(self): + """Setup analytical status for current Newsletter. Can be accessible from desk. + """ if self.email_sent: - throw(_("Newsletter has already been sent")) + status_count = frappe.get_all("Email Queue", + filters={"reference_doctype": self.doctype, "reference_name": self.name}, + fields=["status", "count(name)"], + group_by="status", + order_by="status", + as_list=True, + ) + self.get("__onload").status_count = dict(status_count) - self.recipients = self.get_recipients() + def validate_send(self): + """Validate if Newsletter can be sent. + """ + self.validate_newsletter_status() + self.validate_newsletter_recipients() - if self.recipients: - self.queue_all() - frappe.msgprint(_("Email queued to {0} recipients").format(len(self.recipients))) + def validate_newsletter_status(self): + if self.email_sent: + frappe.throw(_("Newsletter has already been sent"), exc=NewsletterAlreadySentError) - else: - frappe.msgprint(_("Newsletter should have atleast one recipient")) + if self.get("__islocal"): + frappe.throw(_("Please save the Newsletter before sending"), exc=NewsletterNotSavedError) - def queue_all(self, test_email=False): - if not self.get("recipients"): - # in case it is called via worker - self.recipients = self.get_recipients() + def validate_newsletter_recipients(self): + if not self.newsletter_recipients: + frappe.throw(_("Newsletter should have atleast one recipient"), exc=NoRecipientFoundError) + self.validate_recipient_address() - self.validate_send() + def validate_sender_address(self): + """Validate self.send_from is a valid email address or not. + """ + if self.send_from: + frappe.utils.validate_email_address(self.send_from, throw=True) + + def validate_recipient_address(self): + """Validate if self.newsletter_recipients are all valid email addresses or not. + """ + for recipient in self.newsletter_recipients: + frappe.utils.validate_email_address(recipient, throw=True) + + def get_linked_email_queue(self) -> List[str]: + """Get list of email queue linked to this newsletter. + """ + return frappe.get_all("Email Queue", + filters={ + "reference_doctype": self.doctype, + "reference_name": self.name, + }, + pluck="name", + ) - sender = self.send_from or frappe.utils.get_formatted_email(self.owner) + def get_success_recipients(self) -> List[str]: + """Recipients who have already recieved the newsletter. + + Couldn't think of a better name ;) + """ + return frappe.get_all("Email Queue Recipient", + filters={ + "status": ("in", ["Not Sent", "Sending", "Sent"]), + "parentfield": ("in", self.get_linked_email_queue()), + }, + pluck="recipient", + ) - if not frappe.flags.in_test: - frappe.db.auto_commit_on_many_writes = True + def get_pending_recipients(self) -> List[str]: + """Get list of pending recipients of the newsletter. These + recipients may not have receive the newsletter in the previous iteration. + """ + return [ + x for x in self.newsletter_recipients if x not in self.get_success_recipients() + ] + + def queue_all(self, test_emails: List[str] = None): + """Queue Newsletter to all the recipients generated from the `Email Group` + table + + Args: + test_email (List[str], optional): Send test Newsletter to the passed set of emails. + Defaults to None. + """ + if test_emails: + for test_email in test_emails: + frappe.utils.validate_email_address(test_email, throw=True) + else: + self.validate() + self.validate_send() + + newsletter_recipients = test_emails or self.get_pending_recipients() + self.send_newsletter(emails=newsletter_recipients) + + if not test_emails: + self.email_sent = True + self.schedule_send = frappe.utils.now_datetime() + self.scheduled_to_send = len(newsletter_recipients) + self.save() + def get_newsletter_attachments(self) -> List[Dict[str, str]]: + """Get list of attachments on current Newsletter + """ attachments = [] + if self.send_attachments: - files = frappe.get_all("File", fields=["name"], filters={"attached_to_doctype": "Newsletter", - "attached_to_name": self.name}, order_by="creation desc") - - for file in files: - try: - # these attachments will be attached on-demand - # and won't be stored in the message - attachments.append({"fid": file.name}) - except IOError: - frappe.throw(_("Unable to find attachment {0}").format(file.name)) - - args = { - "message": self.get_message(), - "name": self.name - } - frappe.sendmail(recipients=self.recipients, sender=sender, - subject=self.subject, message=self.get_message(), template="newsletter", - reference_doctype=self.doctype, reference_name=self.name, - add_unsubscribe_link=self.send_unsubscribe_link, attachments=attachments, + files = frappe.get_all( + "File", + filters={"attached_to_doctype": "Newsletter", "attached_to_name": self.name}, + order_by="creation desc", + pluck="name", + ) + attachments.extend({"fid": file} for file in files) + + return attachments + + def send_newsletter(self, emails: List[str]): + """Trigger email generation for `emails` and add it in Email Queue. + """ + # TODO: get rid of this maybe? + message = self.get_message() + attachments = self.get_newsletter_attachments() + sender = self.send_from or frappe.utils.get_formatted_email(self.owner) + args = {"message": message, "name": self.name} + + is_auto_commit_set = bool(frappe.db.auto_commit_on_many_writes) + frappe.db.auto_commit_on_many_writes = not frappe.flags.in_test + + frappe.sendmail( + subject=self.subject, + sender=sender, + recipients=emails, + message=message, + attachments=attachments, + template="newsletter", + add_unsubscribe_link=self.send_unsubscribe_link, unsubscribe_method="/unsubscribe", unsubscribe_params={"name": self.name}, - send_priority=0, queue_separately=True, args=args) - - if not frappe.flags.in_test: - frappe.db.auto_commit_on_many_writes = False + reference_doctype=self.doctype, + reference_name=self.name, + queue_separately=True, + send_priority=0, + args=args, + ) - if not test_email: - self.db_set("email_sent", 1) - self.db_set("schedule_send", now_datetime()) - self.db_set("scheduled_to_send", len(self.recipients)) + frappe.db.auto_commit_on_many_writes = is_auto_commit_set - def get_message(self): + def get_message(self) -> str: if self.content_type == "HTML": return frappe.render_template(self.message_html, {"doc": self.as_dict()}) - return { - 'Rich Text': self.message, - 'Markdown': markdown(self.message_md) - }[self.content_type or 'Rich Text'] + if self.content_type == "Markdown": + return frappe.utils.markdown(self.message_md) + # fallback to Rich Text + return self.message - def get_recipients(self): + def get_recipients(self) -> List[str]: """Get recipients from Email Group""" - recipients_list = [] - for email_group in get_email_groups(self.name): - for d in frappe.db.get_all("Email Group Member", ["email"], - {"unsubscribed": 0, "email_group": email_group.email_group}): - recipients_list.append(d.email) - return list(set(recipients_list)) - - def validate_send(self): - if self.get("__islocal"): - throw(_("Please save the Newsletter before sending")) + emails = frappe.get_all( + "Email Group Member", + filters={"unsubscribed": 0, "email_group": ("in", self.get_email_groups())}, + pluck="email", + ) + return list(set(emails)) + + def get_email_groups(self) -> List[str]: + # wondering why the 'or'? i can't figure out why both aren't equivalent - @gavin + return [ + x.email_group for x in self.email_group + ] or frappe.get_all( + "Newsletter Email Group", + filters={"parent": self.name, "parenttype": "Newsletter"}, + pluck="email_group", + ) - if not self.recipients: - frappe.throw(_("Newsletter should have at least one recipient")) + def get_attachments(self) -> List[Dict[str, str]]: + return frappe.get_all( + "File", + fields=["name", "file_name", "file_url", "is_private"], + filters={ + "attached_to_name": self.name, + "attached_to_doctype": "Newsletter", + "is_private": 0, + }, + ) def get_context(self, context): newsletters = get_newsletter_list("Newsletter", None, None, 0) if newsletters: newsletter_list = [d.name for d in newsletters] if self.name not in newsletter_list: - frappe.redirect_to_message(_('Permission Error'), - _("You are not permitted to view the newsletter.")) + frappe.redirect_to_message( + _("Permission Error"), _("You are not permitted to view the newsletter.") + ) frappe.local.flags.redirect_location = frappe.local.response.location raise frappe.Redirect else: - context.attachments = get_attachments(self.name) + context.attachments = self.get_attachments() context.no_cache = 1 context.show_sidebar = True -def get_attachments(name): - return frappe.get_all("File", - fields=["name", "file_name", "file_url", "is_private"], - filters = {"attached_to_name": name, "attached_to_doctype": "Newsletter", "is_private":0}) - - -def get_email_groups(name): - return frappe.db.get_all("Newsletter Email Group", ["email_group"],{"parent":name, "parenttype":"Newsletter"}) - - @frappe.whitelist(allow_guest=True) def confirmed_unsubscribe(email, group): """ unsubscribe the email(user) from the mailing list(email_group) """ - frappe.flags.ignore_permissions=True + frappe.flags.ignore_permissions = True doc = frappe.get_doc("Email Group Member", {"email": email, "email_group": group}) if not doc.unsubscribed: doc.unsubscribed = 1 - doc.save(ignore_permissions = True) - -def create_lead(email_id): - """create a lead if it does not exist""" - from frappe.model.naming import get_default_naming_series - full_name, email_id = parse_addr(email_id) - if frappe.db.get_value("Lead", {"email_id": email_id}): - return - - lead = frappe.get_doc({ - "doctype": "Lead", - "email_id": email_id, - "lead_name": full_name or email_id, - "status": "Lead", - "naming_series": get_default_naming_series("Lead"), - "company": frappe.db.get_default("Company"), - "source": "Email" - }) - lead.insert() + doc.save(ignore_permissions=True) @frappe.whitelist(allow_guest=True) -def subscribe(email, email_group=_('Website')): - url = frappe.utils.get_url("/api/method/frappe.email.doctype.newsletter.newsletter.confirm_subscription") +\ - "?" + get_signed_params({"email": email, "email_group": email_group}) - - email_template = frappe.db.get_value('Email Group', email_group, ['confirmation_email_template']) - - content='' - if email_template: - args = dict( - email=email, - confirmation_url=url, - email_group=email_group - ) - - email_template = frappe.get_doc("Email Template", email_template) +def subscribe(email, email_group=_("Website")): + """API endpoint to subscribe an email to a particular email group. Triggers a confirmation email. + """ + + # build subscription confirmation URL + api_endpoint = frappe.utils.get_url( + "/api/method/frappe.email.doctype.newsletter.newsletter.confirm_subscription" + ) + signed_params = get_signed_params({"email": email, "email_group": email_group}) + confirm_subscription_url = f"{api_endpoint}?{signed_params}" + + # fetch custom template if available + email_confirmation_template = frappe.db.get_value( + "Email Group", email_group, "confirmation_email_template" + ) + + # build email and send + if email_confirmation_template: + args = {"email": email, "confirmation_url": confirm_subscription_url, "email_group": email_group} + email_template = frappe.get_doc("Email Template", email_confirmation_template) + email_subject = email_template.subject content = frappe.render_template(email_template.response, args) - - if not content: - messages = ( + else: + email_subject = _("Confirm Your Email") + translatable_content = ( _("Thank you for your interest in subscribing to our updates"), _("Please verify your Email Address"), - url, - _("Click here to verify") + confirm_subscription_url, + _("Click here to verify"), ) - content = """ -

{0}. {1}.

-

{3}

- """.format(*messages) +

{0}. {1}.

+

{3}

+ """.format(*translatable_content) + + frappe.sendmail( + email, + subject=email_subject, + content=content, + now=True, + ) - frappe.sendmail(email, subject=getattr('email_template', 'subject', '') or _("Confirm Your Email"), content=content, now=True) @frappe.whitelist(allow_guest=True) -def confirm_subscription(email, email_group=_('Website')): +def confirm_subscription(email, email_group=_("Website")): + """API endpoint to confirm email subscription. + This endpoint is called when user clicks on the link sent to their mail. + """ if not verify_request(): return if not frappe.db.exists("Email Group", email_group): - frappe.get_doc({ - "doctype": "Email Group", - "title": email_group - }).insert(ignore_permissions=True) + frappe.get_doc({"doctype": "Email Group", "title": email_group}).insert( + ignore_permissions=True + ) frappe.flags.ignore_permissions = True add_subscribers(email_group, email) frappe.db.commit() - frappe.respond_as_web_page(_("Confirmed"), + frappe.respond_as_web_page( + _("Confirmed"), _("{0} has been successfully added to the Email Group.").format(email), - indicator_color='green') - - -def send_newsletter(newsletter): - try: - doc = frappe.get_doc("Newsletter", newsletter) - doc.queue_all() - - except: - frappe.db.rollback() - - # wasn't able to send emails :( - doc.db_set("email_sent", 0) - frappe.db.commit() - - frappe.log_error(title='Send Newsletter') - - raise - - else: - frappe.db.commit() + indicator_color="green", + ) def get_list_context(context=None): @@ -268,12 +350,35 @@ def get_newsletter_list(doctype, txt, filters, limit_start, limit_page_length=20 '''.format(','.join(['%s'] * len(email_group_list)), limit_page_length, limit_start), email_group_list, as_dict=1) + def send_scheduled_email(): """Send scheduled newsletter to the recipients.""" - scheduled_newsletter = frappe.get_all('Newsletter', filters = { - 'schedule_send': ('<=', now_datetime()), - 'email_sent': 0, - 'schedule_sending': 1 - }, fields = ['name'], ignore_ifnull=True) + scheduled_newsletter = frappe.get_all( + "Newsletter", + filters={ + "schedule_send": ("<=", frappe.utils.now_datetime()), + "email_sent": False, + "schedule_sending": True, + }, + ignore_ifnull=True, + pluck="name", + ) + for newsletter in scheduled_newsletter: - send_newsletter(newsletter.name) + try: + frappe.get_doc("Newsletter", newsletter).queue_all() + + except Exception: + frappe.db.rollback() + + # wasn't able to send emails :( + frappe.db.set_value("Newsletter", newsletter, "email_sent", 0) + message = ( + f"Newsletter {newsletter} failed to send" + "\n\n" + f"Traceback: {frappe.get_traceback()}" + ) + frappe.log_error(title="Send Newsletter", message=message) + + if not frappe.flags.in_test: + frappe.db.commit() diff --git a/frappe/email/doctype/newsletter/test_newsletter.py b/frappe/email/doctype/newsletter/test_newsletter.py index 3abd339ed9..abbcc6440c 100644 --- a/frappe/email/doctype/newsletter/test_newsletter.py +++ b/frappe/email/doctype/newsletter/test_newsletter.py @@ -1,17 +1,26 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See LICENSE + import unittest from random import choice +from typing import Union +from unittest.mock import MagicMock, PropertyMock, patch import frappe +from frappe.desk.form.load import run_onload +from frappe.email.doctype.newsletter.exceptions import ( + NewsletterAlreadySentError, NoRecipientFoundError +) from frappe.email.doctype.newsletter.newsletter import ( + Newsletter, confirmed_unsubscribe, - send_scheduled_email, + get_newsletter_list, + send_scheduled_email ) -from frappe.email.doctype.newsletter.newsletter import get_newsletter_list from frappe.email.queue import flush from frappe.utils import add_days, getdate + test_dependencies = ["Email Group"] emails = [ "test_subscriber1@example.com", @@ -19,23 +28,107 @@ emails = [ "test_subscriber3@example.com", "test1@example.com", ] +newsletters = [] -class TestNewsletter(unittest.TestCase): +def get_dotted_path(obj: type) -> str: + klass = obj.__class__ + module = klass.__module__ + if module == 'builtins': + return klass.__qualname__ # avoid outputs like 'builtins.str' + return f"{module}.{klass.__qualname__}" + + +class TestNewsletterMixin: def setUp(self): frappe.set_user("Administrator") - frappe.db.sql("delete from `tabEmail Group Member`") + self.setup_email_group() + def tearDown(self): + frappe.set_user("Administrator") + for newsletter in newsletters: + frappe.db.delete("Email Queue", { + "reference_doctype": "Newsletter", + "reference_name": newsletter, + }) + frappe.delete_doc("Newsletter", newsletter) + frappe.db.delete("Newsletter Email Group", newsletter) + newsletters.remove(newsletter) + + def setup_email_group(self): if not frappe.db.exists("Email Group", "_Test Email Group"): - frappe.get_doc({"doctype": "Email Group", "title": "_Test Email Group"}).insert() + frappe.get_doc({ + "doctype": "Email Group", + "title": "_Test Email Group" + }).insert() for email in emails: - frappe.get_doc({ - "doctype": "Email Group Member", + doctype = "Email Group Member" + email_filters = { "email": email, "email_group": "_Test Email Group" - }).insert() + } + try: + frappe.get_doc({ + "doctype": doctype, + **email_filters, + }).insert() + except Exception: + frappe.db.update(doctype, email_filters, "unsubscribed", 0) + + def send_newsletter(self, published=0, schedule_send=None) -> Union[str, None]: + frappe.db.delete("Email Queue") + frappe.db.delete("Email Queue Recipient") + frappe.db.delete("Newsletter") + + newsletter_options = { + "published": published, + "schedule_sending": bool(schedule_send), + "schedule_send": schedule_send + } + newsletter = self.get_newsletter(**newsletter_options) + + if schedule_send: + send_scheduled_email() + else: + newsletter.send_emails() + return newsletter.name + + @staticmethod + def get_newsletter(**kwargs) -> "Newsletter": + """Generate and return Newsletter object + """ + doctype = "Newsletter" + newsletter_content = { + "subject": "_Test Newsletter", + "send_from": "Test Sender ", + "content_type": "Rich Text", + "message": "Testing my news.", + } + similar_newsletters = frappe.db.get_all(doctype, newsletter_content, pluck="name") + + for similar_newsletter in similar_newsletters: + frappe.delete_doc(doctype, similar_newsletter) + newsletter = frappe.get_doc({"doctype": doctype, **newsletter_content, **kwargs}) + newsletter.append("email_group", {"email_group": "_Test Email Group"}) + newsletter.save(ignore_permissions=True) + newsletter.reload() + newsletters.append(newsletter.name) + + attached_files = frappe.get_all("File", { + "attached_to_doctype": newsletter.doctype, + "attached_to_name": newsletter.name, + }, + pluck="name", + ) + for file in attached_files: + frappe.delete_doc("File", file) + + return newsletter + + +class TestNewsletter(TestNewsletterMixin, unittest.TestCase): def test_send(self): self.send_newsletter() @@ -64,40 +157,15 @@ class TestNewsletter(unittest.TestCase): if email != to_unsubscribe: self.assertTrue(email in recipients) - @staticmethod - def send_newsletter(published=0, schedule_send=None): - frappe.db.sql("delete from `tabEmail Queue`") - frappe.db.sql("delete from `tabEmail Queue Recipient`") - frappe.db.sql("delete from `tabNewsletter`") - newsletter = frappe.get_doc({ - "doctype": "Newsletter", - "subject": "_Test Newsletter", - "send_from": "Test Sender ", - "content_type": "Rich Text", - "message": "Testing my news.", - "published": published, - "schedule_sending": bool(schedule_send), - "schedule_send": schedule_send - }).insert(ignore_permissions=True) - - newsletter.append("email_group", {"email_group": "_Test Email Group"}) - newsletter.save() - if schedule_send: - send_scheduled_email() - return - - newsletter.send_emails() - return newsletter.name - def test_portal(self): - self.send_newsletter(1) + self.send_newsletter(published=1) frappe.set_user("test1@example.com") - newsletters = get_newsletter_list("Newsletter", None, None, 0) - self.assertEqual(len(newsletters), 1) + newsletter_list = get_newsletter_list("Newsletter", None, None, 0) + self.assertEqual(len(newsletter_list), 1) def test_newsletter_context(self): context = frappe._dict() - newsletter_name = self.send_newsletter(1) + newsletter_name = self.send_newsletter(published=1) frappe.set_user("test2@example.com") doc = frappe.get_doc("Newsletter", newsletter_name) doc.get_context(context) @@ -112,3 +180,68 @@ class TestNewsletter(unittest.TestCase): recipients = [e.recipients[0].recipient for e in email_queue_list] for email in emails: self.assertTrue(email in recipients) + + def test_newsletter_test_send(self): + """Test "Test Send" functionality of Newsletter + """ + newsletter = self.get_newsletter() + newsletter.test_email_id = choice(emails) + newsletter.test_send() + + self.assertFalse(newsletter.email_sent) + newsletter.save = MagicMock() + self.assertFalse(newsletter.save.called) + + def test_newsletter_status(self): + """Test for Newsletter's stats on onload event + """ + newsletter = self.get_newsletter() + newsletter.email_sent = True + # had to use run_onload as calling .onload directly bought weird errors + # like TestNewsletter has no attribute "_TestNewsletter__onload" + run_onload(newsletter) + self.assertIsInstance(newsletter.get("__onload").status_count, dict) + + def test_already_sent_newsletter(self): + newsletter = self.get_newsletter() + newsletter.send_emails() + + with self.assertRaises(NewsletterAlreadySentError): + newsletter.send_emails() + + def test_newsletter_with_no_recipient(self): + newsletter = self.get_newsletter() + property_path = f"{get_dotted_path(newsletter)}.newsletter_recipients" + + with patch(property_path, new_callable=PropertyMock) as mock_newsletter_recipients: + mock_newsletter_recipients.return_value = [] + with self.assertRaises(NoRecipientFoundError): + newsletter.send_emails() + + def test_send_newsletter_with_attachments(self): + newsletter = self.get_newsletter() + newsletter.reload() + file_attachment = frappe.get_doc({ + "doctype": "File", + "file_name": "test1.txt", + "attached_to_doctype": newsletter.doctype, + "attached_to_name": newsletter.name, + "content": frappe.mock("paragraph") + }) + file_attachment.save() + newsletter.send_attachments = True + newsletter_attachments = newsletter.get_newsletter_attachments() + self.assertEqual(len(newsletter_attachments), 1) + self.assertEqual(newsletter_attachments[0]["fid"], file_attachment.name) + + def test_send_scheduled_email_error_handling(self): + newsletter = self.get_newsletter(schedule_send=add_days(getdate(), -1)) + job_path = "frappe.email.doctype.newsletter.newsletter.Newsletter.queue_all" + m = MagicMock(side_effect=frappe.OutgoingEmailError) + + with self.assertRaises(frappe.OutgoingEmailError): + with patch(job_path, new_callable=m): + send_scheduled_email() + + newsletter.reload() + self.assertEqual(newsletter.email_sent, 0) diff --git a/frappe/email/doctype/newsletter_email_group/newsletter_email_group.py b/frappe/email/doctype/newsletter_email_group/newsletter_email_group.py index a453dda9e4..89476c4d53 100644 --- a/frappe/email/doctype/newsletter_email_group/newsletter_email_group.py +++ b/frappe/email/doctype/newsletter_email_group/newsletter_email_group.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 57418515f5..f523d835e9 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe import json, os diff --git a/frappe/email/doctype/notification/test_notification.py b/frappe/email/doctype/notification/test_notification.py index d6358ccbbe..8e814e0245 100644 --- a/frappe/email/doctype/notification/test_notification.py +++ b/frappe/email/doctype/notification/test_notification.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe, frappe.utils, frappe.utils.scheduler from frappe.desk.form import assign_to import unittest @@ -9,7 +9,7 @@ test_dependencies = ["User", "Notification"] class TestNotification(unittest.TestCase): def setUp(self): - frappe.db.sql("""delete from `tabEmail Queue`""") + frappe.db.delete("Email Queue") frappe.set_user("test@example.com") if not frappe.db.exists('Notification', {'name': 'ToDo Status Update'}, 'name'): @@ -50,7 +50,7 @@ class TestNotification(unittest.TestCase): self.assertTrue(frappe.db.get_value("Email Queue", {"reference_doctype": "Communication", "reference_name": communication.name, "status":"Not Sent"})) - frappe.db.sql("""delete from `tabEmail Queue`""") + frappe.db.delete("Email Queue") communication.reload() communication.content = "test 2" @@ -189,9 +189,9 @@ class TestNotification(unittest.TestCase): def test_cc_jinja(self): - frappe.db.sql("""delete from `tabUser` where email='test_jinja@example.com'""") - frappe.db.sql("""delete from `tabEmail Queue`""") - frappe.db.sql("""delete from `tabEmail Queue Recipient`""") + frappe.db.delete("User", {"email": "test_jinja@example.com"}) + frappe.db.delete("Email Queue") + frappe.db.delete("Email Queue Recipient") test_user = frappe.new_doc("User") test_user.name = 'test_jinja' @@ -205,9 +205,9 @@ class TestNotification(unittest.TestCase): self.assertTrue(frappe.db.get_value("Email Queue Recipient", {"recipient": "test_jinja@example.com"})) - frappe.db.sql("""delete from `tabUser` where email='test_jinja@example.com'""") - frappe.db.sql("""delete from `tabEmail Queue`""") - frappe.db.sql("""delete from `tabEmail Queue Recipient`""") + frappe.db.delete("User", {"email": "test_jinja@example.com"}) + frappe.db.delete("Email Queue") + frappe.db.delete("Email Queue Recipient") def test_notification_to_assignee(self): todo = frappe.new_doc('ToDo') diff --git a/frappe/email/doctype/notification_recipient/notification_recipient.py b/frappe/email/doctype/notification_recipient/notification_recipient.py index d8480c5455..68871e5047 100644 --- a/frappe/email/doctype/notification_recipient/notification_recipient.py +++ b/frappe/email/doctype/notification_recipient/notification_recipient.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/email/doctype/unhandled_email/test_unhandled_email.py b/frappe/email/doctype/unhandled_email/test_unhandled_email.py index 5606b8ff30..37c65584e0 100644 --- a/frappe/email/doctype/unhandled_email/test_unhandled_email.py +++ b/frappe/email/doctype/unhandled_email/test_unhandled_email.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/email/doctype/unhandled_email/unhandled_email.py b/frappe/email/doctype/unhandled_email/unhandled_email.py index b445c98aa6..db14a50d09 100644 --- a/frappe/email/doctype/unhandled_email/unhandled_email.py +++ b/frappe/email/doctype/unhandled_email/unhandled_email.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index ffb44d3412..6a32ae6fd9 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe, re, os from frappe.utils.pdf import get_pdf diff --git a/frappe/email/queue.py b/frappe/email/queue.py index ef59302bab..16e3fecf48 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe import msgprint, _ diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 2e42008951..a755ec5e74 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import datetime import email diff --git a/frappe/email/smtp.py b/frappe/email/smtp.py index 74492c09c3..6f73a73f11 100644 --- a/frappe/email/smtp.py +++ b/frappe/email/smtp.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import smtplib diff --git a/frappe/email/test_email_body.py b/frappe/email/test_email_body.py index 2c7d119fce..c542bc2578 100644 --- a/frappe/email/test_email_body.py +++ b/frappe/email/test_email_body.py @@ -1,5 +1,6 @@ -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# License: MIT. See LICENSE + import unittest, os, base64 from frappe import safe_decode from frappe.email.receive import Email diff --git a/frappe/email/utils.py b/frappe/email/utils.py index 24ce77b922..1138698491 100644 --- a/frappe/email/utils.py +++ b/frappe/email/utils.py @@ -1,5 +1,5 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import imaplib, poplib from frappe.utils import cint diff --git a/frappe/event_streaming/doctype/document_type_field_mapping/document_type_field_mapping.py b/frappe/event_streaming/doctype/document_type_field_mapping/document_type_field_mapping.py index fc8164d8a4..3019d70035 100644 --- a/frappe/event_streaming/doctype/document_type_field_mapping/document_type_field_mapping.py +++ b/frappe/event_streaming/doctype/document_type_field_mapping/document_type_field_mapping.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/event_streaming/doctype/document_type_mapping/document_type_mapping.py b/frappe/event_streaming/doctype/document_type_mapping/document_type_mapping.py index 2cf7282a5a..8f1e5504da 100644 --- a/frappe/event_streaming/doctype/document_type_mapping/document_type_mapping.py +++ b/frappe/event_streaming/doctype/document_type_mapping/document_type_mapping.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe import json from frappe import _ diff --git a/frappe/event_streaming/doctype/document_type_mapping/test_document_type_mapping.py b/frappe/event_streaming/doctype/document_type_mapping/test_document_type_mapping.py index b1bb322855..a277139985 100644 --- a/frappe/event_streaming/doctype/document_type_mapping/test_document_type_mapping.py +++ b/frappe/event_streaming/doctype/document_type_mapping/test_document_type_mapping.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/event_streaming/doctype/event_consumer/event_consumer.py b/frappe/event_streaming/doctype/event_consumer/event_consumer.py index 00d304f7f4..e8b84d1345 100644 --- a/frappe/event_streaming/doctype/event_consumer/event_consumer.py +++ b/frappe/event_streaming/doctype/event_consumer/event_consumer.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe import json diff --git a/frappe/event_streaming/doctype/event_consumer/test_event_consumer.py b/frappe/event_streaming/doctype/event_consumer/test_event_consumer.py index b8072ecabd..11c69e7ba3 100644 --- a/frappe/event_streaming/doctype/event_consumer/test_event_consumer.py +++ b/frappe/event_streaming/doctype/event_consumer/test_event_consumer.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/event_streaming/doctype/event_consumer_document_type/event_consumer_document_type.py b/frappe/event_streaming/doctype/event_consumer_document_type/event_consumer_document_type.py index cf5d18edfd..b33313087f 100644 --- a/frappe/event_streaming/doctype/event_consumer_document_type/event_consumer_document_type.py +++ b/frappe/event_streaming/doctype/event_consumer_document_type/event_consumer_document_type.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/event_streaming/doctype/event_producer/event_producer.py b/frappe/event_streaming/doctype/event_producer/event_producer.py index 4836276734..0be15e461b 100644 --- a/frappe/event_streaming/doctype/event_producer/event_producer.py +++ b/frappe/event_streaming/doctype/event_producer/event_producer.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import json import time diff --git a/frappe/event_streaming/doctype/event_producer/test_event_producer.py b/frappe/event_streaming/doctype/event_producer/test_event_producer.py index 883f4f2df2..3d697ceb3a 100644 --- a/frappe/event_streaming/doctype/event_producer/test_event_producer.py +++ b/frappe/event_streaming/doctype/event_producer/test_event_producer.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest import json diff --git a/frappe/event_streaming/doctype/event_producer_document_type/event_producer_document_type.py b/frappe/event_streaming/doctype/event_producer_document_type/event_producer_document_type.py index 9ae70e0f97..3e9623f56f 100644 --- a/frappe/event_streaming/doctype/event_producer_document_type/event_producer_document_type.py +++ b/frappe/event_streaming/doctype/event_producer_document_type/event_producer_document_type.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/event_streaming/doctype/event_producer_last_update/event_producer_last_update.py b/frappe/event_streaming/doctype/event_producer_last_update/event_producer_last_update.py index 391cf79c27..0868e86253 100644 --- a/frappe/event_streaming/doctype/event_producer_last_update/event_producer_last_update.py +++ b/frappe/event_streaming/doctype/event_producer_last_update/event_producer_last_update.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/event_streaming/doctype/event_producer_last_update/test_event_producer_last_update.py b/frappe/event_streaming/doctype/event_producer_last_update/test_event_producer_last_update.py index 62ea71edab..c2d943a463 100644 --- a/frappe/event_streaming/doctype/event_producer_last_update/test_event_producer_last_update.py +++ b/frappe/event_streaming/doctype/event_producer_last_update/test_event_producer_last_update.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/event_streaming/doctype/event_sync_log/event_sync_log.py b/frappe/event_streaming/doctype/event_sync_log/event_sync_log.py index 1d255a5c30..c26ca46e05 100644 --- a/frappe/event_streaming/doctype/event_sync_log/event_sync_log.py +++ b/frappe/event_streaming/doctype/event_sync_log/event_sync_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/event_streaming/doctype/event_sync_log/test_event_sync_log.py b/frappe/event_streaming/doctype/event_sync_log/test_event_sync_log.py index ef55dc0f16..b901f92ef8 100644 --- a/frappe/event_streaming/doctype/event_sync_log/test_event_sync_log.py +++ b/frappe/event_streaming/doctype/event_sync_log/test_event_sync_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/event_streaming/doctype/event_update_log/event_update_log.py b/frappe/event_streaming/doctype/event_update_log/event_update_log.py index ae851c70d1..f4871be312 100644 --- a/frappe/event_streaming/doctype/event_update_log/event_update_log.py +++ b/frappe/event_streaming/doctype/event_update_log/event_update_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/event_streaming/doctype/event_update_log/test_event_update_log.py b/frappe/event_streaming/doctype/event_update_log/test_event_update_log.py index 99ced3c209..752f4bbb44 100644 --- a/frappe/event_streaming/doctype/event_update_log/test_event_update_log.py +++ b/frappe/event_streaming/doctype/event_update_log/test_event_update_log.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/event_streaming/doctype/event_update_log_consumer/event_update_log_consumer.py b/frappe/event_streaming/doctype/event_update_log_consumer/event_update_log_consumer.py index 80a59e4c31..47180db74e 100644 --- a/frappe/event_streaming/doctype/event_update_log_consumer/event_update_log_consumer.py +++ b/frappe/event_streaming/doctype/event_update_log_consumer/event_update_log_consumer.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/exceptions.py b/frappe/exceptions.py index 13abd8f4f8..31a94ac883 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE # BEWARE don't put anything in this file except exceptions from werkzeug.exceptions import NotFound diff --git a/frappe/geo/country_info.py b/frappe/geo/country_info.py index ddebd1fb0e..86f1d9bc2f 100644 --- a/frappe/geo/country_info.py +++ b/frappe/geo/country_info.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE # all country info import os, json, frappe diff --git a/frappe/geo/doctype/country/country.py b/frappe/geo/doctype/country/country.py index 54935e6eaf..a648744058 100644 --- a/frappe/geo/doctype/country/country.py +++ b/frappe/geo/doctype/country/country.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/geo/doctype/country/test_country.py b/frappe/geo/doctype/country/test_country.py index e00d6ecf37..b4d15f81b3 100644 --- a/frappe/geo/doctype/country/test_country.py +++ b/frappe/geo/doctype/country/test_country.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: See license.txt +# License: MIT. See LICENSE import frappe test_records = frappe.get_test_records('Country') \ No newline at end of file diff --git a/frappe/geo/doctype/currency/currency.py b/frappe/geo/doctype/currency/currency.py index b3ce67cc67..fbe37e73bd 100644 --- a/frappe/geo/doctype/currency/currency.py +++ b/frappe/geo/doctype/currency/currency.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: See license.txt +# License: MIT. See LICENSE import frappe from frappe import throw, _ diff --git a/frappe/geo/doctype/currency/test_currency.py b/frappe/geo/doctype/currency/test_currency.py index 5552e675ec..71b963cc86 100644 --- a/frappe/geo/doctype/currency/test_currency.py +++ b/frappe/geo/doctype/currency/test_currency.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: See license.txt +# License: MIT. See LICENSE # pre loaded diff --git a/frappe/geo/utils.py b/frappe/geo/utils.py index 89de176f0b..9b44a2f3d8 100644 --- a/frappe/geo/utils.py +++ b/frappe/geo/utils.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/handler.py b/frappe/handler.py index 2e9fb7b454..352a9672bf 100755 --- a/frappe/handler.py +++ b/frappe/handler.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE from werkzeug.wrappers import Response diff --git a/frappe/installer.py b/frappe/installer.py index d4d8117fcb..23247046f6 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import json import os diff --git a/frappe/integrations/doctype/braintree_settings/braintree_settings.py b/frappe/integrations/doctype/braintree_settings/braintree_settings.py index 9dc9778bee..59751185b9 100644 --- a/frappe/integrations/doctype/braintree_settings/braintree_settings.py +++ b/frappe/integrations/doctype/braintree_settings/braintree_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/braintree_settings/test_braintree_settings.py b/frappe/integrations/doctype/braintree_settings/test_braintree_settings.py index 72a678a92c..721158fb4a 100644 --- a/frappe/integrations/doctype/braintree_settings/test_braintree_settings.py +++ b/frappe/integrations/doctype/braintree_settings/test_braintree_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest class TestBraintreeSettings(unittest.TestCase): diff --git a/frappe/integrations/doctype/connected_app/connected_app.py b/frappe/integrations/doctype/connected_app/connected_app.py index 449e30f6d0..fcb5fe7ee9 100644 --- a/frappe/integrations/doctype/connected_app/connected_app.py +++ b/frappe/integrations/doctype/connected_app/connected_app.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import os from urllib.parse import urljoin diff --git a/frappe/integrations/doctype/connected_app/test_connected_app.py b/frappe/integrations/doctype/connected_app/test_connected_app.py index d1ff19ecb2..eff7104ce0 100644 --- a/frappe/integrations/doctype/connected_app/test_connected_app.py +++ b/frappe/integrations/doctype/connected_app/test_connected_app.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# See license.txt +# License: MIT. See LICENSE import unittest import requests from urllib.parse import urljoin diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py index 53f0935c80..90927e13f8 100644 --- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py +++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import json import os diff --git a/frappe/integrations/doctype/dropbox_settings/test_dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/test_dropbox_settings.py index d34e65de50..458f876444 100644 --- a/frappe/integrations/doctype/dropbox_settings/test_dropbox_settings.py +++ b/frappe/integrations/doctype/dropbox_settings/test_dropbox_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index f93be35aa7..0d4c5bbe5c 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from datetime import datetime, timedelta diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.py b/frappe/integrations/doctype/google_contacts/google_contacts.py index 1705f98e91..a63b0b6d80 100644 --- a/frappe/integrations/doctype/google_contacts/google_contacts.py +++ b/frappe/integrations/doctype/google_contacts/google_contacts.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import google.oauth2.credentials diff --git a/frappe/integrations/doctype/google_drive/google_drive.py b/frappe/integrations/doctype/google_drive/google_drive.py index 93b6fa3f8d..beac7898a9 100644 --- a/frappe/integrations/doctype/google_drive/google_drive.py +++ b/frappe/integrations/doctype/google_drive/google_drive.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import os from urllib.parse import quote diff --git a/frappe/integrations/doctype/google_drive/test_google_drive.py b/frappe/integrations/doctype/google_drive/test_google_drive.py index 96e8577c7c..fbd9dce7f4 100644 --- a/frappe/integrations/doctype/google_drive/test_google_drive.py +++ b/frappe/integrations/doctype/google_drive/test_google_drive.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/integrations/doctype/google_settings/google_settings.py b/frappe/integrations/doctype/google_settings/google_settings.py index db65abdb65..94df43e69c 100644 --- a/frappe/integrations/doctype/google_settings/google_settings.py +++ b/frappe/integrations/doctype/google_settings/google_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/google_settings/test_google_settings.py b/frappe/integrations/doctype/google_settings/test_google_settings.py index 32d43a323b..cddf9f3697 100644 --- a/frappe/integrations/doctype/google_settings/test_google_settings.py +++ b/frappe/integrations/doctype/google_settings/test_google_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE from __future__ import unicode_literals import frappe diff --git a/frappe/integrations/doctype/integration_request/integration_request.py b/frappe/integrations/doctype/integration_request/integration_request.py index 4c4961d96d..ae0e024f58 100644 --- a/frappe/integrations/doctype/integration_request/integration_request.py +++ b/frappe/integrations/doctype/integration_request/integration_request.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/integration_request/test_integration_request.py b/frappe/integrations/doctype/integration_request/test_integration_request.py index a26eb4ba93..e26ccabc96 100644 --- a/frappe/integrations/doctype/integration_request/test_integration_request.py +++ b/frappe/integrations/doctype/integration_request/test_integration_request.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/integrations/doctype/ldap_group_mapping/ldap_group_mapping.py b/frappe/integrations/doctype/ldap_group_mapping/ldap_group_mapping.py index b6bb77d964..b9838b996f 100644 --- a/frappe/integrations/doctype/ldap_group_mapping/ldap_group_mapping.py +++ b/frappe/integrations/doctype/ldap_group_mapping/ldap_group_mapping.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index 7c89c31844..1c5abb454c 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _, safe_encode diff --git a/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py b/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py index 1b2a9b155f..7b0638876b 100644 --- a/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest import functools @@ -172,7 +172,7 @@ class LDAP_TestCase(): # Clear OpenLDAP connection self.connection = None - + @mock_ldap_connection def test_mandatory_fields(self): @@ -218,7 +218,7 @@ class LDAP_TestCase(): localdoc = self.doc.copy() localdoc[non_mandatory_field] = '' - + try: frappe.get_doc(localdoc).save() @@ -324,7 +324,7 @@ class LDAP_TestCase(): self.assertTrue(kwargs['user'] == self.base_dn, 'ldap3.Connection user does not match provided user') - ldap3_connection_method.assert_called_with(server=ldap3_server_method.return_value, + ldap3_connection_method.assert_called_with(server=ldap3_server_method.return_value, auto_bind=True, password=self.base_password, raise_exceptions=True, @@ -467,7 +467,7 @@ class LDAP_TestCase(): self.test_class.create_or_update_user(self.user1doc, test_user_data[test_user]) self.assertTrue(sync_roles_method.called, 'User roles need to be updated for a new user') - self.assertFalse(update_user_fields_method.called, + self.assertFalse(update_user_fields_method.called, 'User roles are not required to be updated for a new user, this will occur during logon') @@ -489,7 +489,7 @@ class LDAP_TestCase(): @mock_ldap_connection def test_fetch_ldap_groups(self): - + if self.TEST_LDAP_SERVER.lower() == 'openldap': test_users = { 'posix.user': ['Users', 'Administrators'], @@ -523,7 +523,7 @@ class LDAP_TestCase(): @mock_ldap_connection def test_authenticate(self): - + with mock.patch('frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.fetch_ldap_groups') as \ fetch_ldap_groups_function: @@ -543,7 +543,7 @@ class LDAP_TestCase(): {'': 'posix_user_password'}, {'': ''} ] # All invalid users should return 'invalid username or password' - + for username, password in enumerate(invalid_users): with self.assertRaises(frappe.exceptions.ValidationError) as display_massage: diff --git a/frappe/integrations/doctype/oauth_authorization_code/oauth_authorization_code.py b/frappe/integrations/doctype/oauth_authorization_code/oauth_authorization_code.py index 0c7f02844c..5a3f380e84 100644 --- a/frappe/integrations/doctype/oauth_authorization_code/oauth_authorization_code.py +++ b/frappe/integrations/doctype/oauth_authorization_code/oauth_authorization_code.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/oauth_authorization_code/test_oauth_authorization_code.py b/frappe/integrations/doctype/oauth_authorization_code/test_oauth_authorization_code.py index 6084dd64b4..bc6d29cbdb 100644 --- a/frappe/integrations/doctype/oauth_authorization_code/test_oauth_authorization_code.py +++ b/frappe/integrations/doctype/oauth_authorization_code/test_oauth_authorization_code.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/integrations/doctype/oauth_bearer_token/oauth_bearer_token.py b/frappe/integrations/doctype/oauth_bearer_token/oauth_bearer_token.py index 916d0205d2..ff6f96cc4d 100644 --- a/frappe/integrations/doctype/oauth_bearer_token/oauth_bearer_token.py +++ b/frappe/integrations/doctype/oauth_bearer_token/oauth_bearer_token.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/oauth_bearer_token/test_oauth_bearer_token.py b/frappe/integrations/doctype/oauth_bearer_token/test_oauth_bearer_token.py index 6028cebcf9..965feb4f78 100644 --- a/frappe/integrations/doctype/oauth_bearer_token/test_oauth_bearer_token.py +++ b/frappe/integrations/doctype/oauth_bearer_token/test_oauth_bearer_token.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/integrations/doctype/oauth_client/oauth_client.py b/frappe/integrations/doctype/oauth_client/oauth_client.py index 0b449ff968..42fba07ecb 100644 --- a/frappe/integrations/doctype/oauth_client/oauth_client.py +++ b/frappe/integrations/doctype/oauth_client/oauth_client.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/integrations/doctype/oauth_client/test_oauth_client.py b/frappe/integrations/doctype/oauth_client/test_oauth_client.py index a4e50e15d8..fa03fa06e7 100644 --- a/frappe/integrations/doctype/oauth_client/test_oauth_client.py +++ b/frappe/integrations/doctype/oauth_client/test_oauth_client.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.py b/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.py index 3ab5df92ac..ec1636659f 100644 --- a/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.py +++ b/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/oauth_scope/oauth_scope.py b/frappe/integrations/doctype/oauth_scope/oauth_scope.py index ae579e6b51..cf5fa1f341 100644 --- a/frappe/integrations/doctype/oauth_scope/oauth_scope.py +++ b/frappe/integrations/doctype/oauth_scope/oauth_scope.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/paypal_settings/paypal_settings.py b/frappe/integrations/doctype/paypal_settings/paypal_settings.py index da045d2c6a..30ac905792 100644 --- a/frappe/integrations/doctype/paypal_settings/paypal_settings.py +++ b/frappe/integrations/doctype/paypal_settings/paypal_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE """ # Integrating PayPal diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index 9f15d73f09..5255360242 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import json import requests diff --git a/frappe/integrations/doctype/paytm_settings/test_paytm_settings.py b/frappe/integrations/doctype/paytm_settings/test_paytm_settings.py index a00ce86327..425fc87a3f 100644 --- a/frappe/integrations/doctype/paytm_settings/test_paytm_settings.py +++ b/frappe/integrations/doctype/paytm_settings/test_paytm_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/integrations/doctype/query_parameters/query_parameters.py b/frappe/integrations/doctype/query_parameters/query_parameters.py index 13fb94dbe3..68e97e9071 100644 --- a/frappe/integrations/doctype/query_parameters/query_parameters.py +++ b/frappe/integrations/doctype/query_parameters/query_parameters.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index d24e15f480..9ae16a31f8 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE """ # Integrating RazorPay diff --git a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py index 1346811652..dc824e18b9 100755 --- a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py +++ b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import os import os.path import frappe diff --git a/frappe/integrations/doctype/s3_backup_settings/test_s3_backup_settings.py b/frappe/integrations/doctype/s3_backup_settings/test_s3_backup_settings.py index 3aecdf3489..2a586c30d4 100755 --- a/frappe/integrations/doctype/s3_backup_settings/test_s3_backup_settings.py +++ b/frappe/integrations/doctype/s3_backup_settings/test_s3_backup_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest class TestS3BackupSettings(unittest.TestCase): diff --git a/frappe/integrations/doctype/slack_webhook_url/slack_webhook_url.py b/frappe/integrations/doctype/slack_webhook_url/slack_webhook_url.py index a970fc1f11..a74c0a36ca 100644 --- a/frappe/integrations/doctype/slack_webhook_url/slack_webhook_url.py +++ b/frappe/integrations/doctype/slack_webhook_url/slack_webhook_url.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/slack_webhook_url/test_slack_webhook_url.py b/frappe/integrations/doctype/slack_webhook_url/test_slack_webhook_url.py index 4285c2c4bc..a256735f81 100644 --- a/frappe/integrations/doctype/slack_webhook_url/test_slack_webhook_url.py +++ b/frappe/integrations/doctype/slack_webhook_url/test_slack_webhook_url.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest class TestSlackWebhookURL(unittest.TestCase): diff --git a/frappe/integrations/doctype/social_login_key/social_login_key.py b/frappe/integrations/doctype/social_login_key/social_login_key.py index 4a4fcd44f4..d6f55e5758 100644 --- a/frappe/integrations/doctype/social_login_key/social_login_key.py +++ b/frappe/integrations/doctype/social_login_key/social_login_key.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe, json from frappe import _ diff --git a/frappe/integrations/doctype/social_login_key/test_social_login_key.py b/frappe/integrations/doctype/social_login_key/test_social_login_key.py index 23effd6a44..880f1ee99c 100644 --- a/frappe/integrations/doctype/social_login_key/test_social_login_key.py +++ b/frappe/integrations/doctype/social_login_key/test_social_login_key.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe from frappe.integrations.doctype.social_login_key.social_login_key import BaseUrlNotSetError import unittest diff --git a/frappe/integrations/doctype/stripe_settings/stripe_settings.py b/frappe/integrations/doctype/stripe_settings/stripe_settings.py index 9bb9c60775..81e40fa72f 100644 --- a/frappe/integrations/doctype/stripe_settings/stripe_settings.py +++ b/frappe/integrations/doctype/stripe_settings/stripe_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/stripe_settings/test_stripe_settings.py b/frappe/integrations/doctype/stripe_settings/test_stripe_settings.py index ba11c3c38b..e7113d3bd9 100644 --- a/frappe/integrations/doctype/stripe_settings/test_stripe_settings.py +++ b/frappe/integrations/doctype/stripe_settings/test_stripe_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest class TestStripeSettings(unittest.TestCase): diff --git a/frappe/integrations/doctype/token_cache/test_token_cache.py b/frappe/integrations/doctype/token_cache/test_token_cache.py index 2ffd57403b..5fe648d225 100644 --- a/frappe/integrations/doctype/token_cache/test_token_cache.py +++ b/frappe/integrations/doctype/token_cache/test_token_cache.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# See license.txt +# License: MIT. See LICENSE import unittest import frappe diff --git a/frappe/integrations/doctype/token_cache/token_cache.py b/frappe/integrations/doctype/token_cache/token_cache.py index 3001d12b2b..ea86100cc2 100644 --- a/frappe/integrations/doctype/token_cache/token_cache.py +++ b/frappe/integrations/doctype/token_cache/token_cache.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE from datetime import datetime, timedelta diff --git a/frappe/integrations/doctype/webhook/__init__.py b/frappe/integrations/doctype/webhook/__init__.py index b92497f16c..6dcc0218a3 100644 --- a/frappe/integrations/doctype/webhook/__init__.py +++ b/frappe/integrations/doctype/webhook/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/integrations/doctype/webhook/test_webhook.py b/frappe/integrations/doctype/webhook/test_webhook.py index b77e311f7e..a1176aa38b 100644 --- a/frappe/integrations/doctype/webhook/test_webhook.py +++ b/frappe/integrations/doctype/webhook/test_webhook.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest import frappe @@ -11,9 +11,9 @@ class TestWebhook(unittest.TestCase): @classmethod def setUpClass(cls): # delete any existing webhooks - frappe.db.sql("DELETE FROM tabWebhook") + frappe.db.delete("Webhook") # Delete existing logs if any - frappe.db.sql("DELETE FROM `tabWebhook Request Log`") + frappe.db.delete("Webhook Request Log") # create test webhooks cls.create_sample_webhooks() @@ -46,7 +46,7 @@ class TestWebhook(unittest.TestCase): @classmethod def tearDownClass(cls): # delete any existing webhooks - frappe.db.sql("DELETE FROM tabWebhook") + frappe.db.delete("Webhook") def setUp(self): # retrieve or create a User webhook for `after_insert` @@ -168,7 +168,7 @@ class TestWebhook(unittest.TestCase): def test_webhook_req_log_creation(self): if not frappe.db.get_value('User', 'user2@integration.webhooks.test.com'): user = frappe.get_doc({ - 'doctype': 'User', + 'doctype': 'User', 'email': 'user2@integration.webhooks.test.com', 'first_name': 'user2' }).insert() diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index e3a8bda2aa..8546a9d2f8 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import base64 import datetime diff --git a/frappe/integrations/doctype/webhook_data/webhook_data.py b/frappe/integrations/doctype/webhook_data/webhook_data.py index dbd9328482..6037ed5390 100644 --- a/frappe/integrations/doctype/webhook_data/webhook_data.py +++ b/frappe/integrations/doctype/webhook_data/webhook_data.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/webhook_header/webhook_header.py b/frappe/integrations/doctype/webhook_header/webhook_header.py index 428b287db2..e1944c84bc 100644 --- a/frappe/integrations/doctype/webhook_header/webhook_header.py +++ b/frappe/integrations/doctype/webhook_header/webhook_header.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/webhook_request_log/test_webhook_request_log.py b/frappe/integrations/doctype/webhook_request_log/test_webhook_request_log.py index dd11bf8a01..5de26a35ed 100644 --- a/frappe/integrations/doctype/webhook_request_log/test_webhook_request_log.py +++ b/frappe/integrations/doctype/webhook_request_log/test_webhook_request_log.py @@ -1,5 +1,5 @@ # Copyright (c) 2021, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE # import frappe import unittest diff --git a/frappe/integrations/doctype/webhook_request_log/webhook_request_log.py b/frappe/integrations/doctype/webhook_request_log/webhook_request_log.py index 493ebfd8f7..3f0558ce80 100644 --- a/frappe/integrations/doctype/webhook_request_log/webhook_request_log.py +++ b/frappe/integrations/doctype/webhook_request_log/webhook_request_log.py @@ -1,5 +1,5 @@ # Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE # import frappe from frappe.model.document import Document diff --git a/frappe/integrations/oauth2_logins.py b/frappe/integrations/oauth2_logins.py index c38b43beb7..b187d29b34 100644 --- a/frappe/integrations/oauth2_logins.py +++ b/frappe/integrations/oauth2_logins.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import frappe.utils diff --git a/frappe/integrations/offsite_backup_utils.py b/frappe/integrations/offsite_backup_utils.py index 7a263e9d04..416d656d90 100644 --- a/frappe/integrations/offsite_backup_utils.py +++ b/frappe/integrations/offsite_backup_utils.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe import glob diff --git a/frappe/integrations/utils.py b/frappe/integrations/utils.py index b897a35062..bda45a765d 100644 --- a/frappe/integrations/utils.py +++ b/frappe/integrations/utils.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe import json,datetime diff --git a/frappe/integrations/workspace/integrations/integrations.json b/frappe/integrations/workspace/integrations/integrations.json index db96304207..4167858db2 100644 --- a/frappe/integrations/workspace/integrations/integrations.json +++ b/frappe/integrations/workspace/integrations/integrations.json @@ -1,22 +1,27 @@ { - "category": "Administration", + "category": "", "charts": [], + "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Backup\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Google Services\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Authentication\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Payments\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]", "creation": "2020-03-02 15:16:18.714190", "developer_mode_only": 0, - "disable_user_customization": 1, + "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "integration", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "Integrations", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Backup", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -25,6 +30,7 @@ "hidden": 0, "is_query_report": 0, "label": "Dropbox Settings", + "link_count": 0, "link_to": "Dropbox Settings", "link_type": "DocType", "onboard": 0, @@ -35,6 +41,7 @@ "hidden": 0, "is_query_report": 0, "label": "S3 Backup Settings", + "link_count": 0, "link_to": "S3 Backup Settings", "link_type": "DocType", "onboard": 0, @@ -45,6 +52,7 @@ "hidden": 0, "is_query_report": 0, "label": "Google Drive", + "link_count": 0, "link_to": "Google Drive", "link_type": "DocType", "onboard": 0, @@ -54,6 +62,7 @@ "hidden": 0, "is_query_report": 0, "label": "Google Services", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -62,6 +71,7 @@ "hidden": 0, "is_query_report": 0, "label": "Google Settings", + "link_count": 0, "link_to": "Google Settings", "link_type": "DocType", "onboard": 0, @@ -72,6 +82,7 @@ "hidden": 0, "is_query_report": 0, "label": "Google Contacts", + "link_count": 0, "link_to": "Google Contacts", "link_type": "DocType", "onboard": 0, @@ -82,6 +93,7 @@ "hidden": 0, "is_query_report": 0, "label": "Google Calendar", + "link_count": 0, "link_to": "Google Calendar", "link_type": "DocType", "onboard": 0, @@ -92,6 +104,7 @@ "hidden": 0, "is_query_report": 0, "label": "Google Drive", + "link_count": 0, "link_to": "Google Drive", "link_type": "DocType", "onboard": 0, @@ -101,6 +114,7 @@ "hidden": 0, "is_query_report": 0, "label": "Authentication", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -109,6 +123,7 @@ "hidden": 0, "is_query_report": 0, "label": "Social Login Key", + "link_count": 0, "link_to": "Social Login Key", "link_type": "DocType", "onboard": 0, @@ -119,6 +134,7 @@ "hidden": 0, "is_query_report": 0, "label": "LDAP Settings", + "link_count": 0, "link_to": "LDAP Settings", "link_type": "DocType", "onboard": 0, @@ -129,6 +145,7 @@ "hidden": 0, "is_query_report": 0, "label": "OAuth Client", + "link_count": 0, "link_to": "OAuth Client", "link_type": "DocType", "onboard": 0, @@ -139,6 +156,7 @@ "hidden": 0, "is_query_report": 0, "label": "OAuth Provider Settings", + "link_count": 0, "link_to": "OAuth Provider Settings", "link_type": "DocType", "onboard": 0, @@ -148,6 +166,7 @@ "hidden": 0, "is_query_report": 0, "label": "Payments", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -156,6 +175,7 @@ "hidden": 0, "is_query_report": 0, "label": "Braintree Settings", + "link_count": 0, "link_to": "Braintree Settings", "link_type": "DocType", "onboard": 0, @@ -166,6 +186,7 @@ "hidden": 0, "is_query_report": 0, "label": "PayPal Settings", + "link_count": 0, "link_to": "PayPal Settings", "link_type": "DocType", "onboard": 0, @@ -176,6 +197,7 @@ "hidden": 0, "is_query_report": 0, "label": "Razorpay Settings", + "link_count": 0, "link_to": "Razorpay Settings", "link_type": "DocType", "onboard": 0, @@ -186,6 +208,7 @@ "hidden": 0, "is_query_report": 0, "label": "Stripe Settings", + "link_count": 0, "link_to": "Stripe Settings", "link_type": "DocType", "onboard": 0, @@ -196,6 +219,7 @@ "hidden": 0, "is_query_report": 0, "label": "Paytm Settings", + "link_count": 0, "link_to": "Paytm Settings", "link_type": "DocType", "onboard": 0, @@ -205,6 +229,7 @@ "hidden": 0, "is_query_report": 0, "label": "Settings", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -213,6 +238,7 @@ "hidden": 0, "is_query_report": 0, "label": "Webhook", + "link_count": 0, "link_to": "Webhook", "link_type": "DocType", "onboard": 0, @@ -223,38 +249,37 @@ "hidden": 0, "is_query_report": 0, "label": "Slack Webhook URL", + "link_count": 0, "link_to": "Slack Webhook URL", "link_type": "DocType", "onboard": 0, "type": "Link" }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Twilio Settings", - "link_to": "Twilio Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, { "dependencies": "", "hidden": 0, "is_query_report": 0, "label": "SMS Settings", + "link_count": 0, "link_to": "SMS Settings", "link_type": "DocType", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:39.706680", + "modified": "2021-08-05 12:16:00.355267", "modified_by": "Administrator", "module": "Integrations", "name": "Integrations", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, - "shortcuts": [] + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 15, + "shortcuts": [], + "title": "Integrations" } \ No newline at end of file diff --git a/frappe/middlewares.py b/frappe/middlewares.py index 05944ec37a..38cb4cea21 100644 --- a/frappe/middlewares.py +++ b/frappe/middlewares.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import os diff --git a/frappe/migrate.py b/frappe/migrate.py index 061e4c98d7..92258502e4 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import json import os diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index 1acd7ee670..ce0a208d50 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE # model __init__.py import frappe diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 752543f46a..b9c8900839 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import datetime from frappe import _ @@ -874,7 +874,7 @@ class BaseDocument(object): return self._precision[cache_key][fieldname] - def get_formatted(self, fieldname, doc=None, currency=None, absolute_value=False, translated=False): + def get_formatted(self, fieldname, doc=None, currency=None, absolute_value=False, translated=False, format=None): from frappe.utils.formatters import format_value df = self.meta.get_field(fieldname) @@ -898,7 +898,7 @@ class BaseDocument(object): if (absolute_value or doc.get('absolute_value')) and isinstance(val, (int, float)): val = abs(self.get(fieldname)) - return format_value(val, df=df, doc=doc, currency=currency) + return format_value(val, df=df, doc=doc, currency=currency, format=format) def is_print_hide(self, fieldname, df=None, for_print=True): """Returns true if fieldname is to be hidden for print. @@ -969,7 +969,7 @@ class BaseDocument(object): return self.cast(val, df) def cast(self, value, df): - return cast_fieldtype(df.fieldtype, value) + return cast_fieldtype(df.fieldtype, value, show_warning=False) def _extract_images_from_text_editor(self): from frappe.core.doctype.file.file import extract_images_from_doc diff --git a/frappe/model/create_new.py b/frappe/model/create_new.py index fba6765479..fff2156a10 100644 --- a/frappe/model/create_new.py +++ b/frappe/model/create_new.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE """ Create a new document with defaults set diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index f007c4874b..3d024fa69f 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE """build query for doclistview and return results""" import frappe.defaults diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index fbbf1a4852..ac976e976c 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import os import shutil @@ -10,7 +10,7 @@ import frappe.model.meta from frappe import _ from frappe import get_module_path from frappe.model.dynamic_links import get_dynamic_link_map -from frappe.core.doctype.file.file import remove_all +from frappe.utils.file_manager import remove_all from frappe.utils.password import delete_all_passwords_for from frappe.model.naming import revert_series_if_last from frappe.utils.global_search import delete_for_document @@ -190,7 +190,7 @@ def delete_from_table(doctype, name, ignore_doctypes, doc): # delete from child tables for t in list(set(tables)): if t not in ignore_doctypes: - frappe.db.sql("delete from `tab%s` where parenttype=%s and parent = %s" % (t, '%s', '%s'), (doctype, name)) + frappe.db.delete(t, {"parenttype": doctype, "parent": name}) def update_flags(doc, flags=None, ignore_permissions=False): if ignore_permissions: @@ -323,9 +323,10 @@ def delete_dynamic_links(doctype, name): def delete_references(doctype, reference_doctype, reference_name, reference_doctype_field = 'reference_doctype', reference_name_field = 'reference_name'): - frappe.db.sql('''delete from `tab{0}` - where {1}=%s and {2}=%s'''.format(doctype, reference_doctype_field, reference_name_field), # nosec - (reference_doctype, reference_name)) + frappe.db.delete(doctype, { + reference_doctype_field: reference_doctype, + reference_name_field: reference_name + }) def clear_references(doctype, reference_doctype, reference_name, reference_doctype_field = 'reference_doctype', reference_name_field = 'reference_name'): diff --git a/frappe/model/docfield.py b/frappe/model/docfield.py index 6360c3866d..c173561b1e 100644 --- a/frappe/model/docfield.py +++ b/frappe/model/docfield.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE """docfield utililtes""" diff --git a/frappe/model/document.py b/frappe/model/document.py index ee12fd89e0..411d447d0f 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import time from frappe import _, msgprint, is_whitelisted @@ -385,8 +385,7 @@ class Document(BaseDocument): [self.name, self.doctype, fieldname] + rows) if len(deleted_rows) > 0: # delete rows that do not match the ones in the document - frappe.db.sql("""delete from `tab{0}` where name in ({1})""".format(df.options, - ','.join(['%s'] * len(deleted_rows))), tuple(row[0] for row in deleted_rows)) + frappe.db.delete(df.options, {"name": ("in", tuple(row[0] for row in deleted_rows))}) else: # no rows found, delete all rows diff --git a/frappe/model/dynamic_links.py b/frappe/model/dynamic_links.py index 676c86d7da..7311b39b30 100644 --- a/frappe/model/dynamic_links.py +++ b/frappe/model/dynamic_links.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/model/mapper.py b/frappe/model/mapper.py index fa8858d950..bde4fb6d73 100644 --- a/frappe/model/mapper.py +++ b/frappe/model/mapper.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import json import frappe diff --git a/frappe/model/meta.py b/frappe/model/meta.py index de794ba77f..207aca089b 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE # metadata @@ -16,7 +16,7 @@ Example: ''' from datetime import datetime import frappe, json, os -from frappe.utils import cstr, cint, cast_fieldtype +from frappe.utils import cstr, cint, cast from frappe.model import default_fields, no_value_fields, optional_fields, data_fieldtypes, table_fields from frappe.model.document import Document from frappe.model.base_document import BaseDocument @@ -322,24 +322,24 @@ class Meta(Document): for ps in property_setters: if ps.doctype_or_field=='DocType': - self.set(ps.property, cast_fieldtype(ps.property_type, ps.value)) + self.set(ps.property, cast(ps.property_type, ps.value)) elif ps.doctype_or_field=='DocField': for d in self.fields: if d.fieldname == ps.field_name: - d.set(ps.property, cast_fieldtype(ps.property_type, ps.value)) + d.set(ps.property, cast(ps.property_type, ps.value)) break elif ps.doctype_or_field=='DocType Link': for d in self.links: if d.name == ps.row_name: - d.set(ps.property, cast_fieldtype(ps.property_type, ps.value)) + d.set(ps.property, cast(ps.property_type, ps.value)) break elif ps.doctype_or_field=='DocType Action': for d in self.actions: if d.name == ps.row_name: - d.set(ps.property, cast_fieldtype(ps.property_type, ps.value)) + d.set(ps.property, cast(ps.property_type, ps.value)) break def add_custom_links_and_actions(self): @@ -532,7 +532,7 @@ class Meta(Document): label = link.group, items = [link.parent_doctype or link.link_doctype] )) - + if not link.is_child_table: if link.link_fieldname != data.fieldname: if data.fieldname: diff --git a/frappe/model/naming.py b/frappe/model/naming.py index 7705002706..71ff281642 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -10,7 +10,7 @@ the cancelled document naming pattern is changed to 'orig_name-CANC-X'. """ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe import _ @@ -346,7 +346,7 @@ def _prompt_autoname(autoname, doc): """ # set from __newname in save.py if not doc.name: - frappe.throw(_("Name not set via prompt")) + frappe.throw(_("Please set the document name")) def _format_autoname(autoname, doc): """ diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 9b8ac2574d..14f1dbf2b0 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe import _, bold from frappe.model.dynamic_links import get_dynamic_link_map diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 836f70dd55..138f9eaad4 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE """ Sync's doctype and docfields from txt files to database perms will get synced only if none exist @@ -80,9 +80,11 @@ def get_doc_files(files, start_path): # load in sequence - warning for devs document_types = ['doctype', 'page', 'report', 'dashboard_chart_source', 'print_format', - 'website_theme', 'web_form', 'web_template', 'notification', 'print_style', - 'data_migration_mapping', 'data_migration_plan', 'workspace', - 'onboarding_step', 'module_onboarding', 'form_tour'] + 'web_page', 'website_theme', 'web_form', 'web_template', + 'notification', 'print_style', + 'data_migration_mapping', 'data_migration_plan', + 'workspace', 'onboarding_step', 'module_onboarding', 'form_tour', + 'client_script', 'server_script', 'custom_field', 'property_setter'] for doctype in document_types: doctype_path = os.path.join(start_path, doctype) diff --git a/frappe/model/utils/__init__.py b/frappe/model/utils/__init__.py index 47615182e4..4cdca5e394 100644 --- a/frappe/model/utils/__init__.py +++ b/frappe/model/utils/__init__.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe import _ from frappe.utils import cstr diff --git a/frappe/model/utils/link_count.py b/frappe/model/utils/link_count.py index 7562aaae45..404b6ec855 100644 --- a/frappe/model/utils/link_count.py +++ b/frappe/model/utils/link_count.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/model/utils/rename_field.py b/frappe/model/utils/rename_field.py index 9fe9d64041..c9c454b7e8 100644 --- a/frappe/model/utils/rename_field.py +++ b/frappe/model/utils/rename_field.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import json from frappe.model import no_value_fields, table_fields diff --git a/frappe/model/workflow.py b/frappe/model/workflow.py index fa2f557370..e74d88c0f2 100644 --- a/frappe/model/workflow.py +++ b/frappe/model/workflow.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe.utils import cint diff --git a/frappe/modules.txt b/frappe/modules.txt index ae10c3ad55..1229116a2e 100644 --- a/frappe/modules.txt +++ b/frappe/modules.txt @@ -12,4 +12,4 @@ Data Migration Chat Social Automation -Event Streaming \ No newline at end of file +Event Streaming diff --git a/frappe/modules/export_file.py b/frappe/modules/export_file.py index ae9f11d53b..17e84ee488 100644 --- a/frappe/modules/export_file.py +++ b/frappe/modules/export_file.py @@ -1,12 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe, os import frappe.model from frappe.modules import scrub, get_module_path, scrub_dt_dn def export_doc(doc): - export_to_files([[doc.doctype, doc.name]]) + write_document_file(doc) def export_to_files(record_list=None, record_module=None, verbose=0, create_init=None): """ @@ -21,16 +21,10 @@ def export_to_files(record_list=None, record_module=None, verbose=0, create_init write_document_file(frappe.get_doc(record[0], record[1]), record_module, create_init=create_init, folder_name=folder_name) def write_document_file(doc, record_module=None, create_init=True, folder_name=None): - newdoc = doc.as_dict(no_nulls=True) - doc.run_method("before_export", newdoc) - - # strip out default fields from children - for df in doc.meta.get_table_fields(): - for d in newdoc.get(df.fieldname): - for fieldname in frappe.model.default_fields: - if fieldname in d: - del d[fieldname] + doc_export = doc.as_dict(no_nulls=True) + doc.run_method("before_export", doc_export) + strip_default_fields(doc, doc_export) module = record_module or get_module_name(doc) # create folder @@ -39,10 +33,33 @@ def write_document_file(doc, record_module=None, create_init=True, folder_name=N else: folder = create_folder(module, doc.doctype, doc.name, create_init) - # write the data file fname = scrub(doc.name) + write_code_files(folder, fname, doc, doc_export) + + # write the data file with open(os.path.join(folder, fname + ".json"), 'w+') as txtfile: - txtfile.write(frappe.as_json(newdoc)) + txtfile.write(frappe.as_json(doc_export)) + +def strip_default_fields(doc, doc_export): + # strip out default fields from children + for df in doc.meta.get_table_fields(): + for d in doc_export.get(df.fieldname): + for fieldname in frappe.model.default_fields: + if fieldname in d: + del d[fieldname] + +def write_code_files(folder, fname, doc, doc_export): + '''Export code files and strip from values''' + if hasattr(doc, 'get_code_fields'): + for key, extn in doc.get_code_fields().items(): + if doc.get(key): + with open(os.path.join(folder, fname + "." + extn), 'w+') as txtfile: + txtfile.write(doc.get(key)) + + # remove from exporting + del doc_export[key] + + def get_module_name(doc): if doc.doctype == 'Module Def': @@ -57,7 +74,10 @@ def get_module_name(doc): return module def create_folder(module, dt, dn, create_init): - module_path = get_module_path(module) + if frappe.db.get_value('Module Def', module, 'custom'): + module_path = get_custom_module_path(module) + else: + module_path = get_module_path(module) dt, dn = scrub_dt_dn(dt, dn) @@ -72,6 +92,23 @@ def create_folder(module, dt, dn, create_init): return folder +def get_custom_module_path(module): + package = frappe.db.get_value('Module Def', module, 'package') + if not package: + frappe.throw('Package must be set for custom Module {module}'.format(module=module)) + + path = os.path.join(get_package_path(package), scrub(module)) + if not os.path.exists(path): + os.makedirs(path) + + return path + +def get_package_path(package): + path = os.path.join(frappe.get_site_path('packages'), frappe.db.get_value('Package', package, 'package_name')) + if not os.path.exists(path): + os.makedirs(path) + return path + def create_init_py(module_path, dt, dn): def create_if_not_exists(path): initpy = os.path.join(path, '__init__.py') diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index e743f0c3da..e7a1f5f97c 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe, os, json from frappe.modules import get_module_path, scrub_dt_dn from frappe.utils import get_datetime_str @@ -54,31 +54,26 @@ def import_file_by_path(path, force=False, data_import=False, pre_process=None, docs = [docs] for doc in docs: - if not force: - # check if timestamps match - db_modified = frappe.db.get_value(doc['doctype'], doc['name'], 'modified') - if db_modified and doc.get('modified')==get_datetime_str(db_modified): - return False + if not force and not is_changed(doc): + return False original_modified = doc.get("modified") - frappe.flags.in_import = True import_doc(doc, force=force, data_import=data_import, pre_process=pre_process, - ignore_version=ignore_version, reset_permissions=reset_permissions) - frappe.flags.in_import = False + ignore_version=ignore_version, reset_permissions=reset_permissions, path=path) if original_modified: - # since there is a new timestamp on the file, update timestamp in - if doc["doctype"] == doc["name"] and doc["name"]!="DocType": - frappe.db.sql("""update tabSingles set value=%s where field="modified" and doctype=%s""", - (original_modified, doc["name"])) - else: - frappe.db.sql("update `tab%s` set modified=%s where name=%s" % \ - (doc['doctype'], '%s', '%s'), - (original_modified, doc['name'])) + update_modified(original_modified, doc) return True +def is_changed(doc): + # check if timestamps match + db_modified = frappe.db.get_value(doc['doctype'], doc['name'], 'modified') + if db_modified and doc.get('modified')==get_datetime_str(db_modified): + return False + return True + def read_doc_from_file(path): doc = None if os.path.exists(path): @@ -93,8 +88,17 @@ def read_doc_from_file(path): return doc +def update_modified(original_modified, doc): + # since there is a new timestamp on the file, update timestamp in + if doc["doctype"] == doc["name"] and doc["name"]!="DocType": + frappe.db.sql("""update tabSingles set value=%s where field="modified" and doctype=%s""", + (original_modified, doc["name"])) + else: + frappe.db.sql("update `tab%s` set modified=%s where name=%s" % (doc['doctype'], + '%s', '%s'), (original_modified, doc['name'])) + def import_doc(docdict, force=False, data_import=False, pre_process=None, - ignore_version=None, reset_permissions=False): + ignore_version=None, reset_permissions=False, path=None): frappe.flags.in_import = True docdict["__islocal"] = 1 @@ -104,14 +108,8 @@ def import_doc(docdict, force=False, data_import=False, pre_process=None, doc = frappe.get_doc(docdict) - # Note on Tree DocTypes: - # The tree structure is maintained in the database via the fields "lft" and - # "rgt". They are automatically set and kept up-to-date. Importing them - # would destroy any existing tree structure. - if getattr(doc.meta, 'is_tree', None) and any([doc.lft, doc.rgt]): - print('Ignoring values of `lft` and `rgt` for {} "{}"'.format(doc.doctype, doc.name)) - doc.lft = None - doc.rgt = None + reset_tree_properties(doc) + load_code_properties(doc, path) doc.run_method("before_import") @@ -119,27 +117,9 @@ def import_doc(docdict, force=False, data_import=False, pre_process=None, if pre_process: pre_process(doc) - ignore = [] - if frappe.db.exists(doc.doctype, doc.name): + delete_old_doc(doc, reset_permissions) - old_doc = frappe.get_doc(doc.doctype, doc.name) - - if doc.doctype in ignore_values: - # update ignore values - for key in ignore_values.get(doc.doctype) or []: - doc.set(key, old_doc.get(key)) - - # update ignored docs into new doc - for df in doc.meta.get_table_fields(): - if df.options in ignore_doctypes and not reset_permissions: - doc.set(df.fieldname, []) - ignore.append(df.options) - - # delete old - frappe.delete_doc(doc.doctype, doc.name, force=1, ignore_doctypes=ignore, for_reload=True) - - doc.flags.ignore_children_type = ignore doc.flags.ignore_links = True if not data_import: doc.flags.ignore_validate = True @@ -149,3 +129,47 @@ def import_doc(docdict, force=False, data_import=False, pre_process=None, doc.insert() frappe.flags.in_import = False + + return doc + +def load_code_properties(doc, path): + '''Load code files stored in separate files with extensions''' + if path: + if hasattr(doc, 'get_code_fields'): + dirname, filename = os.path.split(path) + for key, extn in doc.get_code_fields().items(): + codefile = os.path.join(dirname, filename.split('.')[0]+'.'+extn) + if os.path.exists(codefile): + with open(codefile,'r') as txtfile: + doc.set(key, txtfile.read()) + + +def delete_old_doc(doc, reset_permissions): + ignore = [] + old_doc = frappe.get_doc(doc.doctype, doc.name) + + if doc.doctype in ignore_values: + # update ignore values + for key in ignore_values.get(doc.doctype) or []: + doc.set(key, old_doc.get(key)) + + # update ignored docs into new doc + for df in doc.meta.get_table_fields(): + if df.options in ignore_doctypes and not reset_permissions: + doc.set(df.fieldname, []) + ignore.append(df.options) + + # delete old + frappe.delete_doc(doc.doctype, doc.name, force=1, ignore_doctypes=ignore, for_reload=True) + + doc.flags.ignore_children_type = ignore + +def reset_tree_properties(doc): + # Note on Tree DocTypes: + # The tree structure is maintained in the database via the fields "lft" and + # "rgt". They are automatically set and kept up-to-date. Importing them + # would destroy any existing tree structure. + if getattr(doc.meta, 'is_tree', None) and any([doc.lft, doc.rgt]): + print('Ignoring values of `lft` and `rgt` for {} "{}"'.format(doc.doctype, doc.name)) + doc.lft = None + doc.rgt = None diff --git a/frappe/modules/patch_handler.py b/frappe/modules/patch_handler.py index 029234d5d9..8dfb27c0b8 100644 --- a/frappe/modules/patch_handler.py +++ b/frappe/modules/patch_handler.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE """ Execute Patch Files diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py index 0f3e57a5a0..bbfd63a277 100644 --- a/frappe/modules/utils.py +++ b/frappe/modules/utils.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE """ Utilities for using modules """ @@ -114,8 +114,7 @@ def sync_customizations_for_doctype(data, folder): doc.db_insert() if custom_doctype != 'Custom Field': - frappe.db.sql('delete from `tab{0}` where `{1}` =%s'.format( - custom_doctype, doctype_fieldname), doc_type) + frappe.db.delete(custom_doctype, {doctype_fieldname: doc_type}) for d in data[key]: _insert(d) diff --git a/frappe/monitor.py b/frappe/monitor.py index 34ca7d67f7..6bad03dfe9 100644 --- a/frappe/monitor.py +++ b/frappe/monitor.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE from datetime import datetime import json diff --git a/frappe/patches.txt b/frappe/patches.txt index 989b13e049..41ca1a1724 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -182,3 +182,4 @@ frappe.patches.v13_0.jinja_hook frappe.patches.v13_0.update_notification_channel_if_empty frappe.patches.v14_0.drop_data_import_legacy frappe.patches.v14_0.rename_cancelled_documents +frappe.patches.v14_0.update_workspace2 # 25.08.2021 diff --git a/frappe/patches/v10_0/modify_smallest_currency_fraction.py b/frappe/patches/v10_0/modify_smallest_currency_fraction.py index c9ae477359..9469d546ce 100644 --- a/frappe/patches/v10_0/modify_smallest_currency_fraction.py +++ b/frappe/patches/v10_0/modify_smallest_currency_fraction.py @@ -1,5 +1,5 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v10_0/set_default_locking_time.py b/frappe/patches/v10_0/set_default_locking_time.py index 045fa0e3fa..11993e1163 100644 --- a/frappe/patches/v10_0/set_default_locking_time.py +++ b/frappe/patches/v10_0/set_default_locking_time.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v11_0/change_email_signature_fieldtype.py b/frappe/patches/v11_0/change_email_signature_fieldtype.py index ccfa8541c3..7c57aa044e 100644 --- a/frappe/patches/v11_0/change_email_signature_fieldtype.py +++ b/frappe/patches/v11_0/change_email_signature_fieldtype.py @@ -1,5 +1,5 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py b/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py index 5c54b1e5c1..ff5cf3fc5e 100644 --- a/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py +++ b/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v12_0/set_default_password_reset_limit.py b/frappe/patches/v12_0/set_default_password_reset_limit.py index 188f2383e7..e403b5251e 100644 --- a/frappe/patches/v12_0/set_default_password_reset_limit.py +++ b/frappe/patches/v12_0/set_default_password_reset_limit.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py b/frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py index 776e9c796e..2d9e232da5 100644 --- a/frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py +++ b/frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py @@ -1,5 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v13_0/delete_package_publish_tool.py b/frappe/patches/v13_0/delete_package_publish_tool.py index bf9aaf5a76..5c1678bdbe 100644 --- a/frappe/patches/v13_0/delete_package_publish_tool.py +++ b/frappe/patches/v13_0/delete_package_publish_tool.py @@ -1,5 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v13_0/enable_custom_script.py b/frappe/patches/v13_0/enable_custom_script.py index 0684074fe7..de027ab97a 100644 --- a/frappe/patches/v13_0/enable_custom_script.py +++ b/frappe/patches/v13_0/enable_custom_script.py @@ -1,5 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v13_0/generate_theme_files_in_public_folder.py b/frappe/patches/v13_0/generate_theme_files_in_public_folder.py index dd9fb1961a..6e8e0d7fc5 100644 --- a/frappe/patches/v13_0/generate_theme_files_in_public_folder.py +++ b/frappe/patches/v13_0/generate_theme_files_in_public_folder.py @@ -1,5 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v13_0/jinja_hook.py b/frappe/patches/v13_0/jinja_hook.py index 990ae50f35..e1c9175576 100644 --- a/frappe/patches/v13_0/jinja_hook.py +++ b/frappe/patches/v13_0/jinja_hook.py @@ -1,5 +1,5 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from click import secho diff --git a/frappe/patches/v13_0/queryreport_columns.py b/frappe/patches/v13_0/queryreport_columns.py index 5c381f4f3e..ed22ce4441 100644 --- a/frappe/patches/v13_0/queryreport_columns.py +++ b/frappe/patches/v13_0/queryreport_columns.py @@ -1,5 +1,5 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe import json diff --git a/frappe/patches/v13_0/remove_tailwind_from_page_builder.py b/frappe/patches/v13_0/remove_tailwind_from_page_builder.py index 2bf2c7bf87..b26d2bef4a 100644 --- a/frappe/patches/v13_0/remove_tailwind_from_page_builder.py +++ b/frappe/patches/v13_0/remove_tailwind_from_page_builder.py @@ -1,5 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v13_0/remove_twilio_settings.py b/frappe/patches/v13_0/remove_twilio_settings.py index 7efaf876e2..826edfb951 100644 --- a/frappe/patches/v13_0/remove_twilio_settings.py +++ b/frappe/patches/v13_0/remove_twilio_settings.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v13_0/rename_list_view_setting_to_list_view_settings.py b/frappe/patches/v13_0/rename_list_view_setting_to_list_view_settings.py index 3122de8bea..db3ab1b32a 100644 --- a/frappe/patches/v13_0/rename_list_view_setting_to_list_view_settings.py +++ b/frappe/patches/v13_0/rename_list_view_setting_to_list_view_settings.py @@ -1,5 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v13_0/rename_notification_fields.py b/frappe/patches/v13_0/rename_notification_fields.py index 1413d80358..2f314df9c1 100644 --- a/frappe/patches/v13_0/rename_notification_fields.py +++ b/frappe/patches/v13_0/rename_notification_fields.py @@ -1,5 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe.model.utils.rename_field import rename_field diff --git a/frappe/patches/v13_0/rename_onboarding.py b/frappe/patches/v13_0/rename_onboarding.py index 852065dfd2..cd910195ad 100644 --- a/frappe/patches/v13_0/rename_onboarding.py +++ b/frappe/patches/v13_0/rename_onboarding.py @@ -1,5 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v13_0/replace_old_data_import.py b/frappe/patches/v13_0/replace_old_data_import.py index 838881b48e..7d2692a433 100644 --- a/frappe/patches/v13_0/replace_old_data_import.py +++ b/frappe/patches/v13_0/replace_old_data_import.py @@ -1,5 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v13_0/update_duration_options.py b/frappe/patches/v13_0/update_duration_options.py index e0d8dea4ea..48f0dc0969 100644 --- a/frappe/patches/v13_0/update_duration_options.py +++ b/frappe/patches/v13_0/update_duration_options.py @@ -1,5 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v13_0/update_newsletter_content_type.py b/frappe/patches/v13_0/update_newsletter_content_type.py index 5f047680ee..39758c8257 100644 --- a/frappe/patches/v13_0/update_newsletter_content_type.py +++ b/frappe/patches/v13_0/update_newsletter_content_type.py @@ -1,5 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v13_0/update_notification_channel_if_empty.py b/frappe/patches/v13_0/update_notification_channel_if_empty.py index bcf9a7b28c..43cf813c74 100644 --- a/frappe/patches/v13_0/update_notification_channel_if_empty.py +++ b/frappe/patches/v13_0/update_notification_channel_if_empty.py @@ -1,5 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v13_0/web_template_set_module.py b/frappe/patches/v13_0/web_template_set_module.py index 2ee9e3ba2d..d200f4e0da 100644 --- a/frappe/patches/v13_0/web_template_set_module.py +++ b/frappe/patches/v13_0/web_template_set_module.py @@ -1,5 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe diff --git a/frappe/patches/v14_0/rename_cancelled_documents.py b/frappe/patches/v14_0/rename_cancelled_documents.py index fbe49c2351..4b565d4f76 100644 --- a/frappe/patches/v14_0/rename_cancelled_documents.py +++ b/frappe/patches/v14_0/rename_cancelled_documents.py @@ -129,9 +129,9 @@ def update_linked_doctypes(doctype, cancelled_doc_names): update `tab{linked_dt}` set - {column}=CONCAT({column}, '-CANC') + `{column}`=CONCAT(`{column}`, '-CANC') where - {column} in %(cancelled_doc_names)s; + `{column}` in %(cancelled_doc_names)s; """.format(linked_dt=linked_dt, column=field), {'cancelled_doc_names': cancelled_doc_names}) else: @@ -151,9 +151,9 @@ def update_dynamic_linked_doctypes(doctype, cancelled_doc_names): update `tab{linked_dt}` set - {column}=CONCAT({column}, '-CANC') + `{column}`=CONCAT(`{column}`, '-CANC') where - {column} in %(cancelled_doc_names)s and {doctype_fieldname}=%(dt)s; + `{column}` in %(cancelled_doc_names)s and {doctype_fieldname}=%(dt)s; """.format(linked_dt=linked_dt, column=fieldname, doctype_fieldname=doctype_fieldname), {'cancelled_doc_names': cancelled_doc_names, 'dt': doctype}) else: diff --git a/frappe/patches/v14_0/update_workspace2.py b/frappe/patches/v14_0/update_workspace2.py new file mode 100644 index 0000000000..c212faee76 --- /dev/null +++ b/frappe/patches/v14_0/update_workspace2.py @@ -0,0 +1,69 @@ +import frappe +import json +from frappe import _ + +def execute(): + frappe.reload_doc('desk', 'doctype', 'workspace', force=True) + order_by = "pin_to_top desc, pin_to_bottom asc, name asc" + for seq, wspace in enumerate(frappe.get_all('Workspace', order_by=order_by)): + doc = frappe.get_doc('Workspace', wspace.name) + content = create_content(doc) + update_wspace(doc, seq, content) + frappe.db.commit() + +def create_content(doc): + content = [] + if doc.onboarding: + content.append({"type":"onboarding","data":{"onboarding_name":doc.onboarding,"col":12}}) + if doc.charts: + invalid_links = [] + for c in doc.charts: + if c.get_invalid_links()[0]: + invalid_links.append(c) + else: + content.append({"type":"chart","data":{"chart_name":c.label,"col":12}}) + for l in invalid_links: + del doc.charts[doc.charts.index(l)] + if doc.shortcuts: + invalid_links = [] + if doc.charts: + content.append({"type":"spacer","data":{"col":12}}) + content.append({"type":"header","data":{"text":doc.shortcuts_label or _("Your Shortcuts"),"level":4,"col":12}}) + for s in doc.shortcuts: + if s.get_invalid_links()[0]: + invalid_links.append(s) + else: + content.append({"type":"shortcut","data":{"shortcut_name":s.label,"col":4}}) + for l in invalid_links: + del doc.shortcuts[doc.shortcuts.index(l)] + if doc.links: + invalid_links = [] + content.append({"type":"spacer","data":{"col":12}}) + content.append({"type":"header","data":{"text":doc.cards_label or _("Reports & Masters"),"level":4,"col":12}}) + for l in doc.links: + if l.type == 'Card Break': + content.append({"type":"card","data":{"card_name":l.label,"col":4}}) + if l.get_invalid_links()[0]: + invalid_links.append(l) + for l in invalid_links: + del doc.links[doc.links.index(l)] + return content + +def update_wspace(doc, seq, content): + if not doc.title and not doc.content and not doc.is_standard and not doc.public: + doc.sequence_id = seq + 1 + doc.content = json.dumps(content) + doc.public = 0 + doc.title = doc.extends or doc.label + doc.extends = '' + doc.category = '' + doc.onboarding = '' + doc.extends_another_page = 0 + doc.is_default = 0 + doc.is_standard = 0 + doc.developer_mode_only = 0 + doc.disable_user_customization = 0 + doc.pin_to_top = 0 + doc.pin_to_bottom = 0 + doc.hide_custom = 0 + doc.save(ignore_permissions=True) \ No newline at end of file diff --git a/frappe/permissions.py b/frappe/permissions.py index af7bc3b602..7ee1119ebb 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import copy import frappe diff --git a/frappe/printing/doctype/letter_head/letter_head.py b/frappe/printing/doctype/letter_head/letter_head.py index 948be60b88..eeaef28393 100644 --- a/frappe/printing/doctype/letter_head/letter_head.py +++ b/frappe/printing/doctype/letter_head/letter_head.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt +# License: MIT. See LICENSE import frappe from frappe.utils import is_image diff --git a/frappe/printing/doctype/letter_head/test_letter_head.py b/frappe/printing/doctype/letter_head/test_letter_head.py index 96dfc68705..67d307ee8b 100644 --- a/frappe/printing/doctype/letter_head/test_letter_head.py +++ b/frappe/printing/doctype/letter_head/test_letter_head.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/printing/doctype/print_format/print_format.py b/frappe/printing/doctype/print_format/print_format.py index 5d4ff92fe2..878a864b38 100644 --- a/frappe/printing/doctype/print_format/print_format.py +++ b/frappe/printing/doctype/print_format/print_format.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe import frappe.utils diff --git a/frappe/printing/doctype/print_format/test_print_format.py b/frappe/printing/doctype/print_format/test_print_format.py index e65eb0183f..564a2c750c 100644 --- a/frappe/printing/doctype/print_format/test_print_format.py +++ b/frappe/printing/doctype/print_format/test_print_format.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest import re diff --git a/frappe/printing/doctype/print_heading/print_heading.py b/frappe/printing/doctype/print_heading/print_heading.py index f9955c019d..39c46ad152 100644 --- a/frappe/printing/doctype/print_heading/print_heading.py +++ b/frappe/printing/doctype/print_heading/print_heading.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/printing/doctype/print_heading/test_print_heading.py b/frappe/printing/doctype/print_heading/test_print_heading.py index ce99cde607..7eaa1bc6ba 100644 --- a/frappe/printing/doctype/print_heading/test_print_heading.py +++ b/frappe/printing/doctype/print_heading/test_print_heading.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/printing/doctype/print_settings/print_settings.py b/frappe/printing/doctype/print_settings/print_settings.py index 610c083097..a7e59c9078 100644 --- a/frappe/printing/doctype/print_settings/print_settings.py +++ b/frappe/printing/doctype/print_settings/print_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe import _ diff --git a/frappe/printing/doctype/print_settings/test_print_settings.py b/frappe/printing/doctype/print_settings/test_print_settings.py index d1dec861b2..82883eaee5 100644 --- a/frappe/printing/doctype/print_settings/test_print_settings.py +++ b/frappe/printing/doctype/print_settings/test_print_settings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import unittest class TestPrintSettings(unittest.TestCase): diff --git a/frappe/printing/doctype/print_style/print_style.py b/frappe/printing/doctype/print_style/print_style.py index a91786795c..7985c006f4 100644 --- a/frappe/printing/doctype/print_style/print_style.py +++ b/frappe/printing/doctype/print_style/print_style.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors -# For license information, please see license.txt +# License: MIT. See LICENSE import frappe from frappe.model.document import Document diff --git a/frappe/printing/doctype/print_style/test_print_style.py b/frappe/printing/doctype/print_style/test_print_style.py index b717b23df8..cbf5c465d1 100644 --- a/frappe/printing/doctype/print_style/test_print_style.py +++ b/frappe/printing/doctype/print_style/test_print_style.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors -# See license.txt +# License: MIT. See LICENSE import frappe import unittest diff --git a/frappe/public/icons/timeless/symbol-defs.svg b/frappe/public/icons/timeless/symbol-defs.svg index 0e8e24b768..b2f1428967 100644 --- a/frappe/public/icons/timeless/symbol-defs.svg +++ b/frappe/public/icons/timeless/symbol-defs.svg @@ -57,6 +57,9 @@ + + + @@ -564,7 +567,7 @@ - diff --git a/frappe/public/js/desk.bundle.js b/frappe/public/js/desk.bundle.js index c4ecf67c4f..294ac013fb 100644 --- a/frappe/public/js/desk.bundle.js +++ b/frappe/public/js/desk.bundle.js @@ -82,6 +82,7 @@ import "./frappe/ui/toolbar/toolbar.js"; import "./frappe/views/communication.js"; import "./frappe/views/translation_manager.js"; import "./frappe/views/workspace/workspace.js"; +import "./frappe/views/workspace/blocks/index.js"; import "./frappe/widgets/widget_group.js"; diff --git a/frappe/public/js/frappe/data_import/data_exporter.js b/frappe/public/js/frappe/data_import/data_exporter.js index 03e6288856..8fa5a08945 100644 --- a/frappe/public/js/frappe/data_import/data_exporter.js +++ b/frappe/public/js/frappe/data_import/data_exporter.js @@ -13,6 +13,13 @@ frappe.data_import.DataExporter = class DataExporter { this.dialog = new frappe.ui.Dialog({ title: __('Export Data'), fields: [ + { + fieldtype: 'Select', + fieldname: 'file_type', + label: __('File Type'), + options: ['Excel', 'CSV'], + default: 'CSV' + }, { fieldtype: 'Select', fieldname: 'export_records', @@ -45,13 +52,6 @@ frappe.data_import.DataExporter = class DataExporter { fieldname: 'filter_area', depends_on: doc => doc.export_records === 'by_filter' }, - { - fieldtype: 'Select', - fieldname: 'file_type', - label: __('File Type'), - options: ['Excel', 'CSV'], - default: 'CSV' - }, { fieldtype: 'Section Break' }, @@ -141,7 +141,7 @@ frappe.data_import.DataExporter = class DataExporter { let for_insert = this.exporting_for === 'Insert New Records'; let section_title = for_insert ? __('Select Fields To Insert') : __('Select Fields To Update'); let $select_all_buttons = $(` -
+
${section_title}
`).click(() => { - this.expanded = !this.expanded; - this.refresh_height(); - this.toggle_label(); - }).appendTo(this.$input_wrapper); + // styling this.ace_editor_target.addClass('border rounded'); this.ace_editor_target.css('height', 300); + if (this.df.max_height) { + this.ace_editor_target.css('max-height', this.df.max_height); + } + // initialize const ace = window.ace; this.editor = ace.edit(this.ace_editor_target.get(0)); + + if (this.df.max_lines || this.df.min_lines || this.df.max_height) { + if (this.df.max_lines) + this.editor.setOption("maxLines", this.df.max_lines); + if (this.df.min_lines) + this.editor.setOption("minLines", this.df.min_lines); + } else { + this.expanded = false; + this.$expand_button = $(``).click(() => { + this.expanded = !this.expanded; + this.refresh_height(); + this.toggle_label(); + }).appendTo(this.$input_wrapper); + } + this.editor.setTheme('ace/theme/tomorrow'); this.editor.setOption("showPrintMargin", false); this.editor.setOption("wrap", this.df.wrap); diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index e4a7dd6d59..864a0562ef 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -163,7 +163,7 @@ frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlInp } }; this.$input.on("change", change_handler); - if (this.constructor.trigger_change_on_input_event) { + if (this.constructor.trigger_change_on_input_event && !this.in_grid()) { // debounce to avoid repeated validations on value change this.$input.on("input", frappe.utils.debounce(change_handler, 500)); } @@ -267,4 +267,7 @@ frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlInp let el = this.$input.parents(el_class)[0]; if (el) $(el).toggleClass(scroll_class, add); } + in_grid() { + return this.grid || this.layout && this.layout.grid; + } }; diff --git a/frappe/public/js/frappe/form/controls/datetime.js b/frappe/public/js/frappe/form/controls/datetime.js index 341a933066..f7a2798a99 100644 --- a/frappe/public/js/frappe/form/controls/datetime.js +++ b/frappe/public/js/frappe/form/controls/datetime.js @@ -36,4 +36,9 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co $tp.$secondsText.prev().css('display', 'none'); } } + + get_model_value() { + let value = super.get_model_value(); + return frappe.datetime.get_datetime_as_string(value); + } }; diff --git a/frappe/public/js/frappe/form/controls/text.js b/frappe/public/js/frappe/form/controls/text.js index 3156df6d1c..ccf9a85feb 100644 --- a/frappe/public/js/frappe/form/controls/text.js +++ b/frappe/public/js/frappe/form/controls/text.js @@ -8,6 +8,9 @@ frappe.ui.form.ControlText = class ControlText extends frappe.ui.form.ControlDat make_input() { super.make_input(); this.$input.css({'height': '300px'}); + if (this.df.max_height) { + this.$input.css({'max-height': this.df.max_height}); + } } }; diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js index 420704149b..da06f7228d 100644 --- a/frappe/public/js/frappe/form/dashboard.js +++ b/frappe/public/js/frappe/form/dashboard.js @@ -283,21 +283,13 @@ frappe.ui.form.Dashboard = class FormDashboard { $(frappe.render_template('form_links', this.data)) .appendTo(transactions_area_body); - if (this.data.reports && this.data.reports.length) { - $(frappe.render_template('report_links', this.data)) - .appendTo(transactions_area_body); - } + this.render_report_links(); // bind links transactions_area_body.find(".badge-link").on('click', function() { me.open_document_list($(this).closest('.document-link')); }); - // bind reports - transactions_area_body.find(".report-link").on('click', function() { - me.open_report($(this).parent()); - }); - // bind open notifications transactions_area_body.find('.open-notification').on('click', function() { me.open_document_list($(this).parent(), true); @@ -311,6 +303,18 @@ frappe.ui.form.Dashboard = class FormDashboard { this.data_rendered = true; } + render_report_links() { + let parent = this.transactions_area; + if (this.data.reports && this.data.reports.length) { + $(frappe.render_template('report_links', this.data)) + .appendTo(parent); + // bind reports + parent.find(".report-link").on('click', (e) => { + this.open_report($(e.target).parent()); + }); + } + } + open_report($link) { let report = $link.attr('data-report'); @@ -318,6 +322,7 @@ frappe.ui.form.Dashboard = class FormDashboard { ? (this.data.non_standard_fieldnames[report] || this.data.fieldname) : this.data.fieldname; + frappe.provide('frappe.route_options'); frappe.route_options[fieldname] = this.frm.doc.name; frappe.set_route("query-report", report); } diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index fd49df027c..3588923527 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -339,7 +339,7 @@ frappe.ui.form.Form = class FrappeForm { } } if (action.action_type==='Server Action') { - frappe.xcall(action.action, {'doc': this.doc}).then((doc) => { + return frappe.xcall(action.action, {'doc': this.doc}).then((doc) => { if (doc.doctype) { // document is returned by the method, // apply the changes locally and refresh @@ -354,7 +354,7 @@ frappe.ui.form.Form = class FrappeForm { }); }); } else if (action.action_type==='Route') { - frappe.set_route(action.action); + return frappe.set_route(action.action); } } diff --git a/frappe/public/js/frappe/form/form_tour.js b/frappe/public/js/frappe/form/form_tour.js index 17547b243d..0694aa634a 100644 --- a/frappe/public/js/frappe/form/form_tour.js +++ b/frappe/public/js/frappe/form/form_tour.js @@ -82,10 +82,16 @@ frappe.ui.form.FormTour = class FormTour { get_step(step_info, on_next) { const { name, fieldname, title, description, position, is_table_field } = step_info; + let element = `.frappe-control[data-fieldname='${fieldname}']`; + const field = this.frm.get_field(fieldname); - let element = field ? field.wrapper : `.frappe-control[data-fieldname='${fieldname}']`; + if (field) { + // wrapper for section breaks returns in a list + element = field.wrapper[0] ? field.wrapper[0] : field.wrapper; + } if (is_table_field) { + // TODO: fix wrapper for grid sections element = `.grid-row-open .frappe-control[data-fieldname='${fieldname}']`; } diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index cda052553f..dd1d622bab 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -38,7 +38,7 @@ export default class Grid { this.is_grid = true; this.debounced_refresh = this.refresh.bind(this); - this.debounced_refresh = frappe.utils.debounce(this.debounced_refresh, 500); + this.debounced_refresh = frappe.utils.debounce(this.debounced_refresh, 100); } allow_on_grid_editing() { @@ -502,7 +502,7 @@ export default class Grid { this.set_editable_grid_column_disp(fieldname, show); } - this.refresh(true); + this.debounced_refresh(); } set_editable_grid_column_disp(fieldname, show) { @@ -546,17 +546,17 @@ export default class Grid { toggle_reqd(fieldname, reqd) { this.get_docfield(fieldname).reqd = reqd; - this.refresh(); + this.debounced_refresh(); } toggle_enable(fieldname, enable) { this.get_docfield(fieldname).read_only = enable ? 0 : 1; - this.refresh(); + this.debounced_refresh(); } toggle_display(fieldname, show) { this.get_docfield(fieldname).hidden = show ? 0 : 1; - this.refresh(); + this.debounced_refresh(); } toggle_checkboxes(enable) { @@ -786,6 +786,7 @@ export default class Grid { doctype: link_field.options, fieldname: link, qty_fieldname: qty, + get_query: link_field.get_query, target: this, txt: "" }); diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index 88e0463fa5..8d52c8d592 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -252,14 +252,18 @@ frappe.ui.form.Layout = class Layout { } if (document.activeElement) { - document.activeElement.focus(); - - if (document.activeElement.tagName == 'INPUT') { + if (document.activeElement.tagName == 'INPUT' && this.is_numeric_field_active()) { document.activeElement.select(); } } } + is_numeric_field_active() { + const control = $(document.activeElement).closest(".frappe-control"); + const fieldtype = (control.data() || {}).fieldtype; + return frappe.model.numeric_fieldtypes.includes(fieldtype); + } + refresh_sections() { // hide invisible sections this.wrapper.find(".form-section:not(.hide-control)").each(function() { diff --git a/frappe/public/js/frappe/form/link_selector.js b/frappe/public/js/frappe/form/link_selector.js index b8f1b3a842..3ae03dbe91 100644 --- a/frappe/public/js/frappe/form/link_selector.js +++ b/frappe/public/js/frappe/form/link_selector.js @@ -96,10 +96,10 @@ frappe.ui.form.LinkSelector = class LinkSelector { .attr('data-value', v[0]) .click(function () { var value = $(this).attr("data-value"); - var $link = this; if (me.target.is_grid) { // set in grid - me.set_in_grid(value); + // call search after value is set to get latest filtered results + me.set_in_grid(value).then(() => me.search()); } else { if (me.target.doctype) me.target.parse_validate_and_set_in_model(value); @@ -110,8 +110,8 @@ frappe.ui.form.LinkSelector = class LinkSelector { me.dialog.hide(); } return false; - }) - }) + }); + }); } else { $('


' + __("No Results") + '' + (frappe.model.can_create(me.doctype) ? @@ -130,49 +130,56 @@ frappe.ui.form.LinkSelector = class LinkSelector { }, this.dialog.get_primary_btn()); } - set_in_grid (value) { - var me = this, updated = false; - var d = null; - if (this.qty_fieldname) { - frappe.prompt({ - fieldname: "qty", fieldtype: "Float", label: "Qty", - "default": 1, reqd: 1 - }, function (data) { - $.each(me.target.frm.doc[me.target.df.fieldname] || [], function (i, d) { - if (d[me.fieldname] === value) { - frappe.model.set_value(d.doctype, d.name, me.qty_fieldname, data.qty); - frappe.show_alert(__("Added {0} ({1})", [value, d[me.qty_fieldname]])); - updated = true; - return false; + set_in_grid(value) { + return new Promise((resolve) => { + if (this.qty_fieldname) { + frappe.prompt({ + fieldname: "qty", + fieldtype: "Float", + label: "Qty", + default: 1, + reqd: 1 + }, (data) => { + let updated = (this.target.frm.doc[this.target.df.fieldname] || []).some(d => { + if (d[this.fieldname] === value) { + frappe.model.set_value(d.doctype, d.name, this.qty_fieldname, data.qty).then(() => { + frappe.show_alert(__("Added {0} ({1})", [value, d[this.qty_fieldname]])); + resolve(); + }); + return true; + } + }); + if (!updated) { + let d = null; + frappe.run_serially([ + () => d = this.target.add_new_row(), + () => frappe.timeout(0.1), + () => { + let args = {}; + args[this.fieldname] = value; + args[this.qty_fieldname] = data.qty; + return frappe.model.set_value(d.doctype, d.name, args); + }, + () => frappe.show_alert(__("Added {0} ({1})", [value, data.qty])), + () => resolve() + ]); } + }, __("Set Quantity"), __("Set")); + } else if (this.dynamic_link_field) { + let d = this.target.add_new_row(); + frappe.model.set_value(d.doctype, d.name, this.dynamic_link_field, this.dynamic_link_reference); + frappe.model.set_value(d.doctype, d.name, this.fieldname, value).then(() => { + frappe.show_alert(__("{0} {1} added", [this.dynamic_link_reference, value])); + resolve(); }); - if (!updated) { - frappe.run_serially([ - () => { - d = me.target.add_new_row(); - }, - () => frappe.timeout(0.1), - () => { - let args = {}; - args[me.fieldname] = value; - args[me.qty_fieldname] = data.qty; - - return frappe.model.set_value(d.doctype, d.name, args); - }, - () => frappe.show_alert(__("Added {0} ({1})", [value, data.qty])) - ]); - } - }, __("Set Quantity"), __("Set")); - } else if (me.dynamic_link_field) { - var d = me.target.add_new_row(); - frappe.model.set_value(d.doctype, d.name, me.dynamic_link_field, me.dynamic_link_reference); - frappe.model.set_value(d.doctype, d.name, me.fieldname, value); - frappe.show_alert(__("{0} {1} added", [me.dynamic_link_reference, value])); - } else { - var d = me.target.add_new_row(); - frappe.model.set_value(d.doctype, d.name, me.fieldname, value); - frappe.show_alert(__("{0} added", [value])); - } + } else { + let d = this.target.add_new_row(); + frappe.model.set_value(d.doctype, d.name, this.fieldname, value).then(() => { + frappe.show_alert(__("{0} added", [value])); + resolve(); + }); + } + }); } }; diff --git a/frappe/public/js/frappe/list/bulk_operations.js b/frappe/public/js/frappe/list/bulk_operations.js index 369c8e7a5a..3b99560411 100644 --- a/frappe/public/js/frappe/list/bulk_operations.js +++ b/frappe/public/js/frappe/list/bulk_operations.js @@ -261,4 +261,14 @@ export default class BulkOperations { }); dialog.show(); } + + export(doctype, docnames) { + frappe.require('data_import_tools.bundle.js', () => { + const data_exporter = new frappe.data_import.DataExporter(doctype, 'Insert New Records'); + data_exporter.dialog.set_value('export_records', 'by_filter'); + data_exporter.filter_group.add_filters_to_filter_group( + [[doctype, "name", "in", docnames, false]] + ); + }); + } } diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 8a0e43c8f3..c5db7df88c 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -367,6 +367,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { if ( !this.settings.hide_name_column && + this.meta.title_field && this.meta.title_field !== 'name' ) { this.columns.push({ @@ -867,8 +868,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { filters: this.get_filters_for_args() }).then(total_count => { this.total_count = total_count || current_count; + this.count_without_children = count_without_children !== current_count ? count_without_children : undefined; let str = __('{0} of {1}', [current_count, this.total_count]); - if (count_without_children !== current_count) { + if (this.count_without_children) { str = __('{0} of {1} ({2} rows with children)', [count_without_children, this.total_count, current_count]); } return str; @@ -950,9 +952,16 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { get_indicator_html(doc) { const indicator = frappe.get_indicator(doc, this.doctype); + // sequence is important + const docstatus_description = [ + __('Document is in draft state'), + __('Document has been submitted'), + __('Document has been cancelled') + ]; + const title = docstatus_description[doc.docstatus || 0]; if (indicator) { return ` + data-filter='${indicator[2]}' title='${title}'> ${__(indicator[0])} `; } @@ -1730,11 +1739,25 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { }; }; + const bulk_export = () => { + return { + label: __("Export"), + action: () => { + const docnames = this.get_checked_items(true); + + bulk_operations.export(doctype, docnames); + }, + standard: true + }; + }; + // bulk edit if (has_editable_fields(doctype)) { actions_menu_items.push(bulk_edit()); } + actions_menu_items.push(bulk_export()); + // bulk assignment actions_menu_items.push(bulk_assignment()); diff --git a/frappe/public/js/frappe/recorder/RecorderDetail.vue b/frappe/public/js/frappe/recorder/RecorderDetail.vue index d17a8f0ec4..0b6354db7e 100644 --- a/frappe/public/js/frappe/recorder/RecorderDetail.vue +++ b/frappe/public/js/frappe/recorder/RecorderDetail.vue @@ -1,5 +1,5 @@