diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..4146bb7 --- /dev/null +++ b/.babelrc @@ -0,0 +1,7 @@ +{ + "presets": [ + [ + "@babel/preset-env" + ] + ] +} \ No newline at end of file diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000..650742c --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,10 @@ +[production staging] +>5% +last 2 versions +Firefox ESR +not ie < 11 + +[development] +last 1 chrome version +last 1 firefox version +last 1 edge version diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..6748500 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,30 @@ +[run] +source = + arches_vue_utils/ + +omit = + */python?.?/* + */migrations/* + */settings*.py + */urls.py + */wsgi.py + */hosts.py + */celery.py + */__init__.py + +data_file = coverage/python/.coverage + +[report] +show_missing = true + +exclude_lines = + pragma: no cover + +[html] +directory = coverage/python/htmlcov + +[xml] +output = coverage/python/coverage.xml + +[json] +output = coverage/python/coverage.json diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..924ef88 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Known binary formats +*.png binary +*.jpg binary +*.ico binary +*.gif binary +*.ttf binary +*.woff binary +*.woff2 binary diff --git a/.github/actions/build-and-test-branch/action.yml b/.github/actions/build-and-test-branch/action.yml new file mode 100644 index 0000000..41921f2 --- /dev/null +++ b/.github/actions/build-and-test-branch/action.yml @@ -0,0 +1,109 @@ +name: 'Build and test branch' +description: 'Builds and tests a branch' +inputs: + branch-type: + description: 'String denoting either `target` or `feature` branch' + required: true + project-name: + description: 'String denoting the name of the project' + required: true + secrets: + description: 'Secrets from main.yml as JSON' +runs: + using: 'composite' + steps: + - name: Install Java, GDAL, and other system dependencies + run: | + sudo apt update + sudo apt-get install libxml2-dev libpq-dev openjdk-8-jdk libgdal-dev libxslt-dev + echo Postgres and ES dependencies installed + shell: bash + + - name: Set up Elasticsearch + uses: ankane/setup-elasticsearch@v1 + with: + elasticsearch-version: 8 + + - name: Install Python packages + run: | + python -m pip install --upgrade pip + pip install '.[dev]' + echo Python packages installed + shell: bash + + - name: Ensure frontend configuration files exist + run: | + python manage.py check + shell: bash + + - name: Install Arches applications + uses: ./.github/actions/install-arches-applications + with: + secrets: ${{ inputs.secrets }} + + - name: Install frontend dependencies + run: | + npm install + shell: bash + + - name: Webpack frontend files + run: | + npm run build_test + shell: bash + + - name: Check frontend formatting with prettier + run: | + npm run prettier:check + shell: bash + + - name: Check backend formatting with black + run: | + black . --check --exclude=node_modules + shell: bash + + - name: Check line endings on all but ontology (.xml) files + run: | + ! git ls-files --eol | grep -v '.xml' | grep 'w/crlf\|w/mixed' + shell: bash + + - name: Run frontend tests + run: | + npm run vitest + mv coverage/frontend/coverage.xml ${{ inputs.branch-type }}_branch_frontend_coverage.xml + shell: bash + + - name: Check for missing migrations + run: | + python manage.py makemigrations --check + shell: bash + + - name: Ensure previous Python coverage data is erased + run: | + coverage erase + shell: bash + + - name: Run Python unit tests + run: | + python -W default::DeprecationWarning -m coverage run manage.py test tests --settings="tests.test_settings" + shell: bash + + - name: Generate Python report coverage + run: | + coverage report + coverage json + mv coverage/python/coverage.json ${{ inputs.branch-type }}_branch_python_coverage.json + shell: bash + + - name: Upload frontend coverage report as artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.branch-type }}-branch-frontend-coverage-report + path: ${{ inputs.branch-type }}_branch_frontend_coverage.xml + overwrite: true + + - name: Upload Python coverage report as artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.branch-type }}-branch-python-coverage-report + path: ${{ inputs.branch-type }}_branch_python_coverage.json + overwrite: true diff --git a/.github/actions/install-arches-applications/action.yml b/.github/actions/install-arches-applications/action.yml new file mode 100644 index 0000000..703e33a --- /dev/null +++ b/.github/actions/install-arches-applications/action.yml @@ -0,0 +1,29 @@ +name: 'Install Arches Applications' +description: 'Manually edit this file to install all Arches Applications declared in settings.py, but not declared in `pyproject.toml`' +inputs: + secrets: + description: 'Secrets from main.yml as JSON' +runs: + using: 'composite' + steps: + + # Manually add any ARCHES_APPLICATIONS to this file if not already declared in `pyproject.toml`. + # Below is a template for adding an application in a private repository. + # Be sure to delete the `no-op step` if adding when updating this file. + + - name: No-op step to maintain workflow structure + run: echo "No-op step" + shell: bash + + # - name: Checkout ${my_arches_application_name} + # uses: actions/checkout@v4 + # with: + # repository: ${my_arches_application_repository}/${my_arches_application_name} + # token: ${{ fromJSON(inputs.secrets).${my_github_personal_access_token} }} + # path: ${my_arches_application_name} + + # - name: Install ${my_arches_application_name} + # run: | + # pip install ./${my_arches_application_name} + # echo ${my_arches_application_name} installed + # shell: bash diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..db51c4c --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,272 @@ +name: CI + +on: + # push: -- just run on PRs for now + pull_request: + workflow_dispatch: + +jobs: + build_target_branch: + runs-on: ubuntu-latest + + services: + postgres: + image: postgis/postgis:13-3.0 + env: + POSTGRES_PASSWORD: postgis + POSTGRES_DB: arches_vue_utils + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + strategy: + fail-fast: false + matrix: + python-version: ["3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + check-latest: true + + - name: Checkout into target branch + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }} + ref: ${{ github.event.pull_request.base.ref }} + path: . + + - name: Build and test branch + uses: ./.github/actions/build-and-test-branch + with: + secrets: ${{ toJSON(secrets) }} + project-name: 'arches_vue_utils' + branch-type: 'target' + + build_feature_branch: + runs-on: ubuntu-latest + + services: + postgres: + image: postgis/postgis:13-3.0 + env: + POSTGRES_PASSWORD: postgis + POSTGRES_DB: arches_vue_utils + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + strategy: + fail-fast: false + matrix: + python-version: ["3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + check-latest: true + + - name: Checkout into feature branch + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }} + ref: ${{ github.ref }} + path: . + + - name: Build and test branch + uses: ./.github/actions/build-and-test-branch + with: + secrets: ${{ toJSON(secrets) }} + project-name: 'arches_vue_utils' + branch-type: 'feature' + + check_frontend_coverage: + runs-on: ubuntu-latest + needs: [build_feature_branch, build_target_branch] + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' # Use the latest available version + check-latest: true + + - name: Download feature branch frontend coverage report artifact + uses: actions/download-artifact@v4 + with: + name: feature-branch-frontend-coverage-report + path: . + + - name: Extract feature branch frontend coverage data + shell: pwsh + run: | + [xml]$xml = Get-Content feature_branch_frontend_coverage.xml + $metrics = $xml.coverage.project.metrics + + $statements = [double]$metrics.statements + $coveredstatements = [double]$metrics.coveredstatements + $conditionals = [double]$metrics.conditionals + $coveredconditionals = [double]$metrics.coveredconditionals + $methods = [double]$metrics.methods + $coveredmethods = [double]$metrics.coveredmethods + $elements = [double]$metrics.elements + $coveredelements = [double]$metrics.coveredelements + + $statement_coverage = 0.0 + $conditional_coverage = 0.0 + $method_coverage = 0.0 + $element_coverage = 0.0 + + if ($statements -gt 0) { + $statement_coverage = ($coveredstatements / $statements) * 100 + } + if ($conditionals -gt 0) { + $conditional_coverage = ($coveredconditionals / $conditionals) * 100 + } + if ($methods -gt 0) { + $method_coverage = ($coveredmethods / $methods) * 100 + } + if ($elements -gt 0) { + $element_coverage = ($coveredelements / $elements) * 100 + } + + $nonZeroCount = 0 + $totalCoverage = 0.0 + + if ($statements -gt 0) { $nonZeroCount++; $totalCoverage += $statement_coverage } + if ($conditionals -gt 0) { $nonZeroCount++; $totalCoverage += $conditional_coverage } + if ($methods -gt 0) { $nonZeroCount++; $totalCoverage += $method_coverage } + if ($elements -gt 0) { $nonZeroCount++; $totalCoverage += $element_coverage } + + $feature_branch_frontend_coverage = 0.0 + if ($nonZeroCount -gt 0) { + $feature_branch_frontend_coverage = $totalCoverage / $nonZeroCount + } + + Write-Output "feature_branch_frontend_coverage=$feature_branch_frontend_coverage" | Out-File -Append $env:GITHUB_ENV + + - name: Download target branch frontend coverage report artifact + continue-on-error: true + uses: actions/download-artifact@v4 + with: + name: target-branch-frontend-coverage-report + path: . + + - name: Check if target branch frontend coverage report artifact exists + run: | + if [ -f target_branch_frontend_coverage.xml ]; then + echo "target_branch_frontend_coverage_artifact_exists=true" >> $GITHUB_ENV + else + echo "Target branch coverage not found. Defaulting to 0% coverage." + echo "target_branch_frontend_coverage_artifact_exists=false" >> $GITHUB_ENV + fi + + - name: Extract target branch frontend coverage data + if: ${{ env.target_branch_frontend_coverage_artifact_exists == 'true' }} + shell: pwsh + run: | + [xml]$xml = Get-Content target_branch_frontend_coverage.xml + $metrics = $xml.coverage.project.metrics + + $statements = [double]$metrics.statements + $coveredstatements = [double]$metrics.coveredstatements + $conditionals = [double]$metrics.conditionals + $coveredconditionals = [double]$metrics.coveredconditionals + $methods = [double]$metrics.methods + $coveredmethods = [double]$metrics.coveredmethods + $elements = [double]$metrics.elements + $coveredelements = [double]$metrics.coveredelements + + $statement_coverage = 0.0 + $conditional_coverage = 0.0 + $method_coverage = 0.0 + $element_coverage = 0.0 + + if ($statements -gt 0) { + $statement_coverage = ($coveredstatements / $statements) * 100 + } + if ($conditionals -gt 0) { + $conditional_coverage = ($coveredconditionals / $conditionals) * 100 + } + if ($methods -gt 0) { + $method_coverage = ($coveredmethods / $methods) * 100 + } + if ($elements -gt 0) { + $element_coverage = ($coveredelements / $elements) * 100 + } + + $nonZeroCount = 0 + $totalCoverage = 0.0 + + if ($statements -gt 0) { $nonZeroCount++; $totalCoverage += $statement_coverage } + if ($conditionals -gt 0) { $nonZeroCount++; $totalCoverage += $conditional_coverage } + if ($methods -gt 0) { $nonZeroCount++; $totalCoverage += $method_coverage } + if ($elements -gt 0) { $nonZeroCount++; $totalCoverage += $element_coverage } + + $target_branch_frontend_coverage = 0.0 + if ($nonZeroCount -gt 0) { + $target_branch_frontend_coverage = $totalCoverage / $nonZeroCount + } + + Write-Output "target_branch_frontend_coverage=$target_branch_frontend_coverage" | Out-File -Append $env:GITHUB_ENV + + - name: Compare frontend feature coverage with target coverage + if: github.event_name == 'pull_request' + run: | + feature_branch_frontend_coverage=${feature_branch_frontend_coverage} + target_branch_frontend_coverage=${target_branch_frontend_coverage:-0.0} + + # Compare feature coverage with target coverage using floating-point comparison + if awk -v feature="$feature_branch_frontend_coverage" -v target="$target_branch_frontend_coverage" 'BEGIN { exit (feature < target) ? 0 : 1 }'; then + echo "Coverage decreased from $target_branch_frontend_coverage% to $feature_branch_frontend_coverage%. Please add or update tests to increase coverage." + exit 1 + else + echo "Feature branch coverage ($feature_branch_frontend_coverage%) >= Target branch coverage ($target_branch_frontend_coverage%)." + fi + + check_python_coverage: + runs-on: ubuntu-latest + needs: [build_feature_branch, build_target_branch] + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' # Use the latest available version + check-latest: true + + - name: Download feature branch Python coverage report artifact + uses: actions/download-artifact@v4 + with: + name: feature-branch-python-coverage-report + path: . + + - name: Download target branch Python coverage report artifact + uses: actions/download-artifact@v4 + with: + name: target-branch-python-coverage-report + path: . + + - name: Compare Python feature coverage with target coverage + if: github.event_name == 'pull_request' + run: | + feature_branch_python_coverage=$(cat feature_branch_python_coverage.json | grep -o '"totals": {[^}]*' | grep -o '"percent_covered": [0-9.]*' | awk -F ': ' '{print $2}') + target_branch_python_coverage=$(cat target_branch_python_coverage.json | grep -o '"totals": {[^}]*' | grep -o '"percent_covered": [0-9.]*' | awk -F ': ' '{print $2}') + + # Compare feature coverage with target coverage using floating-point comparison + if awk -v feature="$feature_branch_python_coverage" -v target="$target_branch_python_coverage" 'BEGIN { exit (feature < target) ? 0 : 1 }'; then + echo "Coverage decreased from $target_branch_python_coverage% to $feature_branch_python_coverage%. Please add or update tests to increase coverage." + exit 1 + else + echo "Feature branch coverage ($feature_branch_python_coverage%) >= Target branch coverage ($target_branch_python_coverage%)." + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore index 82f9275..c4cf08f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,162 +1,19 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: +*.pyc *.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/latest/usage/project/#working-with-version-control -.pdm.toml -.pdm-python -.pdm-build/ - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +node_modules +coverage/ +arches_vue_utils/logs +arches_vue_utils/export_deliverables +arches_vue_utils/cantaloupe/* +arches_vue_utils/staticfiles +arches_vue_utils/media/packages +arches_vue_utils/media/build/ +arches_vue_utils/uploadedfiles/* +arches_vue_utils/settings_local.py +webpack-stats.json +.vscode/ +*.egg-info +.DS_STORE +CACHE +.tsconfig-paths.json +.frontend-configuration-settings.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..7b9d4ad --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,34 @@ +--- +repos: + + - repo: meta + hooks: + - id: check-hooks-apply + + - repo: https://github.com/psf/black + rev: 24.4.2 + hooks: + - id: black + args: [--quiet] + exclude: node_modules + + - repo: local + hooks: + - id: prettier + name: prettier + entry: npm run prettier:fix + language: system + files: arches_vue_utils/src + - id: eslint + name: eslint + entry: npm run eslint:fix + language: system + files: arches_vue_utils/src + - id: typescript + name: typescript + entry: bash -c 'npm run ts:check' + language: system + types_or: [ + "ts", + "vue", + ] diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..3baef1c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleAttributePerLine": true, + "tabWidth": 4 +} diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000..40db42c --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "stylelint-config-standard" +} diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..c210332 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +graft * diff --git a/README.md b/README.md index d03f9f8..af108c3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ # arches-vue-utils Vue components and utilities for Arches applications + +# Installation +- `pip install arches-vue-utils` +- Add `arches_vue_utils` to `INSTALLED_APPS` +- Add `arches-vue-utils` to pyproject.toml dependencies block diff --git a/arches_vue_utils/__init__.py b/arches_vue_utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/arches_vue_utils/apps.py b/arches_vue_utils/apps.py new file mode 100644 index 0000000..772c579 --- /dev/null +++ b/arches_vue_utils/apps.py @@ -0,0 +1,13 @@ +from django.apps import AppConfig +from django.conf import settings + +from arches.settings_utils import generate_frontend_configuration + + +class ArchesVueUtilsConfig(AppConfig): + name = "arches_vue_utils" + is_arches_application = True + + def ready(self): + if settings.APP_NAME.lower() == self.name: + generate_frontend_configuration() diff --git a/arches_vue_utils/celery.py b/arches_vue_utils/celery.py new file mode 100644 index 0000000..c0cb831 --- /dev/null +++ b/arches_vue_utils/celery.py @@ -0,0 +1,13 @@ +from __future__ import absolute_import, unicode_literals +import os +from celery import Celery + +import platform + +if platform.system().lower() == "windows": + os.environ.setdefault("FORKED_BY_MULTIPROCESSING", "1") + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "arches_vue_utils.settings") +app = Celery("arches_vue_utils") +app.config_from_object("django.conf:settings", namespace="CELERY") +app.autodiscover_tasks() diff --git a/arches_vue_utils/datatypes/__init__.py b/arches_vue_utils/datatypes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/arches_vue_utils/functions/__init__.py b/arches_vue_utils/functions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/arches_vue_utils/hosts.py b/arches_vue_utils/hosts.py new file mode 100644 index 0000000..9d948de --- /dev/null +++ b/arches_vue_utils/hosts.py @@ -0,0 +1,11 @@ +import re +from django_hosts import patterns, host + +host_patterns = patterns( + "", + host( + re.sub(r"_", r"-", r"arches_vue_utils"), + "arches_vue_utils.urls", + name="arches_vue_utils", + ), +) diff --git a/arches_vue_utils/locale/.gitkeep b/arches_vue_utils/locale/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/arches_vue_utils/management/__init__.py b/arches_vue_utils/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/arches_vue_utils/management/commands/__init__.py b/arches_vue_utils/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/arches_vue_utils/media/css/themes/_project.scss b/arches_vue_utils/media/css/themes/_project.scss new file mode 100644 index 0000000..c4e26c3 --- /dev/null +++ b/arches_vue_utils/media/css/themes/_project.scss @@ -0,0 +1,6 @@ +// use this file to override specific colors in the application +// see https://github.com/archesproject/arches/tree/master/arches/app/media/css/themes/_default.scss +// in the arches repo for the colors that can be overridden +// +// eg: +// $link-color: red; diff --git a/arches_vue_utils/media/img/favicon.png b/arches_vue_utils/media/img/favicon.png new file mode 100644 index 0000000..652c785 Binary files /dev/null and b/arches_vue_utils/media/img/favicon.png differ diff --git a/arches_vue_utils/media/js/.gitkeep b/arches_vue_utils/media/js/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/arches_vue_utils/search_components/__init__.py b/arches_vue_utils/search_components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/arches_vue_utils/search_indexes/__init__.py b/arches_vue_utils/search_indexes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/arches_vue_utils/settings.py b/arches_vue_utils/settings.py new file mode 100644 index 0000000..3ee19b8 --- /dev/null +++ b/arches_vue_utils/settings.py @@ -0,0 +1,435 @@ +""" +Django settings for arches_vue_utils project. +""" + +import os +import inspect +import semantic_version +from datetime import datetime, timedelta +from django.utils.translation import gettext_lazy as _ + +try: + from arches.settings import * +except ImportError: + pass + +APP_NAME = "arches_vue_utils" +APP_VERSION = semantic_version.Version(major=0, minor=0, patch=0) +APP_ROOT = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) + +WEBPACK_LOADER = { + "DEFAULT": { + "STATS_FILE": os.path.join(APP_ROOT, "..", "webpack/webpack-stats.json"), + }, +} + +DATATYPE_LOCATIONS.append("arches_vue_utils.datatypes") +FUNCTION_LOCATIONS.append("arches_vue_utils.functions") +ETL_MODULE_LOCATIONS.append("arches_vue_utils.etl_modules") +SEARCH_COMPONENT_LOCATIONS.append("arches_vue_utils.search_components") + +LOCALE_PATHS.insert(0, os.path.join(APP_ROOT, "locale")) + +FILE_TYPE_CHECKING = "lenient" +FILE_TYPES = [ + "bmp", + "gif", + "jpg", + "jpeg", + "json", + "pdf", + "png", + "psd", + "rtf", + "tif", + "tiff", + "xlsx", + "csv", + "zip", +] +FILENAME_GENERATOR = "arches.app.utils.storage_filename_generator.generate_filename" +UPLOADED_FILES_DIR = "uploadedfiles" + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure-ak-a8(i7%dh=0(t5wr#knc0a_z=vjdbg*k5)v5gx_nd^ajgx)l" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ROOT_URLCONF = "arches_vue_utils.urls" +ROOT_HOSTCONF = "arches_vue_utils.hosts" + +DEFAULT_HOST = "arches_vue_utils" + +# Modify this line as needed for your project to connect to elasticsearch with a password that you generate +ELASTICSEARCH_CONNECTION_OPTIONS = { + "request_timeout": 30, + "verify_certs": False, + "basic_auth": ("elastic", "E1asticSearchforArche5"), +} + +# If you need to connect to Elasticsearch via an API key instead of username/password, use the syntax below: +# ELASTICSEARCH_CONNECTION_OPTIONS = {"request_timeout": 30, "verify_certs": False, "api_key": ""} +# ELASTICSEARCH_CONNECTION_OPTIONS = {"request_timeout": 30, "verify_certs": False, "api_key": ("", "")} + +# Your Elasticsearch instance needs to be configured with xpack.security.enabled=true to use API keys - update elasticsearch.yml or .env file and restart. + +# Set the ELASTIC_PASSWORD environment variable in either the docker-compose.yml or .env file to the password you set for the elastic user, +# otherwise a random password will be generated. + +# API keys can be generated via the Elasticsearch API: https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html +# Or Kibana: https://www.elastic.co/guide/en/kibana/current/api-keys.html + +# a prefix to append to all elasticsearch indexes, note: must be lower case +ELASTICSEARCH_PREFIX = "arches_vue_utils" + +ELASTICSEARCH_CUSTOM_INDEXES = [] +# [{ +# 'module': 'arches_vue_utils.search_indexes.sample_index.SampleIndex', +# 'name': 'my_new_custom_index', <-- follow ES index naming rules +# 'should_update_asynchronously': False <-- denotes if asynchronously updating the index would affect custom functionality within the project. +# }] + +KIBANA_URL = "http://localhost:5601/" +KIBANA_CONFIG_BASEPATH = "kibana" # must match Kibana config.yml setting (server.basePath) but without the leading slash, +# also make sure to set server.rewriteBasePath: true + +LOAD_DEFAULT_ONTOLOGY = False +LOAD_PACKAGE_ONTOLOGIES = True + +# This is the namespace to use for export of data (for RDF/XML for example) +# It must point to the url where you host your site +# Make sure to use a trailing slash +ARCHES_NAMESPACE_FOR_DATA_EXPORT = "http://localhost:8000/" + +DATABASES = { + "default": { + "ATOMIC_REQUESTS": False, + "AUTOCOMMIT": True, + "CONN_MAX_AGE": 0, + "ENGINE": "django.contrib.gis.db.backends.postgis", + "HOST": "localhost", + "NAME": "arches_vue_utils", + "OPTIONS": {}, + "PASSWORD": "postgis", + "PORT": "5432", + "POSTGIS_TEMPLATE": "template_postgis", + "TEST": {"CHARSET": None, "COLLATION": None, "MIRROR": None, "NAME": None}, + "TIME_ZONE": None, + "USER": "postgres", + } +} + +SEARCH_THUMBNAILS = False + +INSTALLED_APPS = ( + "webpack_loader", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.gis", + "django_hosts", + "arches", + "arches.app.models", + "arches.management", + "guardian", + "captcha", + "revproxy", + "corsheaders", + "oauth2_provider", + "django_celery_results", + # "silk", + "arches_vue_utils", # Ensure the project is listed before any other arches applications +) + +# Placing this last ensures any templates provided by Arches Applications +# take precedence over core arches templates in arches/app/templates. +INSTALLED_APPS += ("arches.app",) + +MIDDLEWARE = [ + "corsheaders.middleware.CorsMiddleware", + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + #'arches.app.utils.middleware.TokenMiddleware', + "django.middleware.locale.LocaleMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "arches.app.utils.middleware.ModifyAuthorizationHeader", + "oauth2_provider.middleware.OAuth2TokenMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "arches.app.utils.middleware.SetAnonymousUser", + # "silk.middleware.SilkyMiddleware", +] + +MIDDLEWARE.insert( # this must resolve to first MIDDLEWARE entry + 0, "django_hosts.middleware.HostsRequestMiddleware" +) + +MIDDLEWARE.append( # this must resolve last MIDDLEWARE entry + "django_hosts.middleware.HostsResponseMiddleware" +) + +STATICFILES_DIRS = build_staticfiles_dirs(app_root=APP_ROOT) + +TEMPLATES = build_templates_config( + debug=DEBUG, + app_root=APP_ROOT, +) + +ALLOWED_HOSTS = [] + +SYSTEM_SETTINGS_LOCAL_PATH = os.path.join( + APP_ROOT, "system_settings", "System_Settings.json" +) +WSGI_APPLICATION = "arches_vue_utils.wsgi.application" + +# URL that handles the media served from MEDIA_ROOT, used for managing stored files. +# It must end in a slash if set to a non-empty value. +MEDIA_URL = "/files/" + +# Absolute filesystem path to the directory that will hold user-uploaded files. +MEDIA_ROOT = os.path.join(APP_ROOT) + +# URL prefix for static files. +# Example: "http://media.lawrence.com/static/" +STATIC_URL = "/static/" + +# Absolute path to the directory static files should be collected to. +# Don't put anything in this directory yourself; store your static files +# in apps' "static/" subdirectories and in STATICFILES_DIRS. +# Example: "/home/media/media.lawrence.com/static/" +STATIC_ROOT = os.path.join(APP_ROOT, "staticfiles") + +# when hosting Arches under a sub path set this value to the sub path eg : "/{sub_path}/" +FORCE_SCRIPT_NAME = None + +RESOURCE_IMPORT_LOG = os.path.join(APP_ROOT, "logs", "resource_import.log") +DEFAULT_RESOURCE_IMPORT_USER = {"username": "admin", "userid": 1} + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "console": { + "format": "%(asctime)s %(name)-12s %(levelname)-8s %(message)s", + }, + }, + "handlers": { + "file": { + "level": "WARNING", # DEBUG, INFO, WARNING, ERROR + "class": "logging.FileHandler", + "filename": os.path.join(APP_ROOT, "arches.log"), + "formatter": "console", + }, + "console": { + "level": "WARNING", + "class": "logging.StreamHandler", + "formatter": "console", + }, + }, + "loggers": { + "arches": { + "handlers": ["file", "console"], + "level": "WARNING", + "propagate": True, + } + }, +} + +# Rate limit for authentication views +# See options (including None or python callables): +# https://django-ratelimit.readthedocs.io/en/stable/rates.html#rates-chapter +RATE_LIMIT = "5/m" + +# Sets default max upload size to 15MB +DATA_UPLOAD_MAX_MEMORY_SIZE = 15728640 + +# Unique session cookie ensures that logins are treated separately for each app +SESSION_COOKIE_NAME = "arches_vue_utils" + +# For more info on configuring your cache: https://docs.djangoproject.com/en/2.2/topics/cache/ +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.dummy.DummyCache", + }, + "user_permission": { + "BACKEND": "django.core.cache.backends.db.DatabaseCache", + "LOCATION": "user_permission_cache", + }, +} + +# Hide nodes and cards in a report that have no data +HIDE_EMPTY_NODES_IN_REPORT = False + +BYPASS_UNIQUE_CONSTRAINT_TILE_VALIDATION = False +BYPASS_REQUIRED_VALUE_TILE_VALIDATION = False + +DATE_IMPORT_EXPORT_FORMAT = ( + "%Y-%m-%d" # Custom date format for dates imported from and exported to csv +) + +# This is used to indicate whether the data in the CSV and SHP exports should be +# ordered as seen in the resource cards or not. +EXPORT_DATA_FIELDS_IN_CARD_ORDER = False + +# Identify the usernames and duration (seconds) for which you want to cache the time wheel +CACHE_BY_USER = {"default": 3600 * 24, "anonymous": 3600 * 24} # 24hrs # 24hrs + +TILE_CACHE_TIMEOUT = 600 # seconds +CLUSTER_DISTANCE_MAX = 5000 # meters +GRAPH_MODEL_CACHE_TIMEOUT = None + +OAUTH_CLIENT_ID = "" #'9JCibwrWQ4hwuGn5fu2u1oRZSs9V6gK8Vu8hpRC4' + +APP_TITLE = "Arches | Heritage Data Management" +COPYRIGHT_TEXT = "All Rights Reserved." +COPYRIGHT_YEAR = "2019" + +ENABLE_CAPTCHA = False +# RECAPTCHA_PUBLIC_KEY = '' +# RECAPTCHA_PRIVATE_KEY = '' +# RECAPTCHA_USE_SSL = False +NOCAPTCHA = True +# RECAPTCHA_PROXY = 'http://127.0.0.1:8000' + +# EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' #<-- Only need to uncomment this for testing without an actual email server +# EMAIL_USE_TLS = True +# EMAIL_HOST = 'smtp.gmail.com' +EMAIL_HOST_USER = "xxxx@xxx.com" +# EMAIL_HOST_PASSWORD = 'xxxxxxx' +# EMAIL_PORT = 587 + +DEFAULT_FROM_EMAIL = EMAIL_HOST_USER + +CELERY_BROKER_URL = "" # RabbitMQ --> "amqp://guest:guest@localhost", Redis --> "redis://localhost:6379/0" +CELERY_ACCEPT_CONTENT = ["json"] +CELERY_RESULT_BACKEND = ( + "django-db" # Use 'django-cache' if you want to use your cache as your backend +) +CELERY_TASK_SERIALIZER = "json" + + +CELERY_SEARCH_EXPORT_EXPIRES = 24 * 3600 # seconds +CELERY_SEARCH_EXPORT_CHECK = 3600 # seconds + +CELERY_BEAT_SCHEDULE = { + "delete-expired-search-export": { + "task": "arches.app.tasks.delete_file", + "schedule": CELERY_SEARCH_EXPORT_CHECK, + }, + "notification": { + "task": "arches.app.tasks.message", + "schedule": CELERY_SEARCH_EXPORT_CHECK, + "args": ("Celery Beat is Running",), + }, +} + +# Set to True if you want to send celery tasks to the broker without being able to detect celery. +# This might be necessary if the worker pool is regulary fully active, with no idle workers, or if +# you need to run the celery task using solo pool (e.g. on Windows). You may need to provide another +# way of monitoring celery so you can detect the background task not being available. +CELERY_CHECK_ONLY_INSPECT_BROKER = False + +CANTALOUPE_DIR = os.path.join(ROOT_DIR, UPLOADED_FILES_DIR) +CANTALOUPE_HTTP_ENDPOINT = "http://localhost:8182/" + +ACCESSIBILITY_MODE = False + +RENDERERS = [ + { + "name": "imagereader", + "title": "Image Reader", + "description": "Displays most image file types", + "id": "5e05aa2e-5db0-4922-8938-b4d2b7919733", + "iconclass": "fa fa-camera", + "component": "views/components/cards/file-renderers/imagereader", + "ext": "", + "type": "image/*", + "exclude": "tif,tiff,psd", + }, + { + "name": "pdfreader", + "title": "PDF Reader", + "description": "Displays pdf files", + "id": "09dec059-1ee8-4fbd-85dd-c0ab0428aa94", + "iconclass": "fa fa-file", + "component": "views/components/cards/file-renderers/pdfreader", + "ext": "pdf", + "type": "application/pdf", + "exclude": "tif,tiff,psd", + }, +] + +# By setting RESTRICT_MEDIA_ACCESS to True, media file requests outside of Arches will checked against nodegroup permissions. +RESTRICT_MEDIA_ACCESS = False + +# By setting RESTRICT_CELERY_EXPORT_FOR_ANONYMOUS_USER to True, if the user is attempting +# to export search results above the SEARCH_EXPORT_IMMEDIATE_DOWNLOAD_THRESHOLD +# value and is not signed in with a user account then the request will not be allowed. +RESTRICT_CELERY_EXPORT_FOR_ANONYMOUS_USER = False + +# Dictionary containing any additional context items for customising email templates +EXTRA_EMAIL_CONTEXT = { + "salutation": _("Hi"), + "expiration": ( + datetime.now() + timedelta(seconds=CELERY_SEARCH_EXPORT_EXPIRES) + ).strftime("%A, %d %B %Y"), +} + +# see https://docs.djangoproject.com/en/1.9/topics/i18n/translation/#how-django-discovers-language-preference +# to see how LocaleMiddleware tries to determine the user's language preference +# (make sure to check your accept headers as they will override the LANGUAGE_CODE setting!) +# also see get_language_from_request in django.utils.translation.trans_real.py +# to see how the language code is derived in the actual code + +####### TO GENERATE .PO FILES DO THE FOLLOWING ######## +# run the following commands +# language codes used in the command should be in the form (which is slightly different +# form the form used in the LANGUAGE_CODE and LANGUAGES settings below): +# --local={countrycode}_{REGIONCODE} <-- countrycode is lowercase, regioncode is uppercase, also notice the underscore instead of hyphen +# commands to run (to generate files for "British English, German, and Spanish"): +# django-admin.py makemessages --ignore=env/* --local=de --local=en --local=en_GB --local=es --extension=htm,py +# django-admin.py compilemessages + + +# default language of the application +# language code needs to be all lower case with the form: +# {langcode}-{regioncode} eg: en, en-gb .... +# a list of language codes can be found here http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = "en" + +# list of languages to display in the language switcher, +# if left empty or with a single entry then the switch won't be displayed +# language codes need to be all lower case with the form: +# {langcode}-{regioncode} eg: en, en-gb .... +# a list of language codes can be found here http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGES = [ + # ('de', _('German')), + ("en", _("English")), + # ('en-gb', _('British English')), + # ('es', _('Spanish')), +] + +# override this to permenantly display/hide the language switcher +SHOW_LANGUAGE_SWITCH = len(LANGUAGES) > 1 + +try: + from .package_settings import * +except ImportError: + try: + from package_settings import * + except ImportError as e: + pass + +try: + from .settings_local import * +except ImportError as e: + try: + from settings_local import * + except ImportError as e: + pass diff --git a/arches_vue_utils/src/arches_vue_utils/declarations.d.ts b/arches_vue_utils/src/arches_vue_utils/declarations.d.ts new file mode 100644 index 0000000..6e9a067 --- /dev/null +++ b/arches_vue_utils/src/arches_vue_utils/declarations.d.ts @@ -0,0 +1,4 @@ +// declare untyped modules that have been added to your project in `package.json` +// Module homepage on npmjs.com uses logos "TS" or "DT" to indicate if typed + +import("@/arches/declarations.d.ts"); diff --git a/arches_vue_utils/src/arches_vue_utils/declarations.test.ts b/arches_vue_utils/src/arches_vue_utils/declarations.test.ts new file mode 100644 index 0000000..d351a7d --- /dev/null +++ b/arches_vue_utils/src/arches_vue_utils/declarations.test.ts @@ -0,0 +1 @@ +// empty test file to register coverage of `declarations.d.ts` diff --git a/arches_vue_utils/system_settings/readme.txt b/arches_vue_utils/system_settings/readme.txt new file mode 100644 index 0000000..eb9b884 --- /dev/null +++ b/arches_vue_utils/system_settings/readme.txt @@ -0,0 +1,8 @@ +This directory is used as the default directory for loading package system +settings data. This is also the default directory that will be used when exporting +system settings. Do not edit settings in this directory unless you are doing so for +development purposes. Instead, use the system settings manager in the Arches UI. +If you want to export your system settings from Arches, you can do so with the +following command: + +> python manage.py packages -o save_system_settings diff --git a/arches_vue_utils/urls.py b/arches_vue_utils/urls.py new file mode 100644 index 0000000..4f9c69b --- /dev/null +++ b/arches_vue_utils/urls.py @@ -0,0 +1,22 @@ +from django.conf import settings +from django.conf.urls.static import static +from django.conf.urls.i18n import i18n_patterns +from django.urls import include, path + +urlpatterns = [ + # project-level urls +] + +# Ensure Arches core urls are superseded by project-level urls +urlpatterns.append(path("", include("arches.urls"))) + +# Adds URL pattern to serve media files during development +urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + +# Only handle i18n routing in active project. This will still handle the routes provided by Arches core and Arches applications, +# but handling i18n routes in multiple places causes application errors. +if settings.ROOT_URLCONF == __name__: + if settings.SHOW_LANGUAGE_SWITCH is True: + urlpatterns = i18n_patterns(*urlpatterns) + + urlpatterns.append(path("i18n/", include("django.conf.urls.i18n"))) diff --git a/arches_vue_utils/wsgi.py b/arches_vue_utils/wsgi.py new file mode 100644 index 0000000..083c4fc --- /dev/null +++ b/arches_vue_utils/wsgi.py @@ -0,0 +1,39 @@ +""" +ARCHES - a program developed to inventory and manage immovable cultural heritage. +Copyright (C) 2013 J. Paul Getty Trust and World Monuments Fund + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +""" + +import os +import sys +import inspect + +path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) + +if path not in sys.path: + sys.path.append(path) + +# reverting back to the old style of setting the DJANGO_SETTINGS_MODULE env variable +# refer to the following blog post under the heading "Leaking of process environment variables." +# http://blog.dscpl.com.au/2012/10/requests-running-in-wrong-django.html +os.environ["DJANGO_SETTINGS_MODULE"] = "arches_vue_utils.settings" + +from django.core.wsgi import get_wsgi_application + +application = get_wsgi_application() + +from arches.app.models.system_settings import settings + +settings.update_from_db() diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..ab51240 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,43 @@ + +import js from "@eslint/js"; +import pluginVue from 'eslint-plugin-vue'; +import tseslint from 'typescript-eslint'; +import eslintConfigPrettier from "eslint-config-prettier"; + +import vueESLintParser from 'vue-eslint-parser'; + +export default [ + js.configs.recommended, + ...pluginVue.configs['flat/recommended'], + ...tseslint.configs.recommended, + eslintConfigPrettier, + { + "languageOptions": { + "globals": { + "define": false, + "require": false, + "window": false, + "console": false, + "history": false, + "location": false, + "Promise": false, + "setTimeout": false, + "URL": false, + "URLSearchParams": false, + "fetch": false + }, + "parser": vueESLintParser, + "parserOptions": { + "ecmaVersion": 11, + "sourceType": "module", + "requireConfigFile": false, + "parser": { + "ts": "@typescript-eslint/parser" + } + }, + }, + "rules": { + "semi": ["error", "always"], + }, + }, +]; \ No newline at end of file diff --git a/gettext.config.js b/gettext.config.js new file mode 100644 index 0000000..bde10aa --- /dev/null +++ b/gettext.config.js @@ -0,0 +1,43 @@ + +module.exports = { + input: { + path: "./arches_vue_utils/src", // only files in this directory are considered for extraction + include: ["**/*.vue", "**/*.ts"], // glob patterns to select files for extraction + exclude: [], // glob patterns to exclude files from extraction + jsExtractorOpts: [ // custom extractor keyword. default empty. + { + keyword: "__", // only extractor default keyword such as $gettext,use keyword to custom + options: { // see https://github.com/lukasgeiter/gettext-extractor + content: { + replaceNewLines: "\n", + }, + arguments: { + text: 0, + }, + }, + }, + { + keyword: "_n", // $ngettext + options: { + content: { + replaceNewLines: "\n", + }, + arguments: { + text: 0, + textPlural: 1, + }, + }, + }, + ], + compileTemplate: false, // do not compile