@@ -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 |
@@ -10,3 +10,6 @@ | |||
# Replace use of Class.extend with native JS class | |||
fe20515c23a3ac41f1092bf0eaf0a0a452ec2e85 | |||
# Updating license headers | |||
34460265554242a8d05fb09f049033b1117e1a2b |
@@ -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): | |||
@@ -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 | |||
@@ -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"') |
@@ -98,8 +98,6 @@ rules: | |||
languages: [python] | |||
severity: WARNING | |||
paths: | |||
exclude: | |||
- test_*.py | |||
include: | |||
- "*/**/doctype/*" | |||
@@ -8,10 +8,6 @@ rules: | |||
dynamic content. Avoid it or use safe_eval(). | |||
languages: [python] | |||
severity: ERROR | |||
paths: | |||
exclude: | |||
- frappe/__init__.py | |||
- frappe/commands/utils.py | |||
- id: frappe-sqli-format-strings | |||
patterns: | |||
@@ -11,3 +11,20 @@ allowRevertCommits: true | |||
# For allowed PR types: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json | |||
# Tool Reference: https://github.com/zeke/semantic-pull-requests | |||
# By default types specified in commitizen/conventional-commit-types is used. | |||
# See: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json | |||
# You can override the valid types | |||
types: | |||
- BREAKING CHANGE | |||
- feat | |||
- fix | |||
- docs | |||
- style | |||
- refactor | |||
- perf | |||
- test | |||
- build | |||
- ci | |||
- chore | |||
- revert |
@@ -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 | |||
@@ -1,34 +1,18 @@ | |||
name: Semgrep | |||
on: | |||
pull_request: | |||
branches: | |||
- develop | |||
- version-13-hotfix | |||
- version-13-pre-release | |||
pull_request: { } | |||
jobs: | |||
semgrep: | |||
name: Frappe Linter | |||
runs-on: ubuntu-latest | |||
steps: | |||
- uses: actions/checkout@v2 | |||
- name: Setup python3 | |||
uses: actions/setup-python@v2 | |||
with: | |||
python-version: 3.8 | |||
- name: Setup semgrep | |||
run: | | |||
python -m pip install -q semgrep | |||
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q | |||
- name: Semgrep errors | |||
run: | | |||
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) | |||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files | |||
semgrep --config="r/python.lang.correctness" --quiet --error $files | |||
- name: Semgrep warnings | |||
run: | | |||
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) | |||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files | |||
- uses: actions/checkout@v2 | |||
- uses: returntocorp/semgrep-action@v1 | |||
env: | |||
SEMGREP_TIMEOUT: 120 | |||
with: | |||
config: >- | |||
r/python.lang.correctness | |||
.github/helper/semgrep_rules |
@@ -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,24 @@ 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 | |||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||
uses: codecov/codecov-action@v2 | |||
with: | |||
name: MariaDB | |||
fail_ci_if_error: true | |||
files: /home/runner/frappe-bench/sites/coverage.xml | |||
verbose: true |
@@ -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,24 @@ 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 | |||
if: ${{ steps.check-build.outputs.build == 'strawberry' }} | |||
uses: codecov/codecov-action@v2 | |||
with: | |||
name: Postgres | |||
fail_ci_if_error: true | |||
files: /home/runner/frappe-bench/sites/coverage.xml | |||
verbose: true |
@@ -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,13 +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 |
@@ -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,16 +4,16 @@ | |||
# the repo. Unless a later match takes precedence, | |||
* @frappe/frappe-review-team | |||
website/ @prssanna | |||
web_form/ @prssanna | |||
templates/ @surajshetty3416 | |||
www/ @surajshetty3416 | |||
integrations/ @leela | |||
patches/ @surajshetty3416 | |||
dashboard/ @prssanna | |||
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,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 | |||
@@ -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> | |||
@@ -0,0 +1,13 @@ | |||
codecov: | |||
require_ci_to_pass: yes | |||
coverage: | |||
status: | |||
project: | |||
default: | |||
target: auto | |||
threshold: 0.5% | |||
comment: | |||
layout: "diff" | |||
require_changes: true |
@@ -31,8 +31,13 @@ context('API Resources', () => { | |||
}); | |||
it('Removes the Comments', () => { | |||
cy.get_list('Comment').then(body => body.data.forEach(comment => { | |||
cy.remove_doc('Comment', comment.name); | |||
})); | |||
cy.get_list('Comment').then(body => { | |||
let comment_names = []; | |||
body.data.map(comment => comment_names.push(comment.name)); | |||
comment_names = [...new Set(comment_names)]; // remove duplicates | |||
comment_names.forEach((comment_name) => { | |||
cy.remove_doc('Comment', comment_name); | |||
}); | |||
}); | |||
}); | |||
}); |
@@ -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'); | |||
@@ -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"]') | |||
@@ -0,0 +1,50 @@ | |||
context('Control Icon', () => { | |||
before(() => { | |||
cy.login(); | |||
cy.visit('/app/website'); | |||
}); | |||
function get_dialog_with_icon() { | |||
return cy.dialog({ | |||
title: 'Icon', | |||
fields: [{ | |||
label: 'Icon', | |||
fieldname: 'icon', | |||
fieldtype: 'Icon' | |||
}] | |||
}); | |||
} | |||
it('should set icon', () => { | |||
get_dialog_with_icon().as('dialog'); | |||
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]').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]').findByRole('textbox').should('have.value', 'resting'); | |||
cy.get('@dialog').then(dialog => { | |||
let value = dialog.get_value('icon'); | |||
expect(value).to.equal('resting'); | |||
}); | |||
}); | |||
it('search for icon and clear search input', () => { | |||
let search_text = 'ed'; | |||
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').findByRole('searchbox').clear().blur(); | |||
cy.get('.icon-section .icon-wrapper').should('not.have.class', 'hidden'); | |||
}); | |||
}); |
@@ -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]}`); | |||
@@ -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'); | |||
@@ -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"); | |||
}); | |||
}); | |||
}); |
@@ -0,0 +1,19 @@ | |||
// TODO: Enable this again | |||
// currently this is flaky possibly because of different timezone in CI | |||
// context('Datetime Field Validation', () => { | |||
// before(() => { | |||
// cy.login(); | |||
// cy.visit('/app/communication'); | |||
// }); | |||
// it('datetime field form validation', () => { | |||
// // validating datetime field value when value is set from backend and get validated on form load. | |||
// cy.window().its('frappe').then(frappe => { | |||
// return frappe.xcall("frappe.tests.ui_test_helpers.create_communication_record"); | |||
// }).then(doc => { | |||
// cy.visit(`/app/communication/${doc.name}`); | |||
// cy.get('.indicator-pill').should('contain', 'Open').should('have.class', 'red'); | |||
// }); | |||
// }); | |||
// }); |
@@ -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'); | |||
@@ -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,12 +46,33 @@ 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'); | |||
}); | |||
it('should allow cropping and optimization for valid images', () => { | |||
open_upload_dialog(); | |||
cy.get_open_dialog().find('.file-upload-area').attachFile('sample_image.jpg', { | |||
subjectType: 'drag-n-drop', | |||
}); | |||
cy.get_open_dialog().findAllByText('sample_image.jpg').should('exist'); | |||
cy.get_open_dialog().find('.btn-crop').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().findByRole('button', {name: 'Upload'}).click(); | |||
cy.wait('@upload_file').its('response.statusCode').should('eq', 200); | |||
cy.get('.modal:visible').should('not.exist'); | |||
}); | |||
}); |
@@ -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(); | |||
}); | |||
}); |
@@ -18,6 +18,7 @@ context('Form', () => { | |||
cy.get('.primary-action').click(); | |||
cy.wait('@form_save').its('response.statusCode').should('eq', 200); | |||
cy.visit('/app/todo'); | |||
cy.wait(300); | |||
cy.get('.title-text').should('be.visible').and('contain', 'To Do'); | |||
cy.get('.list-row').should('contain', 'this is a test todo'); | |||
}); | |||
@@ -25,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'); | |||
@@ -0,0 +1,88 @@ | |||
context('Form Tour', () => { | |||
before(() => { | |||
cy.login(); | |||
cy.visit('/app/form-tour'); | |||
return cy.window().its('frappe').then(frappe => { | |||
return frappe.call("frappe.tests.ui_test_helpers.create_form_tour"); | |||
}); | |||
}); | |||
const open_test_form_tour = () => { | |||
cy.visit('/app/form-tour/Test Form 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'); | |||
}; | |||
it('jump to a form tour', open_test_form_tour); | |||
it('navigates a form tour', () => { | |||
open_test_form_tour(); | |||
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('.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(); | |||
cy.wait(500); | |||
cy.get('@first_name').should('have.class', 'driver-highlighted-element'); | |||
// after filling the field, next step should be highlighted | |||
cy.fill_field('first_name', 'Test Name', 'Data'); | |||
cy.wait(500); | |||
cy.get('@next_btn').click(); | |||
cy.wait(500); | |||
// 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); | |||
cy.get('@next_btn').click(); | |||
cy.wait(500); | |||
// 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'); | |||
// add a row & move to next step | |||
cy.wait(500); | |||
cy.get('@add_row').click(); | |||
cy.wait(500); | |||
// assert table field is highlighted | |||
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 | |||
let field = cy.fill_table_field('phone_nos', '1', 'phone', '1234567890'); | |||
field.blur(); | |||
// move to collapse row step | |||
cy.wait(500); | |||
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.wait(500); | |||
cy.get('.frappe-driver').findByRole('button', {name: 'Save'}).should('be.visible'); | |||
}); | |||
}); |
@@ -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'); | |||
@@ -7,11 +7,11 @@ context('List View', () => { | |||
}); | |||
}); | |||
it('enables "Actions" button', () => { | |||
const actions = ['Approve', 'Reject', 'Edit', 'Assign To', 'Apply Assignment Rule', 'Add Tags', 'Print', 'Delete']; | |||
const actions = ['Approve', 'Reject', 'Edit', 'Export', 'Assign To', 'Apply Assignment Rule', 'Add Tags', 'Print', 'Delete']; | |||
cy.go_to_list('ToDo'); | |||
cy.get('.list-row-container:contains("Pending") .list-row-checkbox').click({ multiple: true, force: true }); | |||
cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click(); | |||
cy.get('.dropdown-menu li:visible .dropdown-item').should('have.length', 8).each((el, index) => { | |||
cy.get('.dropdown-menu li:visible .dropdown-item').should('have.length', 9).each((el, index) => { | |||
cy.wrap(el).contains(actions[index]); | |||
}).then((elements) => { | |||
cy.intercept({ | |||
@@ -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(); | |||
}); | |||
}); |
@@ -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')); | |||
@@ -0,0 +1,14 @@ | |||
context('Navigation', () => { | |||
before(() => { | |||
cy.login(); | |||
cy.visit('/app/website'); | |||
}); | |||
it('Navigate to route with hash in document name', () => { | |||
cy.insert_doc('ToDo', {'__newname': 'ABC#123', 'description': 'Test this', 'ignore_duplicate': true}); | |||
cy.visit('/app/todo/ABC#123'); | |||
cy.title().should('eq', 'Test this - ABC#123'); | |||
cy.get_field('description', 'Text Editor').contains('Test this'); | |||
cy.go('back'); | |||
cy.title().should('eq', 'Website'); | |||
}); | |||
}); |
@@ -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'); | |||
@@ -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 => { | |||
@@ -0,0 +1,56 @@ | |||
context('Sidebar', () => { | |||
before(() => { | |||
cy.visit('/login'); | |||
cy.login(); | |||
cy.visit('/app/doctype'); | |||
}); | |||
it('Test for checking "Assigned To" counter value, adding filter and adding & removing an assignment', () => { | |||
cy.click_sidebar_button("Assigned To"); | |||
//To check if no filter is available in "Assigned To" dropdown | |||
cy.get('.empty-state').should('contain', 'No filters found'); | |||
cy.click_sidebar_button("Created By"); | |||
//To check if "Created By" dropdown contains filter | |||
cy.get('.group-by-item > .dropdown-item').should('contain', 'Me'); | |||
//Assigning a doctype to a user | |||
cy.click_listview_row_item(0); | |||
cy.get('.form-assignments > .flex > .text-muted').click(); | |||
cy.get_field('assign_to_me', 'Check').click(); | |||
cy.get('.modal-footer > .standard-actions > .btn-primary').click(); | |||
cy.visit('/app/doctype'); | |||
cy.click_sidebar_button("Assigned To"); | |||
//To check if filter is added in "Assigned To" dropdown after assignment | |||
cy.get('.group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item').should('contain', '1'); | |||
//To check if there is no filter added to the listview | |||
cy.get('.filter-selector > .btn').should('contain', 'Filter'); | |||
//To add a filter to display data into the listview | |||
cy.get('.group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item').click(); | |||
//To check if filter is applied | |||
cy.click_filter_button().should('contain', '1 filter'); | |||
cy.get('.fieldname-select-area > .awesomplete > .form-control').should('have.value', 'Assigned To'); | |||
cy.get('.condition').should('have.value', 'like'); | |||
cy.get('.filter-field > .form-group > .input-with-feedback').should('have.value', '%Administrator%'); | |||
cy.click_filter_button(); | |||
//To remove the applied filter | |||
cy.clear_filters(); | |||
//To remove the assignment | |||
cy.visit('/app/doctype'); | |||
cy.click_listview_row_item(0); | |||
cy.get('.assignments > .avatar-group > .avatar > .avatar-frame').click(); | |||
cy.get('.remove-btn').click({force: true}); | |||
cy.hide_dialog(); | |||
cy.visit('/app/doctype'); | |||
cy.click_sidebar_button("Assigned To"); | |||
cy.get('.empty-state').should('contain', 'No filters found'); | |||
}); | |||
}); |
@@ -9,6 +9,7 @@ context('Table MultiSelect', () => { | |||
cy.new_form('Assignment Rule'); | |||
cy.fill_field('__newname', name); | |||
cy.fill_field('document_type', 'Blog Post'); | |||
cy.get('.section-head').contains('Assignment Rules').scrollIntoView(); | |||
cy.fill_field('assign_condition', 'status=="Open"', 'Code'); | |||
cy.get('input[data-fieldname="users"]').focus().as('input'); | |||
cy.get('input[data-fieldname="users"] + ul').should('be.visible'); | |||
@@ -0,0 +1,94 @@ | |||
import custom_submittable_doctype from '../fixtures/custom_submittable_doctype'; | |||
context('Timeline', () => { | |||
before(() => { | |||
cy.visit('/login'); | |||
cy.login(); | |||
}); | |||
it('Adding new ToDo, adding new comment, verifying comment addition & deletion and deleting ToDo', () => { | |||
//Adding new ToDo | |||
cy.visit('/app/todo'); | |||
cy.click_listview_primary_button('Add ToDo'); | |||
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.findByRole('button', {name: 'Save'}).click(); | |||
cy.wait(700); | |||
cy.visit('/app/todo'); | |||
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('[data-fieldname="comment"] .ql-editor').should('contain', '').type('Testing Timeline'); | |||
//Adding new comment | |||
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("Edit"); | |||
cy.get('.timeline-content [data-fieldname="comment"] .ql-editor').first().type(' 123'); | |||
cy.click_timeline_action_btn("Save"); | |||
//To check if the edited comment text is visible in timeline content | |||
cy.get('.timeline-content').should('contain', 'Testing Timeline 123'); | |||
//Discarding comment | |||
cy.click_timeline_action_btn("Edit"); | |||
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.findByRole('button', {name: 'Yes'}).click(); | |||
cy.click_modal_primary_button('Yes'); | |||
//Deleting the added ToDo | |||
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', () => { | |||
cy.visit('/app/doctype'); | |||
//Creating custom doctype | |||
cy.insert_doc('DocType', custom_submittable_doctype, true); | |||
cy.visit('/app/custom-submittable-doctype'); | |||
cy.click_listview_primary_button('Add Custom Submittable DocType'); | |||
//Adding a new entry for the created custom doctype | |||
cy.fill_field('title', 'Test'); | |||
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.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.findByRole('button', {name: 'Actions'}).click(); | |||
cy.get('.actions-btn-group > .dropdown-menu > li > .dropdown-item').contains("Delete").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.findByRole('button', {name: 'Actions'}).click(); | |||
cy.get('.actions-btn-group [data-label="Delete"]').click(); | |||
cy.click_modal_primary_button('Yes'); | |||
}); | |||
}); |
@@ -0,0 +1,70 @@ | |||
context('Timeline Email', () => { | |||
before(() => { | |||
cy.visit('/login'); | |||
cy.login(); | |||
cy.visit('/app/todo'); | |||
}); | |||
it('Adding new ToDo, adding email and verifying timeline content for email attachment, deleting attachment and ToDo', () => { | |||
//Adding new ToDo | |||
cy.click_listview_primary_button('Add ToDo'); | |||
cy.get('.custom-actions:visible > .btn').contains("Edit in full page").click({delay: 500}); | |||
cy.fill_field("description", "Test ToDo", "Text Editor"); | |||
cy.wait(500); | |||
cy.get('.primary-action').contains('Save').click({force: true}); | |||
cy.wait(700); | |||
cy.visit('/app/todo'); | |||
cy.get('.list-row > .level-left > .list-subject').eq(0).click(); | |||
//Creating a new email | |||
cy.get('.timeline-actions > .btn').click(); | |||
cy.fill_field('recipients', 'test@example.com', 'MultiSelect'); | |||
cy.get('.modal.show > .modal-dialog > .modal-content > .modal-body > :nth-child(1) > .form-layout > .form-page > :nth-child(3) > .section-body > .form-column > form > [data-fieldtype="Text Editor"] > .form-group > .control-input-wrapper > .control-input > .ql-container > .ql-editor').type('Test Mail'); | |||
//Adding attachment to the email | |||
cy.get('.add-more-attachments > .btn').click(); | |||
cy.get('.mt-2 > .btn > .mt-1').eq(2).click(); | |||
cy.get('.input-group > .form-control').type('https://wallpaperplay.com/walls/full/8/2/b/72402.jpg'); | |||
cy.get('.btn-primary').contains('Upload').click(); | |||
//Sending the email | |||
cy.click_modal_primary_button('Send', {delay: 500}); | |||
//To check if the sent mail content is shown in the timeline content | |||
cy.get('[data-doctype="Communication"] > .timeline-content').should('contain', 'Test Mail'); | |||
//To check if the attachment of email is shown in the timeline content | |||
cy.get('.timeline-content').should('contain', 'Added 72402.jpg'); | |||
//Deleting the sent email | |||
cy.get('[title="Open Communication"] > .icon').first().click({force: true}); | |||
cy.get('#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .btn').click(); | |||
cy.get('#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .dropdown-menu > li > .grey-link').eq(9).click(); | |||
cy.get('.modal.show > .modal-dialog > .modal-content > .modal-footer > .standard-actions > .btn-primary').click(); | |||
cy.visit('/app/todo'); | |||
cy.get('.list-row > .level-left > .list-subject > .level-item.ellipsis > .ellipsis').eq(0).click(); | |||
//Removing the added attachment | |||
cy.get('.attachment-row > .data-pill > .remove-btn > .icon').click(); | |||
cy.get('.modal-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'); | |||
cy.wait(500); | |||
//To check if the discard button functionality in email is working correctly | |||
cy.get('.timeline-actions > .btn').click(); | |||
cy.fill_field('recipients', 'test@example.com', 'MultiSelect'); | |||
cy.get('.modal-footer > .standard-actions > .btn-secondary').contains('Discard').click(); | |||
cy.wait(500); | |||
cy.get('.timeline-actions > .btn').click(); | |||
cy.wait(500); | |||
cy.get_field('recipients', 'MultiSelect').should('have.text', ''); | |||
cy.get('.modal-header:visible > .modal-actions > .btn-modal-close > .icon').click(); | |||
//Deleting the added ToDo | |||
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(); | |||
}); | |||
}); |
@@ -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,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 | |||
@@ -186,22 +187,22 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype = 'Data') => { | |||
if (fieldtype === 'Select') { | |||
cy.get('@input').select(value); | |||
} else { | |||
cy.get('@input').type(value, {waitForAnimations: false, force: true}); | |||
cy.get('@input').type(value, {waitForAnimations: false, force: true, delay: 100}); | |||
} | |||
return cy.get('@input'); | |||
}); | |||
Cypress.Commands.add('get_field', (fieldname, fieldtype = 'Data') => { | |||
let selector = `.form-control[data-fieldname="${fieldname}"]`; | |||
let selector = `[data-fieldname="${fieldname}"] input:visible`; | |||
if (fieldtype === 'Text Editor') { | |||
selector = `[data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]`; | |||
selector = `[data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]:visible`; | |||
} | |||
if (fieldtype === 'Code') { | |||
selector = `[data-fieldname="${fieldname}"] .ace_text-input`; | |||
} | |||
return cy.get(selector); | |||
return cy.get(selector).first(); | |||
}); | |||
Cypress.Commands.add('fill_table_field', (tablefieldname, row_idx, fieldname, value, fieldtype = 'Data') => { | |||
@@ -251,7 +252,8 @@ Cypress.Commands.add('new_form', doctype => { | |||
}); | |||
Cypress.Commands.add('go_to_list', doctype => { | |||
cy.visit(`/app/list/${doctype}/list`); | |||
let dt_in_route = doctype.toLowerCase().replace(/ /g, '-'); | |||
cy.visit(`/app/${dt_in_route}`); | |||
}); | |||
Cypress.Commands.add('clear_cache', () => { | |||
@@ -315,7 +317,11 @@ Cypress.Commands.add('add_filter', () => { | |||
}); | |||
Cypress.Commands.add('clear_filters', () => { | |||
cy.get('.filter-section .filter-button').click(); | |||
cy.intercept({ | |||
method: 'POST', | |||
url: 'api/method/frappe.model.utils.user_settings.save' | |||
}).as('filter-saved'); | |||
cy.get('.filter-section .filter-button').click({force: true}); | |||
cy.wait(300); | |||
cy.get('.filter-popover').should('exist'); | |||
cy.get('.filter-popover').find('.clear-filters').click(); | |||
@@ -323,4 +329,29 @@ Cypress.Commands.add('clear_filters', () => { | |||
cy.window().its('cur_list').then(cur_list => { | |||
cur_list && cur_list.filter_area && cur_list.filter_area.clear(); | |||
}); | |||
cy.wait('@filter-saved'); | |||
}); | |||
Cypress.Commands.add('click_modal_primary_button', (btn_name) => { | |||
cy.get('.modal-footer > .standard-actions > .btn-primary').contains(btn_name).trigger('click', {force: true}); | |||
}); | |||
Cypress.Commands.add('click_sidebar_button', (btn_name) => { | |||
cy.get('.list-group-by-fields .list-link > a').contains(btn_name).click({force: true}); | |||
}); | |||
Cypress.Commands.add('click_listview_row_item', (row_no) => { | |||
cy.get('.list-row > .level-left > .list-subject > .bold > .ellipsis').eq(row_no).click({force: true}); | |||
}); | |||
Cypress.Commands.add('click_filter_button', () => { | |||
cy.get('.filter-selector > .btn').click(); | |||
}); | |||
Cypress.Commands.add('click_listview_primary_button', (btn_name) => { | |||
cy.get('.primary-action').contains(btn_name).click({force: true}); | |||
}); | |||
Cypress.Commands.add('click_timeline_action_btn', (btn_name) => { | |||
cy.get('.timeline-content > .timeline-message-box > .justify-between > .actions > .btn').contains(btn_name).click(); | |||
}); |
@@ -8,6 +8,7 @@ let yargs = require("yargs"); | |||
let cliui = require("cliui")(); | |||
let chalk = require("chalk"); | |||
let html_plugin = require("./frappe-html"); | |||
let rtlcss = require('rtlcss'); | |||
let postCssPlugin = require("esbuild-plugin-postcss2").default; | |||
let ignore_assets = require("./ignore-assets"); | |||
let sass_options = require("./sass_options"); | |||
@@ -96,9 +97,9 @@ async function execute() { | |||
await clean_dist_folders(APPS); | |||
} | |||
let result; | |||
let results; | |||
try { | |||
result = await build_assets_for_apps(APPS, FILES_TO_BUILD); | |||
results = await build_assets_for_apps(APPS, FILES_TO_BUILD); | |||
} catch (e) { | |||
log_error("There were some problems during build"); | |||
log(); | |||
@@ -107,13 +108,15 @@ async function execute() { | |||
} | |||
if (!WATCH_MODE) { | |||
log_built_assets(result.metafile); | |||
log_built_assets(results); | |||
console.timeEnd(TOTAL_BUILD_TIME); | |||
log(); | |||
} else { | |||
log("Watching for changes..."); | |||
} | |||
return await write_assets_json(result.metafile); | |||
for (const result of results) { | |||
await write_assets_json(result.metafile); | |||
} | |||
} | |||
function build_assets_for_apps(apps, files) { | |||
@@ -125,6 +128,8 @@ function build_assets_for_apps(apps, files) { | |||
let output_path = assets_path; | |||
let file_map = {}; | |||
let style_file_map = {}; | |||
let rtl_style_file_map = {}; | |||
for (let file of files) { | |||
let relative_app_path = path.relative(apps_path, file); | |||
let app = relative_app_path.split(path.sep)[0]; | |||
@@ -140,19 +145,32 @@ function build_assets_for_apps(apps, files) { | |||
} | |||
output_name = path.join(app, "dist", output_name); | |||
if (Object.keys(file_map).includes(output_name)) { | |||
if (Object.keys(file_map).includes(output_name) || Object.keys(style_file_map).includes(output_name)) { | |||
log_warn( | |||
`Duplicate output file ${output_name} generated from ${file}` | |||
); | |||
} | |||
file_map[output_name] = file; | |||
if ([".css", ".scss", ".less", ".sass", ".styl"].includes(extension)) { | |||
style_file_map[output_name] = file; | |||
rtl_style_file_map[output_name.replace('/css/', '/css-rtl/')] = file; | |||
} else { | |||
file_map[output_name] = file; | |||
} | |||
} | |||
return build_files({ | |||
let build = build_files({ | |||
files: file_map, | |||
outdir: output_path | |||
}); | |||
let style_build = build_style_files({ | |||
files: style_file_map, | |||
outdir: output_path | |||
}); | |||
let rtl_style_build = build_style_files({ | |||
files: rtl_style_file_map, | |||
outdir: output_path, | |||
rtl_style: true | |||
}); | |||
return Promise.all([build, style_build, rtl_style_build]); | |||
}); | |||
} | |||
@@ -203,7 +221,33 @@ function get_files_to_build(files) { | |||
} | |||
function build_files({ files, outdir }) { | |||
return esbuild.build({ | |||
let build_plugins = [ | |||
html_plugin, | |||
vue(), | |||
]; | |||
return esbuild.build(get_build_options(files, outdir, build_plugins)); | |||
} | |||
function build_style_files({ files, outdir, rtl_style=false }) { | |||
let plugins = []; | |||
if (rtl_style) { | |||
plugins.push(rtlcss); | |||
} | |||
let build_plugins = [ | |||
ignore_assets, | |||
postCssPlugin({ | |||
plugins: plugins, | |||
sassOptions: sass_options | |||
}) | |||
]; | |||
plugins.push(require("autoprefixer")); | |||
return esbuild.build(get_build_options(files, outdir, build_plugins)); | |||
} | |||
function get_build_options(files, outdir, plugins) { | |||
return { | |||
entryPoints: files, | |||
entryNames: "[dir]/[name].[hash]", | |||
outdir, | |||
@@ -217,17 +261,9 @@ function build_files({ files, outdir }) { | |||
PRODUCTION ? "production" : "development" | |||
) | |||
}, | |||
plugins: [ | |||
html_plugin, | |||
ignore_assets, | |||
vue(), | |||
postCssPlugin({ | |||
plugins: [require("autoprefixer")], | |||
sassOptions: sass_options | |||
}) | |||
], | |||
plugins: plugins, | |||
watch: get_watch_config() | |||
}); | |||
}; | |||
} | |||
function get_watch_config() { | |||
@@ -258,16 +294,26 @@ function get_watch_config() { | |||
async function clean_dist_folders(apps) { | |||
for (let app of apps) { | |||
let public_path = get_public_path(app); | |||
await fs.promises.rmdir(path.resolve(public_path, "dist", "js"), { | |||
recursive: true | |||
}); | |||
await fs.promises.rmdir(path.resolve(public_path, "dist", "css"), { | |||
recursive: true | |||
}); | |||
let paths = [ | |||
path.resolve(public_path, "dist", "js"), | |||
path.resolve(public_path, "dist", "css"), | |||
path.resolve(public_path, "dist", "css-rtl") | |||
]; | |||
for (let target of paths) { | |||
if (fs.existsSync(target)) { | |||
// rmdir is deprecated in node 16, this will work in both node 14 and 16 | |||
let rmdir = fs.promises.rm || fs.promises.rmdir; | |||
await rmdir(target, { recursive: true }); | |||
} | |||
} | |||
} | |||
} | |||
function log_built_assets(metafile) { | |||
function log_built_assets(results) { | |||
let outputs = {}; | |||
for (const result of results) { | |||
outputs = Object.assign(outputs, result.metafile.outputs); | |||
} | |||
let column_widths = [60, 20]; | |||
cliui.div( | |||
{ | |||
@@ -282,9 +328,9 @@ function log_built_assets(metafile) { | |||
cliui.div(""); | |||
let output_by_dist_path = {}; | |||
for (let outfile in metafile.outputs) { | |||
for (let outfile in outputs) { | |||
if (outfile.endsWith(".map")) continue; | |||
let data = metafile.outputs[outfile]; | |||
let data = outputs[outfile]; | |||
outfile = path.resolve(outfile); | |||
outfile = path.relative(assets_path, outfile); | |||
let filename = path.basename(outfile); | |||
@@ -339,7 +385,11 @@ async function write_assets_json(metafile) { | |||
let info = metafile.outputs[output]; | |||
let asset_path = "/" + path.relative(sites_path, output); | |||
if (info.entryPoint) { | |||
out[path.basename(info.entryPoint)] = asset_path; | |||
let key = path.basename(info.entryPoint); | |||
if (key.endsWith('.css') && asset_path.includes('/css-rtl/')) { | |||
key = `rtl_${key}`; | |||
} | |||
out[key] = asset_path; | |||
} | |||
} | |||
@@ -478,4 +528,4 @@ function log_rebuilt_assets(prev_assets, new_assets) { | |||
log(" " + filename); | |||
} | |||
log(); | |||
} | |||
} |
@@ -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 | |||
@@ -28,6 +28,8 @@ from .exceptions import * | |||
from .utils.jinja import (get_jenv, get_template, render_template, get_email_from_template, get_jloader) | |||
from .utils.lazy_loader import lazy_import | |||
from frappe.query_builder import get_query_builder, patch_query_execute | |||
# Lazy imports | |||
faker = lazy_import('faker') | |||
@@ -118,6 +120,7 @@ def set_user_lang(user, user_language=None): | |||
# local-globals | |||
db = local("db") | |||
qb = local("qb") | |||
conf = local("conf") | |||
form = form_dict = local("form_dict") | |||
request = local("request") | |||
@@ -137,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): | |||
@@ -202,8 +209,10 @@ def init(site, sites_path=None, new_site=False): | |||
local.form_dict = _dict() | |||
local.session = _dict() | |||
local.dev_server = _dev_server | |||
local.qb = get_query_builder(local.conf.db_type or "mariadb") | |||
setup_module_map() | |||
patch_query_execute() | |||
local.initialised = True | |||
@@ -1491,7 +1500,7 @@ def get_print(doctype=None, name=None, print_format=None, style=None, | |||
:param style: Print Format style. | |||
:param as_pdf: Return as PDF. Default False. | |||
:param password: Password to encrypt the pdf with. Default None""" | |||
from frappe.website.render import build_page | |||
from frappe.website.serve import get_response_content | |||
from frappe.utils.pdf import get_pdf | |||
local.form_dict.doctype = doctype | |||
@@ -1506,7 +1515,7 @@ def get_print(doctype=None, name=None, print_format=None, style=None, | |||
options = {'password': password} | |||
if not html: | |||
html = build_page("printview") | |||
html = get_response_content("printview") | |||
if as_pdf: | |||
return get_pdf(html, output = output, options = options) | |||
@@ -1683,7 +1692,7 @@ def get_desk_link(doctype, name): | |||
) | |||
def bold(text): | |||
return '<b>{0}</b>'.format(text) | |||
return '<strong>{0}</strong>'.format(text) | |||
def safe_eval(code, eval_globals=None, eval_locals=None): | |||
'''A safer `eval`''' | |||
@@ -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 | |||
@@ -82,7 +82,7 @@ def handle(): | |||
if frappe.local.request.method=="PUT": | |||
data = get_request_form_data() | |||
doc = frappe.get_doc(doctype, name) | |||
doc = frappe.get_doc(doctype, name, for_update=True) | |||
if "flags" in data: | |||
del data["flags"] | |||
@@ -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 | |||
@@ -16,9 +16,9 @@ import frappe.handler | |||
import frappe.auth | |||
import frappe.api | |||
import frappe.utils.response | |||
import frappe.website.render | |||
from frappe.utils import get_site_name, sanitize_html | |||
from frappe.middlewares import StaticDataMiddleware | |||
from frappe.website.serve import get_response | |||
from frappe.utils.error import make_error_snapshot | |||
from frappe.core.doctype.comment.comment import update_comments_in_parent_after_request | |||
from frappe import _ | |||
@@ -72,7 +72,7 @@ def application(request): | |||
response = frappe.utils.response.download_private_file(request.path) | |||
elif request.method in ('GET', 'HEAD', 'POST'): | |||
response = frappe.website.render.render() | |||
response = get_response() | |||
else: | |||
raise NotFound | |||
@@ -266,8 +266,7 @@ def handle_exception(e): | |||
make_error_snapshot(e) | |||
if return_as_message: | |||
response = frappe.website.render.render("message", | |||
http_status_code=http_status_code) | |||
response = get_response("message", http_status_code=http_status_code) | |||
return response | |||
@@ -1,71 +1,82 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
import datetime | |||
from frappe import _ | |||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See LICENSE | |||
from urllib.parse import quote | |||
import frappe | |||
import frappe.database | |||
import frappe.utils | |||
from frappe.utils import cint, flt, get_datetime, datetime, date_diff, today | |||
import frappe.utils.user | |||
from frappe import conf | |||
from frappe.sessions import Session, clear_sessions, delete_session | |||
from frappe.modules.patch_handler import check_session_stopped | |||
from frappe.translate import get_lang_code | |||
from frappe.utils.password import check_password, delete_login_failed_cache | |||
from frappe import _, conf | |||
from frappe.core.doctype.activity_log.activity_log import add_authentication_log | |||
from frappe.twofactor import (should_run_2fa, authenticate_for_2factor, | |||
confirm_otp_token, get_cached_user_pass) | |||
from frappe.modules.patch_handler import check_session_stopped | |||
from frappe.sessions import Session, clear_sessions, delete_session | |||
from frappe.translate import get_language | |||
from frappe.twofactor import authenticate_for_2factor, confirm_otp_token, get_cached_user_pass, should_run_2fa | |||
from frappe.utils import cint, date_diff, datetime, get_datetime, today | |||
from frappe.utils.password import check_password | |||
from frappe.website.utils import get_home_page | |||
from urllib.parse import quote | |||
class HTTPRequest: | |||
def __init__(self): | |||
# Get Environment variables | |||
self.domain = frappe.request.host | |||
if self.domain and self.domain.startswith('www.'): | |||
self.domain = self.domain[4:] | |||
if frappe.get_request_header('X-Forwarded-For'): | |||
frappe.local.request_ip = (frappe.get_request_header('X-Forwarded-For').split(",")[0]).strip() | |||
elif frappe.get_request_header('REMOTE_ADDR'): | |||
frappe.local.request_ip = frappe.get_request_header('REMOTE_ADDR') | |||
else: | |||
frappe.local.request_ip = '127.0.0.1' | |||
# language | |||
self.set_lang() | |||
# set frappe.local.request_ip | |||
self.set_request_ip() | |||
# load cookies | |||
frappe.local.cookie_manager = CookieManager() | |||
self.set_cookies() | |||
# set db | |||
# set frappe.local.db | |||
self.connect() | |||
# login | |||
frappe.local.login_manager = LoginManager() | |||
# login and start/resume user session | |||
self.set_session() | |||
if frappe.form_dict._lang: | |||
lang = get_lang_code(frappe.form_dict._lang) | |||
if lang: | |||
frappe.local.lang = lang | |||
# set request language | |||
self.set_lang() | |||
# match csrf token from current session | |||
self.validate_csrf_token() | |||
# write out latest cookies | |||
frappe.local.cookie_manager.init_cookies() | |||
# check status | |||
# check session status | |||
check_session_stopped() | |||
@property | |||
def domain(self): | |||
if not getattr(self, "_domain", None): | |||
self._domain = frappe.request.host | |||
if self._domain and self._domain.startswith('www.'): | |||
self._domain = self._domain[4:] | |||
return self._domain | |||
def set_request_ip(self): | |||
if frappe.get_request_header('X-Forwarded-For'): | |||
frappe.local.request_ip = (frappe.get_request_header('X-Forwarded-For').split(",")[0]).strip() | |||
elif frappe.get_request_header('REMOTE_ADDR'): | |||
frappe.local.request_ip = frappe.get_request_header('REMOTE_ADDR') | |||
else: | |||
frappe.local.request_ip = '127.0.0.1' | |||
def set_cookies(self): | |||
frappe.local.cookie_manager = CookieManager() | |||
def set_session(self): | |||
frappe.local.login_manager = LoginManager() | |||
def validate_csrf_token(self): | |||
if frappe.local.request and frappe.local.request.method in ("POST", "PUT", "DELETE"): | |||
if not frappe.local.session: return | |||
if not frappe.local.session.data.csrf_token \ | |||
or frappe.local.session.data.device=="mobile" \ | |||
or frappe.conf.get('ignore_csrf', None): | |||
if not frappe.local.session: | |||
return | |||
if ( | |||
not frappe.local.session.data.csrf_token | |||
or frappe.local.session.data.device == "mobile" | |||
or frappe.conf.get('ignore_csrf', None) | |||
): | |||
# not via boot | |||
return | |||
@@ -79,17 +90,18 @@ class HTTPRequest: | |||
frappe.throw(_("Invalid Request"), frappe.CSRFTokenError) | |||
def set_lang(self): | |||
from frappe.translate import guess_language | |||
frappe.local.lang = guess_language() | |||
frappe.local.lang = get_language() | |||
def get_db_name(self): | |||
"""get database name from conf""" | |||
return conf.db_name | |||
def connect(self, ac_name = None): | |||
def connect(self): | |||
"""connect to db, from ac_name or db_name""" | |||
frappe.local.db = frappe.database.get_db(user = self.get_db_name(), \ | |||
password = getattr(conf, 'db_password', '')) | |||
frappe.local.db = frappe.database.get_db( | |||
user=self.get_db_name(), | |||
password=getattr(conf, 'db_password', '') | |||
) | |||
class LoginManager: | |||
def __init__(self): | |||
@@ -143,7 +155,7 @@ class LoginManager: | |||
self.setup_boot_cache() | |||
self.set_user_info() | |||
def get_user_info(self, resume=False): | |||
def get_user_info(self): | |||
self.info = frappe.db.get_value("User", self.user, | |||
["user_type", "first_name", "last_name", "user_image"], as_dict=1) | |||
@@ -181,11 +193,13 @@ class LoginManager: | |||
frappe.local.response["redirect_to"] = redirect_to | |||
frappe.cache().hdel('redirect_after_login', self.user) | |||
frappe.local.cookie_manager.set_cookie("full_name", self.full_name) | |||
frappe.local.cookie_manager.set_cookie("user_id", self.user) | |||
frappe.local.cookie_manager.set_cookie("user_image", self.info.user_image or "") | |||
def clear_preferred_language(self): | |||
frappe.local.cookie_manager.delete_cookie("preferred_language") | |||
def make_session(self, resume=False): | |||
# start session | |||
frappe.local.session_obj = Session(user=self.user, resume=resume, | |||
@@ -72,6 +72,7 @@ | |||
"fieldtype": "Code", | |||
"in_list_view": 1, | |||
"label": "Assign Condition", | |||
"options": "PythonExpression", | |||
"reqd": 1 | |||
}, | |||
{ | |||
@@ -82,7 +83,8 @@ | |||
"description": "Simple Python Expression, Example: Status in (\"Closed\", \"Cancelled\")", | |||
"fieldname": "unassign_condition", | |||
"fieldtype": "Code", | |||
"label": "Unassign Condition" | |||
"label": "Unassign Condition", | |||
"options": "PythonExpression" | |||
}, | |||
{ | |||
"fieldname": "assign_to_users_section", | |||
@@ -120,7 +122,8 @@ | |||
"description": "Simple Python Expression, Example: Status in (\"Invalid\")", | |||
"fieldname": "close_condition", | |||
"fieldtype": "Code", | |||
"label": "Close Condition" | |||
"label": "Close Condition", | |||
"options": "PythonExpression" | |||
}, | |||
{ | |||
"fieldname": "sb", | |||
@@ -151,7 +154,7 @@ | |||
], | |||
"index_web_pages_for_search": 1, | |||
"links": [], | |||
"modified": "2020-10-20 14:47:20.662954", | |||
"modified": "2021-07-16 22:51:35.505575", | |||
"modified_by": "Administrator", | |||
"module": "Automation", | |||
"name": "Assignment Rule", | |||
@@ -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,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,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,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 | |||
@@ -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,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 _ | |||
@@ -333,7 +333,7 @@ class AutoRepeat(Document): | |||
if self.reference_doctype and self.reference_document: | |||
res = get_contacts_linking_to(self.reference_doctype, self.reference_document, fields=['email_id']) | |||
res += get_contacts_linked_from(self.reference_doctype, self.reference_document, fields=['email_id']) | |||
email_ids = list(set([d.email_id for d in res])) | |||
email_ids = {d.email_id for d in res} | |||
if not email_ids: | |||
frappe.msgprint(_('No contacts linked to document'), alert=True) | |||
else: | |||
@@ -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,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,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,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,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,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() |
@@ -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" | |||
} |
@@ -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,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,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 | |||
@@ -53,7 +53,7 @@ def clear_domain_cache(user=None): | |||
cache.delete_value(domain_cache_keys) | |||
def clear_global_cache(): | |||
from frappe.website.render import clear_cache as clear_website_cache | |||
from frappe.website.utils import clear_website_cache | |||
clear_doctype_cache() | |||
clear_website_cache() | |||
@@ -141,18 +141,13 @@ def build_table_count_cache(): | |||
return | |||
_cache = frappe.cache() | |||
data = frappe.db.multisql({ | |||
"mariadb": """ | |||
SELECT table_name AS name, | |||
table_rows AS count | |||
FROM information_schema.tables""", | |||
"postgres": """ | |||
SELECT "relname" AS name, | |||
"n_tup_ins" AS count | |||
FROM "pg_stat_all_tables" | |||
""" | |||
}, as_dict=1) | |||
table_name = frappe.qb.Field("table_name").as_("name") | |||
table_rows = frappe.qb.Field("table_rows").as_("count") | |||
information_schema = frappe.qb.Schema("information_schema") | |||
data = ( | |||
frappe.qb.from_(information_schema.tables).select(table_name, table_rows) | |||
).run(as_dict=True) | |||
counts = {d.get('name').lstrip('tab'): d.get('count', None) for d in data} | |||
_cache.set_value("information_schema:counts", counts) | |||
@@ -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,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,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 | |||
@@ -102,7 +102,9 @@ def get_commands(): | |||
from .site import commands as site_commands | |||
from .translate import commands as translate_commands | |||
from .utils import commands as utils_commands | |||
from .redis import commands as redis_commands | |||
return list(set(scheduler_commands + site_commands + translate_commands + utils_commands)) | |||
all_commands = scheduler_commands + site_commands + translate_commands + utils_commands + redis_commands | |||
return list(set(all_commands)) | |||
commands = get_commands() |
@@ -0,0 +1,53 @@ | |||
import os | |||
import click | |||
import frappe | |||
from frappe.utils.rq import RedisQueue | |||
from frappe.installer import update_site_config | |||
@click.command('create-rq-users') | |||
@click.option('--set-admin-password', is_flag=True, default=False, help='Set new Redis admin(default user) password') | |||
@click.option('--use-rq-auth', is_flag=True, default=False, help='Enable Redis authentication for sites') | |||
def create_rq_users(set_admin_password=False, use_rq_auth=False): | |||
"""Create Redis Queue users and add to acl and app configs. | |||
acl config file will be used by redis server while starting the server | |||
and app config is used by app while connecting to redis server. | |||
""" | |||
acl_file_path = os.path.abspath('../config/redis_queue.acl') | |||
with frappe.init_site(): | |||
acl_list, user_credentials = RedisQueue.gen_acl_list( | |||
set_admin_password=set_admin_password) | |||
with open(acl_file_path, 'w') as f: | |||
f.writelines([acl+'\n' for acl in acl_list]) | |||
sites_path = os.getcwd() | |||
common_site_config_path = os.path.join(sites_path, 'common_site_config.json') | |||
update_site_config("rq_username", user_credentials['bench'][0], validate=False, | |||
site_config_path=common_site_config_path) | |||
update_site_config("rq_password", user_credentials['bench'][1], validate=False, | |||
site_config_path=common_site_config_path) | |||
update_site_config("use_rq_auth", use_rq_auth, validate=False, | |||
site_config_path=common_site_config_path) | |||
click.secho('* ACL and site configs are updated with new user credentials. ' | |||
'Please restart Redis Queue server to enable namespaces.', | |||
fg='green') | |||
if set_admin_password: | |||
env_key = 'RQ_ADMIN_PASWORD' | |||
click.secho('* Redis admin password is successfully set up. ' | |||
'Include below line in .bashrc file for system to use', | |||
fg='green') | |||
click.secho(f"`export {env_key}={user_credentials['default'][1]}`") | |||
click.secho('NOTE: Please save the admin password as you ' | |||
'can not access redis server without the password', | |||
fg='yellow') | |||
commands = [ | |||
create_rq_users | |||
] |
@@ -172,9 +172,13 @@ def start_scheduler(): | |||
@click.command('worker') | |||
@click.option('--queue', type=str) | |||
@click.option('--quiet', is_flag = True, default = False, help = 'Hide Log Outputs') | |||
def start_worker(queue, quiet = False): | |||
@click.option('-u', '--rq-username', default=None, help='Redis ACL user') | |||
@click.option('-p', '--rq-password', default=None, help='Redis ACL user password') | |||
def start_worker(queue, quiet = False, rq_username=None, rq_password=None): | |||
"""Site is used to find redis credentals. | |||
""" | |||
from frappe.utils.background_jobs import start_worker | |||
start_worker(queue, quiet = quiet) | |||
start_worker(queue, quiet = quiet, rq_username=rq_username, rq_password=rq_password) | |||
@click.command('ready-for-migration') | |||
@click.option('--site', help='site name') | |||
@@ -193,7 +193,7 @@ def install_app(context, apps): | |||
print("App {} is Incompatible with Site {}{}".format(app, site, err_msg)) | |||
exit_code = 1 | |||
except Exception as err: | |||
err_msg = ":\n{}".format(err if str(err) else frappe.get_traceback()) | |||
err_msg = ": {}\n{}".format(str(err), frappe.get_traceback()) | |||
print("An error occurred while installing {}{}".format(app, err_msg)) | |||
exit_code = 1 | |||
@@ -561,30 +561,54 @@ def move(dest_dir, site): | |||
return final_new_path | |||
@click.command('set-password') | |||
@click.argument('user') | |||
@click.argument('password', required=False) | |||
@click.option('--logout-all-sessions', help='Logout from all sessions', is_flag=True, default=False) | |||
@pass_context | |||
def set_password(context, user, password=None, logout_all_sessions=False): | |||
"Set password for a user on a site" | |||
if not context.sites: | |||
raise SiteNotSpecifiedError | |||
for site in context.sites: | |||
set_user_password(site, user, password, logout_all_sessions) | |||
@click.command('set-admin-password') | |||
@click.argument('admin-password') | |||
@click.argument('admin-password', required=False) | |||
@click.option('--logout-all-sessions', help='Logout from all sessions', is_flag=True, default=False) | |||
@pass_context | |||
def set_admin_password(context, admin_password, logout_all_sessions=False): | |||
def set_admin_password(context, admin_password=None, logout_all_sessions=False): | |||
"Set Administrator password for a site" | |||
if not context.sites: | |||
raise SiteNotSpecifiedError | |||
for site in context.sites: | |||
set_user_password(site, "Administrator", admin_password, logout_all_sessions) | |||
def set_user_password(site, user, password, logout_all_sessions=False): | |||
import getpass | |||
from frappe.utils.password import update_password | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
try: | |||
frappe.init(site=site) | |||
while not admin_password: | |||
admin_password = getpass.getpass("Administrator's password for {0}: ".format(site)) | |||
while not password: | |||
password = getpass.getpass(f"{user}'s password for {site}: ") | |||
frappe.connect() | |||
if not frappe.db.exists("User", user): | |||
print(f"User {user} does not exist") | |||
sys.exit(1) | |||
update_password(user=user, pwd=password, logout_all_sessions=logout_all_sessions) | |||
frappe.db.commit() | |||
password = None | |||
finally: | |||
frappe.destroy() | |||
frappe.connect() | |||
update_password(user='Administrator', pwd=admin_password, logout_all_sessions=logout_all_sessions) | |||
frappe.db.commit() | |||
admin_password = None | |||
finally: | |||
frappe.destroy() | |||
if not context.sites: | |||
raise SiteNotSpecifiedError | |||
@click.command('set-last-active-for-user') | |||
@click.option('--user', help="Setup last active date for user") | |||
@@ -729,6 +753,7 @@ commands = [ | |||
remove_from_installed_apps, | |||
restore, | |||
run_patch, | |||
set_password, | |||
set_admin_password, | |||
uninstall, | |||
disable_user, | |||
@@ -1,5 +1,3 @@ | |||
# -*- coding: utf-8 -*- | |||
import json | |||
import os | |||
import subprocess | |||
@@ -11,7 +9,14 @@ import click | |||
import frappe | |||
from frappe.commands import get_site, pass_context | |||
from frappe.exceptions import SiteNotSpecifiedError | |||
from frappe.utils import get_bench_path, update_progress_bar, cint | |||
from frappe.utils import update_progress_bar, cint | |||
from frappe.coverage import CodeCoverage | |||
DATA_IMPORT_DEPRECATION = click.style( | |||
"[DEPRECATED] The `import-csv` command used 'Data Import Legacy' which has been deprecated.\n" | |||
"Use `data-import` command instead to import data via 'Data Import'.", | |||
fg="yellow" | |||
) | |||
@click.command('build') | |||
@@ -69,14 +74,14 @@ def watch(apps=None): | |||
def clear_cache(context): | |||
"Clear cache, doctype cache and defaults" | |||
import frappe.sessions | |||
import frappe.website.render | |||
from frappe.website.utils import clear_website_cache | |||
from frappe.desk.notifications import clear_notifications | |||
for site in context.sites: | |||
try: | |||
frappe.connect(site) | |||
frappe.clear_cache() | |||
clear_notifications() | |||
frappe.website.render.clear_cache() | |||
clear_website_cache() | |||
finally: | |||
frappe.destroy() | |||
if not context.sites: | |||
@@ -86,12 +91,12 @@ def clear_cache(context): | |||
@pass_context | |||
def clear_website_cache(context): | |||
"Clear website cache" | |||
import frappe.website.render | |||
from frappe.website.utils import clear_website_cache | |||
for site in context.sites: | |||
try: | |||
frappe.init(site=site) | |||
frappe.connect() | |||
frappe.website.render.clear_cache() | |||
clear_website_cache() | |||
finally: | |||
frappe.destroy() | |||
if not context.sites: | |||
@@ -350,7 +355,8 @@ def import_doc(context, path, force=False): | |||
if not context.sites: | |||
raise SiteNotSpecifiedError | |||
@click.command('import-csv') | |||
@click.command('import-csv', help=DATA_IMPORT_DEPRECATION) | |||
@click.argument('path') | |||
@click.option('--only-insert', default=False, is_flag=True, help='Do not overwrite existing records') | |||
@click.option('--submit-after-import', default=False, is_flag=True, help='Submit document after importing it') | |||
@@ -358,32 +364,8 @@ def import_doc(context, path, force=False): | |||
@click.option('--no-email', default=True, is_flag=True, help='Send email if applicable') | |||
@pass_context | |||
def import_csv(context, path, only_insert=False, submit_after_import=False, ignore_encoding_errors=False, no_email=True): | |||
"Import CSV using data import" | |||
from frappe.core.doctype.data_import_legacy import importer | |||
from frappe.utils.csvutils import read_csv_content | |||
site = get_site(context) | |||
if not os.path.exists(path): | |||
path = os.path.join('..', path) | |||
if not os.path.exists(path): | |||
print('Invalid path {0}'.format(path)) | |||
sys.exit(1) | |||
with open(path, 'r') as csvfile: | |||
content = read_csv_content(csvfile.read()) | |||
frappe.init(site=site) | |||
frappe.connect() | |||
try: | |||
importer.upload(content, submit_after_import=submit_after_import, no_email=no_email, | |||
ignore_encoding_errors=ignore_encoding_errors, overwrite=not only_insert, | |||
via_console=True) | |||
frappe.db.commit() | |||
except Exception: | |||
print(frappe.get_traceback()) | |||
frappe.destroy() | |||
click.secho(DATA_IMPORT_DEPRECATION) | |||
sys.exit(1) | |||
@click.command('data-import') | |||
@@ -504,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 = [] | |||
@@ -527,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') | |||
@@ -542,74 +537,39 @@ 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, | |||
skip_test_records=False, skip_before_tests=False, failfast=False): | |||
"Run tests" | |||
import frappe.test_runner | |||
tests = test | |||
with CodeCoverage(coverage, app): | |||
import frappe.test_runner | |||
tests = test | |||
site = get_site(context) | |||
site = get_site(context) | |||
allow_tests = frappe.get_conf(site).allow_tests | |||
allow_tests = frappe.get_conf(site).allow_tests | |||
if not (allow_tests or os.environ.get('CI')): | |||
click.secho('Testing is disabled for the site!', bold=True) | |||
click.secho('You can enable tests by entering following command:') | |||
click.secho('bench --site {0} set-config allow_tests true'.format(site), fg='green') | |||
return | |||
if not (allow_tests or os.environ.get('CI')): | |||
click.secho('Testing is disabled for the site!', bold=True) | |||
click.secho('You can enable tests by entering following command:') | |||
click.secho('bench --site {0} set-config allow_tests true'.format(site), fg='green') | |||
return | |||
frappe.init(site=site) | |||
frappe.init(site=site) | |||
frappe.flags.skip_before_tests = skip_before_tests | |||
frappe.flags.skip_test_records = skip_test_records | |||
frappe.flags.skip_before_tests = skip_before_tests | |||
frappe.flags.skip_test_records = skip_test_records | |||
if coverage: | |||
from coverage import Coverage | |||
# Generate coverage report only for app that is being tested | |||
source_path = os.path.join(get_bench_path(), 'apps', app or 'frappe') | |||
incl = [ | |||
'*.py', | |||
] | |||
omit = [ | |||
'*.js', | |||
'*.xml', | |||
'*.pyc', | |||
'*.css', | |||
'*.less', | |||
'*.scss', | |||
'*.vue', | |||
'*.html', | |||
'*/test_*', | |||
'*/node_modules/*', | |||
'*/doctype/*/*_dashboard.py', | |||
'*/patches/*', | |||
] | |||
if not app or app == 'frappe': | |||
omit.append('*/tests/*') | |||
omit.append('*/commands/*') | |||
cov = Coverage(source=[source_path], omit=omit, include=incl) | |||
cov.start() | |||
ret = frappe.test_runner.main(app, module, doctype, context.verbose, tests=tests, | |||
force=context.force, profile=profile, junit_xml_output=junit_xml_output, | |||
ui_tests=ui_tests, doctype_list_path=doctype_list_path, failfast=failfast) | |||
if coverage: | |||
cov.stop() | |||
cov.save() | |||
if len(ret.failures) == 0 and len(ret.errors) == 0: | |||
ret = 0 | |||
if os.environ.get('CI'): | |||
sys.exit(ret) | |||
ret = frappe.test_runner.main(app, module, doctype, context.verbose, tests=tests, | |||
force=context.force, profile=profile, junit_xml_output=junit_xml_output, | |||
ui_tests=ui_tests, doctype_list_path=doctype_list_path, failfast=failfast) | |||
if len(ret.failures) == 0 and len(ret.errors) == 0: | |||
ret = 0 | |||
if os.environ.get('CI'): | |||
sys.exit(ret) | |||
@click.command('run-parallel-tests') | |||
@click.option('--app', help="For App", default='frappe') | |||
@@ -619,13 +579,14 @@ def run_tests(context, app=None, module=None, doctype=None, test=(), profile=Fal | |||
@click.option('--use-orchestrator', is_flag=True, help="Use orchestrator to run parallel tests") | |||
@pass_context | |||
def run_parallel_tests(context, app, build_number, total_builds, with_coverage=False, use_orchestrator=False): | |||
site = get_site(context) | |||
if use_orchestrator: | |||
from frappe.parallel_test_runner import ParallelTestWithOrchestrator | |||
ParallelTestWithOrchestrator(app, site=site, with_coverage=with_coverage) | |||
else: | |||
from frappe.parallel_test_runner import ParallelTestRunner | |||
ParallelTestRunner(app, site=site, build_number=build_number, total_builds=total_builds, with_coverage=with_coverage) | |||
with CodeCoverage(with_coverage, app): | |||
site = get_site(context) | |||
if use_orchestrator: | |||
from frappe.parallel_test_runner import ParallelTestWithOrchestrator | |||
ParallelTestWithOrchestrator(app, site=site) | |||
else: | |||
from frappe.parallel_test_runner import ParallelTestRunner | |||
ParallelTestRunner(app, site=site, build_number=build_number, total_builds=total_builds) | |||
@click.command('run-ui-tests') | |||
@click.argument('app') | |||
@@ -641,27 +602,29 @@ 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 --key 4a48f41c-11b3-425b-aa88-c58048fa69eb' if headless else 'open' | |||
run_or_open = 'run --browser firefox --record' if headless else 'open' | |||
command = '{site_env} {password_env} {cypress} {run_or_open}' | |||
formatted_command = command.format(site_env=site_env, password_env=password_env, cypress=cypress_path, run_or_open=run_or_open) | |||
@@ -669,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) | |||
@@ -767,22 +730,49 @@ def set_config(context, key, value, global_=False, parse=False, as_dict=False): | |||
frappe.destroy() | |||
@click.command('version') | |||
def get_version(): | |||
"Show the versions of all the installed apps" | |||
@click.command("version") | |||
@click.option("-f", "--format", "output", | |||
type=click.Choice(["plain", "table", "json", "legacy"]), help="Output format", default="legacy") | |||
def get_version(output): | |||
"""Show the versions of all the installed apps.""" | |||
from git import Repo | |||
from frappe.utils.commands import render_table | |||
from frappe.utils.change_log import get_app_branch | |||
frappe.init('') | |||
for m in sorted(frappe.get_all_apps()): | |||
branch_name = get_app_branch(m) | |||
module = frappe.get_module(m) | |||
app_hooks = frappe.get_module(m + ".hooks") | |||
if hasattr(app_hooks, '{0}_version'.format(branch_name)): | |||
print("{0} {1}".format(m, getattr(app_hooks, '{0}_version'.format(branch_name)))) | |||
elif hasattr(module, "__version__"): | |||
print("{0} {1}".format(m, module.__version__)) | |||
frappe.init("") | |||
data = [] | |||
for app in sorted(frappe.get_all_apps()): | |||
module = frappe.get_module(app) | |||
app_hooks = frappe.get_module(app + ".hooks") | |||
repo = Repo(frappe.get_app_path(app, "..")) | |||
app_info = frappe._dict() | |||
app_info.app = app | |||
app_info.branch = get_app_branch(app) | |||
app_info.commit = repo.head.object.hexsha[:7] | |||
app_info.version = getattr(app_hooks, f"{app_info.branch}_version", None) or module.__version__ | |||
data.append(app_info) | |||
{ | |||
"legacy": lambda: [ | |||
click.echo(f"{app_info.app} {app_info.version}") | |||
for app_info in data | |||
], | |||
"plain": lambda: [ | |||
click.echo(f"{app_info.app} {app_info.version} {app_info.branch} ({app_info.commit})") | |||
for app_info in data | |||
], | |||
"table": lambda: render_table( | |||
[["App", "Version", "Branch", "Commit"]] + | |||
[ | |||
[app_info.app, app_info.version, app_info.branch, app_info.commit] | |||
for app_info in data | |||
] | |||
), | |||
"json": lambda: click.echo(json.dumps(data, indent=4)), | |||
}[output]() | |||
@click.command('rebuild-global-search') | |||
@@ -39,18 +39,17 @@ def get_modules_from_app(app): | |||
) | |||
def get_all_empty_tables_by_module(): | |||
empty_tables = set(r[0] for r in frappe.db.multisql({ | |||
"mariadb": """ | |||
SELECT table_name | |||
FROM information_schema.tables | |||
WHERE table_rows = 0 and table_schema = "{}" | |||
""".format(frappe.conf.db_name), | |||
"postgres": """ | |||
SELECT "relname" as "table_name" | |||
FROM "pg_stat_all_tables" | |||
WHERE n_tup_ins = 0 | |||
""" | |||
})) | |||
table_rows = frappe.qb.Field("table_rows") | |||
table_name = frappe.qb.Field("table_name") | |||
information_schema = frappe.qb.Schema("information_schema") | |||
empty_tables = ( | |||
frappe.qb.from_(information_schema.tables) | |||
.select(table_name) | |||
.where(table_rows == 0) | |||
).run() | |||
empty_tables = {r[0] for r in empty_tables} | |||
results = frappe.get_all("DocType", fields=["name", "module"]) | |||
empty_tables_by_module = {} | |||
@@ -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 | |||
@@ -153,7 +153,7 @@ def filter_dynamic_link_doctypes(doctype, txt, searchfield, start, page_len, fil | |||
doctypes = frappe.db.get_all("DocField", filters=filters, fields=["parent"], | |||
distinct=True, as_list=True) | |||
doctypes = tuple([d for d in doctypes if re.search(txt+".*", _(d[0]), re.IGNORECASE)]) | |||
doctypes = tuple(d for d in doctypes if re.search(txt+".*", _(d[0]), re.IGNORECASE)) | |||
filters.update({ | |||
"dt": ("not in", [d[0] for d in doctypes]) | |||
@@ -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 | |||
@@ -257,7 +257,7 @@ def address_query(doctype, txt, searchfield, start, page_len, filters): | |||
def get_condensed_address(doc): | |||
fields = ["address_title", "address_line1", "address_line2", "city", "county", "state", "country"] | |||
return ", ".join([doc.get(d) for d in fields if doc.get(d)]) | |||
return ", ".join(doc.get(d) for d in fields if doc.get(d)) | |||
def update_preferred_address(address, field): | |||
frappe.db.set_value('Address', address, field, 0) |
@@ -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,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,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): | |||
@@ -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,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,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,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,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,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,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,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,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,2 +1,2 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
# License: MIT. See LICENSE |
@@ -1,3 +1,3 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
# License: MIT. See LICENSE | |||
@@ -1,9 +1,5 @@ | |||
# -*- coding: utf-8 -*- | |||
# Copyright (c) 2019, Frappe Technologies and contributors | |||
# For license information, please see license.txt | |||
# imports - standard imports | |||
# imports - module imports | |||
# License: MIT. See LICENSE | |||
import frappe | |||
from frappe.model.document import Document | |||
@@ -33,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,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,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,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 | |||
@@ -29,10 +29,12 @@ def update_feed(doc, method=None): | |||
name = feed.name or doc.name | |||
# delete earlier feed | |||
frappe.db.sql("""delete from `tabActivity Log` | |||
where | |||
reference_doctype=%s and reference_name=%s | |||
and link_doctype=%s""", (doctype, name,feed.link_doctype)) | |||
frappe.db.delete("Activity Log", { | |||
"reference_doctype": doctype, | |||
"reference_name": name, | |||
"link_doctype": feed.link_doctype | |||
}) | |||
frappe.get_doc({ | |||
"doctype": "Activity Log", | |||
"reference_doctype": doctype, | |||
@@ -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,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,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 | |||
@@ -9,7 +9,7 @@ from frappe.core.doctype.user.user import extract_mentions | |||
from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification,\ | |||
get_title, get_title_html | |||
from frappe.utils import get_fullname | |||
from frappe.website.render import clear_cache | |||
from frappe.website.utils import clear_cache | |||
from frappe.database.schema import add_column | |||
from frappe.exceptions import ImplicitCommitError | |||
@@ -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,3 +1,3 @@ | |||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
# License: MIT. See LICENSE | |||