瀏覽代碼

Merge branch 'develop' into compare-columns-db_query

version-14
Gavin D'souza 3 年之前
父節點
當前提交
9f4f096bf1
共有 100 個文件被更改,包括 698 次插入275 次删除
  1. +1
    -1
      .editorconfig
  2. +3
    -0
      .git-blame-ignore-revs
  3. +3
    -3
      .github/helper/documentation.py
  4. +0
    -5
      .github/helper/install_dependencies.sh
  5. +56
    -40
      .github/helper/roulette.py
  6. +22
    -0
      .github/workflows/patch-mariadb-tests.yml
  7. +30
    -31
      .github/workflows/server-mariadb-tests.yml
  8. +33
    -1
      .github/workflows/server-postgres-tests.yml
  9. +24
    -0
      .github/workflows/ui-tests.yml
  10. +16
    -0
      .mergify.yml
  11. +4
    -1
      CODEOWNERS
  12. +1
    -1
      LICENSE
  13. +2
    -2
      README.md
  14. +9
    -0
      codecov.yml
  15. +7
    -7
      cypress/integration/awesome_bar.js
  16. +3
    -3
      cypress/integration/control_barcode.js
  17. +5
    -5
      cypress/integration/control_icon.js
  18. +2
    -2
      cypress/integration/control_link.js
  19. +2
    -0
      cypress/integration/control_select.js
  20. +63
    -0
      cypress/integration/dashboard_links.js
  21. +19
    -0
      cypress/integration/datetime_field_form_validation.js
  22. +3
    -3
      cypress/integration/depends_on.js
  23. +15
    -14
      cypress/integration/file_uploader.js
  24. +79
    -0
      cypress/integration/folder_navigation.js
  25. +1
    -1
      cypress/integration/form.js
  26. +12
    -12
      cypress/integration/form_tour.js
  27. +2
    -2
      cypress/integration/grid_pagination.js
  28. +6
    -6
      cypress/integration/list_view_settings.js
  29. +6
    -6
      cypress/integration/login.js
  30. +8
    -8
      cypress/integration/recorder.js
  31. +1
    -1
      cypress/integration/report_view.js
  32. +20
    -20
      cypress/integration/timeline.js
  33. +9
    -10
      cypress/integration/timeline_email.js
  34. +90
    -0
      cypress/integration/workspace.js
  35. +1
    -0
      cypress/support/commands.js
  36. +5
    -1
      frappe/__init__.py
  37. +1
    -1
      frappe/api.py
  38. +1
    -1
      frappe/app.py
  39. +1
    -1
      frappe/automation/doctype/assignment_rule/assignment_rule.py
  40. +5
    -5
      frappe/automation/doctype/assignment_rule/test_assignment_rule.py
  41. +1
    -1
      frappe/automation/doctype/assignment_rule_day/assignment_rule_day.py
  42. +1
    -1
      frappe/automation/doctype/assignment_rule_user/assignment_rule_user.py
  43. +1
    -1
      frappe/automation/doctype/auto_repeat/auto_repeat.js
  44. +1
    -1
      frappe/automation/doctype/auto_repeat/auto_repeat.py
  45. +1
    -1
      frappe/automation/doctype/auto_repeat/test_auto_repeat.py
  46. +1
    -1
      frappe/automation/doctype/auto_repeat_day/auto_repeat_day.py
  47. +1
    -1
      frappe/automation/doctype/milestone/milestone.py
  48. +1
    -1
      frappe/automation/doctype/milestone/test_milestone.py
  49. +1
    -1
      frappe/automation/doctype/milestone_tracker/milestone_tracker.py
  50. +3
    -3
      frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py
  51. +34
    -4
      frappe/automation/workspace/tools/tools.json
  52. +3
    -3
      frappe/boot.py
  53. +1
    -1
      frappe/build.py
  54. +1
    -1
      frappe/cache_manager.py
  55. +1
    -1
      frappe/chat/doctype/chat_token/chat_token.py
  56. +1
    -1
      frappe/client.py
  57. +1
    -1
      frappe/commands/__init__.py
  58. +25
    -10
      frappe/commands/utils.py
  59. +2
    -2
      frappe/contacts/address_and_contact.py
  60. +1
    -1
      frappe/contacts/doctype/address/address.py
  61. +1
    -1
      frappe/contacts/doctype/address/test_address.py
  62. +1
    -1
      frappe/contacts/doctype/address_template/address_template.py
  63. +1
    -1
      frappe/contacts/doctype/address_template/test_address_template.py
  64. +2
    -2
      frappe/contacts/doctype/contact/contact.py
  65. +1
    -1
      frappe/contacts/doctype/contact/test_contact.py
  66. +1
    -1
      frappe/contacts/doctype/contact_email/contact_email.py
  67. +1
    -1
      frappe/contacts/doctype/contact_phone/contact_phone.py
  68. +1
    -1
      frappe/contacts/doctype/gender/gender.py
  69. +1
    -1
      frappe/contacts/doctype/gender/test_gender.py
  70. +1
    -1
      frappe/contacts/doctype/salutation/salutation.py
  71. +1
    -1
      frappe/contacts/doctype/salutation/test_salutation.py
  72. +1
    -1
      frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py
  73. +1
    -1
      frappe/core/__init__.py
  74. +1
    -1
      frappe/core/doctype/__init__.py
  75. +3
    -2
      frappe/core/doctype/access_log/access_log.py
  76. +1
    -1
      frappe/core/doctype/access_log/test_access_log.py
  77. +1
    -1
      frappe/core/doctype/activity_log/activity_log.py
  78. +1
    -1
      frappe/core/doctype/activity_log/feed.py
  79. +1
    -1
      frappe/core/doctype/activity_log/test_activity_log.py
  80. +1
    -1
      frappe/core/doctype/block_module/block_module.py
  81. +1
    -1
      frappe/core/doctype/comment/comment.py
  82. +3
    -3
      frappe/core/doctype/comment/test_comment.py
  83. +1
    -1
      frappe/core/doctype/communication/__init__.py
  84. +1
    -1
      frappe/core/doctype/communication/communication.py
  85. +1
    -1
      frappe/core/doctype/communication/email.py
  86. +1
    -1
      frappe/core/doctype/communication/test_communication.py
  87. +1
    -1
      frappe/core/doctype/communication_link/communication_link.py
  88. +1
    -1
      frappe/core/doctype/custom_docperm/custom_docperm.py
  89. +1
    -1
      frappe/core/doctype/custom_docperm/test_custom_docperm.py
  90. +1
    -1
      frappe/core/doctype/custom_role/custom_role.py
  91. +1
    -1
      frappe/core/doctype/custom_role/test_custom_role.py
  92. +1
    -1
      frappe/core/doctype/data_export/data_export.py
  93. +1
    -1
      frappe/core/doctype/data_export/exporter.py
  94. +1
    -1
      frappe/core/doctype/data_import/data_import.py
  95. +1
    -1
      frappe/core/doctype/data_import/exporter.py
  96. +1
    -1
      frappe/core/doctype/data_import/importer.py
  97. +1
    -1
      frappe/core/doctype/data_import/test_data_import.py
  98. +1
    -1
      frappe/core/doctype/data_import/test_exporter.py
  99. +1
    -1
      frappe/core/doctype/data_import/test_importer.py
  100. +1
    -1
      frappe/core/doctype/defaultvalue/__init__.py

