diff --git a/.backportrc.json b/.backportrc.json new file mode 100644 index 0000000..e48e270 --- /dev/null +++ b/.backportrc.json @@ -0,0 +1,11 @@ +{ + "repoOwner": "agritheory", + "repoName": "cloud_storage", + "targetBranchChoices": ["version-14", "version-15"], + "targetBranches": ["version-14", "version-15"], + "autoMerge": true, + "autoMergeMethod": "squash", + "branchLabelMapping": { + "^auto-backport-to-(.+)$": "$1" + } +} diff --git a/.editorconfig b/.editorconfig index b72767b..50d1755 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,3 +13,9 @@ charset = utf-8 indent_style = tab indent_size = 2 max_line_length = 99 + +# JSON files - mostly doctype schema files +[{*.json}] +insert_final_newline = false +indent_style = space +indent_size = 2 diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..3127e88 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +cloud_storage/public/js/lib/* +cloud_storage/templates/includes/* +cloud_storage/www/website_script.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..d4b870b --- /dev/null +++ b/.eslintrc @@ -0,0 +1,124 @@ +{ + "env": { + "browser": true, + "node": true, + "es2022": true + }, + "parserOptions": { + "sourceType": "module" + }, + "extends": "eslint:recommended", + "rules": { + "indent": "off", + "brace-style": "off", + "no-mixed-spaces-and-tabs": "off", + "no-useless-escape": "off", + "space-unary-ops": ["error", { "words": true }], + "linebreak-style": "off", + "quotes": ["off"], + "semi": "off", + "camelcase": "off", + "no-unused-vars": "off", + "no-console": ["warn"], + "no-extra-boolean-cast": ["off"], + "no-control-regex": ["off"] + }, + "root": true, + "globals": { + "frappe": true, + "Vue": true, + "SetVueGlobals": true, + "__": true, + "repl": true, + "Class": true, + "locals": true, + "cint": true, + "cstr": true, + "cur_frm": true, + "cur_dialog": true, + "cur_page": true, + "cur_list": true, + "cur_tree": true, + "msg_dialog": true, + "is_null": true, + "in_list": true, + "has_common": true, + "posthog": true, + "has_words": true, + "validate_email": true, + "open_web_template_values_editor": true, + "validate_name": true, + "validate_phone": true, + "validate_url": true, + "get_number_format": true, + "format_number": true, + "format_currency": true, + "comment_when": true, + "open_url_post": true, + "toTitle": true, + "lstrip": true, + "rstrip": true, + "strip": true, + "strip_html": true, + "replace_all": true, + "flt": true, + "precision": true, + "CREATE": true, + "AMEND": true, + "CANCEL": true, + "copy_dict": true, + "get_number_format_info": true, + "strip_number_groups": true, + "print_table": true, + "Layout": true, + "web_form_settings": true, + "$c": true, + "$a": true, + "$i": true, + "$bg": true, + "$y": true, + "$c_obj": true, + "refresh_many": true, + "refresh_field": true, + "toggle_field": true, + "get_field_obj": true, + "get_query_params": true, + "unhide_field": true, + "hide_field": true, + "set_field_options": true, + "getCookie": true, + "getCookies": true, + "get_url_arg": true, + "md5": true, + "$": true, + "jQuery": true, + "moment": true, + "hljs": true, + "Awesomplete": true, + "Sortable": true, + "Showdown": true, + "Taggle": true, + "Gantt": true, + "Slick": true, + "Webcam": true, + "PhotoSwipe": true, + "PhotoSwipeUI_Default": true, + "io": true, + "JsBarcode": true, + "L": true, + "Chart": true, + "DataTable": true, + "Cypress": true, + "cy": true, + "it": true, + "describe": true, + "expect": true, + "context": true, + "before": true, + "beforeEach": true, + "after": true, + "qz": true, + "localforage": true, + "extend_cscript": true + } +} diff --git a/.flake8 b/.flake8 index 2de7a15..e783fbb 100644 --- a/.flake8 +++ b/.flake8 @@ -69,6 +69,7 @@ ignore = F841, E713, E712, + B028, max-line-length = 200 exclude=,test_*.py diff --git a/.github/helper/install.sh b/.github/helper/install.sh new file mode 100644 index 0000000..3b39bc9 --- /dev/null +++ b/.github/helper/install.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +set -e + +cd ~ || exit + +sudo apt update +sudo apt remove mysql-server mysql-client +sudo apt install libcups2-dev redis-server mariadb-client-10.6 + +pip install frappe-bench + +bench init --skip-redis-config-generation --skip-assets --python "$(which python)" --frappe-branch version-15 frappe-bench + +mkdir ~/frappe-bench/sites/test_site +cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/test_site/ + +mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'" +mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" +mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" +mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe" +mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'" +mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES" + +install_whktml() { + 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 + sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf + sudo chmod o+x /usr/local/bin/wkhtmltopdf +} +install_whktml & + +cd ~/frappe-bench || exit + +sed -i 's/watch:/# watch:/g' Procfile +sed -i 's/schedule:/# schedule:/g' Procfile +sed -i 's/socketio:/# socketio:/g' Procfile +sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile + +bench get-app erpnext --branch "version-15" --resolve-deps +bench get-app cloud_storage "${GITHUB_WORKSPACE}" +bench setup requirements --dev + +bench start &> bench_run_logs.txt & +CI=Yes bench build --app frappe & + +bench --site test_site reinstall --yes +bench --site test_site install-app cloud_storage \ No newline at end of file diff --git a/.github/helper/site_config.json b/.github/helper/site_config.json new file mode 100644 index 0000000..b9d0173 --- /dev/null +++ b/.github/helper/site_config.json @@ -0,0 +1,19 @@ +{ + "db_host": "127.0.0.1", + "db_port": 3306, + "db_name": "test_frappe", + "db_password": "test_frappe", + "allow_tests": true, + "db_type": "mariadb", + "auto_email_id": "test@example.com", + "mail_server": "localhost", + "mail_port": 2525, + "mail_login": "test@example.com", + "mail_password": "test", + "admin_password": "admin", + "root_login": "root", + "root_password": "root", + "host_name": "http://test_site:8000", + "monitor": 1, + "server_script_enabled": true +} \ No newline at end of file diff --git a/.github/validate_customizations.py b/.github/validate_customizations.py new file mode 100644 index 0000000..f942698 --- /dev/null +++ b/.github/validate_customizations.py @@ -0,0 +1,164 @@ +import json +import pathlib +import sys + + +def scrub(txt: str) -> str: + return txt.replace(" ", "_").replace("-", "_").lower() + + +def unscrub(txt: str) -> str: + return txt.replace("_", " ").replace("-", " ").title() + + +def get_customized_doctypes(): + apps_dir = pathlib.Path(__file__).resolve().parent.parent.parent + apps_order = pathlib.Path(__file__).resolve().parent.parent.parent.parent / "sites" / "apps.txt" + apps_order = apps_order.read_text().split("\n") + customized_doctypes = {} + for _app_dir in apps_order: + app_dir = (apps_dir / _app_dir).resolve() + if not app_dir.is_dir(): + continue + modules = (app_dir / _app_dir / "modules.txt").read_text().split("\n") + for module in modules: + if not (app_dir / _app_dir / scrub(module) / "custom").exists(): + continue + for custom_file in list((app_dir / _app_dir / scrub(module) / "custom").glob("**/*.json")): + if custom_file.stem in customized_doctypes: + customized_doctypes[custom_file.stem].append(custom_file.resolve()) + else: + customized_doctypes[custom_file.stem] = [custom_file.resolve()] + + return dict(sorted(customized_doctypes.items())) + + +def validate_module(customized_doctypes, set_module=False): + exceptions = [] + app_dir = pathlib.Path(__file__).resolve().parent.parent + this_app = app_dir.stem + for doctype, customize_files in customized_doctypes.items(): + for customize_file in customize_files: + if not this_app in str(customize_file): + continue + module = customize_file.parent.parent.stem + file_contents = json.loads(customize_file.read_text()) + if file_contents.get("custom_fields"): + for custom_field in file_contents.get("custom_fields"): + if set_module: + custom_field["module"] = unscrub(module) + continue + if not custom_field.get("module"): + exceptions.append( + f"Custom Field for {custom_field.get('dt')} in {this_app} '{custom_field.get('fieldname')}' does not have a module key" + ) + continue + elif custom_field.get("module") != unscrub(module): + exceptions.append( + f"Custom Field for {custom_field.get('dt')} in {this_app} '{custom_field.get('fieldname')}' has module key ({custom_field.get('module')}) associated with another app" + ) + continue + if file_contents.get("property_setters"): + for ps in file_contents.get("property_setters"): + if set_module: + ps["module"] = unscrub(module) + continue + if not ps.get("module"): + exceptions.append( + f"Property Setter for {ps.get('doc_type')} in {this_app} '{ps.get('property')}' on {ps.get('field_name')} does not have a module key" + ) + continue + elif ps.get("module") != unscrub(module): + exceptions.append( + f"Property Setter for {ps.get('doc_type')} in {this_app} '{ps.get('property')}' on {ps.get('field_name')} has module key ({ps.get('module')}) associated with another app" + ) + continue + if set_module: + with customize_file.open("w", encoding="UTF-8") as target: + json.dump(file_contents, target, sort_keys=True, indent=2) + + return exceptions + + +def validate_no_custom_perms(customized_doctypes): + exceptions = [] + this_app = pathlib.Path(__file__).resolve().parent.parent.stem + for doctype, customize_files in customized_doctypes.items(): + for customize_file in customize_files: + if not this_app in str(customize_file): + continue + file_contents = json.loads(customize_file.read_text()) + if file_contents.get("custom_perms"): + exceptions.append(f"Customization for {doctype} in {this_app} contains custom permissions") + return exceptions + + +def validate_duplicate_customizations(customized_doctypes): + exceptions = [] + common_fields = {} + common_property_setters = {} + app_dir = pathlib.Path(__file__).resolve().parent.parent + this_app = app_dir.stem + for doctype, customize_files in customized_doctypes.items(): + if len(customize_files) == 1: + continue + common_fields[doctype] = {} + common_property_setters[doctype] = {} + for customize_file in customize_files: + module = customize_file.parent.parent.stem + app = customize_file.parent.parent.parent.parent.stem + file_contents = json.loads(customize_file.read_text()) + if file_contents.get("custom_fields"): + fields = [cf.get("fieldname") for cf in file_contents.get("custom_fields")] + common_fields[doctype][module] = fields + if file_contents.get("property_setters"): + ps = [ps.get("name") for ps in file_contents.get("property_setters")] + common_property_setters[doctype][module] = ps + + for doctype, module_and_fields in common_fields.items(): + if this_app not in module_and_fields.keys(): + continue + this_modules_fields = module_and_fields.pop(this_app) + for module, fields in module_and_fields.items(): + for field in fields: + if field in this_modules_fields: + exceptions.append( + f"Custom Field for {unscrub(doctype)} in {this_app} '{field}' also appears in customizations for {module}" + ) + + for doctype, module_and_ps in common_property_setters.items(): + if this_app not in module_and_ps.keys(): + continue + this_modules_ps = module_and_ps.pop(this_app) + for module, ps in module_and_ps.items(): + for p in ps: + if p in this_modules_ps: + exceptions.append( + f"Property Setter for {unscrub(doctype)} in {this_app} on '{p}' also appears in customizations for {module}" + ) + + return exceptions + + +def validate_customizations(set_module): + customized_doctypes = get_customized_doctypes() + exceptions = validate_no_custom_perms(customized_doctypes) + exceptions += validate_module(customized_doctypes, set_module) + exceptions += validate_duplicate_customizations(customized_doctypes) + + return exceptions + + +if __name__ == "__main__": + exceptions = [] + set_module = False + for arg in sys.argv: + if arg == "--set-module": + set_module = True + exceptions.append(validate_customizations(set_module)) + + if exceptions: + for exception in exceptions: + [print(e) for e in exception] # TODO: colorize + + sys.exit(1) if all(exceptions) else sys.exit(0) diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml new file mode 100644 index 0000000..94d5d17 --- /dev/null +++ b/.github/workflows/backport.yaml @@ -0,0 +1,24 @@ +name: Automatic backport action + +on: + pull_request_target: + types: ["labeled", "closed"] + +jobs: + backport: + name: Backport PR + runs-on: ubuntu-latest + steps: + - name: Backport Action + uses: sorenlouv/backport-github-action@v9.3.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + auto_backport_label_prefix: backport-to- + + - name: Info log + if: ${{ success() }} + run: cat ~/.backport/backport.info.log + + - name: Debug log + if: ${{ failure() }} + run: cat ~/.backport/backport.debug.log \ No newline at end of file diff --git a/.github/workflows/frappe.yaml b/.github/workflows/frappe.yaml deleted file mode 100644 index f9776d7..0000000 --- a/.github/workflows/frappe.yaml +++ /dev/null @@ -1,94 +0,0 @@ - -name: Frappe CI - -on: - push: - branches: - - version-14 - pull_request: - -concurrency: - group: develop-cloud_storage-${{ github.event.number }} - cancel-in-progress: true - -jobs: - tests: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - fail-fast: false - name: Server - - services: - mariadb: - image: mariadb:10.6 - env: - MYSQL_ROOT_PASSWORD: root - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 - - steps: - - name: Clone - uses: actions/checkout@v3 - - - name: Setup Python - uses: actions/setup-python@v3 - with: - python-version: '3.10' - - - name: Setup Node - uses: actions/setup-node@v3 - with: - node-version: 18 - check-latest: true - - - name: Cache pip - uses: actions/cache@v3 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py', '**/setup.cfg') }} - restore-keys: | - ${{ runner.os }}-pip- - ${{ runner.os }}- - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: 'echo "::set-output name=dir::$(yarn cache dir)"' - - - uses: actions/cache@v3 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - - name: Setup - run: | - pip install frappe-bench - bench init --skip-redis-config-generation --skip-assets --python "$(which python)" --frappe-branch version-14 ~/frappe-bench - mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'" - mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" - - - name: Install - working-directory: /home/runner/frappe-bench - run: | - bench get-app cloud_storage $GITHUB_WORKSPACE - bench setup requirements --dev - bench new-site --db-root-password root --admin-password admin test_site - bench --site test_site install-app cloud_storage - bench build - env: - CI: 'Yes' - - - name: Run Tests - working-directory: /home/runner/frappe-bench - run: | - bench --site test_site set-config allow_tests true - bench --site test_site run-tests --app cloud_storage - source env/bin/activate - pytest ./apps/cloud_storage/cloud_storage/tests/test_file_association.py --disable-warnings -s - env: - TYPE: server diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 77c5a8f..fc39f29 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -4,7 +4,11 @@ on: push: branches: - version-14 + - version-15 pull_request: + branches: + - version-14 + - version-15 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -15,18 +19,18 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} fetch-depth: 2 - name: Setup Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: '3.10' - - name: Install mypy - run: pip install mypy + - name: Install mypy and types + run: python -m pip install mypy types-python-dateutil types-pytz --no-input - name: Run mypy uses: sasanquaneuf/mypy-github-action@releases/v1 @@ -40,13 +44,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} fetch-depth: 2 - name: Setup Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: '3.10' @@ -61,29 +65,37 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} fetch-depth: 2 - name: Prettify code - uses: creyD/prettier_action@v4.3 + uses: rutajdash/prettier-cli-action@v1.0.0 with: - commit_message: "style: prettify code" + config_path: ./.prettierrc.js + ignore_path: ./.prettierignore + + - name: Prettier Output + if: ${{ failure() }} + shell: bash + run: | + echo "The following files are not formatted:" + echo "${{steps.prettier-run.outputs.prettier_output}}" >> $GITHUB_OUTPUT json_diff: needs: [ py_json_merge ] runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.ref }} fetch-depth: 2 - name: Find JSON changes id: changed-json - uses: tj-actions/changed-files@v37 + uses: tj-actions/changed-files@v43 with: files: | **/*.json @@ -127,25 +139,17 @@ jobs: echo "D,${file}" >> base/mrd.txt done - - name: Setup requirements and script - run: | - pip install rich - pip install json_source_map - git clone --depth 1 https://gist.github.com/3eea518743067f1b971114f1a2016f69 fsjd - - - name: Diff table - run: python3 fsjd/frappe_schema_json_diff.py base/mrd.txt head/acmr.txt 1 py_json_merge: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Fetch validator run: git clone --depth 1 https://gist.github.com/f1bf2c11f78331b2417189c385022c28.git validate_json - name: Validate JSON - run: python3 validate_json/validate_json.py ./ + run: python3 validate_json/validate_json.py ./inventory_tools/inventory_tools/ - name: Compile run: python3 -m compileall -q ./ diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml new file mode 100644 index 0000000..9f30e89 --- /dev/null +++ b/.github/workflows/pytest.yaml @@ -0,0 +1,88 @@ + +name: Frappe CI + +on: + push: + branches: + - version-15 + pull_request: + +concurrency: + group: version-15-cloud_storage-${{ github.event.number }} + cancel-in-progress: true + +jobs: + tests: + name: Server + runs-on: ubuntu-latest + timeout-minutes: 60 + strategy: + fail-fast: false + + services: + mariadb: + image: mariadb:10.6 + env: + MARIADB_ROOT_PASSWORD: 'root' + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + + steps: + - name: Clone + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Check for valid Python & Merge Conflicts + run: | + python -m compileall -q -f "${GITHUB_WORKSPACE}" + if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}" + then echo "Found merge conflicts" + exit 1 + fi + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 18 + check-latest: true + + - name: Add to Hosts + run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts + + - name: Cache pip + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install + run: | + bash ${GITHUB_WORKSPACE}/.github/helper/install.sh + + - name: Run Tests + working-directory: /home/runner/frappe-bench + run: | + source env/bin/activate + pytest ./apps/cloud_storage/cloud_storage/tests/ --disable-warnings -s + env: + TYPE: server \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 217a6c8..841a92d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,8 +1,10 @@ name: Release + on: push: branches: - version-14 + - version-15 jobs: release: name: Release @@ -11,12 +13,13 @@ jobs: permissions: id-token: write contents: write + steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Python Semantic Release uses: python-semantic-release/python-semantic-release@master with: - github_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0f3d4aa..87a76a0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,8 +7,8 @@ repos: rev: v4.3.0 hooks: - id: trailing-whitespace - files: "cloud_storage.*" - exclude: ".*json$|.*txt$|.*csv|.*md|.*svg" + files: 'cloud_storage.*' + exclude: '.*json$|.*txt$|.*csv|.*md|.*svg' - id: check-yaml - id: no-commit-to-branch args: ['--branch', 'develop'] @@ -20,7 +20,7 @@ repos: - id: debug-statements - repo: https://github.com/asottile/pyupgrade - rev: v2.34.0 + rev: v3.9.0 hooks: - id: pyupgrade args: ['--py38-plus'] @@ -34,18 +34,32 @@ repos: rev: v2.7.1 hooks: - id: prettier - types_or: [javascript] + types_or: [javascript, vue, scss] # Ignore any files that might contain jinja / bundles exclude: | - (?x)^( - .*boilerplate.*| + (?x)^( .*node_modules.*| cloud_storage/public/dist/.*| cloud_storage/public/js/lib/.*| cloud_storage/templates/includes/.*| cloud_storage/www/website_script.js - )$ + )$ + - repo: https://github.com/pre-commit/mirrors-eslint + rev: v8.44.0 + hooks: + - id: eslint + types_or: [javascript] + args: ['--quiet'] + # Ignore any files that might contain jinja / bundles + exclude: | + (?x)^( + .*node_modules.*| + cloud_storage/public/dist/.*| + cloud_storage/public/js/lib/.*| + cloud_storage/templates/includes/.*| + cloud_storage/www/website_script.js + )$ - repo: https://github.com/PyCQA/isort rev: 5.12.0 @@ -53,12 +67,21 @@ repos: - id: isort - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 6.0.0 hooks: - id: flake8 - additional_dependencies: ['flake8-bugbear',] + additional_dependencies: ['flake8-bugbear'] + + - repo: local + hooks: + - id: validate_customizations + always_run: true + name: .github/validate_customizations.py + entry: python .github/validate_customizations.py + language: system + types: [python] ci: - autoupdate_schedule: weekly - skip: [] - submodules: false + autoupdate_schedule: weekly + skip: [] + submodules: false diff --git a/.prettierrc.js b/.prettierrc.cjs similarity index 100% rename from .prettierrc.js rename to .prettierrc.cjs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0842645 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,311 @@ +# CHANGELOG + + + +## v14.4.0 (2024-02-07) + +### Feature + +* feat: move FileUploader to Vue 3 (#50) ([`4c528c8`](https://github.com/agritheory/cloud_storage/commit/4c528c85922ba11a998390654c55ddac1bb1d621)) + +### Fix + +* fix: handle URL formats with hashtag characters (v15) (#55) ([`41d55a8`](https://github.com/agritheory/cloud_storage/commit/41d55a865f49ad57aa384b17bdec54ce41d42b8a)) + + +## v14.3.0 (2024-01-19) + +### Ci + +* ci: migrate to Python semantic release (#40) + +* ci: migrate to Python semantic release + +* ci: change db logger + +* chore: add type ignore for File doctype + +--------- + +Co-authored-by: Tyler Matteson <tyler@agritheory.com> ([`38398f0`](https://github.com/agritheory/cloud_storage/commit/38398f0031bbb93031828bc5cd46db508b9f6dc2)) + +### Feature + +* feat: versioned files (#36) + +* wip: versioning doctype + +* wip: file versioning + +* fix: remove print + +* wip: override upload dialog + +* feat: run name and content validations on adding file + +* feat: setup rename for name-conflicts + +* feat: show existing filenames for content hash conflicts + +* fix: error text on filename conflict + +* feat: override existing file instead of creating a new one + +* ci: update pre-commit config + +* test: fix write_file test + +* feat: add file association if content hash matches + +* fix: catch errors while stripping EXIF data from file + +* ci: update json-diff dependency + +* fix: allow error message translations + +* test: fix write_file test + +* fix: set values in database if a record exists already + +* test: fix write_file test + +* test: disable db logger in test + +* fix: avoid file size checks since that data may not exist + +* fix: allow skipping content hash conflicts + +* Revert "fix: allow skipping content hash conflicts" + +This reverts commit e731eb0c6ffa7bcfb3f78d3cf8032c0b5c40a450. + +* fix: optimize content validation logic + +* fix: check file permission with full object + +* fix: file permission for orphaned files + +* style: ignore type errors + +* fix: handle file attachments from existing library + +* fix: version table indexes when associations are removed + +* fix: add metadata to version and file association tables + +* fix: allow keeping file name structure + +* fix: index numbering issues for version table + +--------- + +Co-authored-by: Rohan Bansal <rohan@parsimony.com> ([`331c1ef`](https://github.com/agritheory/cloud_storage/commit/331c1ef19fadeda63b66768725ced4609ba96bde)) + +### Fix + +* fix: update dependencies for version 15 (#49) ([`a467f3f`](https://github.com/agritheory/cloud_storage/commit/a467f3fa78726adb59ec84477282cae7bbee9be5)) + +* fix: run file validation on drag-n-drop and capture events (#43) ([`605aae6`](https://github.com/agritheory/cloud_storage/commit/605aae6e7a2a69300e507015fddd0b6cd61221f4)) + +* fix: allow whitespace in file name (#42) ([`9d16ffa`](https://github.com/agritheory/cloud_storage/commit/9d16ffa631b56b9872da5d89091ec3ef5cd73629)) + + +## v14.2.3 (2023-08-02) + +### Fix + +* fix: set correct data type for permission check (#39) ([`114d579`](https://github.com/agritheory/cloud_storage/commit/114d579663ef4e8d1a76e01be3b395596c665511)) + + +## v14.2.2 (2023-07-24) + +### Fix + +* fix: explicitly use s3v4 protocol for compatibility with backblaze (#29) ([`c813f69`](https://github.com/agritheory/cloud_storage/commit/c813f69df93fac59f72af66311638ce3a4ea7d63)) + + +## v14.2.1 (2023-05-17) + +### Fix + +* fix: recursion bug while adding file associations (#33) + +* fix: recursion bug while adding file associations + +* test: add test for file associations ([`dd7ee93`](https://github.com/agritheory/cloud_storage/commit/dd7ee932fb4c6b18e4880791d4c016b2ee3caa87)) + + +## v14.2.0 (2023-05-04) + +### Feature + +* feat: get remote file content before sending email (#32) ([`d4e05e7`](https://github.com/agritheory/cloud_storage/commit/d4e05e798542a499463bbfcf07f2bd047080d98f)) + + +## v14.1.3 (2023-05-02) + +### Fix + +* fix: check if the app is installed before monkey-patch ([`7f5b036`](https://github.com/agritheory/cloud_storage/commit/7f5b036bf4d831a8aad20d8ec9eb58b4b01174fe)) + + +## v14.1.2 (2023-04-11) + +### Fix + +* fix: ignore user permissions when renaming file ([`ce5874f`](https://github.com/agritheory/cloud_storage/commit/ce5874fe15eac7da8d45d37114b4a859edc84e7d)) + + +## v14.1.1 (2023-04-04) + +### Unknown + +* Merge pull request #27 from agritheory/ci + +Ci ([`4dd5441`](https://github.com/agritheory/cloud_storage/commit/4dd5441bf6c5c5522dbae63c957ae4382ee50c8e)) + +* Merge branch 'version-14' into ci ([`49b61c6`](https://github.com/agritheory/cloud_storage/commit/49b61c6d4f48c38a3310ff615a84237d0068cbbc)) + + +## v14.1.0 (2023-04-04) + +### Chore + +* chore: get ci working with semantic release ([`8c5354b`](https://github.com/agritheory/cloud_storage/commit/8c5354b4351d6e825da0088f4b40a6a39cbd225e)) + +### Documentation + +* docs: fix routing in index.md ([`f6550e6`](https://github.com/agritheory/cloud_storage/commit/f6550e606efb77eeeb62db54497813472fd6d44a)) + +* docs: add index page, screen shots, sharing links info ([`04e9a3f`](https://github.com/agritheory/cloud_storage/commit/04e9a3fd64080c0af179c968716a9114a2373af3)) + +* docs: add configuration docs for backblaze ([`d6fefbc`](https://github.com/agritheory/cloud_storage/commit/d6fefbcda90260f9c81e8157a0384f53fc714b81)) + +* docs: add documentation for setting up the app (#9) + +* docs: add documentation for setting up the app + +* docs: grammar edits + +* docs: remove ERPNext from installation guides + +Co-authored-by: Heather Kusmierz <heather.kusmierz@gmail.com> ([`05812a4`](https://github.com/agritheory/cloud_storage/commit/05812a4240c3415601ccbe5e2e7c062987221c43)) + +### Feature + +* feat: add method for preservinvg customizations ([`88c94c6`](https://github.com/agritheory/cloud_storage/commit/88c94c6e7dd56a524e5358366b116f60d355983b)) + +* feat: shorten public urls, add public persistent sharing link API ([`139e370`](https://github.com/agritheory/cloud_storage/commit/139e3703887ef6b0cacc15d961e8065dedf38288)) + +* feat: allow attachments with Amazon S3 (#1) + +* feat: allow attachments with Amazon S3 + +* docs: add typings and documentation + +* fix: add client validations + +* test: add test for uploading files + +* test: add test for processing files for upload + +* test: add test for deleting files + +* ci: default ci branch to v14 + +* ci: add linter and release action + +* style: prettify code ([`a44ad57`](https://github.com/agritheory/cloud_storage/commit/a44ad572b097c178863b36482f82d02174173ccb)) + +* feat: initial commit ([`4fa4d56`](https://github.com/agritheory/cloud_storage/commit/4fa4d567b2569b2099c0932a2074e9f97426159a)) + +* feat: Initialize App ([`cd3b50f`](https://github.com/agritheory/cloud_storage/commit/cd3b50f262be1596ff65ad5b7192480c4e977a51)) + +### Fix + +* fix: release ([`f112fe7`](https://github.com/agritheory/cloud_storage/commit/f112fe7a6357ad0d0ea2355c2523be1931d4c577)) + +* fix: release CI (#26) ([`258e1c2`](https://github.com/agritheory/cloud_storage/commit/258e1c217e308aaf503c957d1c3b6ef3f690c5a9)) + +* fix: release CI ([`2ce184e`](https://github.com/agritheory/cloud_storage/commit/2ce184e555a95cebc91d45de35280b6ade8a28f8)) + +* fix: allow selection from library ([`aba4445`](https://github.com/agritheory/cloud_storage/commit/aba44451f55c6c5ae8f057b9a568f6cbfb7e681e)) + +* fix: user permission check ([`82cdf4d`](https://github.com/agritheory/cloud_storage/commit/82cdf4d35252874ca942f9eac716568278dfd1c4)) + +* fix: fsjd linter ([`c1e67b9`](https://github.com/agritheory/cloud_storage/commit/c1e67b9b3f716bea59403fa0cb9d4ac90e58b9cc)) + +* fix: code cleanup ([`904dba1`](https://github.com/agritheory/cloud_storage/commit/904dba1226ed68d165ba7a5eecdff541f53700d9)) + +* fix: mypy / return statements that don't actually return anything ([`218cb8b`](https://github.com/agritheory/cloud_storage/commit/218cb8b95508e417379a3c54ea87deccfc8019c4)) + +* fix: error in markdown/yml format (#10) ([`14e1b01`](https://github.com/agritheory/cloud_storage/commit/14e1b0122033b74dde66ea277d7765374f53ae96)) + +* fix: tab size editor config (#3) ([`46e6e00`](https://github.com/agritheory/cloud_storage/commit/46e6e005951c8a01faa15d6972c2e0f120fb23bd)) + +### Style + +* style: prettify code ([`f72ade2`](https://github.com/agritheory/cloud_storage/commit/f72ade2edff2135f6a05709ad2fcf21dbc900d2a)) + +### Test + +* test: file permissions ([`2e709f0`](https://github.com/agritheory/cloud_storage/commit/2e709f053b5433204cc5757f4270ddc614cdcb2c)) + +### Unknown + +* File association (#23) + +* fix: allow selection from library + +* wip: file association + + -[x] monkey patch `get_attachments` + -[x] add schema / child table + -[ ] add validation for file association + -[ ] rollback if associating to an existing file instead of creating a new record + +* feat: remove association with file instead of deleting unless it's the only one + +* feat: remove associations correctly, make storage details perm level 1 + +* chore: black and mypy + +* cust: allow preview to be hidden, change column names + +* docs: add multiple file association explanation + +* docs: address review (comments and typing) + +* wip: refactor to pytest ([`fbdb975`](https://github.com/agritheory/cloud_storage/commit/fbdb975a322f5529384f7ebdb1179188806d9938)) + +* wip: stub permissions test + +tThis isn't going to work as-is since it's checking a PO ([`310d627`](https://github.com/agritheory/cloud_storage/commit/310d627aea25d649cfa45dbc3924d68bcffd291e)) + +* docs/test: fix tests, add documentation ([`05a0e78`](https://github.com/agritheory/cloud_storage/commit/05a0e78058d6f26a751593b51310bbe483e14c64)) + +* feat/docs: appropriate time expiry and permissions checks ([`e0932f2`](https://github.com/agritheory/cloud_storage/commit/e0932f2bf1413aed9021a6251e4b5b6fe271dfde)) + +* Release fix (#6) + +* fix: tab size editor config + +* fix: add release to package.json + +* fix: try releaserc.json instead + +* fix: add name to package.json for release ([`cbdf818`](https://github.com/agritheory/cloud_storage/commit/cbdf81854b75951244f3533dd56d9e377b8f6792)) + +* Release fix (#5) + +* fix: tab size editor config + +* fix: add release to package.json + +* fix: try releaserc.json instead ([`a5536d5`](https://github.com/agritheory/cloud_storage/commit/a5536d5ec79e861e43a21607015e90091c3345a7)) + +* Release fix (#4) + +* fix: tab size editor config + +* fix: add release to package.json ([`fff7566`](https://github.com/agritheory/cloud_storage/commit/fff756620527ca1abf824c4214ce7c5abdf825ce)) diff --git a/cloud_storage/__init__.py b/cloud_storage/__init__.py index 48a6227..89980be 100644 --- a/cloud_storage/__init__.py +++ b/cloud_storage/__init__.py @@ -1,4 +1,4 @@ -__version__ = "14.0.0" +__version__ = "14.4.0" import frappe.desk.form.load diff --git a/cloud_storage/cloud_storage/custom/file.json b/cloud_storage/cloud_storage/custom/file.json index 11617aa..70a7500 100644 --- a/cloud_storage/cloud_storage/custom/file.json +++ b/cloud_storage/cloud_storage/custom/file.json @@ -1,468 +1,342 @@ { - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-02-03 12:03:51.204553", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "File", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "s3_key", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 23, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "uploaded_to_google_drive", - "is_system_generated": 0, - "is_virtual": 0, - "label": "S3 Key", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-07-06 02:04:01.131489", - "modified_by": "Administrator", - "module": null, - "name": "File-s3_key", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-02-15 14:00:22.073075", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "File", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "sharing_link", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 24, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "s3_key", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Sharing Link", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-07-06 02:04:01.272045", - "modified_by": "Administrator", - "module": null, - "name": "File-sharing_link", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "sort_options": 0, - "translatable": 1, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-03-20 12:48:48.264679", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "File", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "section_break_1lqhx", - "fieldtype": "Section Break", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 25, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "sharing_link", - "is_system_generated": 0, - "is_virtual": 0, - "label": null, - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-07-06 02:04:01.389708", - "modified_by": "Administrator", - "module": null, - "name": "File-section_break_1lqhx", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-03-20 12:48:48.360631", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "File", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "file_association", - "fieldtype": "Table", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 26, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "section_break_1lqhx", - "is_system_generated": 0, - "is_virtual": 0, - "label": "", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-07-06 02:04:01.499473", - "modified_by": "Administrator", - "module": null, - "name": "File-file_association", - "no_copy": 0, - "non_negative": 0, - "options": "File Association", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-07-06 05:39:34.115162", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "File", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "section_break_1xyhd", - "fieldtype": "Section Break", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 27, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "file_association", - "is_system_generated": 0, - "is_virtual": 0, - "label": null, - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-07-06 05:39:34.115162", - "modified_by": "Administrator", - "module": null, - "name": "File-section_break_1xyhd", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-07-06 05:39:57.024056", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "File", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "versions", - "fieldtype": "Table", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 28, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "section_break_1xyhd", - "is_system_generated": 0, - "is_virtual": 0, - "label": "", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-07-06 05:39:57.024056", - "modified_by": "Administrator", - "module": null, - "name": "File-versions", - "no_copy": 0, - "non_negative": 0, - "options": "File Version", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "File", - "links": [], - "property_setters": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-07-06 02:04:01.645547", - "default_value": null, - "doc_type": "File", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "preview", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-07-06 02:04:01.645547", - "modified_by": "Administrator", - "module": null, - "name": "File-preview-collapsible_depends_on", - "owner": "Administrator", - "property": "collapsible_depends_on", - "property_type": "Data", - "row_name": null, - "value": "eval:true" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-07-06 02:04:01.636840", - "default_value": null, - "doc_type": "File", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "preview", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-07-06 02:04:01.636840", - "modified_by": "Administrator", - "module": null, - "name": "File-preview-collapsible", - "owner": "Administrator", - "property": "collapsible", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-07-06 02:04:01.627758", - "default_value": null, - "doc_type": "File", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "section_break_8", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-07-06 02:04:01.627758", - "modified_by": "Administrator", - "module": null, - "name": "File-section_break_8-permlevel", - "owner": "Administrator", - "property": "permlevel", - "property_type": "Int", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-07-06 02:04:01.616923", - "default_value": null, - "doc_type": "File", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "section_break_8", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-07-06 02:04:01.616923", - "modified_by": "Administrator", - "module": null, - "name": "File-section_break_8-label", - "owner": "Administrator", - "property": "label", - "property_type": "Data", - "row_name": null, - "value": "Storage Details" - } - ], - "sync_on_migrate": 1 -} \ No newline at end of file + "custom_fields": [ + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2023-02-03 12:03:51.204553", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "File", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "s3_key", + "fieldtype": "Data", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 23, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "uploaded_to_google_drive", + "is_system_generated": 0, + "is_virtual": 0, + "label": "S3 Key", + "length": 0, + "mandatory_depends_on": null, + "modified": "2023-04-28 00:05:56.057223", + "modified_by": "Administrator", + "module": "Cloud Storage", + "name": "File-s3_key", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2023-02-15 14:00:22.073075", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "File", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "sharing_link", + "fieldtype": "Data", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 24, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "s3_key", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Sharing Link", + "length": 0, + "mandatory_depends_on": null, + "modified": "2023-04-28 00:05:56.148197", + "modified_by": "Administrator", + "module": "Cloud Storage", + "name": "File-sharing_link", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 1, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2023-03-20 12:48:48.264679", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "File", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "section_break_1lqhx", + "fieldtype": "Section Break", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 25, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "sharing_link", + "is_system_generated": 0, + "is_virtual": 0, + "label": null, + "length": 0, + "mandatory_depends_on": null, + "modified": "2023-04-28 00:05:56.255829", + "modified_by": "Administrator", + "module": "Cloud Storage", + "name": "File-section_break_1lqhx", + "no_copy": 0, + "non_negative": 0, + "options": null, + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2023-03-20 12:48:48.360631", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "File", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "file_association", + "fieldtype": "Table", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 26, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "section_break_1lqhx", + "is_system_generated": 0, + "is_virtual": 0, + "label": "", + "length": 0, + "mandatory_depends_on": null, + "modified": "2023-04-28 00:05:56.364321", + "modified_by": "Administrator", + "module": "Cloud Storage", + "name": "File-file_association", + "no_copy": 0, + "non_negative": 0, + "options": "File Association", + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0, + "width": null + } + ], + "custom_perms": [], + "doctype": "File", + "links": [], + "property_setters": [ + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-04-28 00:05:56.507961", + "default_value": null, + "doc_type": "File", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "section_break_8", + "idx": 0, + "is_system_generated": 0, + "modified": "2023-04-28 00:05:56.507961", + "modified_by": "Administrator", + "module": "Cloud Storage", + "name": "File-section_break_8-label", + "owner": "Administrator", + "property": "label", + "property_type": "Data", + "row_name": null, + "value": "Storage Details" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-04-28 00:05:56.499487", + "default_value": null, + "doc_type": "File", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "section_break_8", + "idx": 0, + "is_system_generated": 0, + "modified": "2023-04-28 00:05:56.499487", + "modified_by": "Administrator", + "module": "Cloud Storage", + "name": "File-section_break_8-permlevel", + "owner": "Administrator", + "property": "permlevel", + "property_type": "Int", + "row_name": null, + "value": "1" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-04-28 00:05:56.491129", + "default_value": null, + "doc_type": "File", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "preview", + "idx": 0, + "is_system_generated": 0, + "modified": "2023-04-28 00:05:56.491129", + "modified_by": "Administrator", + "module": "Cloud Storage", + "name": "File-preview-collapsible", + "owner": "Administrator", + "property": "collapsible", + "property_type": "Check", + "row_name": null, + "value": "1" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2023-04-28 00:05:56.481095", + "default_value": null, + "doc_type": "File", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "preview", + "idx": 0, + "is_system_generated": 0, + "modified": "2023-04-28 00:05:56.481095", + "modified_by": "Administrator", + "module": "Cloud Storage", + "name": "File-preview-collapsible_depends_on", + "owner": "Administrator", + "property": "collapsible_depends_on", + "property_type": "Data", + "row_name": null, + "value": "eval:true" + } + ], + "sync_on_migrate": 1 +} diff --git a/cloud_storage/cloud_storage/doctype/file_association/file_association.json b/cloud_storage/cloud_storage/doctype/file_association/file_association.json index e1fc88c..bac4e0e 100644 --- a/cloud_storage/cloud_storage/doctype/file_association/file_association.json +++ b/cloud_storage/cloud_storage/doctype/file_association/file_association.json @@ -1,59 +1,54 @@ { - "actions": [], - "creation": "2023-03-20 12:48:05.086487", - "default_view": "List", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "link_doctype", - "link_name", - "user", - "timestamp" - ], - "fields": [ - { - "fieldname": "link_doctype", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Document Type", - "options": "DocType", - "reqd": 1 - }, - { - "fieldname": "link_name", - "fieldtype": "Dynamic Link", - "in_list_view": 1, - "label": "Document Name", - "options": "link_doctype", - "reqd": 1 - }, - { - "fieldname": "timestamp", - "fieldtype": "Datetime", - "in_list_view": 1, - "label": "Timestamp", - "read_only": 1 - }, - { - "fieldname": "user", - "fieldtype": "Link", - "in_list_view": 1, - "label": "User", - "options": "User" - } - ], - "istable": 1, - "links": [], - "modified": "2023-09-27 23:42:57.356690", - "modified_by": "Administrator", - "module": "Cloud Storage", - "name": "File Association", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "track_changes": 1 -} \ No newline at end of file + "actions": [], + "creation": "2023-03-20 12:48:05.086487", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": ["link_doctype", "link_name", "user", "timestamp"], + "fields": [ + { + "fieldname": "link_doctype", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Document Type", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "link_name", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Document Name", + "options": "link_doctype", + "reqd": 1 + }, + { + "fieldname": "timestamp", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Timestamp", + "read_only": 1 + }, + { + "fieldname": "user", + "fieldtype": "Link", + "in_list_view": 1, + "label": "User", + "options": "User" + } + ], + "istable": 1, + "links": [], + "modified": "2023-09-27 23:42:57.356690", + "modified_by": "Administrator", + "module": "Cloud Storage", + "name": "File Association", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} diff --git a/cloud_storage/cloud_storage/doctype/file_version/file_version.json b/cloud_storage/cloud_storage/doctype/file_version/file_version.json index 0b57e6d..fdc6092 100644 --- a/cloud_storage/cloud_storage/doctype/file_version/file_version.json +++ b/cloud_storage/cloud_storage/doctype/file_version/file_version.json @@ -1,46 +1,42 @@ { - "actions": [], - "creation": "2023-07-06 05:38:35.891393", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "version", - "user", - "timestamp" - ], - "fields": [ - { - "fieldname": "version", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Version", - "read_only": 1 - }, - { - "fieldname": "timestamp", - "fieldtype": "Datetime", - "in_list_view": 1, - "label": "Timestamp", - "read_only": 1 - }, - { - "fieldname": "user", - "fieldtype": "Link", - "in_list_view": 1, - "label": "User", - "options": "User" - } - ], - "istable": 1, - "links": [], - "modified": "2023-09-27 23:45:54.628246", - "modified_by": "Administrator", - "module": "Cloud Storage", - "name": "File Version", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "states": [] -} \ No newline at end of file + "actions": [], + "creation": "2023-07-06 05:38:35.891393", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": ["version", "user", "timestamp"], + "fields": [ + { + "fieldname": "version", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Version", + "read_only": 1 + }, + { + "fieldname": "timestamp", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Timestamp", + "read_only": 1 + }, + { + "fieldname": "user", + "fieldtype": "Link", + "in_list_view": 1, + "label": "User", + "options": "User" + } + ], + "istable": 1, + "links": [], + "modified": "2023-09-27 23:45:54.628246", + "modified_by": "Administrator", + "module": "Cloud Storage", + "name": "File Version", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} diff --git a/cloud_storage/cloud_storage/overrides/file.py b/cloud_storage/cloud_storage/overrides/file.py index e37e61e..039b645 100644 --- a/cloud_storage/cloud_storage/overrides/file.py +++ b/cloud_storage/cloud_storage/overrides/file.py @@ -27,9 +27,48 @@ class CustomFile(File): + @File.is_remote_file.getter + def is_remote_file(self) -> bool: + if self.file_url: # type: ignore + return self.file_url.startswith(URL_PREFIXES) # type: ignore + return not self.content + def has_permission(self, ptype: Optional[str] = None, user: Optional[str] = None) -> bool: return has_permission(self, ptype, user) + def validate(self) -> None: + self.associate_files() + if self.flags.cloud_storage or self.flags.ignore_file_validate: + return + if not self.is_remote_file: + super().validate() + else: + self.validate_file_url() + + def after_insert(self) -> File: + if self.attached_to_doctype and self.attached_to_name and not self.file_association: # type: ignore + if not self.content_hash and "/api/method/retrieve" in self.file_url: # type: ignore + associated_doc = frappe.get_value("File", {"file_url": self.file_url}, "name") # type: ignore + else: + associated_doc = frappe.get_value( + "File", + {"content_hash": self.content_hash, "name": ["!=", self.name], "is_folder": False}, # type: ignore + ) + if associated_doc: + self.db_set( + "file_url", "" + ) # this is done to prevent deletion of the remote file with the delete_file hook + rename_doc( + self.doctype, + self.name, + associated_doc, + merge=True, + force=True, + show_alert=False, + ignore_permissions=True, + # validate=False, + ) + def on_trash(self) -> None: user_roles = frappe.get_roles(frappe.session.user) if ( @@ -64,7 +103,7 @@ def associate_files( path = get_file_path(self, client.folder) self.file_url = FILE_URL.format(path=path) if not self.content_hash and "/api/method/retrieve" in self.file_url: # type: ignore - associated_doc = frappe.get_value("File", {"file_url": self.file_url}, "name") + associated_doc = frappe.get_value("File", {"file_url": self.file_url}, "name") # type: ignore else: associated_doc = frappe.get_value( "File", @@ -76,7 +115,7 @@ def associate_files( existing_file.attached_to_name = attached_to_name self.content_hash = existing_file.content_hash # if a File exists already where this association should be, we continue validating that File at this time - # the original File will then be removed in the after insert hook + # the original File will then be removed in the after insert hook (also avoids recursion issues) self = existing_file existing_attachment = list( @@ -98,39 +137,6 @@ def associate_files( if associated_doc and associated_doc != self.name: self.save() - def validate(self) -> None: - self.associate_files() - if self.flags.cloud_storage or self.flags.ignore_file_validate: - return - if not self.is_remote_file: - super().validate() - else: - self.validate_file_url() - - def after_insert(self) -> File: - if self.attached_to_doctype and self.attached_to_name and not self.file_association: # type: ignore - if not self.content_hash and "/api/method/retrieve" in self.file_url: - associated_doc = frappe.get_value("File", {"file_url": self.file_url}, "name") - else: - associated_doc = frappe.get_value( - "File", - {"content_hash": self.content_hash, "name": ["!=", self.name], "is_folder": False}, - "name", - ) - if associated_doc: - self.db_set( - "file_url", "" - ) # this is done to prevent deletion of the remote file with the delete_file hook - rename_doc( - self.doctype, - self.name, - associated_doc, - merge=True, - force=True, - show_alert=False, - ignore_permissions=True, - ) - def add_file_version(self, version_id): self.append( "versions", @@ -161,12 +167,6 @@ def remove_file_association(self, dt: str, dn: str) -> None: association.idx = idx self.save() - @property - def is_remote_file(self) -> bool: - if self.file_url: - return self.file_url.startswith(URL_PREFIXES) - return not self.content - def get_content(self) -> bytes: if self.is_folder: frappe.throw(_("Cannot get file contents of a Folder")) @@ -394,8 +394,6 @@ def upload_file(file: File) -> File: except Exception as e: frappe.log_error("File Upload Error", e) file.db_set("s3_key", path) - if not file.name: - file.save() return file @@ -405,8 +403,8 @@ def get_file_path(file: File, folder: Optional[str] = None) -> str: fragments = [ folder, parent_doctype, - file.attached_to_name, - file.file_name, + file.attached_to_name.replace("#", "%23"), + file.file_name.replace("#", "%23"), ] valid_fragments: list[str] = list(filter(None, fragments)) @@ -486,7 +484,8 @@ def delete_file(file: File, **kwargs) -> File: except ClientError: frappe.throw(_("Access denied: Could not delete file")) except Exception as e: - frappe.log_error(str(e), "Cloud Storage Error: Cloud not delete file") + print(f"EXCEPTION: {e}") + frappe.log_error(str(e), "Cloud Storage Error: Could not delete file") return file diff --git a/cloud_storage/customize.py b/cloud_storage/customize.py index fd2fccd..56aae59 100644 --- a/cloud_storage/customize.py +++ b/cloud_storage/customize.py @@ -15,6 +15,8 @@ def load_customizations(): for file in files: customizations = json.loads(Path(file).read_text()) for field in customizations.get("custom_fields"): + if field.get("module") != "Cloud Storage": + continue existing_field = frappe.get_value("Custom Field", field.get("name")) custom_field = ( frappe.get_doc("Custom Field", field.get("name")) @@ -27,6 +29,8 @@ def load_customizations(): custom_field.flags.ignore_version = True custom_field.save() for prop in customizations.get("property_setters"): + if field.get("module") != "Cloud Storage": + continue property_setter = frappe.get_doc( { "name": prop.get("name"), diff --git a/cloud_storage/public/js/components/FilePreview.vue b/cloud_storage/public/js/components/FilePreview.vue index 68696d9..29c4178 100644 --- a/cloud_storage/public/js/components/FilePreview.vue +++ b/cloud_storage/public/js/components/FilePreview.vue @@ -28,13 +28,12 @@
{{ __('Optimize') }} {{ __('Private') }}
-
{{ file.error_message }} @@ -55,92 +54,92 @@
- -