+ 1
- 1
.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

+ 3
- 0
.git-blame-ignore-revs 查看文件

@@ -10,3 +10,6 @@

# Replace use of Class.extend with native JS class
fe20515c23a3ac41f1092bf0eaf0a0a452ec2e85

# Updating license headers
34460265554242a8d05fb09f049033b1117e1a2b

+ 3
- 3
.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):


+ 0
- 5
.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


+ 56
- 40
.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"')

+ 22
- 0
.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


+ 30
- 31
.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

+ 33
- 1
.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

+ 24
- 0
.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

+ 16
- 0
.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


+ 4
- 1
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

+ 1
- 1
LICENSE 查看文件

@@ -1,6 +1,6 @@
The MIT License

Copyright (c) 2016-2018 Frappe Technologies Pvt. Ltd. <developers@frappe.io>
Copyright (c) 2016-2021 Frappe Technologies Pvt. Ltd. <developers@frappe.io>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal


+ 2
- 2
README.md 查看文件

@@ -26,8 +26,8 @@
<a href='https://www.codetriage.com/frappe/frappe'>
<img src='https://www.codetriage.com/frappe/frappe/badges/users.svg'>
</a>
<a href='https://coveralls.io/github/frappe/frappe?branch=develop'>
<img src='https://coveralls.io/repos/github/frappe/frappe/badge.svg?branch=develop'>
<a href="https://codecov.io/gh/frappe/frappe">
<img src="https://codecov.io/gh/frappe/frappe/branch/develop/graph/badge.svg?token=XoTa679hIj"/>
</a>
</div>



+ 9
- 0
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

+ 7
- 7
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');


+ 3
- 3
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"]')


+ 5
- 5
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');
});


+ 2
- 2
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]}`);


+ 2
- 0
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');




+ 63
- 0
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");
});
});
});

+ 19
- 0
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');
});
});
});

+ 3
- 3
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');


+ 15
- 14
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');
});


+ 79
- 0
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();
});
});

+ 1
- 1
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');


+ 12
- 12
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');

});
});

+ 2
- 2
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');


+ 6
- 6
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();
});
});

+ 6
- 6
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'));


+ 8
- 8
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');


+ 1
- 1
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 => {


+ 20
- 20
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');
});
});

+ 9
- 10
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();
});
});

+ 90
- 0
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');
});

});

+ 1
- 0
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


+ 5
- 1
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):


+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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


+ 5
- 5
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')


+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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 = `<a href="/app/customize form">${__('Customize Form')}</a>`;
let customize_form_link = `<a href="/app/customize-form">${__('Customize Form')}</a>`;
frm.dashboard.set_headline(__('To configure Auto Repeat, enable "Allow Auto Repeat" from {0}.', [customize_form_link]));
}



+ 1
- 1
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 _


+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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



+ 1
- 1
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


+ 3
- 3
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()

+ 34
- 4
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"
}

+ 3
- 3
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")



+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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


+ 25
- 10
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)


+ 2
- 2
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



+ 1
- 1
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



+ 1
- 1
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



+ 1
- 1
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


+ 1
- 1
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):


+ 2
- 2
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 _


+ 1
- 1
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



+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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



+ 1
- 1
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):


+ 1
- 1
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



+ 1
- 1
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):


+ 1
- 1
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 _



+ 1
- 1
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

+ 1
- 1
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


+ 3
- 2
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()

+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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


+ 3
- 3
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)


+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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



+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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



+ 1
- 1
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


+ 1
- 1
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



+ 1
- 1
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



+ 1
- 1
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 _


+ 1
- 1
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



+ 1
- 1
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



+ 1
- 1
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


+ 1
- 1
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



+ 1
- 1
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


+ 1
- 1
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


+ 1
- 1
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


部分文件因文件數量過多而無法顯示

Loading…
取消
儲存