mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-24 18:04:39 -05:00
Compare commits
17 Commits
0db6f0fa16
...
bd998dac63
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bd998dac63 | ||
![]() |
aaaa6c1393 | ||
![]() |
bed82215a0 | ||
![]() |
f8aaa5cb32 | ||
![]() |
1e489a0666 | ||
![]() |
edc7181843 | ||
![]() |
89e5c08a1f | ||
![]() |
0faa9e8865 | ||
![]() |
f205c4d0e2 | ||
![]() |
344b2bc0eb | ||
![]() |
817aad7c8b | ||
![]() |
d82555e644 | ||
![]() |
f3e6ed56b9 | ||
![]() |
780d1c67e9 | ||
![]() |
2b72397a4d | ||
![]() |
6c13ffaa01 | ||
![]() |
eb8e124971 |
@ -76,18 +76,15 @@ RUN set -eux \
|
|||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install --yes --quiet --no-install-recommends ${RUNTIME_PACKAGES}
|
&& apt-get install --yes --quiet --no-install-recommends ${RUNTIME_PACKAGES}
|
||||||
|
|
||||||
ARG PYTHON_PACKAGES="\
|
ARG PYTHON_PACKAGES="ca-certificates"
|
||||||
python3 \
|
|
||||||
python3-pip \
|
|
||||||
python3-wheel \
|
|
||||||
pipenv \
|
|
||||||
ca-certificates"
|
|
||||||
|
|
||||||
RUN set -eux \
|
RUN set -eux \
|
||||||
echo "Installing python packages" \
|
echo "Installing python packages" \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install --yes --quiet ${PYTHON_PACKAGES}
|
&& apt-get install --yes --quiet ${PYTHON_PACKAGES}
|
||||||
|
|
||||||
|
COPY --from=ghcr.io/astral-sh/uv:0.6 /uv /bin/uv
|
||||||
|
|
||||||
RUN set -eux \
|
RUN set -eux \
|
||||||
&& echo "Installing pre-built updates" \
|
&& echo "Installing pre-built updates" \
|
||||||
&& echo "Installing qpdf ${QPDF_VERSION}" \
|
&& echo "Installing qpdf ${QPDF_VERSION}" \
|
||||||
@ -131,6 +128,8 @@ RUN set -eux \
|
|||||||
&& echo "Configuring ImageMagick" \
|
&& echo "Configuring ImageMagick" \
|
||||||
&& mv paperless-policy.xml /etc/ImageMagick-6/policy.xml
|
&& mv paperless-policy.xml /etc/ImageMagick-6/policy.xml
|
||||||
|
|
||||||
|
COPY --from=ghcr.io/astral-sh/uv:0.6 /uv /bin/uv
|
||||||
|
|
||||||
# Packages needed only for building a few quick Python
|
# Packages needed only for building a few quick Python
|
||||||
# dependencies
|
# dependencies
|
||||||
ARG BUILD_PACKAGES="\
|
ARG BUILD_PACKAGES="\
|
||||||
@ -140,11 +139,10 @@ ARG BUILD_PACKAGES="\
|
|||||||
libpq-dev \
|
libpq-dev \
|
||||||
# https://github.com/PyMySQL/mysqlclient#linux
|
# https://github.com/PyMySQL/mysqlclient#linux
|
||||||
default-libmysqlclient-dev \
|
default-libmysqlclient-dev \
|
||||||
pkg-config \
|
pkg-config"
|
||||||
pre-commit"
|
|
||||||
|
|
||||||
# hadolint ignore=DL3042
|
# hadolint ignore=DL3042
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip/,id=pip-cache \
|
RUN --mount=type=cache,target=/root/.cache/uv,id=pip-cache \
|
||||||
set -eux \
|
set -eux \
|
||||||
&& echo "Installing build system packages" \
|
&& echo "Installing build system packages" \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
@ -169,9 +167,6 @@ RUN set -eux \
|
|||||||
&& mkdir --parents --verbose /usr/src/paperless/paperless-ngx/.venv \
|
&& mkdir --parents --verbose /usr/src/paperless/paperless-ngx/.venv \
|
||||||
&& echo "Adjusting all permissions" \
|
&& echo "Adjusting all permissions" \
|
||||||
&& chown --from root:root --changes --recursive paperless:paperless /usr/src/paperless
|
&& chown --from root:root --changes --recursive paperless:paperless /usr/src/paperless
|
||||||
# && echo "Collecting static files" \
|
|
||||||
# && gosu paperless python3 manage.py collectstatic --clear --no-input --link \
|
|
||||||
# && gosu paperless python3 manage.py compilemessages
|
|
||||||
|
|
||||||
VOLUME ["/usr/src/paperless/paperless-ngx/data", \
|
VOLUME ["/usr/src/paperless/paperless-ngx/data", \
|
||||||
"/usr/src/paperless/paperless-ngx/media", \
|
"/usr/src/paperless/paperless-ngx/media", \
|
||||||
|
117
.devcontainer/README.md
Normal file
117
.devcontainer/README.md
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
# Paperless-ngx Development Environment
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Welcome to the Paperless-ngx development environment! This setup uses VSCode DevContainers to provide a consistent and seamless development experience.
|
||||||
|
|
||||||
|
### What are DevContainers?
|
||||||
|
|
||||||
|
DevContainers are a feature in VSCode that allows you to develop within a Docker container. This ensures that your development environment is consistent across different machines and setups. By defining a containerized environment, you can eliminate the "works on my machine" problem.
|
||||||
|
|
||||||
|
### Advantages of DevContainers
|
||||||
|
|
||||||
|
- **Consistency**: Same environment for all developers.
|
||||||
|
- **Isolation**: Separate development environment from your local machine.
|
||||||
|
- **Reproducibility**: Easily recreate the environment on any machine.
|
||||||
|
- **Pre-configured Tools**: Include all necessary tools and dependencies in the container.
|
||||||
|
|
||||||
|
## DevContainer Setup
|
||||||
|
|
||||||
|
The DevContainer configuration provides up all the necessary services for Paperless-ngx, including:
|
||||||
|
|
||||||
|
- Redis
|
||||||
|
- Gotenberg
|
||||||
|
- Tika
|
||||||
|
|
||||||
|
Data is stored using Docker volumes to ensure persistence across container restarts.
|
||||||
|
|
||||||
|
## Configuration Files
|
||||||
|
|
||||||
|
The setup includes debugging configurations (`launch.json`) and tasks (`tasks.json`) to help you manage and debug various parts of the project:
|
||||||
|
|
||||||
|
- **Backend Debugging:**
|
||||||
|
- `manage.py runserver`
|
||||||
|
- `manage.py document-consumer`
|
||||||
|
- `celery`
|
||||||
|
- **Maintenance Tasks:**
|
||||||
|
- Create superuser
|
||||||
|
- Run migrations
|
||||||
|
- Recreate virtual environment (`.venv` with `uv`)
|
||||||
|
- Compile frontend assets
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Step 1: Running the DevContainer
|
||||||
|
|
||||||
|
To start the DevContainer:
|
||||||
|
|
||||||
|
1. Open VSCode.
|
||||||
|
2. Open the project folder.
|
||||||
|
3. Open the command palette:
|
||||||
|
- **Windows/Linux**: `Ctrl+Shift+P`
|
||||||
|
- **Mac**: `Cmd+Shift+P`
|
||||||
|
4. Type and select `Dev Containers: Rebuild and Reopen in Container`.
|
||||||
|
|
||||||
|
VSCode will build and start the DevContainer environment.
|
||||||
|
|
||||||
|
### Step 2: Initial Setup
|
||||||
|
|
||||||
|
Once the DevContainer is up and running, perform the following steps:
|
||||||
|
|
||||||
|
1. **Compile Frontend Assets**:
|
||||||
|
|
||||||
|
- Open the command palette:
|
||||||
|
- **Windows/Linux**: `Ctrl+Shift+P`
|
||||||
|
- **Mac**: `Cmd+Shift+P`
|
||||||
|
- Select `Tasks: Run Task`.
|
||||||
|
- Choose `Frontend Compile`.
|
||||||
|
|
||||||
|
2. **Run Database Migrations**:
|
||||||
|
|
||||||
|
- Open the command palette:
|
||||||
|
- **Windows/Linux**: `Ctrl+Shift+P`
|
||||||
|
- **Mac**: `Cmd+Shift+P`
|
||||||
|
- Select `Tasks: Run Task`.
|
||||||
|
- Choose `Migrate Database`.
|
||||||
|
|
||||||
|
3. **Create Superuser**:
|
||||||
|
- Open the command palette:
|
||||||
|
- **Windows/Linux**: `Ctrl+Shift+P`
|
||||||
|
- **Mac**: `Cmd+Shift+P`
|
||||||
|
- Select `Tasks: Run Task`.
|
||||||
|
- Choose `Create Superuser`.
|
||||||
|
|
||||||
|
### Debugging and Running Services
|
||||||
|
|
||||||
|
You can start and debug backend services either as debugging sessions via `launch.json` or as tasks.
|
||||||
|
|
||||||
|
#### Using `launch.json`
|
||||||
|
|
||||||
|
1. Press `F5` or go to the **Run and Debug** view in VSCode.
|
||||||
|
2. Select the desired configuration:
|
||||||
|
- `Runserver`
|
||||||
|
- `Document Consumer`
|
||||||
|
- `Celery`
|
||||||
|
|
||||||
|
#### Using Tasks
|
||||||
|
|
||||||
|
1. Open the command palette:
|
||||||
|
- **Windows/Linux**: `Ctrl+Shift+P`
|
||||||
|
- **Mac**: `Cmd+Shift+P`
|
||||||
|
2. Select `Tasks: Run Task`.
|
||||||
|
3. Choose the desired task:
|
||||||
|
- `Runserver`
|
||||||
|
- `Document Consumer`
|
||||||
|
- `Celery`
|
||||||
|
|
||||||
|
### Additional Maintenance Tasks
|
||||||
|
|
||||||
|
Additional tasks are available for common maintenance operations:
|
||||||
|
|
||||||
|
- **Recreate .venv**: For setting up the virtual environment using `uv`.
|
||||||
|
- **Migrate Database**: To apply database migrations.
|
||||||
|
- **Create Superuser**: To create an admin user for the application.
|
||||||
|
|
||||||
|
## Let's Get Started!
|
||||||
|
|
||||||
|
Follow the steps above to get your development environment up and running. Happy coding!
|
@ -3,7 +3,7 @@
|
|||||||
"dockerComposeFile": "docker-compose.devcontainer.sqlite-tika.yml",
|
"dockerComposeFile": "docker-compose.devcontainer.sqlite-tika.yml",
|
||||||
"service": "paperless-development",
|
"service": "paperless-development",
|
||||||
"workspaceFolder": "/usr/src/paperless/paperless-ngx",
|
"workspaceFolder": "/usr/src/paperless/paperless-ngx",
|
||||||
"postCreateCommand": "pipenv install --dev && pipenv run pre-commit install",
|
"postCreateCommand": "/bin/bash -c uv sync --dev && uv run pre-commit install",
|
||||||
"customizations": {
|
"customizations": {
|
||||||
"vscode": {
|
"vscode": {
|
||||||
"extensions": [
|
"extensions": [
|
||||||
|
@ -43,7 +43,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ..:/usr/src/paperless/paperless-ngx:delegated
|
- ..:/usr/src/paperless/paperless-ngx:delegated
|
||||||
- ../.devcontainer/vscode:/usr/src/paperless/paperless-ngx/.vscode:delegated # VSCode config files
|
- ../.devcontainer/vscode:/usr/src/paperless/paperless-ngx/.vscode:delegated # VSCode config files
|
||||||
- pipenv:/usr/src/paperless/paperless-ngx/.venv
|
- virtualenv:/usr/src/paperless/paperless-ngx/.venv # Virtual environment persisted in volume
|
||||||
- /usr/src/paperless/paperless-ngx/src/documents/static/frontend # Static frontend files exist only in container
|
- /usr/src/paperless/paperless-ngx/src/documents/static/frontend # Static frontend files exist only in container
|
||||||
- /usr/src/paperless/paperless-ngx/src/.pytest_cache
|
- /usr/src/paperless/paperless-ngx/src/.pytest_cache
|
||||||
- /usr/src/paperless/paperless-ngx/.ruff_cache
|
- /usr/src/paperless/paperless-ngx/.ruff_cache
|
||||||
@ -80,4 +80,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pipenv:
|
data:
|
||||||
|
media:
|
||||||
|
redisdata:
|
||||||
|
virtualenv:
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"label": "Start: Celery Worker",
|
"label": "Start: Celery Worker",
|
||||||
"description": "Start the Celery Worker which processes background and consume tasks",
|
"description": "Start the Celery Worker which processes background and consume tasks",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "pipenv run celery --app paperless worker -l DEBUG",
|
"command": "uv run celery --app paperless worker -l DEBUG",
|
||||||
"isBackground": true,
|
"isBackground": true,
|
||||||
"options": {
|
"options": {
|
||||||
"cwd": "${workspaceFolder}/src"
|
"cwd": "${workspaceFolder}/src"
|
||||||
@ -61,7 +61,7 @@
|
|||||||
"label": "Start: Consumer Service (manage.py document_consumer)",
|
"label": "Start: Consumer Service (manage.py document_consumer)",
|
||||||
"description": "Start the Consumer Service which processes files from a directory",
|
"description": "Start the Consumer Service which processes files from a directory",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "pipenv run python manage.py document_consumer",
|
"command": "uv run python manage.py document_consumer",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"echo": true,
|
"echo": true,
|
||||||
@ -80,7 +80,7 @@
|
|||||||
"label": "Start: Backend Server (manage.py runserver)",
|
"label": "Start: Backend Server (manage.py runserver)",
|
||||||
"description": "Start the Backend Server which serves the Django API and the compiled Angular frontend",
|
"description": "Start the Backend Server which serves the Django API and the compiled Angular frontend",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "pipenv run python manage.py runserver",
|
"command": "uv run python manage.py runserver",
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"echo": true,
|
"echo": true,
|
||||||
@ -99,7 +99,7 @@
|
|||||||
"label": "Maintenance: manage.py migrate",
|
"label": "Maintenance: manage.py migrate",
|
||||||
"description": "Apply database migrations",
|
"description": "Apply database migrations",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "pipenv run python manage.py migrate",
|
"command": "uv run python manage.py migrate",
|
||||||
"group": "none",
|
"group": "none",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"echo": true,
|
"echo": true,
|
||||||
@ -118,7 +118,7 @@
|
|||||||
"label": "Maintenance: Build Documentation",
|
"label": "Maintenance: Build Documentation",
|
||||||
"description": "Build the documentation with MkDocs",
|
"description": "Build the documentation with MkDocs",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "pipenv run mkdocs build --config-file mkdocs.yml && pipenv run mkdocs serve",
|
"command": "uv run mkdocs build --config-file mkdocs.yml && uv run mkdocs serve",
|
||||||
"group": "none",
|
"group": "none",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"echo": true,
|
"echo": true,
|
||||||
@ -137,7 +137,7 @@
|
|||||||
"label": "Maintenance: manage.py createsuperuser",
|
"label": "Maintenance: manage.py createsuperuser",
|
||||||
"description": "Create a superuser",
|
"description": "Create a superuser",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "pipenv run python manage.py createsuperuser",
|
"command": "uv run python manage.py createsuperuser",
|
||||||
"group": "none",
|
"group": "none",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"echo": true,
|
"echo": true,
|
||||||
@ -156,7 +156,7 @@
|
|||||||
"label": "Maintenance: recreate .venv",
|
"label": "Maintenance: recreate .venv",
|
||||||
"description": "Recreate the python virtual environment and install python dependencies",
|
"description": "Recreate the python virtual environment and install python dependencies",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "rm -R -v .venv/* || pipenv install --dev",
|
"command": "rm -R -v .venv/* || uv install --dev",
|
||||||
"group": "none",
|
"group": "none",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"echo": true,
|
"echo": true,
|
||||||
|
@ -27,9 +27,6 @@ indent_style = space
|
|||||||
[*.md]
|
[*.md]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
|
||||||
[Pipfile.lock]
|
|
||||||
indent_style = space
|
|
||||||
|
|
||||||
# Tests don't get a line width restriction. It's still a good idea to follow
|
# Tests don't get a line width restriction. It's still a good idea to follow
|
||||||
# the 79 character rule, but in the interests of clarity, tests often need to
|
# the 79 character rule, but in the interests of clarity, tests often need to
|
||||||
# violate it.
|
# violate it.
|
||||||
|
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@ -1,6 +1,8 @@
|
|||||||
# https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates#package-ecosystem
|
# https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates#package-ecosystem
|
||||||
|
|
||||||
version: 2
|
version: 2
|
||||||
|
# Required for uv support for now
|
||||||
|
enable-beta-ecosystems: true
|
||||||
updates:
|
updates:
|
||||||
|
|
||||||
# Enable version updates for npm
|
# Enable version updates for npm
|
||||||
@ -34,9 +36,8 @@ updates:
|
|||||||
- "eslint"
|
- "eslint"
|
||||||
|
|
||||||
# Enable version updates for Python
|
# Enable version updates for Python
|
||||||
- package-ecosystem: "pip"
|
- package-ecosystem: "uv"
|
||||||
target-branch: "dev"
|
target-branch: "dev"
|
||||||
# Look for a `Pipfile` in the `root` directory
|
|
||||||
directory: "/"
|
directory: "/"
|
||||||
# Check for updates once a week
|
# Check for updates once a week
|
||||||
schedule:
|
schedule:
|
||||||
@ -53,6 +54,7 @@ updates:
|
|||||||
- "*pytest*"
|
- "*pytest*"
|
||||||
- "ruff"
|
- "ruff"
|
||||||
- "mkdocs-material"
|
- "mkdocs-material"
|
||||||
|
- "pre-commit*"
|
||||||
django:
|
django:
|
||||||
patterns:
|
patterns:
|
||||||
- "*django*"
|
- "*django*"
|
||||||
@ -63,6 +65,10 @@ updates:
|
|||||||
update-types:
|
update-types:
|
||||||
- "minor"
|
- "minor"
|
||||||
- "patch"
|
- "patch"
|
||||||
|
pre-built:
|
||||||
|
patterns:
|
||||||
|
- psycopg*
|
||||||
|
- zxing-cpp
|
||||||
|
|
||||||
# Enable updates for GitHub Actions
|
# Enable updates for GitHub Actions
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
|
146
.github/workflows/ci.yml
vendored
146
.github/workflows/ci.yml
vendored
@ -14,9 +14,7 @@ on:
|
|||||||
- 'translations**'
|
- 'translations**'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# This is the version of pipenv all the steps will use
|
DEFAULT_UV_VERSION: "0.6.x"
|
||||||
# If changing this, change Dockerfile
|
|
||||||
DEFAULT_PIP_ENV_VERSION: "2024.4.1"
|
|
||||||
# This is the default version of Python to use in most steps which aren't specific
|
# This is the default version of Python to use in most steps which aren't specific
|
||||||
DEFAULT_PYTHON_VERSION: "3.11"
|
DEFAULT_PYTHON_VERSION: "3.11"
|
||||||
|
|
||||||
@ -59,24 +57,25 @@ jobs:
|
|||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||||
cache: "pipenv"
|
|
||||||
cache-dependency-path: 'Pipfile.lock'
|
|
||||||
-
|
-
|
||||||
name: Install pipenv
|
name: Install uv
|
||||||
run: |
|
uses: astral-sh/setup-uv@v5
|
||||||
pip install --user pipenv==${{ env.DEFAULT_PIP_ENV_VERSION }}
|
with:
|
||||||
|
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||||
|
enable-cache: true
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install Python dependencies
|
||||||
run: |
|
run: |
|
||||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} sync --dev
|
uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen
|
||||||
-
|
|
||||||
name: List installed Python dependencies
|
|
||||||
run: |
|
|
||||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} run pip list
|
|
||||||
-
|
-
|
||||||
name: Make documentation
|
name: Make documentation
|
||||||
run: |
|
run: |
|
||||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} run mkdocs build --config-file ./mkdocs.yml
|
uv run \
|
||||||
|
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||||
|
--dev \
|
||||||
|
--frozen \
|
||||||
|
mkdocs build --config-file ./mkdocs.yml
|
||||||
-
|
-
|
||||||
name: Deploy documentation
|
name: Deploy documentation
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
@ -84,7 +83,11 @@ jobs:
|
|||||||
echo "docs.paperless-ngx.com" > "${{ github.workspace }}/docs/CNAME"
|
echo "docs.paperless-ngx.com" > "${{ github.workspace }}/docs/CNAME"
|
||||||
git config --global user.name "${{ github.actor }}"
|
git config --global user.name "${{ github.actor }}"
|
||||||
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
|
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
|
||||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} run mkdocs gh-deploy --force --no-history
|
uv run \
|
||||||
|
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||||
|
--dev \
|
||||||
|
--frozen \
|
||||||
|
mkdocs gh-deploy --force --no-history
|
||||||
-
|
-
|
||||||
name: Upload artifact
|
name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@ -117,12 +120,13 @@ jobs:
|
|||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "${{ matrix.python-version }}"
|
python-version: "${{ matrix.python-version }}"
|
||||||
cache: "pipenv"
|
|
||||||
cache-dependency-path: 'Pipfile.lock'
|
|
||||||
-
|
-
|
||||||
name: Install pipenv
|
name: Install uv
|
||||||
run: |
|
uses: astral-sh/setup-uv@v5
|
||||||
pip install --user pipenv==${{ env.DEFAULT_PIP_ENV_VERSION }}
|
with:
|
||||||
|
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||||
|
enable-cache: true
|
||||||
|
python-version: ${{ steps.setup-python.outputs.python-version }}
|
||||||
-
|
-
|
||||||
name: Install system dependencies
|
name: Install system dependencies
|
||||||
run: |
|
run: |
|
||||||
@ -135,12 +139,14 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Install Python dependencies
|
name: Install Python dependencies
|
||||||
run: |
|
run: |
|
||||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} run python --version
|
uv sync \
|
||||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} sync --dev
|
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||||
|
--group testing \
|
||||||
|
--frozen
|
||||||
-
|
-
|
||||||
name: List installed Python dependencies
|
name: List installed Python dependencies
|
||||||
run: |
|
run: |
|
||||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} run pip list
|
uv pip list
|
||||||
-
|
-
|
||||||
name: Tests
|
name: Tests
|
||||||
env:
|
env:
|
||||||
@ -150,17 +156,22 @@ jobs:
|
|||||||
PAPERLESS_MAIL_TEST_USER: ${{ secrets.TEST_MAIL_USER }}
|
PAPERLESS_MAIL_TEST_USER: ${{ secrets.TEST_MAIL_USER }}
|
||||||
PAPERLESS_MAIL_TEST_PASSWD: ${{ secrets.TEST_MAIL_PASSWD }}
|
PAPERLESS_MAIL_TEST_PASSWD: ${{ secrets.TEST_MAIL_PASSWD }}
|
||||||
run: |
|
run: |
|
||||||
cd src/
|
uv run \
|
||||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} run pytest -ra
|
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||||
|
--dev \
|
||||||
|
--frozen \
|
||||||
|
pytest
|
||||||
-
|
-
|
||||||
name: Upload coverage
|
name: Upload coverage
|
||||||
if: ${{ matrix.python-version == env.DEFAULT_PYTHON_VERSION }}
|
if: ${{ matrix.python-version == env.DEFAULT_PYTHON_VERSION }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: backend-coverage-report
|
name: backend-coverage-report
|
||||||
path: src/coverage.xml
|
path: |
|
||||||
|
coverage.xml
|
||||||
|
junit.xml
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
if-no-files-found: warn
|
if-no-files-found: error
|
||||||
-
|
-
|
||||||
name: Stop containers
|
name: Stop containers
|
||||||
if: always()
|
if: always()
|
||||||
@ -234,6 +245,8 @@ jobs:
|
|||||||
run: cd src-ui && npm run lint
|
run: cd src-ui && npm run lint
|
||||||
-
|
-
|
||||||
name: Run Jest unit tests
|
name: Run Jest unit tests
|
||||||
|
env:
|
||||||
|
JEST_JUNIT_OUTPUT_FILE: junit-report-${{ matrix.shard-index }}.xml
|
||||||
run: cd src-ui && npm run test -- --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
run: cd src-ui && npm run test -- --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
||||||
-
|
-
|
||||||
name: Upload Jest coverage
|
name: Upload Jest coverage
|
||||||
@ -246,7 +259,7 @@ jobs:
|
|||||||
src-ui/coverage/lcov.info
|
src-ui/coverage/lcov.info
|
||||||
src-ui/coverage/clover.xml
|
src-ui/coverage/clover.xml
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
if-no-files-found: warn
|
if-no-files-found: error
|
||||||
-
|
-
|
||||||
name: Run Playwright e2e tests
|
name: Run Playwright e2e tests
|
||||||
run: cd src-ui && npx playwright test --shard ${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
run: cd src-ui && npx playwright test --shard ${{ matrix.shard-index }}/${{ matrix.shard-count }}
|
||||||
@ -258,6 +271,14 @@ jobs:
|
|||||||
name: playwright-report-${{ matrix.shard-index }}
|
name: playwright-report-${{ matrix.shard-index }}
|
||||||
path: src-ui/playwright-report
|
path: src-ui/playwright-report
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
-
|
||||||
|
name: Upload frontend test results
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: junit-report-${{ matrix.shard-index }}
|
||||||
|
path: src-ui/junit-report-${{ matrix.shard-index }}.xml
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
tests-coverage-upload:
|
tests-coverage-upload:
|
||||||
name: "Upload to Codecov"
|
name: "Upload to Codecov"
|
||||||
@ -281,6 +302,13 @@ jobs:
|
|||||||
path: src-ui/coverage/
|
path: src-ui/coverage/
|
||||||
pattern: playwright-report-*
|
pattern: playwright-report-*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
-
|
||||||
|
name: Download frontend test results
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: src-ui/junit/
|
||||||
|
pattern: junit-report-*
|
||||||
|
merge-multiple: true
|
||||||
-
|
-
|
||||||
name: Upload frontend coverage to Codecov
|
name: Upload frontend coverage to Codecov
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@v5
|
||||||
@ -291,6 +319,14 @@ jobs:
|
|||||||
directory: src-ui/coverage/
|
directory: src-ui/coverage/
|
||||||
# dont include backend coverage files here
|
# dont include backend coverage files here
|
||||||
files: '!coverage.xml'
|
files: '!coverage.xml'
|
||||||
|
-
|
||||||
|
name: Upload frontend test results to Codecov
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: codecov/test-results-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
flags: frontend
|
||||||
|
directory: src-ui/junit/
|
||||||
-
|
-
|
||||||
name: Download backend coverage
|
name: Download backend coverage
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
@ -306,6 +342,14 @@ jobs:
|
|||||||
# future expansion
|
# future expansion
|
||||||
flags: backend
|
flags: backend
|
||||||
directory: src/
|
directory: src/
|
||||||
|
-
|
||||||
|
name: Upload backend test results to Codecov
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
uses: codecov/test-results-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
flags: backend
|
||||||
|
directory: src/
|
||||||
-
|
-
|
||||||
name: Use Node.js 20
|
name: Use Node.js 20
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
@ -472,16 +516,17 @@ jobs:
|
|||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||||
cache: "pipenv"
|
|
||||||
cache-dependency-path: 'Pipfile.lock'
|
|
||||||
-
|
-
|
||||||
name: Install pipenv + tools
|
name: Install uv
|
||||||
run: |
|
uses: astral-sh/setup-uv@v5
|
||||||
pip install --upgrade --user pipenv==${{ env.DEFAULT_PIP_ENV_VERSION }} setuptools wheel
|
with:
|
||||||
|
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||||
|
enable-cache: true
|
||||||
|
python-version: ${{ steps.setup-python.outputs.python-version }}
|
||||||
-
|
-
|
||||||
name: Install Python dependencies
|
name: Install Python dependencies
|
||||||
run: |
|
run: |
|
||||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} sync --dev
|
uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen
|
||||||
-
|
-
|
||||||
name: Install system dependencies
|
name: Install system dependencies
|
||||||
run: |
|
run: |
|
||||||
@ -502,17 +547,21 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Generate requirements file
|
name: Generate requirements file
|
||||||
run: |
|
run: |
|
||||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} requirements > requirements.txt
|
uv export --quiet --no-dev --all-extras --format requirements-txt --output-file requirements.txt
|
||||||
-
|
-
|
||||||
name: Compile messages
|
name: Compile messages
|
||||||
run: |
|
run: |
|
||||||
cd src/
|
cd src/
|
||||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} run python3 manage.py compilemessages
|
uv run \
|
||||||
|
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||||
|
manage.py compilemessages
|
||||||
-
|
-
|
||||||
name: Collect static files
|
name: Collect static files
|
||||||
run: |
|
run: |
|
||||||
cd src/
|
cd src/
|
||||||
pipenv --python ${{ steps.setup-python.outputs.python-version }} run python3 manage.py collectstatic --no-input
|
uv run \
|
||||||
|
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||||
|
manage.py collectstatic --no-input
|
||||||
-
|
-
|
||||||
name: Move files
|
name: Move files
|
||||||
run: |
|
run: |
|
||||||
@ -528,8 +577,8 @@ jobs:
|
|||||||
for file_name in .dockerignore \
|
for file_name in .dockerignore \
|
||||||
.env \
|
.env \
|
||||||
Dockerfile \
|
Dockerfile \
|
||||||
Pipfile \
|
pyproject.toml \
|
||||||
Pipfile.lock \
|
uv.lock \
|
||||||
requirements.txt \
|
requirements.txt \
|
||||||
LICENSE \
|
LICENSE \
|
||||||
README.md \
|
README.md \
|
||||||
@ -631,15 +680,17 @@ jobs:
|
|||||||
ref: main
|
ref: main
|
||||||
-
|
-
|
||||||
name: Set up Python
|
name: Set up Python
|
||||||
|
id: setup-python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||||
cache: "pipenv"
|
|
||||||
cache-dependency-path: 'Pipfile.lock'
|
|
||||||
-
|
-
|
||||||
name: Install pipenv + tools
|
name: Install uv
|
||||||
run: |
|
uses: astral-sh/setup-uv@v5
|
||||||
pip install --upgrade --user pipenv==${{ env.DEFAULT_PIP_ENV_VERSION }} setuptools wheel
|
with:
|
||||||
|
version: ${{ env.DEFAULT_UV_VERSION }}
|
||||||
|
enable-cache: true
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
|
||||||
-
|
-
|
||||||
name: Append Changelog to docs
|
name: Append Changelog to docs
|
||||||
id: append-Changelog
|
id: append-Changelog
|
||||||
@ -655,7 +706,10 @@ jobs:
|
|||||||
CURRENT_CHANGELOG=`tail --lines +2 changelog.md`
|
CURRENT_CHANGELOG=`tail --lines +2 changelog.md`
|
||||||
echo -e "$CURRENT_CHANGELOG" >> changelog-new.md
|
echo -e "$CURRENT_CHANGELOG" >> changelog-new.md
|
||||||
mv changelog-new.md changelog.md
|
mv changelog-new.md changelog.md
|
||||||
pipenv run pre-commit run --files changelog.md || true
|
uv run \
|
||||||
|
--python ${{ steps.setup-python.outputs.python-version }} \
|
||||||
|
--dev \
|
||||||
|
pre-commit run --files changelog.md || true
|
||||||
git config --global user.name "github-actions"
|
git config --global user.name "github-actions"
|
||||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
git commit -am "Changelog ${{ needs.publish-release.outputs.version }} - GHA"
|
git commit -am "Changelog ${{ needs.publish-release.outputs.version }} - GHA"
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -44,6 +44,7 @@ nosetests.xml
|
|||||||
coverage.xml
|
coverage.xml
|
||||||
*,cover
|
*,cover
|
||||||
.pytest_cache
|
.pytest_cache
|
||||||
|
junit.xml
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
|
@ -45,7 +45,6 @@ repos:
|
|||||||
- javascript
|
- javascript
|
||||||
- ts
|
- ts
|
||||||
- markdown
|
- markdown
|
||||||
exclude: "(^Pipfile\\.lock$)"
|
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- prettier@3.3.3
|
- prettier@3.3.3
|
||||||
- 'prettier-plugin-organize-imports@4.1.0'
|
- 'prettier-plugin-organize-imports@4.1.0'
|
||||||
@ -55,6 +54,10 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
|
rev: "v2.5.1"
|
||||||
|
hooks:
|
||||||
|
- id: pyproject-fmt
|
||||||
# Dockerfile hooks
|
# Dockerfile hooks
|
||||||
- repo: https://github.com/AleksaC/hadolint-py
|
- repo: https://github.com/AleksaC/hadolint-py
|
||||||
rev: v2.12.0.3
|
rev: v2.12.0.3
|
||||||
|
@ -1 +0,0 @@
|
|||||||
3.10.15
|
|
87
.ruff.toml
87
.ruff.toml
@ -1,87 +0,0 @@
|
|||||||
fix = true
|
|
||||||
line-length = 88
|
|
||||||
respect-gitignore = true
|
|
||||||
src = ["src"]
|
|
||||||
target-version = "py310"
|
|
||||||
output-format = "grouped"
|
|
||||||
show-fixes = true
|
|
||||||
|
|
||||||
# https://docs.astral.sh/ruff/settings/
|
|
||||||
# https://docs.astral.sh/ruff/rules/
|
|
||||||
[lint]
|
|
||||||
extend-select = [
|
|
||||||
"W", # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w
|
|
||||||
"I", # https://docs.astral.sh/ruff/rules/#isort-i
|
|
||||||
"UP", # https://docs.astral.sh/ruff/rules/#pyupgrade-up
|
|
||||||
"COM", # https://docs.astral.sh/ruff/rules/#flake8-commas-com
|
|
||||||
"DJ", # https://docs.astral.sh/ruff/rules/#flake8-django-dj
|
|
||||||
"EXE", # https://docs.astral.sh/ruff/rules/#flake8-executable-exe
|
|
||||||
"ISC", # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc
|
|
||||||
"ICN", # https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn
|
|
||||||
"G201", # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g
|
|
||||||
"INP", # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp
|
|
||||||
"PIE", # https://docs.astral.sh/ruff/rules/#flake8-pie-pie
|
|
||||||
"Q", # https://docs.astral.sh/ruff/rules/#flake8-quotes-q
|
|
||||||
"RSE", # https://docs.astral.sh/ruff/rules/#flake8-raise-rse
|
|
||||||
"T20", # https://docs.astral.sh/ruff/rules/#flake8-print-t20
|
|
||||||
"SIM", # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim
|
|
||||||
"TID", # https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid
|
|
||||||
"TC", # https://docs.astral.sh/ruff/rules/#flake8-type-checking-tc
|
|
||||||
"PLC", # https://docs.astral.sh/ruff/rules/#pylint-pl
|
|
||||||
"PLE", # https://docs.astral.sh/ruff/rules/#pylint-pl
|
|
||||||
"RUF", # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf
|
|
||||||
"FLY", # https://docs.astral.sh/ruff/rules/#flynt-fly
|
|
||||||
"PTH", # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth
|
|
||||||
"FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt
|
|
||||||
]
|
|
||||||
ignore = ["DJ001", "SIM105", "RUF012"]
|
|
||||||
|
|
||||||
[lint.per-file-ignores]
|
|
||||||
".github/scripts/*.py" = ["E501", "INP001", "SIM117"]
|
|
||||||
"docker/wait-for-redis.py" = ["INP001", "T201"]
|
|
||||||
"src/documents/file_handling.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/management/commands/document_consumer.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/management/commands/document_exporter.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/migrations/0012_auto_20160305_0040.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/migrations/0014_document_checksum.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/migrations/1003_mime_types.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/migrations/1012_fix_archive_files.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/models.py" = ["SIM115", "PTH"] # TODO PTH Enable & remove
|
|
||||||
"src/documents/parsers.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/signals/handlers.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/tasks.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/tests/test_api_app_config.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/tests/test_classifier.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/tests/test_consumer.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/tests/test_file_handling.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/tests/test_management.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/tests/test_management_consumer.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/tests/test_management_exporter.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/tests/test_management_thumbnails.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/tests/test_migration_archive_files.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/tests/test_migration_document_pages_count.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/tests/test_migration_mime_type.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/tests/test_sanity_check.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/tests/test_tasks.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/tests/test_views.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/documents/views.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/paperless/checks.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/paperless/settings.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/paperless/tests/test_checks.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/paperless/urls.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/paperless/views.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/paperless_mail/mail.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/paperless_mail/preprocessor.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/paperless_tesseract/parsers.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/paperless_tesseract/tests/test_parser.py" = ["RUF001", "PTH"] # TODO PTH Enable & remove
|
|
||||||
"src/paperless_tika/tests/test_live_tika.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
"src/paperless_tika/tests/test_tika_parser.py" = ["PTH"] # TODO Enable & remove
|
|
||||||
# Testing
|
|
||||||
"*/tests/*.py" = ["E501", "SIM117"]
|
|
||||||
# Migrations
|
|
||||||
"*/migrations/*.py" = ["E501", "SIM", "T201"]
|
|
||||||
# Docker specific
|
|
||||||
"docker/rootfs/usr/local/bin/wait-for-redis.py" = ["INP001", "T201"]
|
|
||||||
|
|
||||||
[lint.isort]
|
|
||||||
force-single-line = true
|
|
@ -5,5 +5,6 @@
|
|||||||
/src-ui/ @paperless-ngx/frontend
|
/src-ui/ @paperless-ngx/frontend
|
||||||
|
|
||||||
/src/ @paperless-ngx/backend
|
/src/ @paperless-ngx/backend
|
||||||
Pipfile* @paperless-ngx/backend
|
pyproject.toml @paperless-ngx/backend
|
||||||
|
uv.lock @paperless-ngx/backend
|
||||||
*.py @paperless-ngx/backend
|
*.py @paperless-ngx/backend
|
||||||
|
45
Dockerfile
45
Dockerfile
@ -26,28 +26,11 @@ esac
|
|||||||
RUN set -eux \
|
RUN set -eux \
|
||||||
&& ./node_modules/.bin/ng build --configuration production
|
&& ./node_modules/.bin/ng build --configuration production
|
||||||
|
|
||||||
# Stage: pipenv-base
|
|
||||||
# Purpose: Generates a requirements.txt file for building
|
|
||||||
# Comments:
|
|
||||||
# - pipenv dependencies are not left in the final image
|
|
||||||
# - pipenv can't touch the final image somehow
|
|
||||||
FROM --platform=$BUILDPLATFORM docker.io/python:3.12-alpine AS pipenv-base
|
|
||||||
|
|
||||||
WORKDIR /usr/src/pipenv
|
|
||||||
|
|
||||||
COPY Pipfile* ./
|
|
||||||
|
|
||||||
RUN set -eux \
|
|
||||||
&& echo "Installing pipenv" \
|
|
||||||
&& python3 -m pip install --no-cache-dir --upgrade pipenv==2024.4.1 \
|
|
||||||
&& echo "Generating requirement.txt" \
|
|
||||||
&& pipenv requirements > requirements.txt
|
|
||||||
|
|
||||||
# Stage: s6-overlay-base
|
# Stage: s6-overlay-base
|
||||||
# Purpose: Installs s6-overlay and rootfs
|
# Purpose: Installs s6-overlay and rootfs
|
||||||
# Comments:
|
# Comments:
|
||||||
# - Don't leave anything extra in here either
|
# - Don't leave anything extra in here either
|
||||||
FROM docker.io/python:3.12-slim-bookworm AS s6-overlay-base
|
FROM ghcr.io/astral-sh/uv:0.6.3-python3.12-bookworm-slim AS s6-overlay-base
|
||||||
|
|
||||||
WORKDIR /usr/src/s6
|
WORKDIR /usr/src/s6
|
||||||
|
|
||||||
@ -123,9 +106,12 @@ ARG GS_VERSION=10.03.1
|
|||||||
# Set Python environment variables
|
# Set Python environment variables
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
# Ignore warning from Whitenoise
|
# Ignore warning from Whitenoise about async iterators
|
||||||
PYTHONWARNINGS="ignore:::django.http.response:517" \
|
PYTHONWARNINGS="ignore:::django.http.response:517" \
|
||||||
PNGX_CONTAINERIZED=1
|
PNGX_CONTAINERIZED=1 \
|
||||||
|
# https://docs.astral.sh/uv/reference/settings/#link-mode
|
||||||
|
UV_LINK_MODE=copy \
|
||||||
|
UV_CACHE_DIR=/cache/uv/
|
||||||
|
|
||||||
#
|
#
|
||||||
# Begin installation and configuration
|
# Begin installation and configuration
|
||||||
@ -213,36 +199,25 @@ WORKDIR /usr/src/paperless/src/
|
|||||||
|
|
||||||
# Python dependencies
|
# Python dependencies
|
||||||
# Change pretty frequently
|
# Change pretty frequently
|
||||||
COPY --chown=1000:1000 --from=pipenv-base /usr/src/pipenv/requirements.txt ./
|
COPY --chown=1000:1000 ["pyproject.toml", "uv.lock", "/usr/src/paperless/src/"]
|
||||||
|
|
||||||
# Packages needed only for building a few quick Python
|
# Packages needed only for building a few quick Python
|
||||||
# dependencies
|
# dependencies
|
||||||
ARG BUILD_PACKAGES="\
|
ARG BUILD_PACKAGES="\
|
||||||
build-essential \
|
build-essential \
|
||||||
git \
|
|
||||||
# https://www.psycopg.org/docs/install.html#prerequisites
|
|
||||||
libpq-dev \
|
|
||||||
# https://github.com/PyMySQL/mysqlclient#linux
|
# https://github.com/PyMySQL/mysqlclient#linux
|
||||||
default-libmysqlclient-dev \
|
default-libmysqlclient-dev \
|
||||||
pkg-config"
|
pkg-config"
|
||||||
|
|
||||||
ARG ZXING_VERSION=2.3.0
|
|
||||||
ARG PSYCOPG_VERSION=3.2.4
|
|
||||||
|
|
||||||
# hadolint ignore=DL3042
|
# hadolint ignore=DL3042
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip/,id=pip-cache \
|
RUN --mount=type=cache,target=${UV_CACHE_DIR},id=python-cache \
|
||||||
set -eux \
|
set -eux \
|
||||||
&& echo "Installing build system packages" \
|
&& echo "Installing build system packages" \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \
|
&& apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \
|
||||||
&& python3 -m pip install --upgrade wheel \
|
|
||||||
&& echo "Installing Python requirements" \
|
&& echo "Installing Python requirements" \
|
||||||
&& curl --fail --silent --no-progress-meter --show-error --location --remote-name-all --parallel --parallel-max 4 \
|
&& uv export --quiet --no-dev --all-extras --format requirements-txt --output-file requirements.txt \
|
||||||
https://github.com/paperless-ngx/builder/releases/download/psycopg-${PSYCOPG_VERSION}/psycopg_c-${PSYCOPG_VERSION}-cp312-cp312-linux_x86_64.whl \
|
&& uv pip install --system --no-python-downloads --python-preference system --requirements requirements.txt \
|
||||||
https://github.com/paperless-ngx/builder/releases/download/psycopg-${PSYCOPG_VERSION}/psycopg_c-${PSYCOPG_VERSION}-cp312-cp312-linux_aarch64.whl \
|
|
||||||
https://github.com/paperless-ngx/builder/releases/download/zxing-${ZXING_VERSION}/zxing_cpp-${ZXING_VERSION}-cp312-cp312-linux_aarch64.whl \
|
|
||||||
https://github.com/paperless-ngx/builder/releases/download/zxing-${ZXING_VERSION}/zxing_cpp-${ZXING_VERSION}-cp312-cp312-linux_x86_64.whl \
|
|
||||||
&& python3 -m pip install --default-timeout=1000 --find-links . --requirement requirements.txt \
|
|
||||||
&& echo "Installing NLTK data" \
|
&& echo "Installing NLTK data" \
|
||||||
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" snowball_data \
|
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" snowball_data \
|
||||||
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" stopwords \
|
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" stopwords \
|
||||||
|
99
Pipfile
99
Pipfile
@ -1,99 +0,0 @@
|
|||||||
[[source]]
|
|
||||||
url = "https://pypi.python.org/simple"
|
|
||||||
verify_ssl = true
|
|
||||||
name = "pypi"
|
|
||||||
|
|
||||||
[packages]
|
|
||||||
dateparser = "~=1.2"
|
|
||||||
# WARNING: django does not use semver.
|
|
||||||
# Only patch versions are guaranteed to not introduce breaking changes.
|
|
||||||
django = "~=5.1.5"
|
|
||||||
django-allauth = {extras = ["mfa", "socialaccount"], version = "*"}
|
|
||||||
django-auditlog = "*"
|
|
||||||
django-celery-results = "*"
|
|
||||||
django-compression-middleware = "*"
|
|
||||||
django-cors-headers = "*"
|
|
||||||
django-extensions = "*"
|
|
||||||
django-filter = "~=25.1"
|
|
||||||
django-guardian = "*"
|
|
||||||
django-multiselectfield = "*"
|
|
||||||
django-soft-delete = "*"
|
|
||||||
djangorestframework = "~=3.15.2"
|
|
||||||
djangorestframework-guardian = "*"
|
|
||||||
drf-spectacular = "*"
|
|
||||||
drf-spectacular-sidecar = "*"
|
|
||||||
drf-writable-nested = "*"
|
|
||||||
bleach = "*"
|
|
||||||
celery = {extras = ["redis"], version = "*"}
|
|
||||||
channels = "~=4.2"
|
|
||||||
channels-redis = "*"
|
|
||||||
concurrent-log-handler = "*"
|
|
||||||
filelock = "*"
|
|
||||||
flower = "*"
|
|
||||||
gotenberg-client = "*"
|
|
||||||
granian = "*"
|
|
||||||
httpx-oauth = "*"
|
|
||||||
imap-tools = "*"
|
|
||||||
inotifyrecursive = "~=0.3"
|
|
||||||
jinja2 = "~=3.1"
|
|
||||||
langdetect = "*"
|
|
||||||
mysqlclient = "*"
|
|
||||||
nltk = "*"
|
|
||||||
ocrmypdf = "~=16.9"
|
|
||||||
pathvalidate = "*"
|
|
||||||
pdf2image = "*"
|
|
||||||
psycopg = {version = "*", extras = ["c"]}
|
|
||||||
python-dateutil = "*"
|
|
||||||
python-dotenv = "*"
|
|
||||||
python-gnupg = "*"
|
|
||||||
python-ipware = "*"
|
|
||||||
python-magic = "*"
|
|
||||||
pyzbar = "*"
|
|
||||||
rapidfuzz = "*"
|
|
||||||
redis = {extras = ["hiredis"], version = "*"}
|
|
||||||
scikit-learn = "~=1.6"
|
|
||||||
setproctitle = "*"
|
|
||||||
tika-client = "*"
|
|
||||||
tqdm = "*"
|
|
||||||
watchdog = "~=6.0"
|
|
||||||
whitenoise = "~=6.9"
|
|
||||||
whoosh = "~=2.7"
|
|
||||||
zxing-cpp = "*"
|
|
||||||
|
|
||||||
[dev-packages]
|
|
||||||
# Linting
|
|
||||||
pre-commit = "*"
|
|
||||||
ruff = "*"
|
|
||||||
# Testing
|
|
||||||
factory-boy = "*"
|
|
||||||
pytest = "*"
|
|
||||||
pytest-cov = "*"
|
|
||||||
pytest-django = "*"
|
|
||||||
pytest-httpx = "*"
|
|
||||||
pytest-env = "*"
|
|
||||||
pytest-sugar = "*"
|
|
||||||
pytest-xdist = "*"
|
|
||||||
pytest-mock = "*"
|
|
||||||
pytest-rerunfailures = "*"
|
|
||||||
imagehash = "*"
|
|
||||||
daphne = "*"
|
|
||||||
# Documentation
|
|
||||||
mkdocs-material = "*"
|
|
||||||
mkdocs-glightbox = "*"
|
|
||||||
|
|
||||||
[typing-dev]
|
|
||||||
mypy = "*"
|
|
||||||
types-Pillow = "*"
|
|
||||||
django-filter-stubs = "*"
|
|
||||||
types-python-dateutil = "*"
|
|
||||||
djangorestframework-stubs = {extras= ["compatible-mypy"], version="*"}
|
|
||||||
celery-types = "*"
|
|
||||||
django-stubs = {extras= ["compatible-mypy"], version="*"}
|
|
||||||
types-dateparser = "*"
|
|
||||||
types-bleach = "*"
|
|
||||||
types-redis = "*"
|
|
||||||
types-tqdm = "*"
|
|
||||||
types-Markdown = "*"
|
|
||||||
types-Pygments = "*"
|
|
||||||
types-colorama = "*"
|
|
||||||
types-setuptools = "*"
|
|
4812
Pipfile.lock
generated
4812
Pipfile.lock
generated
File diff suppressed because it is too large
Load Diff
@ -60,7 +60,7 @@ first-time setup.
|
|||||||
|
|
||||||
Every command is executed directly from the root folder of the project unless specified otherwise.
|
Every command is executed directly from the root folder of the project unless specified otherwise.
|
||||||
|
|
||||||
1. Install prerequisites + pipenv as mentioned in
|
1. Install prerequisites + [uv](https://github.com/astral-sh/uv) as mentioned in
|
||||||
[Bare metal route](setup.md#bare_metal).
|
[Bare metal route](setup.md#bare_metal).
|
||||||
|
|
||||||
2. Copy `paperless.conf.example` to `paperless.conf` and enable debug
|
2. Copy `paperless.conf.example` to `paperless.conf` and enable debug
|
||||||
@ -75,17 +75,13 @@ first-time setup.
|
|||||||
4. Install the Python dependencies:
|
4. Install the Python dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pipenv install --dev
|
$ uv sync --dev
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! note
|
|
||||||
|
|
||||||
Using a virtual environment is highly recommended. You can spawn one via `pipenv shell`.
|
|
||||||
|
|
||||||
5. Install pre-commit hooks:
|
5. Install pre-commit hooks:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pre-commit install
|
$ uv run pre-commit install
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Apply migrations and create a superuser for your development instance:
|
6. Apply migrations and create a superuser for your development instance:
|
||||||
@ -93,8 +89,8 @@ first-time setup.
|
|||||||
```bash
|
```bash
|
||||||
# src/
|
# src/
|
||||||
|
|
||||||
python3 manage.py migrate
|
$ uv run manage.py migrate
|
||||||
python3 manage.py createsuperuser
|
$ uv run manage.py createsuperuser
|
||||||
```
|
```
|
||||||
|
|
||||||
7. You can now either ...
|
7. You can now either ...
|
||||||
@ -164,6 +160,19 @@ $ ng build --configuration production
|
|||||||
complicated IF cases. Append `# noqa: E501` to disable this check
|
complicated IF cases. Append `# noqa: E501` to disable this check
|
||||||
for certain lines.
|
for certain lines.
|
||||||
|
|
||||||
|
### Package Management
|
||||||
|
|
||||||
|
Paperless uses `uv` to manage packages and virtual environments for both development and production.
|
||||||
|
To accomplish some common tasks using `uv`, follow the shortcuts below:
|
||||||
|
|
||||||
|
To upgrade all locked packages to the latest allowed versions: `uv lock --upgrade`
|
||||||
|
|
||||||
|
To upgrade a single locked package: `uv lock --upgrade-package <package>`
|
||||||
|
|
||||||
|
To add a new package: `uv add <package>`
|
||||||
|
|
||||||
|
To add a new development package `uv add --dev <package>`
|
||||||
|
|
||||||
## Front end development
|
## Front end development
|
||||||
|
|
||||||
The front end is built using AngularJS. In order to get started, you need Node.js (version 14.15+) and
|
The front end is built using AngularJS. In order to get started, you need Node.js (version 14.15+) and
|
||||||
@ -332,27 +341,21 @@ LANGUAGES = [
|
|||||||
The documentation is built using material-mkdocs, see their [documentation](https://squidfunk.github.io/mkdocs-material/reference/).
|
The documentation is built using material-mkdocs, see their [documentation](https://squidfunk.github.io/mkdocs-material/reference/).
|
||||||
If you want to build the documentation locally, this is how you do it:
|
If you want to build the documentation locally, this is how you do it:
|
||||||
|
|
||||||
1. Have an active pipenv shell (`pipenv shell`) and install Python dependencies:
|
1. Build the documentation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pipenv install --dev
|
$ uv run mkdocs build --config-file mkdocs.yml
|
||||||
```
|
|
||||||
|
|
||||||
2. Build the documentation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdocs build --config-file mkdocs.yml
|
|
||||||
```
|
```
|
||||||
|
|
||||||
_alternatively..._
|
_alternatively..._
|
||||||
|
|
||||||
3. Serve the documentation. This will spin up a
|
2. Serve the documentation. This will spin up a
|
||||||
copy of the documentation at http://127.0.0.1:8000
|
copy of the documentation at http://127.0.0.1:8000
|
||||||
that will automatically refresh every time you change
|
that will automatically refresh every time you change
|
||||||
something.
|
something.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdocs serve
|
$ uv run mkdocs serve
|
||||||
```
|
```
|
||||||
|
|
||||||
## Building the Docker image
|
## Building the Docker image
|
||||||
|
@ -380,6 +380,12 @@ are released, dependency support is confirmed, etc.
|
|||||||
dependencies. This is an alternative to the above and may require adjusting
|
dependencies. This is an alternative to the above and may require adjusting
|
||||||
the example scripts to utilize the virtual environment paths
|
the example scripts to utilize the virtual environment paths
|
||||||
|
|
||||||
|
!!! tip
|
||||||
|
|
||||||
|
If you use modern Python tooling, such as `uv`, installation will not include
|
||||||
|
dependencies for Postgres or Mariadb. You can select those extras with `--extra <EXTRA>`
|
||||||
|
or all with `--all-extras`
|
||||||
|
|
||||||
9. Go to `/opt/paperless/src`, and execute the following commands:
|
9. Go to `/opt/paperless/src`, and execute the following commands:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
355
pyproject.toml
Normal file
355
pyproject.toml
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
[project]
|
||||||
|
name = "paperless-ngx"
|
||||||
|
version = "2.14.7"
|
||||||
|
description = "A community-supported supercharged version of paperless: scan, index and archive all your physical documents"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
]
|
||||||
|
# TODO: Move certain things to groups and then utilize that further
|
||||||
|
# This will allow testing to not install a webserver, mysql, etc
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
"bleach~=6.2.0",
|
||||||
|
"celery[redis]~=5.4.0",
|
||||||
|
"channels~=4.2",
|
||||||
|
"channels-redis~=4.2",
|
||||||
|
"concurrent-log-handler~=0.9.25",
|
||||||
|
"dateparser~=1.2",
|
||||||
|
# WARNING: django does not use semver.
|
||||||
|
# Only patch versions are guaranteed to not introduce breaking changes.
|
||||||
|
"django~=5.1.6",
|
||||||
|
"django-allauth[socialaccount,mfa]~=65.4.0",
|
||||||
|
"django-auditlog~=3.0.0",
|
||||||
|
"django-celery-results~=2.5.1",
|
||||||
|
"django-compression-middleware~=0.5.0",
|
||||||
|
"django-cors-headers~=4.7.0",
|
||||||
|
"django-extensions~=3.2.3",
|
||||||
|
"django-filter~=25.1",
|
||||||
|
"django-guardian~=2.4.0",
|
||||||
|
"django-multiselectfield~=0.1.13",
|
||||||
|
"django-soft-delete~=1.0.18",
|
||||||
|
"djangorestframework~=3.15",
|
||||||
|
"djangorestframework-guardian~=0.3.0",
|
||||||
|
"drf-spectacular~=0.28",
|
||||||
|
"drf-spectacular-sidecar~=2025.2.1",
|
||||||
|
"drf-writable-nested~=0.7.1",
|
||||||
|
"filelock~=3.17.0",
|
||||||
|
"flower~=2.0.1",
|
||||||
|
"gotenberg-client~=0.9.0",
|
||||||
|
"httpx-oauth~=0.16",
|
||||||
|
"imap-tools~=1.10.0",
|
||||||
|
"inotifyrecursive~=0.3",
|
||||||
|
"jinja2~=3.1.5",
|
||||||
|
"langdetect~=1.0.9",
|
||||||
|
"nltk~=3.9.1",
|
||||||
|
"ocrmypdf~=16.9.0",
|
||||||
|
"pathvalidate~=3.2.3",
|
||||||
|
"pdf2image~=1.17.0",
|
||||||
|
"python-dateutil~=2.9.0",
|
||||||
|
"python-dotenv~=1.0.1",
|
||||||
|
"python-gnupg~=0.5.4",
|
||||||
|
"python-ipware~=3.0.0",
|
||||||
|
"python-magic~=0.4.27",
|
||||||
|
"pyzbar~=0.1.9",
|
||||||
|
"rapidfuzz~=3.12.1",
|
||||||
|
"redis[hiredis]~=5.2.1",
|
||||||
|
"scikit-learn~=1.6.1",
|
||||||
|
"setproctitle~=1.3.4",
|
||||||
|
"tika-client~=0.9.0",
|
||||||
|
"tqdm~=4.67.1",
|
||||||
|
"watchdog~=6.0",
|
||||||
|
"whitenoise~=6.9",
|
||||||
|
"whoosh~=2.7",
|
||||||
|
"zxing-cpp~=2.3.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
optional-dependencies.mariadb = [
|
||||||
|
"mysqlclient~=2.2.7",
|
||||||
|
]
|
||||||
|
optional-dependencies.postgres = [
|
||||||
|
"psycopg[c]==3.2.4",
|
||||||
|
# Direct dependency for proper resolution of the pre-built wheels
|
||||||
|
"psycopg-c==3.2.4",
|
||||||
|
]
|
||||||
|
optional-dependencies.webserver = [
|
||||||
|
"granian~=1.7.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
|
||||||
|
dev = [
|
||||||
|
{ "include-group" = "docs" },
|
||||||
|
{ "include-group" = "testing" },
|
||||||
|
{ "include-group" = "lint" },
|
||||||
|
]
|
||||||
|
|
||||||
|
docs = [
|
||||||
|
"mkdocs-glightbox~=0.4.0",
|
||||||
|
"mkdocs-material~=9.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
testing = [
|
||||||
|
"daphne",
|
||||||
|
"factory-boy~=3.3.1",
|
||||||
|
"imagehash",
|
||||||
|
"pytest~=8.3.3",
|
||||||
|
"pytest-cov~=6.0.0",
|
||||||
|
"pytest-django~=4.10.0",
|
||||||
|
"pytest-env",
|
||||||
|
"pytest-httpx",
|
||||||
|
"pytest-mock",
|
||||||
|
"pytest-rerunfailures",
|
||||||
|
"pytest-sugar",
|
||||||
|
"pytest-xdist",
|
||||||
|
]
|
||||||
|
|
||||||
|
lint = [
|
||||||
|
"pre-commit~=4.1.0",
|
||||||
|
"pre-commit-uv~=4.1.3",
|
||||||
|
"ruff~=0.9.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
typing = [
|
||||||
|
"celery-types",
|
||||||
|
"django-filter-stubs",
|
||||||
|
"django-stubs[compatible-mypy]",
|
||||||
|
"djangorestframework-stubs[compatible-mypy]",
|
||||||
|
"mypy",
|
||||||
|
"types-bleach",
|
||||||
|
"types-colorama",
|
||||||
|
"types-dateparser",
|
||||||
|
"types-markdown",
|
||||||
|
"types-pygments",
|
||||||
|
"types-python-dateutil",
|
||||||
|
"types-redis",
|
||||||
|
"types-setuptools",
|
||||||
|
"types-tqdm",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
target-version = "py310"
|
||||||
|
line-length = 88
|
||||||
|
src = [
|
||||||
|
"src",
|
||||||
|
]
|
||||||
|
respect-gitignore = true
|
||||||
|
# https://docs.astral.sh/ruff/settings/
|
||||||
|
fix = true
|
||||||
|
show-fixes = true
|
||||||
|
|
||||||
|
output-format = "grouped"
|
||||||
|
# https://docs.astral.sh/ruff/rules/
|
||||||
|
lint.extend-select = [
|
||||||
|
"COM", # https://docs.astral.sh/ruff/rules/#flake8-commas-com
|
||||||
|
"DJ", # https://docs.astral.sh/ruff/rules/#flake8-django-dj
|
||||||
|
"EXE", # https://docs.astral.sh/ruff/rules/#flake8-executable-exe
|
||||||
|
"FBT", # https://docs.astral.sh/ruff/rules/#flake8-boolean-trap-fbt
|
||||||
|
"FLY", # https://docs.astral.sh/ruff/rules/#flynt-fly
|
||||||
|
"G201", # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g
|
||||||
|
"I", # https://docs.astral.sh/ruff/rules/#isort-i
|
||||||
|
"ICN", # https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn
|
||||||
|
"INP", # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp
|
||||||
|
"ISC", # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc
|
||||||
|
"PIE", # https://docs.astral.sh/ruff/rules/#flake8-pie-pie
|
||||||
|
"PLC", # https://docs.astral.sh/ruff/rules/#pylint-pl
|
||||||
|
"PLE", # https://docs.astral.sh/ruff/rules/#pylint-pl
|
||||||
|
"PTH", # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth
|
||||||
|
"Q", # https://docs.astral.sh/ruff/rules/#flake8-quotes-q
|
||||||
|
"RSE", # https://docs.astral.sh/ruff/rules/#flake8-raise-rse
|
||||||
|
"RUF", # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf
|
||||||
|
"SIM", # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim
|
||||||
|
"T20", # https://docs.astral.sh/ruff/rules/#flake8-print-t20
|
||||||
|
"TC", # https://docs.astral.sh/ruff/rules/#flake8-type-checking-tc
|
||||||
|
"TID", # https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid
|
||||||
|
"UP", # https://docs.astral.sh/ruff/rules/#pyupgrade-up
|
||||||
|
"W", # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w
|
||||||
|
]
|
||||||
|
lint.ignore = [
|
||||||
|
"DJ001",
|
||||||
|
"RUF012",
|
||||||
|
"SIM105",
|
||||||
|
]
|
||||||
|
# Migrations
|
||||||
|
lint.per-file-ignores."*/migrations/*.py" = [
|
||||||
|
"E501",
|
||||||
|
"SIM",
|
||||||
|
"T201",
|
||||||
|
]
|
||||||
|
# Testing
|
||||||
|
lint.per-file-ignores."*/tests/*.py" = [
|
||||||
|
"E501",
|
||||||
|
"SIM117",
|
||||||
|
]
|
||||||
|
lint.per-file-ignores.".github/scripts/*.py" = [
|
||||||
|
"E501",
|
||||||
|
"INP001",
|
||||||
|
"SIM117",
|
||||||
|
]
|
||||||
|
# Docker specific
|
||||||
|
lint.per-file-ignores."docker/rootfs/usr/local/bin/wait-for-redis.py" = [
|
||||||
|
"INP001",
|
||||||
|
"T201",
|
||||||
|
]
|
||||||
|
lint.per-file-ignores."docker/wait-for-redis.py" = [
|
||||||
|
"INP001",
|
||||||
|
"T201",
|
||||||
|
]
|
||||||
|
lint.per-file-ignores."src/documents/file_handling.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/documents/management/commands/document_consumer.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/documents/management/commands/document_exporter.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/documents/migrations/1012_fix_archive_files.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/documents/models.py" = [
|
||||||
|
"SIM115",
|
||||||
|
]
|
||||||
|
lint.per-file-ignores."src/documents/parsers.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/documents/signals/handlers.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/documents/tests/test_consumer.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/documents/tests/test_file_handling.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/documents/tests/test_management.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/documents/tests/test_management_consumer.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/documents/tests/test_management_exporter.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/documents/tests/test_migration_archive_files.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/documents/tests/test_migration_document_pages_count.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/documents/tests/test_migration_mime_type.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/documents/tests/test_sanity_check.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/documents/views.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/paperless/checks.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/paperless/settings.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/paperless/views.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/paperless_mail/mail.py" = [
|
||||||
|
"PTH",
|
||||||
|
] # TODO Enable & remove
|
||||||
|
lint.per-file-ignores."src/paperless_tesseract/tests/test_parser.py" = [
|
||||||
|
"PTH",
|
||||||
|
"RUF001",
|
||||||
|
] # TODO PTH Enable & remove
|
||||||
|
lint.isort.force-single-line = true
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
minversion = "8.0"
|
||||||
|
pythonpath = [
|
||||||
|
"src",
|
||||||
|
]
|
||||||
|
testpaths = [
|
||||||
|
"src/documents/tests/",
|
||||||
|
"src/paperless/tests/",
|
||||||
|
"src/paperless_mail/tests/",
|
||||||
|
"src/paperless_tesseract/tests/",
|
||||||
|
"src/paperless_tika/tests",
|
||||||
|
]
|
||||||
|
addopts = [
|
||||||
|
"--pythonwarnings=all",
|
||||||
|
"--cov",
|
||||||
|
"--cov-report=html",
|
||||||
|
"--cov-report=xml",
|
||||||
|
"--numprocesses=auto",
|
||||||
|
"--maxprocesses=16",
|
||||||
|
"--quiet",
|
||||||
|
"--durations=50",
|
||||||
|
"--junitxml=junit.xml",
|
||||||
|
"-o junit_family=legacy",
|
||||||
|
]
|
||||||
|
norecursedirs = [ "src/locale/", ".venv/", "src-ui/" ]
|
||||||
|
|
||||||
|
DJANGO_SETTINGS_MODULE = "paperless.settings"
|
||||||
|
|
||||||
|
[tool.pytest_env]
|
||||||
|
PAPERLESS_DISABLE_DBHANDLER = "true"
|
||||||
|
PAPERLESS_CACHE_BACKEND = "django.core.cache.backends.locmem.LocMemCache"
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
source = [
|
||||||
|
"src/",
|
||||||
|
]
|
||||||
|
omit = [
|
||||||
|
"*/tests/*",
|
||||||
|
"manage.py",
|
||||||
|
"paperless/wsgi.py",
|
||||||
|
"paperless/auth.py",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
exclude_also = [
|
||||||
|
"if settings.AUDIT_LOG_ENABLED:",
|
||||||
|
"if AUDIT_LOG_ENABLED:",
|
||||||
|
"if TYPE_CHECKING:",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
plugins = [
|
||||||
|
"mypy_django_plugin.main",
|
||||||
|
"mypy_drf_plugin.main",
|
||||||
|
"numpy.typing.mypy_plugin",
|
||||||
|
]
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_any_generics = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
warn_redundant_casts = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
required-version = ">=0.5.14"
|
||||||
|
package = false
|
||||||
|
environments = [
|
||||||
|
"sys_platform == 'darwin'",
|
||||||
|
"sys_platform == 'linux'",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
# Markers are chosen to select these almost exclusively when building the Docker image
|
||||||
|
psycopg-c = [
|
||||||
|
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },
|
||||||
|
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.12'" },
|
||||||
|
]
|
||||||
|
zxing-cpp = [
|
||||||
|
{ url = "https://github.com/paperless-ngx/builder/releases/download/zxing-2.3.0/zxing_cpp-2.3.0-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },
|
||||||
|
{ url = "https://github.com/paperless-ngx/builder/releases/download/zxing-2.3.0/zxing_cpp-2.3.0-cp312-cp312-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.12'" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.django-stubs]
|
||||||
|
django_settings_module = "paperless.settings"
|
@ -83,10 +83,17 @@ test('date filtering', async ({ page }) => {
|
|||||||
await page.routeFromHAR(REQUESTS_HAR3, { notFound: 'fallback' })
|
await page.routeFromHAR(REQUESTS_HAR3, { notFound: 'fallback' })
|
||||||
await page.goto('/documents')
|
await page.goto('/documents')
|
||||||
await page.getByRole('button', { name: 'Dates' }).click()
|
await page.getByRole('button', { name: 'Dates' }).click()
|
||||||
await page.getByRole('menuitem', { name: 'Within 3 months' }).first().click()
|
await page.locator('.ng-arrow-wrapper').first().click()
|
||||||
|
await page.getByRole('option', { name: 'Within 3 months' }).click()
|
||||||
await expect(page.locator('pngx-document-list')).toHaveText(/one document/i)
|
await expect(page.locator('pngx-document-list')).toHaveText(/one document/i)
|
||||||
await page.getByRole('menuitem', { name: 'Within 3 months' }).first().click()
|
await page
|
||||||
await page.getByLabel('Datesselected').getByRole('button').first().click()
|
.getByRole('menuitem', { name: 'Relative dates' })
|
||||||
|
.locator('span')
|
||||||
|
.first()
|
||||||
|
.click()
|
||||||
|
await page.getByRole('option', { name: 'Within 3 months' }).click()
|
||||||
|
await page.getByLabel('Dates selected').locator('button').first().click()
|
||||||
|
await page.getByLabel('Dates selected').locator('button').first().click()
|
||||||
await page.getByRole('combobox', { name: 'Select month' }).selectOption('12')
|
await page.getByRole('combobox', { name: 'Select month' }).selectOption('12')
|
||||||
await page.getByRole('combobox', { name: 'Select year' }).selectOption('2022')
|
await page.getByRole('combobox', { name: 'Select year' }).selectOption('2022')
|
||||||
await page.getByText('11', { exact: true }).click()
|
await page.getByText('11', { exact: true }).click()
|
||||||
|
@ -12,4 +12,13 @@ module.exports = {
|
|||||||
'^src/(.*)': '<rootDir>/src/$1',
|
'^src/(.*)': '<rootDir>/src/$1',
|
||||||
},
|
},
|
||||||
workerIdleMemoryLimit: '512MB',
|
workerIdleMemoryLimit: '512MB',
|
||||||
|
reporters: [
|
||||||
|
'default',
|
||||||
|
[
|
||||||
|
'jest-junit',
|
||||||
|
{
|
||||||
|
classNameTemplate: '{filepath}/{classname}: {title}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
@ -1120,7 +1120,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">173</context>
|
<context context-type="linenumber">193</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8508424367627989968" datatype="html">
|
<trans-unit id="8508424367627989968" datatype="html">
|
||||||
@ -1198,7 +1198,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||||
<context context-type="linenumber">105</context>
|
<context context-type="linenumber">106</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</context>
|
||||||
@ -1284,19 +1284,19 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">200</context>
|
<context context-type="linenumber">201</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">219</context>
|
<context context-type="linenumber">220</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">286</context>
|
<context context-type="linenumber">287</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">305</context>
|
<context context-type="linenumber">306</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
|
||||||
@ -1319,19 +1319,19 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">208</context>
|
<context context-type="linenumber">209</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">227</context>
|
<context context-type="linenumber">228</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">294</context>
|
<context context-type="linenumber">295</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">313</context>
|
<context context-type="linenumber">314</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
|
||||||
@ -1357,11 +1357,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">233</context>
|
<context context-type="linenumber">234</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">319</context>
|
<context context-type="linenumber">320</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/input/permissions/permissions-form/permissions-form.component.html</context>
|
||||||
@ -1732,11 +1732,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||||
<context context-type="linenumber">11</context>
|
<context context-type="linenumber">8</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">87</context>
|
<context context-type="linenumber">88</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||||
@ -3246,18 +3246,25 @@
|
|||||||
<context context-type="linenumber">24</context>
|
<context context-type="linenumber">24</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="2710430925353472741" datatype="html">
|
||||||
|
<source>Try to include archive version in merge for non-PDF files</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html</context>
|
||||||
|
<context context-type="linenumber">32</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="5612366187076076264" datatype="html">
|
<trans-unit id="5612366187076076264" datatype="html">
|
||||||
<source>Delete original documents after successful merge</source>
|
<source>Delete original documents after successful merge</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html</context>
|
||||||
<context context-type="linenumber">32</context>
|
<context context-type="linenumber">36</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5138283234724909648" datatype="html">
|
<trans-unit id="5138283234724909648" datatype="html">
|
||||||
<source>Note that only PDFs will be included.</source>
|
<source>Note that only PDFs will be included.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component.html</context>
|
||||||
<context context-type="linenumber">34</context>
|
<context context-type="linenumber">39</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8157388568390631653" datatype="html">
|
<trans-unit id="8157388568390631653" datatype="html">
|
||||||
@ -3344,7 +3351,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||||
<context context-type="linenumber">50</context>
|
<context context-type="linenumber">52</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||||
@ -3352,12 +3359,16 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||||
<context context-type="linenumber">126</context>
|
<context context-type="linenumber">128</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||||
<context context-type="linenumber">152</context>
|
<context context-type="linenumber">152</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
||||||
|
<context context-type="linenumber">103</context>
|
||||||
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/input/date/date.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/input/date/date.component.html</context>
|
||||||
<context context-type="linenumber">21</context>
|
<context context-type="linenumber">21</context>
|
||||||
@ -3371,7 +3382,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||||
<context context-type="linenumber">51</context>
|
<context context-type="linenumber">53</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||||
@ -3379,7 +3390,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||||
<context context-type="linenumber">127</context>
|
<context context-type="linenumber">129</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||||
@ -3502,8 +3513,8 @@
|
|||||||
<context context-type="linenumber">168</context>
|
<context context-type="linenumber">168</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6052766076365105714" datatype="html">
|
<trans-unit id="6312759212949884929" datatype="html">
|
||||||
<source>now</source>
|
<source>Relative dates</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||||
<context context-type="linenumber">25</context>
|
<context context-type="linenumber">25</context>
|
||||||
@ -3513,15 +3524,26 @@
|
|||||||
<context context-type="linenumber">101</context>
|
<context context-type="linenumber">101</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="6052766076365105714" datatype="html">
|
||||||
|
<source>now</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||||
|
<context context-type="linenumber">29</context>
|
||||||
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||||
|
<context context-type="linenumber">105</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="5203279511751768967" datatype="html">
|
<trans-unit id="5203279511751768967" datatype="html">
|
||||||
<source>From</source>
|
<source>From</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||||
<context context-type="linenumber">42</context>
|
<context context-type="linenumber">44</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||||
<context context-type="linenumber">118</context>
|
<context context-type="linenumber">120</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1640609344969975994" datatype="html">
|
<trans-unit id="1640609344969975994" datatype="html">
|
||||||
@ -3539,11 +3561,11 @@
|
|||||||
<source>Added</source>
|
<source>Added</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.html</context>
|
||||||
<context context-type="linenumber">86</context>
|
<context context-type="linenumber">84</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">83</context>
|
<context context-type="linenumber">84</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||||
@ -3562,28 +3584,53 @@
|
|||||||
<source>Within 1 week</source>
|
<source>Within 1 week</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
||||||
<context context-type="linenumber">67</context>
|
<context context-type="linenumber">73</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="123064370501514576" datatype="html">
|
<trans-unit id="123064370501514576" datatype="html">
|
||||||
<source>Within 1 month</source>
|
<source>Within 1 month</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
||||||
<context context-type="linenumber">72</context>
|
<context context-type="linenumber">78</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1027161426440526546" datatype="html">
|
<trans-unit id="1027161426440526546" datatype="html">
|
||||||
<source>Within 3 months</source>
|
<source>Within 3 months</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
||||||
<context context-type="linenumber">77</context>
|
<context context-type="linenumber">83</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="226779700214642230" datatype="html">
|
<trans-unit id="226779700214642230" datatype="html">
|
||||||
<source>Within 1 year</source>
|
<source>Within 1 year</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
||||||
<context context-type="linenumber">82</context>
|
<context context-type="linenumber">88</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8462417627724236320" datatype="html">
|
||||||
|
<source>This year</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
||||||
|
<context context-type="linenumber">93</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="842657237693374355" datatype="html">
|
||||||
|
<source>This month</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
||||||
|
<context context-type="linenumber">98</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="4498682414491138092" datatype="html">
|
||||||
|
<source>Yesterday</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/components/common/dates-dropdown/dates-dropdown.component.ts</context>
|
||||||
|
<context context-type="linenumber">108</context>
|
||||||
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
|
||||||
|
<context context-type="linenumber">29</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8743659855412792665" datatype="html">
|
<trans-unit id="8743659855412792665" datatype="html">
|
||||||
@ -4396,7 +4443,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">129</context>
|
<context context-type="linenumber">130</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
||||||
@ -4787,227 +4834,227 @@
|
|||||||
<source>Assign owner</source>
|
<source>Assign owner</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">194</context>
|
<context context-type="linenumber">195</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1749184201773078639" datatype="html">
|
<trans-unit id="1749184201773078639" datatype="html">
|
||||||
<source>Assign view permissions</source>
|
<source>Assign view permissions</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">196</context>
|
<context context-type="linenumber">197</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1744964187586405039" datatype="html">
|
<trans-unit id="1744964187586405039" datatype="html">
|
||||||
<source>Assign edit permissions</source>
|
<source>Assign edit permissions</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">215</context>
|
<context context-type="linenumber">216</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6236311670364192011" datatype="html">
|
<trans-unit id="6236311670364192011" datatype="html">
|
||||||
<source>Remove tags</source>
|
<source>Remove tags</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">242</context>
|
<context context-type="linenumber">243</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7890599006071681081" datatype="html">
|
<trans-unit id="7890599006071681081" datatype="html">
|
||||||
<source>Remove all</source>
|
<source>Remove all</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">243</context>
|
<context context-type="linenumber">244</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">249</context>
|
<context context-type="linenumber">250</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">255</context>
|
<context context-type="linenumber">256</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">261</context>
|
<context context-type="linenumber">262</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">267</context>
|
<context context-type="linenumber">268</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">274</context>
|
<context context-type="linenumber">275</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">280</context>
|
<context context-type="linenumber">281</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8636414563726517994" datatype="html">
|
<trans-unit id="8636414563726517994" datatype="html">
|
||||||
<source>Remove correspondents</source>
|
<source>Remove correspondents</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">248</context>
|
<context context-type="linenumber">249</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5305293055593064952" datatype="html">
|
<trans-unit id="5305293055593064952" datatype="html">
|
||||||
<source>Remove document types</source>
|
<source>Remove document types</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">254</context>
|
<context context-type="linenumber">255</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2400388879708187" datatype="html">
|
<trans-unit id="2400388879708187" datatype="html">
|
||||||
<source>Remove storage paths</source>
|
<source>Remove storage paths</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">260</context>
|
<context context-type="linenumber">261</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4324304327041955720" datatype="html">
|
<trans-unit id="4324304327041955720" datatype="html">
|
||||||
<source>Remove custom fields</source>
|
<source>Remove custom fields</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">266</context>
|
<context context-type="linenumber">267</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8367536502602515064" datatype="html">
|
<trans-unit id="8367536502602515064" datatype="html">
|
||||||
<source>Remove owners</source>
|
<source>Remove owners</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">273</context>
|
<context context-type="linenumber">274</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3393772184866313281" datatype="html">
|
<trans-unit id="3393772184866313281" datatype="html">
|
||||||
<source>Remove permissions</source>
|
<source>Remove permissions</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">279</context>
|
<context context-type="linenumber">280</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3145629643370481114" datatype="html">
|
<trans-unit id="3145629643370481114" datatype="html">
|
||||||
<source>View permissions</source>
|
<source>View permissions</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">282</context>
|
<context context-type="linenumber">283</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1946660694635960249" datatype="html">
|
<trans-unit id="1946660694635960249" datatype="html">
|
||||||
<source>Edit permissions</source>
|
<source>Edit permissions</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">301</context>
|
<context context-type="linenumber">302</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8987736563240025468" datatype="html">
|
<trans-unit id="8987736563240025468" datatype="html">
|
||||||
<source>Email subject</source>
|
<source>Email subject</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">329</context>
|
<context context-type="linenumber">330</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8239445959209739142" datatype="html">
|
<trans-unit id="8239445959209739142" datatype="html">
|
||||||
<source>Email body</source>
|
<source>Email body</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">330</context>
|
<context context-type="linenumber">331</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1222152280703048012" datatype="html">
|
<trans-unit id="1222152280703048012" datatype="html">
|
||||||
<source>Email recipients</source>
|
<source>Email recipients</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">331</context>
|
<context context-type="linenumber">332</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7916910101279824329" datatype="html">
|
<trans-unit id="7916910101279824329" datatype="html">
|
||||||
<source>Attach document</source>
|
<source>Attach document</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">332</context>
|
<context context-type="linenumber">333</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5028001922785731600" datatype="html">
|
<trans-unit id="5028001922785731600" datatype="html">
|
||||||
<source>Webhook url</source>
|
<source>Webhook url</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">340</context>
|
<context context-type="linenumber">341</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7491983459027245019" datatype="html">
|
<trans-unit id="7491983459027245019" datatype="html">
|
||||||
<source>Use parameters for webhook body</source>
|
<source>Use parameters for webhook body</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">342</context>
|
<context context-type="linenumber">343</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4078214298308732810" datatype="html">
|
<trans-unit id="4078214298308732810" datatype="html">
|
||||||
<source>Send webhook payload as JSON</source>
|
<source>Send webhook payload as JSON</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">343</context>
|
<context context-type="linenumber">344</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6806149889743731985" datatype="html">
|
<trans-unit id="6806149889743731985" datatype="html">
|
||||||
<source>Webhook params</source>
|
<source>Webhook params</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">346</context>
|
<context context-type="linenumber">347</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7089924379374330" datatype="html">
|
<trans-unit id="7089924379374330" datatype="html">
|
||||||
<source>Webhook body</source>
|
<source>Webhook body</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">348</context>
|
<context context-type="linenumber">349</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3829826512656746316" datatype="html">
|
<trans-unit id="3829826512656746316" datatype="html">
|
||||||
<source>Webhook headers</source>
|
<source>Webhook headers</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">350</context>
|
<context context-type="linenumber">351</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2114525789021600887" datatype="html">
|
<trans-unit id="2114525789021600887" datatype="html">
|
||||||
<source>Include document</source>
|
<source>Include document</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.html</context>
|
||||||
<context context-type="linenumber">351</context>
|
<context context-type="linenumber">352</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4626030417479279989" datatype="html">
|
<trans-unit id="4626030417479279989" datatype="html">
|
||||||
<source>Consume Folder</source>
|
<source>Consume Folder</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">64</context>
|
<context context-type="linenumber">65</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="526966086395145275" datatype="html">
|
<trans-unit id="526966086395145275" datatype="html">
|
||||||
<source>API Upload</source>
|
<source>API Upload</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">68</context>
|
<context context-type="linenumber">69</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7502272564743467653" datatype="html">
|
<trans-unit id="7502272564743467653" datatype="html">
|
||||||
<source>Mail Fetch</source>
|
<source>Mail Fetch</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">72</context>
|
<context context-type="linenumber">73</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="235571817610183244" datatype="html">
|
<trans-unit id="235571817610183244" datatype="html">
|
||||||
<source>Web UI</source>
|
<source>Web UI</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">76</context>
|
<context context-type="linenumber">77</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3553216189604488439" datatype="html">
|
<trans-unit id="3553216189604488439" datatype="html">
|
||||||
<source>Modified</source>
|
<source>Modified</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">91</context>
|
<context context-type="linenumber">92</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<context context-type="sourcefile">src/app/data/document.ts</context>
|
||||||
@ -5018,70 +5065,70 @@
|
|||||||
<source>Custom Field</source>
|
<source>Custom Field</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">95</context>
|
<context context-type="linenumber">96</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8696908693776094667" datatype="html">
|
<trans-unit id="8696908693776094667" datatype="html">
|
||||||
<source>Consumption Started</source>
|
<source>Consumption Started</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">102</context>
|
<context context-type="linenumber">103</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7858311467093621703" datatype="html">
|
<trans-unit id="7858311467093621703" datatype="html">
|
||||||
<source>Document Added</source>
|
<source>Document Added</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">106</context>
|
<context context-type="linenumber">107</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7955486237346046731" datatype="html">
|
<trans-unit id="7955486237346046731" datatype="html">
|
||||||
<source>Document Updated</source>
|
<source>Document Updated</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">110</context>
|
<context context-type="linenumber">111</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="9172233176401579786" datatype="html">
|
<trans-unit id="9172233176401579786" datatype="html">
|
||||||
<source>Scheduled</source>
|
<source>Scheduled</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">114</context>
|
<context context-type="linenumber">115</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5502398334173581061" datatype="html">
|
<trans-unit id="5502398334173581061" datatype="html">
|
||||||
<source>Assignment</source>
|
<source>Assignment</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">121</context>
|
<context context-type="linenumber">122</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6234812824772766804" datatype="html">
|
<trans-unit id="6234812824772766804" datatype="html">
|
||||||
<source>Removal</source>
|
<source>Removal</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">125</context>
|
<context context-type="linenumber">126</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4206419737792796794" datatype="html">
|
<trans-unit id="4206419737792796794" datatype="html">
|
||||||
<source>Webhook</source>
|
<source>Webhook</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">133</context>
|
<context context-type="linenumber">134</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3138206142174978019" datatype="html">
|
<trans-unit id="3138206142174978019" datatype="html">
|
||||||
<source>Create new workflow</source>
|
<source>Create new workflow</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">229</context>
|
<context context-type="linenumber">231</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5996779210524133604" datatype="html">
|
<trans-unit id="5996779210524133604" datatype="html">
|
||||||
<source>Edit workflow</source>
|
<source>Edit workflow</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
<context context-type="sourcefile">src/app/components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component.ts</context>
|
||||||
<context context-type="linenumber">233</context>
|
<context context-type="linenumber">235</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7376342558017986274" datatype="html">
|
<trans-unit id="7376342558017986274" datatype="html">
|
||||||
@ -6508,7 +6555,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">160</context>
|
<context context-type="linenumber">180</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<context context-type="sourcefile">src/app/data/document.ts</context>
|
||||||
@ -7136,7 +7183,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">168</context>
|
<context context-type="linenumber">188</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6475890479659129881" datatype="html">
|
<trans-unit id="6475890479659129881" datatype="html">
|
||||||
@ -7431,21 +7478,21 @@
|
|||||||
<source>Merged document will be queued for consumption.</source>
|
<source>Merged document will be queued for consumption.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||||
<context context-type="linenumber">863</context>
|
<context context-type="linenumber">866</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="476913782630693351" datatype="html">
|
<trans-unit id="476913782630693351" datatype="html">
|
||||||
<source>Custom fields updated.</source>
|
<source>Custom fields updated.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||||
<context context-type="linenumber">885</context>
|
<context context-type="linenumber">888</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3873496751167944011" datatype="html">
|
<trans-unit id="3873496751167944011" datatype="html">
|
||||||
<source>Error updating custom fields.</source>
|
<source>Error updating custom fields.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||||
<context context-type="linenumber">894</context>
|
<context context-type="linenumber">897</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6307402210351946694" datatype="html">
|
<trans-unit id="6307402210351946694" datatype="html">
|
||||||
@ -7713,7 +7760,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
|
||||||
<context context-type="linenumber">111</context>
|
<context context-type="linenumber">112</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1559883523769732271" datatype="html">
|
<trans-unit id="1559883523769732271" datatype="html">
|
||||||
@ -7738,7 +7785,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">165</context>
|
<context context-type="linenumber">185</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<context context-type="sourcefile">src/app/data/document.ts</context>
|
||||||
@ -7934,154 +7981,154 @@
|
|||||||
<source>Title & content</source>
|
<source>Title & content</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">163</context>
|
<context context-type="linenumber">183</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7408932238599462499" datatype="html">
|
<trans-unit id="7408932238599462499" datatype="html">
|
||||||
<source>File type</source>
|
<source>File type</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">170</context>
|
<context context-type="linenumber">190</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2649431021108393503" datatype="html">
|
<trans-unit id="2649431021108393503" datatype="html">
|
||||||
<source>More like</source>
|
<source>More like</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">179</context>
|
<context context-type="linenumber">199</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3697582909018473071" datatype="html">
|
<trans-unit id="3697582909018473071" datatype="html">
|
||||||
<source>equals</source>
|
<source>equals</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">185</context>
|
<context context-type="linenumber">205</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5325481293405718739" datatype="html">
|
<trans-unit id="5325481293405718739" datatype="html">
|
||||||
<source>is empty</source>
|
<source>is empty</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">189</context>
|
<context context-type="linenumber">209</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6166785695326182482" datatype="html">
|
<trans-unit id="6166785695326182482" datatype="html">
|
||||||
<source>is not empty</source>
|
<source>is not empty</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">193</context>
|
<context context-type="linenumber">213</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4686622206659266699" datatype="html">
|
<trans-unit id="4686622206659266699" datatype="html">
|
||||||
<source>greater than</source>
|
<source>greater than</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">197</context>
|
<context context-type="linenumber">217</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8014012170270529279" datatype="html">
|
<trans-unit id="8014012170270529279" datatype="html">
|
||||||
<source>less than</source>
|
<source>less than</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">201</context>
|
<context context-type="linenumber">221</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5195932016807797291" datatype="html">
|
<trans-unit id="5195932016807797291" datatype="html">
|
||||||
<source>Correspondent: <x id="PH" equiv-text="this.correspondents.find((c) => c.id == +rule.value)?.name"/></source>
|
<source>Correspondent: <x id="PH" equiv-text="this.correspondents.find((c) => c.id == +rule.value)?.name"/></source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">233,235</context>
|
<context context-type="linenumber">253,255</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8170755470576301659" datatype="html">
|
<trans-unit id="8170755470576301659" datatype="html">
|
||||||
<source>Without correspondent</source>
|
<source>Without correspondent</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">237</context>
|
<context context-type="linenumber">257</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="317796810569008208" datatype="html">
|
<trans-unit id="317796810569008208" datatype="html">
|
||||||
<source>Document type: <x id="PH" equiv-text="this.documentTypes.find((dt) => dt.id == +rule.value)?.name"/></source>
|
<source>Document type: <x id="PH" equiv-text="this.documentTypes.find((dt) => dt.id == +rule.value)?.name"/></source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">243,245</context>
|
<context context-type="linenumber">263,265</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4362173610367509215" datatype="html">
|
<trans-unit id="4362173610367509215" datatype="html">
|
||||||
<source>Without document type</source>
|
<source>Without document type</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">247</context>
|
<context context-type="linenumber">267</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="232202047340644471" datatype="html">
|
<trans-unit id="232202047340644471" datatype="html">
|
||||||
<source>Storage path: <x id="PH" equiv-text="this.storagePaths.find((sp) => sp.id == +rule.value)?.name"/></source>
|
<source>Storage path: <x id="PH" equiv-text="this.storagePaths.find((sp) => sp.id == +rule.value)?.name"/></source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">253,255</context>
|
<context context-type="linenumber">273,275</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1562820715074533164" datatype="html">
|
<trans-unit id="1562820715074533164" datatype="html">
|
||||||
<source>Without storage path</source>
|
<source>Without storage path</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">257</context>
|
<context context-type="linenumber">277</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8180755793012580465" datatype="html">
|
<trans-unit id="8180755793012580465" datatype="html">
|
||||||
<source>Tag: <x id="PH" equiv-text="this.tags.find((t) => t.id == +rule.value)?.name"/></source>
|
<source>Tag: <x id="PH" equiv-text="this.tags.find((t) => t.id == +rule.value)?.name"/></source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">261,263</context>
|
<context context-type="linenumber">281,283</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6494566478302448576" datatype="html">
|
<trans-unit id="6494566478302448576" datatype="html">
|
||||||
<source>Without any tag</source>
|
<source>Without any tag</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">267</context>
|
<context context-type="linenumber">287</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8644099678903817943" datatype="html">
|
<trans-unit id="8644099678903817943" datatype="html">
|
||||||
<source>Custom fields query</source>
|
<source>Custom fields query</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">271</context>
|
<context context-type="linenumber">291</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6523384805359286307" datatype="html">
|
<trans-unit id="6523384805359286307" datatype="html">
|
||||||
<source>Title: <x id="PH" equiv-text="rule.value"/></source>
|
<source>Title: <x id="PH" equiv-text="rule.value"/></source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">274</context>
|
<context context-type="linenumber">294</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1872523635812236432" datatype="html">
|
<trans-unit id="1872523635812236432" datatype="html">
|
||||||
<source>ASN: <x id="PH" equiv-text="rule.value"/></source>
|
<source>ASN: <x id="PH" equiv-text="rule.value"/></source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">277</context>
|
<context context-type="linenumber">297</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="102674688969746976" datatype="html">
|
<trans-unit id="102674688969746976" datatype="html">
|
||||||
<source>Owner: <x id="PH" equiv-text="rule.value"/></source>
|
<source>Owner: <x id="PH" equiv-text="rule.value"/></source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">280</context>
|
<context context-type="linenumber">300</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3550877650686009106" datatype="html">
|
<trans-unit id="3550877650686009106" datatype="html">
|
||||||
<source>Owner not in: <x id="PH" equiv-text="rule.value"/></source>
|
<source>Owner not in: <x id="PH" equiv-text="rule.value"/></source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">283</context>
|
<context context-type="linenumber">303</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1082034558646673343" datatype="html">
|
<trans-unit id="1082034558646673343" datatype="html">
|
||||||
<source>Without an owner</source>
|
<source>Without an owner</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||||
<context context-type="linenumber">286</context>
|
<context context-type="linenumber">306</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7210076240260527720" datatype="html">
|
<trans-unit id="7210076240260527720" datatype="html">
|
||||||
@ -9415,13 +9462,6 @@
|
|||||||
<context context-type="linenumber">25</context>
|
<context context-type="linenumber">25</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4498682414491138092" datatype="html">
|
|
||||||
<source>Yesterday</source>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
|
|
||||||
<context context-type="linenumber">29</context>
|
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="5601594741748068208" datatype="html">
|
<trans-unit id="5601594741748068208" datatype="html">
|
||||||
<source>%s days ago</source>
|
<source>%s days ago</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
|
245
src-ui/package-lock.json
generated
245
src-ui/package-lock.json
generated
@ -44,23 +44,24 @@
|
|||||||
"@angular-devkit/build-angular": "^19.0.4",
|
"@angular-devkit/build-angular": "^19.0.4",
|
||||||
"@angular-devkit/core": "^19.2.0",
|
"@angular-devkit/core": "^19.2.0",
|
||||||
"@angular-devkit/schematics": "^19.2.0",
|
"@angular-devkit/schematics": "^19.2.0",
|
||||||
"@angular-eslint/builder": "19.1.0",
|
"@angular-eslint/builder": "19.2.0",
|
||||||
"@angular-eslint/eslint-plugin": "19.1.0",
|
"@angular-eslint/eslint-plugin": "19.2.0",
|
||||||
"@angular-eslint/eslint-plugin-template": "19.1.0",
|
"@angular-eslint/eslint-plugin-template": "19.2.0",
|
||||||
"@angular-eslint/schematics": "19.1.0",
|
"@angular-eslint/schematics": "19.2.0",
|
||||||
"@angular-eslint/template-parser": "19.1.0",
|
"@angular-eslint/template-parser": "19.2.0",
|
||||||
"@angular/cli": "~19.2.0",
|
"@angular/cli": "~19.2.0",
|
||||||
"@angular/compiler-cli": "~19.2.0",
|
"@angular/compiler-cli": "~19.2.0",
|
||||||
"@codecov/webpack-plugin": "^1.9.0",
|
"@codecov/webpack-plugin": "^1.9.0",
|
||||||
"@playwright/test": "^1.50.1",
|
"@playwright/test": "^1.50.1",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/node": "^22.13.8",
|
"@types/node": "^22.13.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.25.0",
|
"@typescript-eslint/eslint-plugin": "^8.26.0",
|
||||||
"@typescript-eslint/parser": "^8.25.0",
|
"@typescript-eslint/parser": "^8.26.0",
|
||||||
"@typescript-eslint/utils": "^8.0.0",
|
"@typescript-eslint/utils": "^8.0.0",
|
||||||
"eslint": "^9.21.0",
|
"eslint": "^9.21.0",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
|
"jest-junit": "^16.0.0",
|
||||||
"jest-preset-angular": "^14.5.3",
|
"jest-preset-angular": "^14.5.3",
|
||||||
"jest-websocket-mock": "^2.5.0",
|
"jest-websocket-mock": "^2.5.0",
|
||||||
"patch-package": "^8.0.0",
|
"patch-package": "^8.0.0",
|
||||||
@ -1148,9 +1149,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@angular-eslint/builder": {
|
"node_modules/@angular-eslint/builder": {
|
||||||
"version": "19.1.0",
|
"version": "19.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.2.0.tgz",
|
||||||
"integrity": "sha512-LWdQMTES/7GySlpTNFJn3k33ZGmjjWlHI/+IHV7B3xHQ9hj4MPK4ACmE/PNOAIQ9LwQm7sKS+3cTMxOZQ/cvSg==",
|
"integrity": "sha512-8Lx24MrMJT8RlgDtwqfiLiJo4DzSaktjco6RmELUdWO2chJgRe9y+2iIgOeB2pmyD9UCsubwsfjBXlrnV/MPhQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1163,21 +1164,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@angular-eslint/bundled-angular-compiler": {
|
"node_modules/@angular-eslint/bundled-angular-compiler": {
|
||||||
"version": "19.1.0",
|
"version": "19.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.2.0.tgz",
|
||||||
"integrity": "sha512-HUJyukRvnh8Z9lIdxdblBRuBaPYEVv4iAYZMw3d+dn4rrM27Nt5oh3/zkwYrrPkt36tZdeXdDWrOuz9jgjVN5w==",
|
"integrity": "sha512-hmmAogTpYGbBvnJ0j7DNLi8YQ+YEEuwFdx0heU8XjTpZlRoSRIP7MJJVlaQCt+ZT5f5XwdGtqi9lOXqqcyGHLA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@angular-eslint/eslint-plugin": {
|
"node_modules/@angular-eslint/eslint-plugin": {
|
||||||
"version": "19.1.0",
|
"version": "19.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-19.2.0.tgz",
|
||||||
"integrity": "sha512-TDO0+Ry+oNkxnaLHogKp1k2aey6IkJef5d7hathE4UFT6owjRizltWaRoX6bGw7Qu1yagVLL8L2Se8SddxSPAQ==",
|
"integrity": "sha512-QQWWDrTdJ22tBd7RLFG/FdPwNyYEhg7YwWgn29z6XcdnV00ZFtf7FRbv/te1kqVNPvfjtht7bvtHcPQ432aUdQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular-eslint/bundled-angular-compiler": "19.1.0",
|
"@angular-eslint/bundled-angular-compiler": "19.2.0",
|
||||||
"@angular-eslint/utils": "19.1.0"
|
"@angular-eslint/utils": "19.2.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@typescript-eslint/utils": "^7.11.0 || ^8.0.0",
|
"@typescript-eslint/utils": "^7.11.0 || ^8.0.0",
|
||||||
@ -1186,14 +1187,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@angular-eslint/eslint-plugin-template": {
|
"node_modules/@angular-eslint/eslint-plugin-template": {
|
||||||
"version": "19.1.0",
|
"version": "19.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-19.2.0.tgz",
|
||||||
"integrity": "sha512-bIUizkCY40mnU8oAO1tLV7uN2H/cHf1evLlhpqlb9JYwc5dT2moiEhNDo61OtOgkJmDGNuThAeO9Xk9hGQc7nA==",
|
"integrity": "sha512-lUSzmk5/Dr0bNc2Omb5CZDu3zQZh70bJyuXnN5MKd00V1b3u90eqvMSveFzWFJ6Eot8Hh8+FxtiozPwGqOE+Og==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular-eslint/bundled-angular-compiler": "19.1.0",
|
"@angular-eslint/bundled-angular-compiler": "19.2.0",
|
||||||
"@angular-eslint/utils": "19.1.0",
|
"@angular-eslint/utils": "19.2.0",
|
||||||
"aria-query": "5.3.2",
|
"aria-query": "5.3.2",
|
||||||
"axobject-query": "4.1.0"
|
"axobject-query": "4.1.0"
|
||||||
},
|
},
|
||||||
@ -1204,17 +1205,47 @@
|
|||||||
"typescript": "*"
|
"typescript": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@angular-eslint/eslint-plugin-template/node_modules/@angular-eslint/utils": {
|
||||||
|
"version": "19.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-19.2.0.tgz",
|
||||||
|
"integrity": "sha512-1XQXzIqYadKUxcAgW1DPev56SVbR8Uld6TthgolU7rfIX23RYMIIRtQlrQCk7zoXLXm5fzcGqjTR4wHfoD+iWg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@angular-eslint/bundled-angular-compiler": "19.2.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@typescript-eslint/utils": "^7.11.0 || ^8.0.0",
|
||||||
|
"eslint": "^8.57.0 || ^9.0.0",
|
||||||
|
"typescript": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@angular-eslint/eslint-plugin/node_modules/@angular-eslint/utils": {
|
||||||
|
"version": "19.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-19.2.0.tgz",
|
||||||
|
"integrity": "sha512-1XQXzIqYadKUxcAgW1DPev56SVbR8Uld6TthgolU7rfIX23RYMIIRtQlrQCk7zoXLXm5fzcGqjTR4wHfoD+iWg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@angular-eslint/bundled-angular-compiler": "19.2.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@typescript-eslint/utils": "^7.11.0 || ^8.0.0",
|
||||||
|
"eslint": "^8.57.0 || ^9.0.0",
|
||||||
|
"typescript": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@angular-eslint/schematics": {
|
"node_modules/@angular-eslint/schematics": {
|
||||||
"version": "19.1.0",
|
"version": "19.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-19.2.0.tgz",
|
||||||
"integrity": "sha512-6S1FjmM7rZxc0u0W0KjqWYOkFQ0q89IGyjPkdUt1a8NwRnWg3VoXp4WYfeuZOjda/FEYuBS/E6rckLAMp0h6Aw==",
|
"integrity": "sha512-SQfbKgPEJNkK5TVXRsdnWp6TjvVZOczvf8lELF1n+I/Uwmp7ulUjTRgTo59ZQnXoPSs2qCPgS4gAOVR6CD91zQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular-devkit/core": ">= 19.0.0 < 20.0.0",
|
"@angular-devkit/core": ">= 19.0.0 < 20.0.0",
|
||||||
"@angular-devkit/schematics": ">= 19.0.0 < 20.0.0",
|
"@angular-devkit/schematics": ">= 19.0.0 < 20.0.0",
|
||||||
"@angular-eslint/eslint-plugin": "19.1.0",
|
"@angular-eslint/eslint-plugin": "19.2.0",
|
||||||
"@angular-eslint/eslint-plugin-template": "19.1.0",
|
"@angular-eslint/eslint-plugin-template": "19.2.0",
|
||||||
"ignore": "7.0.3",
|
"ignore": "7.0.3",
|
||||||
"semver": "7.7.1",
|
"semver": "7.7.1",
|
||||||
"strip-json-comments": "3.1.1"
|
"strip-json-comments": "3.1.1"
|
||||||
@ -1231,13 +1262,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@angular-eslint/template-parser": {
|
"node_modules/@angular-eslint/template-parser": {
|
||||||
"version": "19.1.0",
|
"version": "19.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-19.2.0.tgz",
|
||||||
"integrity": "sha512-wbMi7adlC+uYqZo7NHNBShpNhFJRZsXLqihqvFpAUt1Ei6uDX8HR6MyMEDZ9tUnlqtPVW5nmbedPyLVG7HkjAA==",
|
"integrity": "sha512-VqgvFrILhoMe0GHZrx+Bjy8kx7/LJfJTd+x/wzE/X1cCChSU81MBZFMVeFMnoI75OOQUf4fwaaKrtUhUvAkVyw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular-eslint/bundled-angular-compiler": "19.1.0",
|
"@angular-eslint/bundled-angular-compiler": "19.2.0",
|
||||||
"eslint-scope": "^8.0.2"
|
"eslint-scope": "^8.0.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@ -1245,21 +1276,6 @@
|
|||||||
"typescript": "*"
|
"typescript": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@angular-eslint/utils": {
|
|
||||||
"version": "19.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-19.1.0.tgz",
|
|
||||||
"integrity": "sha512-mcb7hPMH/u6wwUwvsewrmgb9y9NWN6ZacvpUvKlTOxF/jOtTdsu0XfV4YB43sp2A8NWzYzX0Str4c8K1xSmuBQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@angular-eslint/bundled-angular-compiler": "19.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@typescript-eslint/utils": "^7.11.0 || ^8.0.0",
|
|
||||||
"eslint": "^8.57.0 || ^9.0.0",
|
|
||||||
"typescript": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@angular/cdk": {
|
"node_modules/@angular/cdk": {
|
||||||
"version": "19.2.1",
|
"version": "19.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.1.tgz",
|
||||||
@ -7150,9 +7166,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.13.8",
|
"version": "22.13.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz",
|
||||||
"integrity": "sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==",
|
"integrity": "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -7271,17 +7287,17 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.25.0",
|
"version": "8.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz",
|
||||||
"integrity": "sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==",
|
"integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.25.0",
|
"@typescript-eslint/scope-manager": "8.26.0",
|
||||||
"@typescript-eslint/type-utils": "8.25.0",
|
"@typescript-eslint/type-utils": "8.26.0",
|
||||||
"@typescript-eslint/utils": "8.25.0",
|
"@typescript-eslint/utils": "8.26.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.25.0",
|
"@typescript-eslint/visitor-keys": "8.26.0",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.3.1",
|
"ignore": "^5.3.1",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@ -7297,20 +7313,20 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
|
"@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
|
||||||
"eslint": "^8.57.0 || ^9.0.0",
|
"eslint": "^8.57.0 || ^9.0.0",
|
||||||
"typescript": ">=4.8.4 <5.8.0"
|
"typescript": ">=4.8.4 <5.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.25.0",
|
"version": "8.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz",
|
||||||
"integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==",
|
"integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.25.0",
|
"@typescript-eslint/scope-manager": "8.26.0",
|
||||||
"@typescript-eslint/types": "8.25.0",
|
"@typescript-eslint/types": "8.26.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.25.0",
|
"@typescript-eslint/typescript-estree": "8.26.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.25.0",
|
"@typescript-eslint/visitor-keys": "8.26.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -7322,18 +7338,18 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"eslint": "^8.57.0 || ^9.0.0",
|
"eslint": "^8.57.0 || ^9.0.0",
|
||||||
"typescript": ">=4.8.4 <5.8.0"
|
"typescript": ">=4.8.4 <5.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.25.0",
|
"version": "8.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz",
|
||||||
"integrity": "sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==",
|
"integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.25.0",
|
"@typescript-eslint/types": "8.26.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.25.0"
|
"@typescript-eslint/visitor-keys": "8.26.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -7344,14 +7360,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.25.0",
|
"version": "8.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz",
|
||||||
"integrity": "sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==",
|
"integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "8.25.0",
|
"@typescript-eslint/typescript-estree": "8.26.0",
|
||||||
"@typescript-eslint/utils": "8.25.0",
|
"@typescript-eslint/utils": "8.26.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^2.0.1"
|
"ts-api-utils": "^2.0.1"
|
||||||
},
|
},
|
||||||
@ -7364,13 +7380,13 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"eslint": "^8.57.0 || ^9.0.0",
|
"eslint": "^8.57.0 || ^9.0.0",
|
||||||
"typescript": ">=4.8.4 <5.8.0"
|
"typescript": ">=4.8.4 <5.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.25.0",
|
"version": "8.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz",
|
||||||
"integrity": "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==",
|
"integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -7382,14 +7398,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.25.0",
|
"version": "8.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz",
|
||||||
"integrity": "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==",
|
"integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.25.0",
|
"@typescript-eslint/types": "8.26.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.25.0",
|
"@typescript-eslint/visitor-keys": "8.26.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@ -7405,7 +7421,7 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": ">=4.8.4 <5.8.0"
|
"typescript": ">=4.8.4 <5.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
||||||
@ -7435,16 +7451,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.25.0",
|
"version": "8.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz",
|
||||||
"integrity": "sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==",
|
"integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@typescript-eslint/scope-manager": "8.25.0",
|
"@typescript-eslint/scope-manager": "8.26.0",
|
||||||
"@typescript-eslint/types": "8.25.0",
|
"@typescript-eslint/types": "8.26.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.25.0"
|
"@typescript-eslint/typescript-estree": "8.26.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -7455,17 +7471,17 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"eslint": "^8.57.0 || ^9.0.0",
|
"eslint": "^8.57.0 || ^9.0.0",
|
||||||
"typescript": ">=4.8.4 <5.8.0"
|
"typescript": ">=4.8.4 <5.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.25.0",
|
"version": "8.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.25.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz",
|
||||||
"integrity": "sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==",
|
"integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.25.0",
|
"@typescript-eslint/types": "8.26.0",
|
||||||
"eslint-visitor-keys": "^4.2.0"
|
"eslint-visitor-keys": "^4.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -12403,6 +12419,32 @@
|
|||||||
"fsevents": "^2.3.2"
|
"fsevents": "^2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jest-junit": {
|
||||||
|
"version": "16.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz",
|
||||||
|
"integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"mkdirp": "^1.0.4",
|
||||||
|
"strip-ansi": "^6.0.1",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
|
"xml": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jest-junit/node_modules/uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jest-leak-detector": {
|
"node_modules/jest-leak-detector": {
|
||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz",
|
||||||
@ -19250,6 +19292,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/xml": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/xml-name-validator": {
|
"node_modules/xml-name-validator": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||||
|
@ -46,23 +46,24 @@
|
|||||||
"@angular-devkit/build-angular": "^19.0.4",
|
"@angular-devkit/build-angular": "^19.0.4",
|
||||||
"@angular-devkit/core": "^19.2.0",
|
"@angular-devkit/core": "^19.2.0",
|
||||||
"@angular-devkit/schematics": "^19.2.0",
|
"@angular-devkit/schematics": "^19.2.0",
|
||||||
"@angular-eslint/builder": "19.1.0",
|
"@angular-eslint/builder": "19.2.0",
|
||||||
"@angular-eslint/eslint-plugin": "19.1.0",
|
"@angular-eslint/eslint-plugin": "19.2.0",
|
||||||
"@angular-eslint/eslint-plugin-template": "19.1.0",
|
"@angular-eslint/eslint-plugin-template": "19.2.0",
|
||||||
"@angular-eslint/schematics": "19.1.0",
|
"@angular-eslint/schematics": "19.2.0",
|
||||||
"@angular-eslint/template-parser": "19.1.0",
|
"@angular-eslint/template-parser": "19.2.0",
|
||||||
"@angular/cli": "~19.2.0",
|
"@angular/cli": "~19.2.0",
|
||||||
"@angular/compiler-cli": "~19.2.0",
|
"@angular/compiler-cli": "~19.2.0",
|
||||||
"@codecov/webpack-plugin": "^1.9.0",
|
"@codecov/webpack-plugin": "^1.9.0",
|
||||||
"@playwright/test": "^1.50.1",
|
"@playwright/test": "^1.50.1",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/node": "^22.13.8",
|
"@types/node": "^22.13.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.25.0",
|
"@typescript-eslint/eslint-plugin": "^8.26.0",
|
||||||
"@typescript-eslint/parser": "^8.25.0",
|
"@typescript-eslint/parser": "^8.26.0",
|
||||||
"@typescript-eslint/utils": "^8.0.0",
|
"@typescript-eslint/utils": "^8.0.0",
|
||||||
"eslint": "^9.21.0",
|
"eslint": "^9.21.0",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
|
"jest-junit": "^16.0.0",
|
||||||
"jest-preset-angular": "^14.5.3",
|
"jest-preset-angular": "^14.5.3",
|
||||||
"jest-websocket-mock": "^2.5.0",
|
"jest-websocket-mock": "^2.5.0",
|
||||||
"patch-package": "^8.0.0",
|
"patch-package": "^8.0.0",
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
<div class="scroll-list">
|
<div class="scroll-list">
|
||||||
@for (toast of toasts; track toast.id) {
|
@for (toast of toasts; track toast.id) {
|
||||||
<pngx-toast [autohide]="false" [toast]="toast" (hidden)="onHidden(toast)" (close)="toastService.closeToast(toast)"></pngx-toast>
|
<pngx-toast [autohide]="false" [toast]="toast" (hidden)="onHidden(toast)" (closed)="toastService.closeToast(toast)"></pngx-toast>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,10 +28,16 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch mt-4">
|
<div class="form-check form-switch mt-4">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="archiveFallbackSwitch" [(ngModel)]="archiveFallback">
|
||||||
|
<label class="form-check-label" for="archiveFallbackSwitch" i18n>Try to include archive version in merge for non-PDF files</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-switch mt-2">
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="deleteOriginalsSwitch" [(ngModel)]="deleteOriginals" [disabled]="!userOwnsAllDocuments">
|
<input class="form-check-input" type="checkbox" role="switch" id="deleteOriginalsSwitch" [(ngModel)]="deleteOriginals" [disabled]="!userOwnsAllDocuments">
|
||||||
<label class="form-check-label" for="deleteOriginalsSwitch" i18n>Delete original documents after successful merge</label>
|
<label class="form-check-label" for="deleteOriginalsSwitch" i18n>Delete original documents after successful merge</label>
|
||||||
</div>
|
</div>
|
||||||
<p class="small text-muted fst-italic mt-4" i18n>Note that only PDFs will be included.</p>
|
@if (!archiveFallback) {
|
||||||
|
<p class="small text-muted fst-italic mt-4" i18n>Note that only PDFs will be included.</p>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">
|
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">
|
||||||
|
@ -29,6 +29,7 @@ export class MergeConfirmDialogComponent
|
|||||||
implements OnInit
|
implements OnInit
|
||||||
{
|
{
|
||||||
public documentIDs: number[] = []
|
public documentIDs: number[] = []
|
||||||
|
public archiveFallback: boolean = false
|
||||||
public deleteOriginals: boolean = false
|
public deleteOriginals: boolean = false
|
||||||
private _documents: Document[] = []
|
private _documents: Document[] = []
|
||||||
get documents(): Document[] {
|
get documents(): Document[] {
|
||||||
|
@ -34,7 +34,7 @@ import {
|
|||||||
CustomFieldQueryElement,
|
CustomFieldQueryElement,
|
||||||
CustomFieldQueryExpression,
|
CustomFieldQueryExpression,
|
||||||
} from 'src/app/utils/custom-field-query-element'
|
} from 'src/app/utils/custom-field-query-element'
|
||||||
import { popperOptionsReenablePreventOverflow } from 'src/app/utils/popper-options'
|
import { pngxPopperOptions } from 'src/app/utils/popper-options'
|
||||||
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
|
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
|
||||||
import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component'
|
import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component'
|
||||||
import { DocumentLinkComponent } from '../input/document-link/document-link.component'
|
import { DocumentLinkComponent } from '../input/document-link/document-link.component'
|
||||||
@ -183,7 +183,7 @@ export class CustomFieldsQueryDropdownComponent extends LoadingComponentWithPerm
|
|||||||
public CustomFieldDataType = CustomFieldDataType
|
public CustomFieldDataType = CustomFieldDataType
|
||||||
public CUSTOM_FIELD_QUERY_MAX_DEPTH = CUSTOM_FIELD_QUERY_MAX_DEPTH
|
public CUSTOM_FIELD_QUERY_MAX_DEPTH = CUSTOM_FIELD_QUERY_MAX_DEPTH
|
||||||
public CUSTOM_FIELD_QUERY_MAX_ATOMS = CUSTOM_FIELD_QUERY_MAX_ATOMS
|
public CUSTOM_FIELD_QUERY_MAX_ATOMS = CUSTOM_FIELD_QUERY_MAX_ATOMS
|
||||||
public popperOptions = popperOptionsReenablePreventOverflow
|
public popperOptions = pngxPopperOptions
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
title: string
|
title: string
|
||||||
|
@ -1,161 +1,158 @@
|
|||||||
<div class="btn-group w-100" ngbDropdown role="group" [popperOptions]="popperOptions">
|
<div class="btn-group w-100" ngbDropdown role="group" [popperOptions]="popperOptions" [placement]="placement">
|
||||||
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="createdDateTo || createdDateFrom ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
|
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="createdDateTo || createdDateFrom ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
|
||||||
<i-bs width="1em" height="1em" name="calendar-event-fill"></i-bs>
|
<i-bs width="1em" height="1em" name="calendar-event-fill"></i-bs>
|
||||||
<div class="d-none d-sm-inline"> {{title}}</div>
|
<div class="d-none d-sm-inline"> {{title}}</div>
|
||||||
<pngx-clearable-badge [selected]="isActive" (cleared)="reset()"></pngx-clearable-badge><span class="visually-hidden">selected</span>
|
<pngx-clearable-badge [selected]="isActive" (cleared)="reset()"></pngx-clearable-badge><span class="visually-hidden">selected</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu date-dropdown shadow p-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
<div class="dropdown-menu date-dropdown shadow p-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
||||||
<div class="row d-flex">
|
<h6 class="dropdown-header border-bottom" i18n>Created</h6>
|
||||||
<div class="col border-end">
|
<div class="list-group list-group-flush">
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group-item d-flex p-2 select-item" role="menuitem">
|
||||||
<h6 class="dropdown-header border-bottom" i18n>Created</h6>
|
<div class="selected-icon">
|
||||||
@for (rd of relativeDates; track rd) {
|
@if (createdRelativeDate) {
|
||||||
<button class="list-group-item small list-goup list-group-item-action d-flex p-2" role="menuitem" (click)="setCreatedRelativeDate(rd.id)">
|
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearCreatedRelativeDate()">
|
||||||
<div class="selected-icon">
|
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||||
@if (createdRelativeDate === rd.id) {
|
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||||
<i-bs width="1em" height="1em" name="check"></i-bs>
|
</a>
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between w-100 align-items-center ps-2">
|
|
||||||
<div class="pe-4">
|
|
||||||
{{rd.name}}
|
|
||||||
</div>
|
|
||||||
<div class="text-muted small pe-2">
|
|
||||||
<span class="small">
|
|
||||||
{{ rd.date | customDate:'mediumDate' }} – <ng-container i18n>now</ng-container>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
}
|
}
|
||||||
<div class="list-group-item d-flex p-2" role="menuitem">
|
</div>
|
||||||
|
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||||
<div class="selected-icon">
|
<ng-select class="w-100" name="createdRelativeDate"
|
||||||
@if (createdDateFrom) {
|
[items]="relativeDates" [(ngModel)]="createdRelativeDate"
|
||||||
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearCreatedFrom()">
|
bindValue="id"
|
||||||
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
bindLabel="name"
|
||||||
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
clearable="false"
|
||||||
</a>
|
placeholder="Relative dates"
|
||||||
}
|
i18n-placeholder
|
||||||
</div>
|
(change)="onSetCreatedRelativeDate($event)">
|
||||||
<div class="input-group input-group-sm small ps-1 pe-2">
|
<ng-template ng-option-tmp let-item="item">
|
||||||
<span class="input-group-text w-25 small text-muted" i18n>From</span>
|
<div class="d-flex">{{ item.name }}<span class="ms-auto text-muted small">{{ item.date | customDate:'mediumDate' }} – <ng-container i18n>now</ng-container></span></div>
|
||||||
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
</ng-template>
|
||||||
maxlength="10" [(ngModel)]="createdDateFrom" ngbDatepicker #createdDateFromPicker="ngbDatepicker" [footerTemplate]="createdFromFooterTemplate">
|
</ng-select>
|
||||||
<button class="btn btn-outline-secondary" (click)="createdDateFromPicker.toggle()" type="button">
|
|
||||||
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
|
||||||
</button>
|
|
||||||
<ng-template #createdFromFooterTemplate>
|
|
||||||
<div class="btn-group-xs border-top p-2 d-flex">
|
|
||||||
<button class="btn btn-primary" (click)="createdDateFrom = today; onChangeDebounce()" i18n>Today</button>
|
|
||||||
<button class="btn btn-secondary ms-auto" (click)="createdDateFromPicker.close()" i18n>Close</button>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="list-group-item d-flex p-2" role="menuitem">
|
|
||||||
|
|
||||||
<div class="selected-icon">
|
|
||||||
@if (createdDateTo) {
|
|
||||||
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearCreatedTo()">
|
|
||||||
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
|
||||||
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="input-group input-group-sm small ps-1 pe-2">
|
|
||||||
<span class="input-group-text w-25 small text-muted" i18n>To</span>
|
|
||||||
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
|
||||||
maxlength="10" [(ngModel)]="createdDateTo" ngbDatepicker #createdDateToPicker="ngbDatepicker" [footerTemplate]="createdToFooterTemplate">
|
|
||||||
<button class="btn btn-outline-secondary" (click)="createdDateToPicker.toggle()" type="button">
|
|
||||||
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
|
||||||
</button>
|
|
||||||
<ng-template #createdToFooterTemplate>
|
|
||||||
<div class="btn-group-xs border-top p-2 d-flex">
|
|
||||||
<button class="btn btn-primary" (click)="createdDateTo = today; onChangeDebounce()" i18n>Today</button>
|
|
||||||
<button class="btn btn-secondary ms-auto" (click)="createdDateToPicker.close()" i18n>Close</button>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="list-group-item d-flex p-2" role="menuitem">
|
||||||
<h6 class="dropdown-header border-bottom" i18n>Added</h6>
|
<div class="selected-icon">
|
||||||
<div class="list-group list-group-flush">
|
@if (createdDateFrom) {
|
||||||
@for (rd of relativeDates; track rd) {
|
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearCreatedFrom()">
|
||||||
<button class="list-group-item small list-goup list-group-item-action d-flex p-2" role="menuitem" (click)="setAddedRelativeDate(rd.id)">
|
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||||
<div class="selected-icon">
|
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||||
@if (addedRelativeDate === rd.id) {
|
</a>
|
||||||
<i-bs width="1em" height="1em" name="check"></i-bs>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between w-100 align-items-center ps-2">
|
|
||||||
<div class="pe-4">
|
|
||||||
{{rd.name}}
|
|
||||||
</div>
|
|
||||||
<div class="text-muted small pe-2">
|
|
||||||
<span class="small">
|
|
||||||
{{ rd.date | customDate:'mediumDate' }} – <ng-container i18n>now</ng-container>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
}
|
}
|
||||||
<div class="list-group-item d-flex p-2" role="menuitem">
|
</div>
|
||||||
|
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||||
<div class="selected-icon">
|
<span class="input-group-text w-25 small text-muted" i18n>From</span>
|
||||||
@if (addedDateFrom) {
|
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||||
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearAddedFrom()">
|
maxlength="10" [(ngModel)]="createdDateFrom" ngbDatepicker #createdDateFromPicker="ngbDatepicker" [footerTemplate]="createdFromFooterTemplate">
|
||||||
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
<button class="btn btn-outline-secondary" (click)="createdDateFromPicker.toggle()" type="button">
|
||||||
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
||||||
</a>
|
</button>
|
||||||
}
|
<ng-template #createdFromFooterTemplate>
|
||||||
|
<div class="btn-group-xs border-top p-2 d-flex">
|
||||||
|
<button class="btn btn-primary" (click)="createdDateFrom = today; onChangeDebounce()" i18n>Today</button>
|
||||||
|
<button class="btn btn-secondary ms-auto" (click)="createdDateFromPicker.close()" i18n>Close</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group input-group-sm small ps-1 pe-2">
|
</ng-template>
|
||||||
<span class="input-group-text w-25 small text-muted" i18n>From</span>
|
</div>
|
||||||
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
</div>
|
||||||
maxlength="10" [(ngModel)]="addedDateFrom" ngbDatepicker #addedDateFromPicker="ngbDatepicker" [footerTemplate]="addedFromFooterTemplate">
|
<div class="list-group-item d-flex p-2" role="menuitem">
|
||||||
<button class="btn btn-outline-secondary" (click)="addedDateFromPicker.toggle()" type="button">
|
<div class="selected-icon">
|
||||||
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
@if (createdDateTo) {
|
||||||
</button>
|
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearCreatedTo()">
|
||||||
<ng-template #addedFromFooterTemplate>
|
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||||
<div class="btn-group-xs border-top p-2 d-flex">
|
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||||
<button class="btn btn-primary" (click)="addedDateFrom = today; onChangeDebounce()" i18n>Today</button>
|
</a>
|
||||||
<button class="btn btn-secondary ms-auto" (click)="addedDateFromPicker.close()" i18n>Close</button>
|
}
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||||
|
<span class="input-group-text w-25 small text-muted" i18n>To</span>
|
||||||
|
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||||
|
maxlength="10" [(ngModel)]="createdDateTo" ngbDatepicker #createdDateToPicker="ngbDatepicker" [footerTemplate]="createdToFooterTemplate">
|
||||||
|
<button class="btn btn-outline-secondary" (click)="createdDateToPicker.toggle()" type="button">
|
||||||
|
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
||||||
|
</button>
|
||||||
|
<ng-template #createdToFooterTemplate>
|
||||||
|
<div class="btn-group-xs border-top p-2 d-flex">
|
||||||
|
<button class="btn btn-primary" (click)="createdDateTo = today; onChangeDebounce()" i18n>Today</button>
|
||||||
|
<button class="btn btn-secondary ms-auto" (click)="createdDateToPicker.close()" i18n>Close</button>
|
||||||
</div>
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="list-group-item d-flex p-2" role="menuitem">
|
</div>
|
||||||
|
<h6 class="dropdown-header border-bottom" i18n>Added</h6>
|
||||||
<div class="selected-icon">
|
<div class="list-group list-group-flush">
|
||||||
@if (addedDateTo) {
|
<div class="list-group-item d-flex p-2 select-item" role="menuitem">
|
||||||
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearAddedTo()">
|
<div class="selected-icon">
|
||||||
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
@if (addedRelativeDate) {
|
||||||
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearAddedRelativeDate()">
|
||||||
</a>
|
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||||
}
|
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||||
|
<ng-select class="w-100" name="addedRelativeDate"
|
||||||
|
[items]="relativeDates" [(ngModel)]="addedRelativeDate"
|
||||||
|
bindValue="id"
|
||||||
|
bindLabel="name"
|
||||||
|
clearable="false"
|
||||||
|
placeholder="Relative dates"
|
||||||
|
i18n-placeholder
|
||||||
|
(change)="onSetAddedRelativeDate($event)">
|
||||||
|
<ng-template ng-option-tmp let-item="item">
|
||||||
|
<div class="d-flex">{{ item.name }}<span class="ms-auto text-muted small">{{ item.date | customDate:'mediumDate' }} – <ng-container i18n>now</ng-container></span></div>
|
||||||
|
</ng-template>
|
||||||
|
</ng-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="list-group-item d-flex p-2" role="menuitem">
|
||||||
|
<div class="selected-icon">
|
||||||
|
@if (addedDateFrom) {
|
||||||
|
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearAddedFrom()">
|
||||||
|
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||||
|
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||||
|
<span class="input-group-text w-25 small text-muted" i18n>From</span>
|
||||||
|
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||||
|
maxlength="10" [(ngModel)]="addedDateFrom" ngbDatepicker #addedDateFromPicker="ngbDatepicker" [footerTemplate]="addedFromFooterTemplate">
|
||||||
|
<button class="btn btn-outline-secondary" (click)="addedDateFromPicker.toggle()" type="button">
|
||||||
|
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
||||||
|
</button>
|
||||||
|
<ng-template #addedFromFooterTemplate>
|
||||||
|
<div class="btn-group-xs border-top p-2 d-flex">
|
||||||
|
<button class="btn btn-primary" (click)="addedDateFrom = today; onChangeDebounce()" i18n>Today</button>
|
||||||
|
<button class="btn btn-secondary ms-auto" (click)="addedDateFromPicker.close()" i18n>Close</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group input-group-sm small ps-1 pe-2">
|
</ng-template>
|
||||||
<span class="input-group-text w-25 small text-muted" i18n>To</span>
|
</div>
|
||||||
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
</div>
|
||||||
maxlength="10" [(ngModel)]="addedDateTo" ngbDatepicker #addedDateToPicker="ngbDatepicker" [footerTemplate]="addedToFooterTemplate">
|
<div class="list-group-item d-flex p-2" role="menuitem">
|
||||||
<button class="btn btn-outline-secondary" (click)="addedDateToPicker.toggle()" type="button">
|
<div class="selected-icon">
|
||||||
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
@if (addedDateTo) {
|
||||||
</button>
|
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearAddedTo()">
|
||||||
<ng-template #addedToFooterTemplate>
|
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||||
<div class="btn-group-xs border-top p-2 d-flex">
|
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||||
<button class="btn btn-primary" (click)="addedDateTo = today; onChangeDebounce()" i18n>Today</button>
|
</a>
|
||||||
<button class="btn btn-secondary ms-auto" (click)="addedDateToPicker.close()" i18n>Close</button>
|
}
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||||
|
<span class="input-group-text w-25 small text-muted" i18n>To</span>
|
||||||
|
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||||
|
maxlength="10" [(ngModel)]="addedDateTo" ngbDatepicker #addedDateToPicker="ngbDatepicker" [footerTemplate]="addedToFooterTemplate">
|
||||||
|
<button class="btn btn-outline-secondary" (click)="addedDateToPicker.toggle()" type="button">
|
||||||
|
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
||||||
|
</button>
|
||||||
|
<ng-template #addedToFooterTemplate>
|
||||||
|
<div class="btn-group-xs border-top p-2 d-flex">
|
||||||
|
<button class="btn btn-primary" (click)="addedDateTo = today; onChangeDebounce()" i18n>Today</button>
|
||||||
|
<button class="btn btn-secondary ms-auto" (click)="addedDateToPicker.close()" i18n>Close</button>
|
||||||
</div>
|
</div>
|
||||||
|
</ng-template>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,16 +1,7 @@
|
|||||||
.date-dropdown {
|
.date-dropdown {
|
||||||
|
--bs-dropdown-min-width: 22rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
@media(min-width: 768px) {
|
|
||||||
--bs-dropdown-min-width: 40rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 767px) {
|
|
||||||
.border-end {
|
|
||||||
border: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-link {
|
.btn-link {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
@ -21,6 +12,10 @@
|
|||||||
min-height: 1em;
|
min-height: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select-item .selected-icon {
|
||||||
|
line-height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
.input-group-sm {
|
.input-group-sm {
|
||||||
.form-control {
|
.form-control {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
@ -82,10 +82,12 @@ describe('DatesDropdownComponent', () => {
|
|||||||
it('should support relative dates', fakeAsync(() => {
|
it('should support relative dates', fakeAsync(() => {
|
||||||
let result: DateSelection
|
let result: DateSelection
|
||||||
component.datesSet.subscribe((date) => (result = date))
|
component.datesSet.subscribe((date) => (result = date))
|
||||||
component.setCreatedRelativeDate(null)
|
component.createdRelativeDate = RelativeDate.WITHIN_1_WEEK // normally set by ngModel binding in dropdown
|
||||||
component.setCreatedRelativeDate(RelativeDate.WITHIN_1_WEEK)
|
component.onSetCreatedRelativeDate({
|
||||||
component.setAddedRelativeDate(null)
|
id: RelativeDate.WITHIN_1_WEEK,
|
||||||
component.setAddedRelativeDate(RelativeDate.WITHIN_1_WEEK)
|
} as any)
|
||||||
|
component.addedRelativeDate = RelativeDate.WITHIN_1_WEEK // normally set by ngModel binding in dropdown
|
||||||
|
component.onSetAddedRelativeDate({ id: RelativeDate.WITHIN_1_WEEK } as any)
|
||||||
tick(500)
|
tick(500)
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
createdFrom: null,
|
createdFrom: null,
|
||||||
@ -147,8 +149,19 @@ describe('DatesDropdownComponent', () => {
|
|||||||
expect(component.addedDateTo).toBeNull()
|
expect(component.addedDateTo).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should support clearRelativeDate', () => {
|
||||||
|
component.createdRelativeDate = RelativeDate.WITHIN_1_WEEK
|
||||||
|
component.clearCreatedRelativeDate()
|
||||||
|
expect(component.createdRelativeDate).toBeNull()
|
||||||
|
|
||||||
|
component.addedRelativeDate = RelativeDate.WITHIN_1_WEEK
|
||||||
|
component.clearAddedRelativeDate()
|
||||||
|
expect(component.addedRelativeDate).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
it('should limit keyboard events', () => {
|
it('should limit keyboard events', () => {
|
||||||
const input: HTMLInputElement = fixture.nativeElement.querySelector('input')
|
const input: HTMLInputElement =
|
||||||
|
fixture.nativeElement.querySelector('input.form-control')
|
||||||
let event: KeyboardEvent = new KeyboardEvent('keypress', {
|
let event: KeyboardEvent = new KeyboardEvent('keypress', {
|
||||||
key: '9',
|
key: '9',
|
||||||
})
|
})
|
||||||
@ -163,4 +176,19 @@ describe('DatesDropdownComponent', () => {
|
|||||||
input.dispatchEvent(event)
|
input.dispatchEvent(event)
|
||||||
expect(eventSpy).toHaveBeenCalled()
|
expect(eventSpy).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should support debounce', fakeAsync(() => {
|
||||||
|
let result: DateSelection
|
||||||
|
component.datesSet.subscribe((date) => (result = date))
|
||||||
|
component.onChangeDebounce()
|
||||||
|
tick(500)
|
||||||
|
expect(result).toEqual({
|
||||||
|
createdFrom: null,
|
||||||
|
createdTo: null,
|
||||||
|
createdRelativeDateID: null,
|
||||||
|
addedFrom: null,
|
||||||
|
addedTo: null,
|
||||||
|
addedRelativeDateID: null,
|
||||||
|
})
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
@ -13,13 +13,14 @@ import {
|
|||||||
NgbDatepickerModule,
|
NgbDatepickerModule,
|
||||||
NgbDropdownModule,
|
NgbDropdownModule,
|
||||||
} from '@ng-bootstrap/ng-bootstrap'
|
} from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { NgSelectModule } from '@ng-select/ng-select'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { Subject, Subscription } from 'rxjs'
|
import { Subject, Subscription } from 'rxjs'
|
||||||
import { debounceTime } from 'rxjs/operators'
|
import { debounceTime } from 'rxjs/operators'
|
||||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
|
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
|
||||||
import { popperOptionsReenablePreventOverflow } from 'src/app/utils/popper-options'
|
import { pngxPopperOptions } from 'src/app/utils/popper-options'
|
||||||
import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component'
|
import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component'
|
||||||
|
|
||||||
export interface DateSelection {
|
export interface DateSelection {
|
||||||
@ -32,10 +33,14 @@ export interface DateSelection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum RelativeDate {
|
export enum RelativeDate {
|
||||||
WITHIN_1_WEEK = 0,
|
WITHIN_1_WEEK = 1,
|
||||||
WITHIN_1_MONTH = 1,
|
WITHIN_1_MONTH = 2,
|
||||||
WITHIN_3_MONTHS = 2,
|
WITHIN_3_MONTHS = 3,
|
||||||
WITHIN_1_YEAR = 3,
|
WITHIN_1_YEAR = 4,
|
||||||
|
THIS_YEAR = 5,
|
||||||
|
THIS_MONTH = 6,
|
||||||
|
TODAY = 7,
|
||||||
|
YESTERDAY = 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -49,13 +54,14 @@ export enum RelativeDate {
|
|||||||
NgxBootstrapIconsModule,
|
NgxBootstrapIconsModule,
|
||||||
NgbDatepickerModule,
|
NgbDatepickerModule,
|
||||||
NgbDropdownModule,
|
NgbDropdownModule,
|
||||||
|
NgSelectModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
NgClass,
|
NgClass,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class DatesDropdownComponent implements OnInit, OnDestroy {
|
export class DatesDropdownComponent implements OnInit, OnDestroy {
|
||||||
public popperOptions = popperOptionsReenablePreventOverflow
|
public popperOptions = pngxPopperOptions
|
||||||
|
|
||||||
constructor(settings: SettingsService) {
|
constructor(settings: SettingsService) {
|
||||||
this.datePlaceHolder = settings.getLocalizedDateInputFormat()
|
this.datePlaceHolder = settings.getLocalizedDateInputFormat()
|
||||||
@ -82,44 +88,64 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
|
|||||||
name: $localize`Within 1 year`,
|
name: $localize`Within 1 year`,
|
||||||
date: new Date().setFullYear(new Date().getFullYear() - 1),
|
date: new Date().setFullYear(new Date().getFullYear() - 1),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: RelativeDate.THIS_YEAR,
|
||||||
|
name: $localize`This year`,
|
||||||
|
date: new Date('1/1/' + new Date().getFullYear()),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: RelativeDate.THIS_MONTH,
|
||||||
|
name: $localize`This month`,
|
||||||
|
date: new Date().setDate(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: RelativeDate.TODAY,
|
||||||
|
name: $localize`Today`,
|
||||||
|
date: new Date().setHours(0, 0, 0, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: RelativeDate.YESTERDAY,
|
||||||
|
name: $localize`Yesterday`,
|
||||||
|
date: new Date().setDate(new Date().getDate() - 1),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
datePlaceHolder: string
|
datePlaceHolder: string
|
||||||
|
|
||||||
// created
|
// created
|
||||||
@Input()
|
@Input()
|
||||||
createdDateTo: string
|
createdDateTo: string = null
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
createdDateToChange = new EventEmitter<string>()
|
createdDateToChange = new EventEmitter<string>()
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
createdDateFrom: string
|
createdDateFrom: string = null
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
createdDateFromChange = new EventEmitter<string>()
|
createdDateFromChange = new EventEmitter<string>()
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
createdRelativeDate: RelativeDate
|
createdRelativeDate: RelativeDate = null
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
createdRelativeDateChange = new EventEmitter<number>()
|
createdRelativeDateChange = new EventEmitter<number>()
|
||||||
|
|
||||||
// added
|
// added
|
||||||
@Input()
|
@Input()
|
||||||
addedDateTo: string
|
addedDateTo: string = null
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
addedDateToChange = new EventEmitter<string>()
|
addedDateToChange = new EventEmitter<string>()
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
addedDateFrom: string
|
addedDateFrom: string = null
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
addedDateFromChange = new EventEmitter<string>()
|
addedDateFromChange = new EventEmitter<string>()
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
addedRelativeDate: RelativeDate
|
addedRelativeDate: RelativeDate = null
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
addedRelativeDateChange = new EventEmitter<number>()
|
addedRelativeDateChange = new EventEmitter<number>()
|
||||||
@ -133,6 +159,9 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
|
|||||||
@Input()
|
@Input()
|
||||||
disabled: boolean = false
|
disabled: boolean = false
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
placement: string = 'bottom-start'
|
||||||
|
|
||||||
public readonly today: string = new Date().toISOString().split('T')[0]
|
public readonly today: string = new Date().toISOString().split('T')[0]
|
||||||
|
|
||||||
get isActive(): boolean {
|
get isActive(): boolean {
|
||||||
@ -172,17 +201,17 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
|
|||||||
this.onChange()
|
this.onChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
setCreatedRelativeDate(rd: RelativeDate) {
|
onSetCreatedRelativeDate(rd: { id: number; name: string; date: number }) {
|
||||||
|
// createdRelativeDate is set by ngModel
|
||||||
this.createdDateTo = null
|
this.createdDateTo = null
|
||||||
this.createdDateFrom = null
|
this.createdDateFrom = null
|
||||||
this.createdRelativeDate = this.createdRelativeDate == rd ? null : rd
|
|
||||||
this.onChange()
|
this.onChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
setAddedRelativeDate(rd: RelativeDate) {
|
onSetAddedRelativeDate(rd: { id: number; name: string; date: number }) {
|
||||||
|
// addedRelativeDate is set by ngModel
|
||||||
this.addedDateTo = null
|
this.addedDateTo = null
|
||||||
this.addedDateFrom = null
|
this.addedDateFrom = null
|
||||||
this.addedRelativeDate = this.addedRelativeDate == rd ? null : rd
|
|
||||||
this.onChange()
|
this.onChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,6 +253,11 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
|
|||||||
this.onChange()
|
this.onChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearCreatedRelativeDate() {
|
||||||
|
this.createdRelativeDate = null
|
||||||
|
this.onChange()
|
||||||
|
}
|
||||||
|
|
||||||
clearAddedTo() {
|
clearAddedTo() {
|
||||||
this.addedDateTo = null
|
this.addedDateTo = null
|
||||||
this.onChange()
|
this.onChange()
|
||||||
@ -234,6 +268,11 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
|
|||||||
this.onChange()
|
this.onChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearAddedRelativeDate() {
|
||||||
|
this.addedRelativeDate = null
|
||||||
|
this.onChange()
|
||||||
|
}
|
||||||
|
|
||||||
// prevent chars other than numbers and separators
|
// prevent chars other than numbers and separators
|
||||||
onKeyPress(event: KeyboardEvent) {
|
onKeyPress(event: KeyboardEvent) {
|
||||||
if ('Enter' !== event.key && !/[0-9,\.\/-]+/.test(event.key)) {
|
if ('Enter' !== event.key && !/[0-9,\.\/-]+/.test(event.key)) {
|
||||||
|
@ -189,6 +189,7 @@
|
|||||||
<pngx-input-select i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
|
<pngx-input-select i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
|
||||||
<pngx-input-select i18n-title title="Assign storage path" [items]="storagePaths" [allowNull]="true" formControlName="assign_storage_path"></pngx-input-select>
|
<pngx-input-select i18n-title title="Assign storage path" [items]="storagePaths" [allowNull]="true" formControlName="assign_storage_path"></pngx-input-select>
|
||||||
<pngx-input-select i18n-title title="Assign custom fields" multiple="true" [items]="customFields" [allowNull]="true" formControlName="assign_custom_fields"></pngx-input-select>
|
<pngx-input-select i18n-title title="Assign custom fields" multiple="true" [items]="customFields" [allowNull]="true" formControlName="assign_custom_fields"></pngx-input-select>
|
||||||
|
<pngx-input-custom-fields-values formControlName="assign_custom_fields_values" [selectedFields]="formGroup.get('assign_custom_fields').value" (removeSelectedField)="removeSelectedCustomField($event, formGroup)"></pngx-input-custom-fields-values>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<pngx-input-select i18n-title title="Assign owner" [items]="users" bindLabel="username" formControlName="assign_owner" [allowNull]="true"></pngx-input-select>
|
<pngx-input-select i18n-title title="Assign owner" [items]="users" bindLabel="username" formControlName="assign_owner" [allowNull]="true"></pngx-input-select>
|
||||||
|
@ -2,7 +2,12 @@ import { CdkDragDrop } from '@angular/cdk/drag-drop'
|
|||||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormGroup,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
} from '@angular/forms'
|
||||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgSelectModule } from '@ng-select/ng-select'
|
import { NgSelectModule } from '@ng-select/ng-select'
|
||||||
import { of } from 'rxjs'
|
import { of } from 'rxjs'
|
||||||
@ -369,4 +374,19 @@ describe('WorkflowEditDialogComponent', () => {
|
|||||||
expect(component.objectForm.get('actions').value[0].email).toBeNull()
|
expect(component.objectForm.get('actions').value[0].email).toBeNull()
|
||||||
expect(component.objectForm.get('actions').value[0].webhook).toBeNull()
|
expect(component.objectForm.get('actions').value[0].webhook).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should remove selected custom field from the form group', () => {
|
||||||
|
const formGroup = new FormGroup({
|
||||||
|
assign_custom_fields: new FormControl([1, 2, 3]),
|
||||||
|
})
|
||||||
|
|
||||||
|
component.removeSelectedCustomField(2, formGroup)
|
||||||
|
expect(formGroup.get('assign_custom_fields').value).toEqual([1, 3])
|
||||||
|
|
||||||
|
component.removeSelectedCustomField(1, formGroup)
|
||||||
|
expect(formGroup.get('assign_custom_fields').value).toEqual([3])
|
||||||
|
|
||||||
|
component.removeSelectedCustomField(3, formGroup)
|
||||||
|
expect(formGroup.get('assign_custom_fields').value).toEqual([])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -47,6 +47,7 @@ import { WorkflowService } from 'src/app/services/rest/workflow.service'
|
|||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
import { ConfirmButtonComponent } from '../../confirm-button/confirm-button.component'
|
import { ConfirmButtonComponent } from '../../confirm-button/confirm-button.component'
|
||||||
import { CheckComponent } from '../../input/check/check.component'
|
import { CheckComponent } from '../../input/check/check.component'
|
||||||
|
import { CustomFieldsValuesComponent } from '../../input/custom-fields-values/custom-fields-values.component'
|
||||||
import { EntriesComponent } from '../../input/entries/entries.component'
|
import { EntriesComponent } from '../../input/entries/entries.component'
|
||||||
import { NumberComponent } from '../../input/number/number.component'
|
import { NumberComponent } from '../../input/number/number.component'
|
||||||
import { PermissionsGroupComponent } from '../../input/permissions/permissions-group/permissions-group.component'
|
import { PermissionsGroupComponent } from '../../input/permissions/permissions-group/permissions-group.component'
|
||||||
@ -151,6 +152,7 @@ const TRIGGER_MATCHING_ALGORITHMS = MATCHING_ALGORITHMS.filter(
|
|||||||
SelectComponent,
|
SelectComponent,
|
||||||
TextAreaComponent,
|
TextAreaComponent,
|
||||||
TagsComponent,
|
TagsComponent,
|
||||||
|
CustomFieldsValuesComponent,
|
||||||
PermissionsGroupComponent,
|
PermissionsGroupComponent,
|
||||||
PermissionsUserComponent,
|
PermissionsUserComponent,
|
||||||
ConfirmButtonComponent,
|
ConfirmButtonComponent,
|
||||||
@ -439,6 +441,9 @@ export class WorkflowEditDialogComponent
|
|||||||
assign_change_users: new FormControl(action.assign_change_users),
|
assign_change_users: new FormControl(action.assign_change_users),
|
||||||
assign_change_groups: new FormControl(action.assign_change_groups),
|
assign_change_groups: new FormControl(action.assign_change_groups),
|
||||||
assign_custom_fields: new FormControl(action.assign_custom_fields),
|
assign_custom_fields: new FormControl(action.assign_custom_fields),
|
||||||
|
assign_custom_fields_values: new FormControl(
|
||||||
|
action.assign_custom_fields_values
|
||||||
|
),
|
||||||
remove_tags: new FormControl(action.remove_tags),
|
remove_tags: new FormControl(action.remove_tags),
|
||||||
remove_all_tags: new FormControl(action.remove_all_tags),
|
remove_all_tags: new FormControl(action.remove_all_tags),
|
||||||
remove_document_types: new FormControl(action.remove_document_types),
|
remove_document_types: new FormControl(action.remove_document_types),
|
||||||
@ -565,6 +570,7 @@ export class WorkflowEditDialogComponent
|
|||||||
assign_change_users: [],
|
assign_change_users: [],
|
||||||
assign_change_groups: [],
|
assign_change_groups: [],
|
||||||
assign_custom_fields: [],
|
assign_custom_fields: [],
|
||||||
|
assign_custom_fields_values: {},
|
||||||
remove_tags: [],
|
remove_tags: [],
|
||||||
remove_all_tags: false,
|
remove_all_tags: false,
|
||||||
remove_document_types: [],
|
remove_document_types: [],
|
||||||
@ -643,4 +649,12 @@ export class WorkflowEditDialogComponent
|
|||||||
})
|
})
|
||||||
super.save()
|
super.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public removeSelectedCustomField(fieldId: number, group: FormGroup) {
|
||||||
|
group
|
||||||
|
.get('assign_custom_fields')
|
||||||
|
.setValue(
|
||||||
|
group.get('assign_custom_fields').value.filter((id) => id !== fieldId)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
|
|||||||
import { FilterPipe } from 'src/app/pipes/filter.pipe'
|
import { FilterPipe } from 'src/app/pipes/filter.pipe'
|
||||||
import { HotKeyService } from 'src/app/services/hot-key.service'
|
import { HotKeyService } from 'src/app/services/hot-key.service'
|
||||||
import { SelectionDataItem } from 'src/app/services/rest/document.service'
|
import { SelectionDataItem } from 'src/app/services/rest/document.service'
|
||||||
import { popperOptionsReenablePreventOverflow } from 'src/app/utils/popper-options'
|
import { pngxPopperOptions } from 'src/app/utils/popper-options'
|
||||||
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
|
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
|
||||||
import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component'
|
import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component'
|
||||||
import {
|
import {
|
||||||
@ -380,7 +380,7 @@ export class FilterableDropdownComponent
|
|||||||
@ViewChild('dropdown') dropdown: NgbDropdown
|
@ViewChild('dropdown') dropdown: NgbDropdown
|
||||||
@ViewChild('buttonItems') buttonItems: ElementRef
|
@ViewChild('buttonItems') buttonItems: ElementRef
|
||||||
|
|
||||||
public popperOptions = popperOptionsReenablePreventOverflow
|
public popperOptions = pngxPopperOptions
|
||||||
|
|
||||||
filterText: string
|
filterText: string
|
||||||
|
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
<div class="list-group mt-3 selected-fields">
|
||||||
|
@for (fieldId of selectedFields; track fieldId) {
|
||||||
|
<div class="list-group-item
|
||||||
|
d-flex
|
||||||
|
justify-content-between
|
||||||
|
align-items-center">
|
||||||
|
@switch (getCustomField(fieldId)?.data_type) {
|
||||||
|
@case (CustomFieldDataType.String) {
|
||||||
|
<pngx-input-text [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"></pngx-input-text>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Date) {
|
||||||
|
<pngx-input-date [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"></pngx-input-date>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Integer) {
|
||||||
|
<pngx-input-number [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"
|
||||||
|
[showAdd]="false"></pngx-input-number>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Float) {
|
||||||
|
<pngx-input-number [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"
|
||||||
|
[showAdd]="false"
|
||||||
|
[step]=".1"></pngx-input-number>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Monetary) {
|
||||||
|
<pngx-input-monetary [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[defaultCurrency]="getCustomField(fieldId)?.extra_data?.default_currency"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"></pngx-input-monetary>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Boolean) {
|
||||||
|
<pngx-input-check [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"></pngx-input-check>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Url) {
|
||||||
|
<pngx-input-url [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"></pngx-input-url>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.DocumentLink) {
|
||||||
|
<pngx-input-document-link [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[horizontal]="true"></pngx-input-document-link>
|
||||||
|
}
|
||||||
|
@case (CustomFieldDataType.Select) {
|
||||||
|
<pngx-input-select [(ngModel)]="value[fieldId]" (ngModelChange)="onChange(value)"
|
||||||
|
[title]="getCustomField(fieldId)?.name"
|
||||||
|
class="flex-grow-1"
|
||||||
|
[items]="getCustomField(fieldId)?.extra_data.select_options"
|
||||||
|
class="flex-grow-1"
|
||||||
|
bindLabel="label"
|
||||||
|
[allowNull]="true"
|
||||||
|
[horizontal]="true"></pngx-input-select>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<button type="button" class="btn btn-link text-danger" (click)="removeSelectedField.next(fieldId)">
|
||||||
|
<i-bs name="trash"></i-bs>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
@ -0,0 +1,3 @@
|
|||||||
|
:host ::ng-deep .list-group-item .mb-3 {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||||
|
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||||
|
import {
|
||||||
|
FormsModule,
|
||||||
|
NG_VALUE_ACCESSOR,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
} from '@angular/forms'
|
||||||
|
import { of } from 'rxjs'
|
||||||
|
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
|
||||||
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
|
import { CustomFieldsValuesComponent } from './custom-fields-values.component'
|
||||||
|
|
||||||
|
describe('CustomFieldsValuesComponent', () => {
|
||||||
|
let component: CustomFieldsValuesComponent
|
||||||
|
let fixture: ComponentFixture<CustomFieldsValuesComponent>
|
||||||
|
let customFieldsService: CustomFieldsService
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [FormsModule, ReactiveFormsModule, CustomFieldsValuesComponent],
|
||||||
|
providers: [
|
||||||
|
provideHttpClient(withInterceptorsFromDi()),
|
||||||
|
provideHttpClientTesting(),
|
||||||
|
],
|
||||||
|
}).compileComponents()
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CustomFieldsValuesComponent)
|
||||||
|
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
|
||||||
|
component = fixture.componentInstance
|
||||||
|
customFieldsService = TestBed.inject(CustomFieldsService)
|
||||||
|
jest.spyOn(customFieldsService, 'listAll').mockReturnValue(
|
||||||
|
of({
|
||||||
|
all: [1],
|
||||||
|
count: 1,
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Field 1',
|
||||||
|
data_type: CustomFieldDataType.String,
|
||||||
|
} as CustomField,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
fixture.detectChanges()
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CustomFieldsValuesComponent)
|
||||||
|
component = fixture.componentInstance
|
||||||
|
fixture.detectChanges()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should set selectedFields and map values correctly', () => {
|
||||||
|
component.value = { 1: 'value1' }
|
||||||
|
component.selectedFields = [1, 2]
|
||||||
|
expect(component.selectedFields).toEqual([1, 2])
|
||||||
|
expect(component.value).toEqual({ 1: 'value1', 2: null })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return the correct custom field by id', () => {
|
||||||
|
const field = component.getCustomField(1)
|
||||||
|
expect(field).toEqual({
|
||||||
|
id: 1,
|
||||||
|
name: 'Field 1',
|
||||||
|
data_type: CustomFieldDataType.String,
|
||||||
|
} as CustomField)
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,90 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
forwardRef,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
} from '@angular/core'
|
||||||
|
import {
|
||||||
|
FormsModule,
|
||||||
|
NG_VALUE_ACCESSOR,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
} from '@angular/forms'
|
||||||
|
import { RouterModule } from '@angular/router'
|
||||||
|
import { NgSelectModule } from '@ng-select/ng-select'
|
||||||
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
|
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
|
||||||
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
|
import { AbstractInputComponent } from '../abstract-input'
|
||||||
|
import { CheckComponent } from '../check/check.component'
|
||||||
|
import { DateComponent } from '../date/date.component'
|
||||||
|
import { DocumentLinkComponent } from '../document-link/document-link.component'
|
||||||
|
import { MonetaryComponent } from '../monetary/monetary.component'
|
||||||
|
import { NumberComponent } from '../number/number.component'
|
||||||
|
import { SelectComponent } from '../select/select.component'
|
||||||
|
import { TextComponent } from '../text/text.component'
|
||||||
|
import { UrlComponent } from '../url/url.component'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => CustomFieldsValuesComponent),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
selector: 'pngx-input-custom-fields-values',
|
||||||
|
templateUrl: './custom-fields-values.component.html',
|
||||||
|
styleUrl: './custom-fields-values.component.scss',
|
||||||
|
imports: [
|
||||||
|
TextComponent,
|
||||||
|
DateComponent,
|
||||||
|
NumberComponent,
|
||||||
|
DocumentLinkComponent,
|
||||||
|
UrlComponent,
|
||||||
|
SelectComponent,
|
||||||
|
MonetaryComponent,
|
||||||
|
CheckComponent,
|
||||||
|
NgSelectModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
RouterModule,
|
||||||
|
NgxBootstrapIconsModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CustomFieldsValuesComponent extends AbstractInputComponent<Object> {
|
||||||
|
public CustomFieldDataType = CustomFieldDataType
|
||||||
|
|
||||||
|
constructor(customFieldsService: CustomFieldsService) {
|
||||||
|
super()
|
||||||
|
customFieldsService.listAll().subscribe((items) => {
|
||||||
|
this.fields = items.results
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fields: CustomField[]
|
||||||
|
|
||||||
|
private _selectedFields: number[]
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set selectedFields(newFields: number[]) {
|
||||||
|
this._selectedFields = newFields
|
||||||
|
// map the selected fields to an object with field_id as key and value as value
|
||||||
|
this.value = newFields.reduce((acc, fieldId) => {
|
||||||
|
acc[fieldId] = this.value?.[fieldId] || null
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
this.onChange(this.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedFields(): number[] {
|
||||||
|
return this._selectedFields
|
||||||
|
}
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
public removeSelectedField: EventEmitter<number> = new EventEmitter<number>()
|
||||||
|
|
||||||
|
public getCustomField(id: number): CustomField {
|
||||||
|
return this.fields.find((field) => field.id === id)
|
||||||
|
}
|
||||||
|
}
|
@ -51,6 +51,6 @@
|
|||||||
<p class="mb-0 mt-2"><button class="btn btn-sm btn-outline-secondary" (click)="close.emit(toast); toast.action()">{{toast.actionName}}</button></p>
|
<p class="mb-0 mt-2"><button class="btn btn-sm btn-outline-secondary" (click)="close.emit(toast); toast.action()">{{toast.actionName}}</button></p>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn-close ms-auto flex-shrink-0" data-bs-dismiss="toast" aria-label="Close" (click)="close.emit(toast);"></button>
|
<button type="button" class="btn-close ms-auto flex-shrink-0" data-bs-dismiss="toast" aria-label="Close" (click)="closed.emit(toast);"></button>
|
||||||
</div>
|
</div>
|
||||||
</ngb-toast>
|
</ngb-toast>
|
||||||
|
@ -27,7 +27,7 @@ export class ToastComponent {
|
|||||||
|
|
||||||
@Output() hidden: EventEmitter<Toast> = new EventEmitter<Toast>()
|
@Output() hidden: EventEmitter<Toast> = new EventEmitter<Toast>()
|
||||||
|
|
||||||
@Output() close: EventEmitter<Toast> = new EventEmitter<Toast>()
|
@Output() closed: EventEmitter<Toast> = new EventEmitter<Toast>()
|
||||||
|
|
||||||
public copied: boolean = false
|
public copied: boolean = false
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
@for (toast of toasts; track toast.id) {
|
@for (toast of toasts; track toast.id) {
|
||||||
<pngx-toast [toast]="toast" [autohide]="true" (close)="closeToast()"></pngx-toast>
|
<pngx-toast [toast]="toast" [autohide]="true" (closed)="closeToast()"></pngx-toast>
|
||||||
}
|
}
|
||||||
|
@ -1040,6 +1040,27 @@ describe('BulkEditorComponent', () => {
|
|||||||
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
|
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
|
||||||
) // listAllFilteredIds
|
) // listAllFilteredIds
|
||||||
expect(documentListViewService.selected.size).toEqual(0)
|
expect(documentListViewService.selected.size).toEqual(0)
|
||||||
|
|
||||||
|
// Test with archiveFallback enabled
|
||||||
|
modal.componentInstance.deleteOriginals = false
|
||||||
|
modal.componentInstance.archiveFallback = true
|
||||||
|
modal.componentInstance.confirm()
|
||||||
|
req = httpTestingController.expectOne(
|
||||||
|
`${environment.apiBaseUrl}documents/bulk_edit/`
|
||||||
|
)
|
||||||
|
req.flush(true)
|
||||||
|
expect(req.request.body).toEqual({
|
||||||
|
documents: [3, 4],
|
||||||
|
method: 'merge',
|
||||||
|
parameters: { metadata_document_id: 3, archive_fallback: true },
|
||||||
|
})
|
||||||
|
httpTestingController.match(
|
||||||
|
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
|
||||||
|
) // list reload
|
||||||
|
httpTestingController.match(
|
||||||
|
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
|
||||||
|
) // listAllFilteredIds
|
||||||
|
expect(documentListViewService.selected.size).toEqual(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should support bulk download with archive, originals or both and file formatting', () => {
|
it('should support bulk download with archive, originals or both and file formatting', () => {
|
||||||
|
@ -857,6 +857,9 @@ export class BulkEditorComponent
|
|||||||
if (mergeDialog.deleteOriginals) {
|
if (mergeDialog.deleteOriginals) {
|
||||||
args['delete_originals'] = true
|
args['delete_originals'] = true
|
||||||
}
|
}
|
||||||
|
if (mergeDialog.archiveFallback) {
|
||||||
|
args['archive_fallback'] = true
|
||||||
|
}
|
||||||
mergeDialog.buttonsEnabled = false
|
mergeDialog.buttonsEnabled = false
|
||||||
this.executeBulkOperation(modal, 'merge', args, mergeDialog.documentIDs)
|
this.executeBulkOperation(modal, 'merge', args, mergeDialog.documentIDs)
|
||||||
this.toastService.showInfo(
|
this.toastService.showInfo(
|
||||||
|
@ -93,6 +93,7 @@
|
|||||||
}
|
}
|
||||||
<pngx-dates-dropdown class="flex-fill fade" [class.show]="show"
|
<pngx-dates-dropdown class="flex-fill fade" [class.show]="show"
|
||||||
title="Dates" i18n-title
|
title="Dates" i18n-title
|
||||||
|
placement="bottom-end"
|
||||||
(datesSet)="updateRules()"
|
(datesSet)="updateRules()"
|
||||||
[(createdDateTo)]="dateCreatedTo"
|
[(createdDateTo)]="dateCreatedTo"
|
||||||
[(createdDateFrom)]="dateCreatedFrom"
|
[(createdDateFrom)]="dateCreatedFrom"
|
||||||
|
@ -96,7 +96,10 @@ import {
|
|||||||
import { environment } from 'src/environments/environment'
|
import { environment } from 'src/environments/environment'
|
||||||
import { ClearableBadgeComponent } from '../../common/clearable-badge/clearable-badge.component'
|
import { ClearableBadgeComponent } from '../../common/clearable-badge/clearable-badge.component'
|
||||||
import { CustomFieldsQueryDropdownComponent } from '../../common/custom-fields-query-dropdown/custom-fields-query-dropdown.component'
|
import { CustomFieldsQueryDropdownComponent } from '../../common/custom-fields-query-dropdown/custom-fields-query-dropdown.component'
|
||||||
import { DatesDropdownComponent } from '../../common/dates-dropdown/dates-dropdown.component'
|
import {
|
||||||
|
DatesDropdownComponent,
|
||||||
|
RelativeDate,
|
||||||
|
} from '../../common/dates-dropdown/dates-dropdown.component'
|
||||||
import {
|
import {
|
||||||
FilterableDropdownComponent,
|
FilterableDropdownComponent,
|
||||||
Intersection,
|
Intersection,
|
||||||
@ -422,7 +425,7 @@ describe('FilterEditorComponent', () => {
|
|||||||
value: 'created:[-1 week to now]',
|
value: 'created:[-1 week to now]',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
expect(component.dateCreatedRelativeDate).toEqual(0) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now']
|
expect(component.dateCreatedRelativeDate).toEqual(1) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now']
|
||||||
expect(component.textFilter).toBeNull()
|
expect(component.textFilter).toBeNull()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -434,7 +437,7 @@ describe('FilterEditorComponent', () => {
|
|||||||
value: 'added:[-1 week to now]',
|
value: 'added:[-1 week to now]',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
expect(component.dateAddedRelativeDate).toEqual(0) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now']
|
expect(component.dateAddedRelativeDate).toEqual(1) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now']
|
||||||
expect(component.textFilter).toBeNull()
|
expect(component.textFilter).toBeNull()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -1587,10 +1590,8 @@ describe('FilterEditorComponent', () => {
|
|||||||
const dateCreatedDropdown = fixture.debugElement.queryAll(
|
const dateCreatedDropdown = fixture.debugElement.queryAll(
|
||||||
By.directive(DatesDropdownComponent)
|
By.directive(DatesDropdownComponent)
|
||||||
)[0]
|
)[0]
|
||||||
const dateCreatedBeforeRelativeButton = dateCreatedDropdown.queryAll(
|
component.dateCreatedRelativeDate = RelativeDate.WITHIN_1_WEEK
|
||||||
By.css('button')
|
dateCreatedDropdown.triggerEventHandler('datesSet')
|
||||||
)[1]
|
|
||||||
dateCreatedBeforeRelativeButton.triggerEventHandler('click')
|
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
tick(400)
|
tick(400)
|
||||||
expect(component.filterRules).toEqual([
|
expect(component.filterRules).toEqual([
|
||||||
@ -1606,10 +1607,8 @@ describe('FilterEditorComponent', () => {
|
|||||||
const dateCreatedDropdown = fixture.debugElement.queryAll(
|
const dateCreatedDropdown = fixture.debugElement.queryAll(
|
||||||
By.directive(DatesDropdownComponent)
|
By.directive(DatesDropdownComponent)
|
||||||
)[0]
|
)[0]
|
||||||
const dateCreatedBeforeRelativeButton = dateCreatedDropdown.queryAll(
|
component.dateCreatedRelativeDate = RelativeDate.WITHIN_1_WEEK
|
||||||
By.css('button')
|
dateCreatedDropdown.triggerEventHandler('datesSet')
|
||||||
)[1]
|
|
||||||
dateCreatedBeforeRelativeButton.triggerEventHandler('click')
|
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
tick(400)
|
tick(400)
|
||||||
expect(component.filterRules).toEqual([
|
expect(component.filterRules).toEqual([
|
||||||
@ -1692,16 +1691,14 @@ describe('FilterEditorComponent', () => {
|
|||||||
const datesDropdown = fixture.debugElement.query(
|
const datesDropdown = fixture.debugElement.query(
|
||||||
By.directive(DatesDropdownComponent)
|
By.directive(DatesDropdownComponent)
|
||||||
)
|
)
|
||||||
const dateCreatedBeforeRelativeButton = datesDropdown.queryAll(
|
component.dateAddedRelativeDate = RelativeDate.WITHIN_1_WEEK
|
||||||
By.css('button')
|
datesDropdown.triggerEventHandler('datesSet')
|
||||||
)[1]
|
|
||||||
dateCreatedBeforeRelativeButton.triggerEventHandler('click')
|
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
tick(400)
|
tick(400)
|
||||||
expect(component.filterRules).toEqual([
|
expect(component.filterRules).toEqual([
|
||||||
{
|
{
|
||||||
rule_type: FILTER_FULLTEXT_QUERY,
|
rule_type: FILTER_FULLTEXT_QUERY,
|
||||||
value: 'created:[-1 week to now]',
|
value: 'added:[-1 week to now]',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
}))
|
}))
|
||||||
@ -1711,16 +1708,14 @@ describe('FilterEditorComponent', () => {
|
|||||||
const datesDropdown = fixture.debugElement.query(
|
const datesDropdown = fixture.debugElement.query(
|
||||||
By.directive(DatesDropdownComponent)
|
By.directive(DatesDropdownComponent)
|
||||||
)
|
)
|
||||||
const dateCreatedBeforeRelativeButton = datesDropdown.queryAll(
|
component.dateAddedRelativeDate = RelativeDate.WITHIN_1_WEEK
|
||||||
By.css('button')
|
datesDropdown.triggerEventHandler('datesSet')
|
||||||
)[1]
|
|
||||||
dateCreatedBeforeRelativeButton.triggerEventHandler('click')
|
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
tick(400)
|
tick(400)
|
||||||
expect(component.filterRules).toEqual([
|
expect(component.filterRules).toEqual([
|
||||||
{
|
{
|
||||||
rule_type: FILTER_FULLTEXT_QUERY,
|
rule_type: FILTER_FULLTEXT_QUERY,
|
||||||
value: 'foo,created:[-1 week to now]',
|
value: 'foo,added:[-1 week to now]',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
}))
|
}))
|
||||||
|
@ -135,24 +135,44 @@ const TEXT_FILTER_MODIFIER_NOTNULL = 'not null'
|
|||||||
const TEXT_FILTER_MODIFIER_GT = 'greater'
|
const TEXT_FILTER_MODIFIER_GT = 'greater'
|
||||||
const TEXT_FILTER_MODIFIER_LT = 'less'
|
const TEXT_FILTER_MODIFIER_LT = 'less'
|
||||||
|
|
||||||
const RELATIVE_DATE_QUERY_REGEXP_CREATED = /created:\[([^\]]+)\]/g
|
const RELATIVE_DATE_QUERY_REGEXP_CREATED = /created:[\["]([^\]]+)[\]"]/g
|
||||||
const RELATIVE_DATE_QUERY_REGEXP_ADDED = /added:\[([^\]]+)\]/g
|
const RELATIVE_DATE_QUERY_REGEXP_ADDED = /added:[\["]([^\]]+)[\]"]/g
|
||||||
const RELATIVE_DATE_QUERYSTRINGS = [
|
const RELATIVE_DATE_QUERYSTRINGS = [
|
||||||
{
|
{
|
||||||
relativeDate: RelativeDate.WITHIN_1_WEEK,
|
relativeDate: RelativeDate.WITHIN_1_WEEK,
|
||||||
dateQuery: '-1 week to now',
|
dateQuery: '-1 week to now',
|
||||||
|
isRange: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
relativeDate: RelativeDate.WITHIN_1_MONTH,
|
relativeDate: RelativeDate.WITHIN_1_MONTH,
|
||||||
dateQuery: '-1 month to now',
|
dateQuery: '-1 month to now',
|
||||||
|
isRange: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
relativeDate: RelativeDate.WITHIN_3_MONTHS,
|
relativeDate: RelativeDate.WITHIN_3_MONTHS,
|
||||||
dateQuery: '-3 month to now',
|
dateQuery: '-3 month to now',
|
||||||
|
isRange: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
relativeDate: RelativeDate.WITHIN_1_YEAR,
|
relativeDate: RelativeDate.WITHIN_1_YEAR,
|
||||||
dateQuery: '-1 year to now',
|
dateQuery: '-1 year to now',
|
||||||
|
isRange: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relativeDate: RelativeDate.THIS_YEAR,
|
||||||
|
dateQuery: 'this year',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relativeDate: RelativeDate.THIS_MONTH,
|
||||||
|
dateQuery: 'this month',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relativeDate: RelativeDate.TODAY,
|
||||||
|
dateQuery: 'today',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relativeDate: RelativeDate.YESTERDAY,
|
||||||
|
dateQuery: 'yesterday',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -907,12 +927,11 @@ export class FilterEditorComponent
|
|||||||
|
|
||||||
let existingRuleArgs = existingRule?.value.split(',')
|
let existingRuleArgs = existingRule?.value.split(',')
|
||||||
if (this.dateCreatedRelativeDate !== null) {
|
if (this.dateCreatedRelativeDate !== null) {
|
||||||
|
const rd = RELATIVE_DATE_QUERYSTRINGS.find(
|
||||||
|
(qS) => qS.relativeDate == this.dateCreatedRelativeDate
|
||||||
|
)
|
||||||
queryArgs.push(
|
queryArgs.push(
|
||||||
`created:[${
|
`created:${rd.isRange ? `[${rd.dateQuery}]` : `"${rd.dateQuery}"`}`
|
||||||
RELATIVE_DATE_QUERYSTRINGS.find(
|
|
||||||
(qS) => qS.relativeDate == this.dateCreatedRelativeDate
|
|
||||||
).dateQuery
|
|
||||||
}]`
|
|
||||||
)
|
)
|
||||||
if (existingRule) {
|
if (existingRule) {
|
||||||
queryArgs = existingRuleArgs
|
queryArgs = existingRuleArgs
|
||||||
@ -921,12 +940,11 @@ export class FilterEditorComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.dateAddedRelativeDate !== null) {
|
if (this.dateAddedRelativeDate !== null) {
|
||||||
|
const rd = RELATIVE_DATE_QUERYSTRINGS.find(
|
||||||
|
(qS) => qS.relativeDate == this.dateAddedRelativeDate
|
||||||
|
)
|
||||||
queryArgs.push(
|
queryArgs.push(
|
||||||
`added:[${
|
`added:${rd.isRange ? `[${rd.dateQuery}]` : `"${rd.dateQuery}"`}`
|
||||||
RELATIVE_DATE_QUERYSTRINGS.find(
|
|
||||||
(qS) => qS.relativeDate == this.dateAddedRelativeDate
|
|
||||||
).dateQuery
|
|
||||||
}]`
|
|
||||||
)
|
)
|
||||||
if (existingRule) {
|
if (existingRule) {
|
||||||
queryArgs = existingRuleArgs
|
queryArgs = existingRuleArgs
|
||||||
|
@ -58,6 +58,8 @@ export interface WorkflowAction extends ObjectWithId {
|
|||||||
|
|
||||||
assign_custom_fields?: number[] // [CustomField.id]
|
assign_custom_fields?: number[] // [CustomField.id]
|
||||||
|
|
||||||
|
assign_custom_fields_values?: object
|
||||||
|
|
||||||
remove_tags?: number[] // Tag.id
|
remove_tags?: number[] // Tag.id
|
||||||
|
|
||||||
remove_all_tags?: boolean
|
remove_all_tags?: boolean
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { Options } from '@popperjs/core'
|
import { Options } from '@popperjs/core'
|
||||||
import { popperOptionsReenablePreventOverflow } from './popper-options'
|
import { pngxPopperOptions } from './popper-options'
|
||||||
|
|
||||||
describe('popperOptionsReenablePreventOverflow', () => {
|
describe('popperOptionsReenablePreventOverflow', () => {
|
||||||
it('should return the config without the empty fun preventOverflow, add padding to other', () => {
|
it('should return the config with add padding', () => {
|
||||||
const config: Partial<Options> = {
|
const config: Partial<Options> = {
|
||||||
modifiers: [
|
modifiers: [
|
||||||
{ name: 'preventOverflow', fn: function () {} },
|
|
||||||
{
|
{
|
||||||
name: 'preventOverflow',
|
name: 'preventOverflow',
|
||||||
fn: function (arg0) {
|
fn: function (arg0) {
|
||||||
@ -15,7 +14,7 @@ describe('popperOptionsReenablePreventOverflow', () => {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = popperOptionsReenablePreventOverflow(config)
|
const result = pngxPopperOptions(config)
|
||||||
|
|
||||||
expect(result.modifiers.length).toBe(1)
|
expect(result.modifiers.length).toBe(1)
|
||||||
expect(result.modifiers[0].name).toBe('preventOverflow')
|
expect(result.modifiers[0].name).toBe('preventOverflow')
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
import { Options } from '@popperjs/core'
|
import { Options } from '@popperjs/core'
|
||||||
|
|
||||||
export function popperOptionsReenablePreventOverflow(
|
export function pngxPopperOptions(config: Partial<Options>): Partial<Options> {
|
||||||
config: Partial<Options>
|
const preventOverflowModifier = config.modifiers.find(
|
||||||
): Partial<Options> {
|
|
||||||
config.modifiers = config.modifiers?.filter(
|
|
||||||
(m) => !(m.name === 'preventOverflow' && m.fn?.length === 0)
|
|
||||||
)
|
|
||||||
const ogPreventOverflowModifier = config.modifiers.find(
|
|
||||||
(m) => m.name === 'preventOverflow'
|
(m) => m.name === 'preventOverflow'
|
||||||
)
|
)
|
||||||
if (ogPreventOverflowModifier) {
|
if (preventOverflowModifier) {
|
||||||
ogPreventOverflowModifier.options = {
|
preventOverflowModifier.options = {
|
||||||
padding: 10,
|
padding: 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -318,6 +318,7 @@ def merge(
|
|||||||
*,
|
*,
|
||||||
metadata_document_id: int | None = None,
|
metadata_document_id: int | None = None,
|
||||||
delete_originals: bool = False,
|
delete_originals: bool = False,
|
||||||
|
archive_fallback: bool = False,
|
||||||
user: User | None = None,
|
user: User | None = None,
|
||||||
) -> Literal["OK"]:
|
) -> Literal["OK"]:
|
||||||
logger.info(
|
logger.info(
|
||||||
@ -333,7 +334,14 @@ def merge(
|
|||||||
for doc_id in doc_ids:
|
for doc_id in doc_ids:
|
||||||
doc = qs.get(id=doc_id)
|
doc = qs.get(id=doc_id)
|
||||||
try:
|
try:
|
||||||
with pikepdf.open(str(doc.source_path)) as pdf:
|
doc_path = (
|
||||||
|
doc.archive_path
|
||||||
|
if archive_fallback
|
||||||
|
and doc.mime_type != "application/pdf"
|
||||||
|
and doc.has_archive_version
|
||||||
|
else doc.source_path
|
||||||
|
)
|
||||||
|
with pikepdf.open(str(doc_path)) as pdf:
|
||||||
version = max(version, pdf.pdf_version)
|
version = max(version, pdf.pdf_version)
|
||||||
merged_pdf.pages.extend(pdf.pages)
|
merged_pdf.pages.extend(pdf.pages)
|
||||||
affected_docs.append(doc.id)
|
affected_docs.append(doc.id)
|
||||||
@ -349,7 +357,7 @@ def merge(
|
|||||||
Path(
|
Path(
|
||||||
tempfile.mkdtemp(dir=settings.SCRATCH_DIR),
|
tempfile.mkdtemp(dir=settings.SCRATCH_DIR),
|
||||||
)
|
)
|
||||||
/ f"{'_'.join([str(doc_id) for doc_id in doc_ids])[:100]}_merged.pdf"
|
/ f"{'_'.join([str(doc_id) for doc_id in affected_docs])[:100]}_merged.pdf"
|
||||||
)
|
)
|
||||||
merged_pdf.remove_unreferenced_resources()
|
merged_pdf.remove_unreferenced_resources()
|
||||||
merged_pdf.save(filepath, min_version=version)
|
merged_pdf.save(filepath, min_version=version)
|
||||||
|
@ -26,7 +26,6 @@ from documents.models import CustomField
|
|||||||
from documents.models import CustomFieldInstance
|
from documents.models import CustomFieldInstance
|
||||||
from documents.models import Document
|
from documents.models import Document
|
||||||
from documents.models import DocumentType
|
from documents.models import DocumentType
|
||||||
from documents.models import FileInfo
|
|
||||||
from documents.models import StoragePath
|
from documents.models import StoragePath
|
||||||
from documents.models import Tag
|
from documents.models import Tag
|
||||||
from documents.models import WorkflowTrigger
|
from documents.models import WorkflowTrigger
|
||||||
@ -705,8 +704,6 @@ class ConsumerPlugin(
|
|||||||
) -> Document:
|
) -> Document:
|
||||||
# If someone gave us the original filename, use it instead of doc.
|
# If someone gave us the original filename, use it instead of doc.
|
||||||
|
|
||||||
file_info = FileInfo.from_filename(self.filename)
|
|
||||||
|
|
||||||
self.log.debug("Saving record to database")
|
self.log.debug("Saving record to database")
|
||||||
|
|
||||||
if self.metadata.created is not None:
|
if self.metadata.created is not None:
|
||||||
@ -714,9 +711,6 @@ class ConsumerPlugin(
|
|||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"Creation date from post_documents parameter: {create_date}",
|
f"Creation date from post_documents parameter: {create_date}",
|
||||||
)
|
)
|
||||||
elif file_info.created is not None:
|
|
||||||
create_date = file_info.created
|
|
||||||
self.log.debug(f"Creation date from FileInfo: {create_date}")
|
|
||||||
elif date is not None:
|
elif date is not None:
|
||||||
create_date = date
|
create_date = date
|
||||||
self.log.debug(f"Creation date from parse_date: {create_date}")
|
self.log.debug(f"Creation date from parse_date: {create_date}")
|
||||||
@ -729,7 +723,11 @@ class ConsumerPlugin(
|
|||||||
|
|
||||||
storage_type = Document.STORAGE_TYPE_UNENCRYPTED
|
storage_type = Document.STORAGE_TYPE_UNENCRYPTED
|
||||||
|
|
||||||
title = file_info.title
|
if self.metadata.filename:
|
||||||
|
title = Path(self.metadata.filename).stem
|
||||||
|
else:
|
||||||
|
title = self.input_doc.original_file.stem
|
||||||
|
|
||||||
if self.metadata.title is not None:
|
if self.metadata.title is not None:
|
||||||
try:
|
try:
|
||||||
title = self._parse_title_placeholders(self.metadata.title)
|
title = self._parse_title_placeholders(self.metadata.title)
|
||||||
@ -808,13 +806,19 @@ class ConsumerPlugin(
|
|||||||
}
|
}
|
||||||
set_permissions_for_object(permissions=permissions, object=document)
|
set_permissions_for_object(permissions=permissions, object=document)
|
||||||
|
|
||||||
if self.metadata.custom_field_ids:
|
if self.metadata.custom_fields:
|
||||||
for field_id in self.metadata.custom_field_ids:
|
for field in CustomField.objects.filter(
|
||||||
field = CustomField.objects.get(pk=field_id)
|
id__in=self.metadata.custom_fields.keys(),
|
||||||
CustomFieldInstance.objects.create(
|
).distinct():
|
||||||
field=field,
|
value_field_name = CustomFieldInstance.get_value_field_name(
|
||||||
document=document,
|
data_type=field.data_type,
|
||||||
) # adds to document
|
)
|
||||||
|
args = {
|
||||||
|
"field": field,
|
||||||
|
"document": document,
|
||||||
|
value_field_name: self.metadata.custom_fields.get(field.id, None),
|
||||||
|
}
|
||||||
|
CustomFieldInstance.objects.create(**args) # adds to document
|
||||||
|
|
||||||
def _write(self, storage_type, source, target):
|
def _write(self, storage_type, source, target):
|
||||||
with (
|
with (
|
||||||
|
@ -29,7 +29,7 @@ class DocumentMetadataOverrides:
|
|||||||
view_groups: list[int] | None = None
|
view_groups: list[int] | None = None
|
||||||
change_users: list[int] | None = None
|
change_users: list[int] | None = None
|
||||||
change_groups: list[int] | None = None
|
change_groups: list[int] | None = None
|
||||||
custom_field_ids: list[int] | None = None
|
custom_fields: dict | None = None
|
||||||
|
|
||||||
def update(self, other: "DocumentMetadataOverrides") -> "DocumentMetadataOverrides":
|
def update(self, other: "DocumentMetadataOverrides") -> "DocumentMetadataOverrides":
|
||||||
"""
|
"""
|
||||||
@ -81,11 +81,10 @@ class DocumentMetadataOverrides:
|
|||||||
self.change_groups.extend(other.change_groups)
|
self.change_groups.extend(other.change_groups)
|
||||||
self.change_groups = list(set(self.change_groups))
|
self.change_groups = list(set(self.change_groups))
|
||||||
|
|
||||||
if self.custom_field_ids is None:
|
if self.custom_fields is None:
|
||||||
self.custom_field_ids = other.custom_field_ids
|
self.custom_fields = other.custom_fields
|
||||||
elif other.custom_field_ids is not None:
|
elif other.custom_fields is not None:
|
||||||
self.custom_field_ids.extend(other.custom_field_ids)
|
self.custom_fields.update(other.custom_fields)
|
||||||
self.custom_field_ids = list(set(self.custom_field_ids))
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@ -114,9 +113,10 @@ class DocumentMetadataOverrides:
|
|||||||
only_with_perms_in=["change_document"],
|
only_with_perms_in=["change_document"],
|
||||||
).values_list("id", flat=True),
|
).values_list("id", flat=True),
|
||||||
)
|
)
|
||||||
overrides.custom_field_ids = list(
|
overrides.custom_fields = {
|
||||||
doc.custom_fields.values_list("field", flat=True),
|
custom_field.id: custom_field.value
|
||||||
)
|
for custom_field in doc.custom_fields.all()
|
||||||
|
}
|
||||||
|
|
||||||
groups_with_perms = get_groups_with_perms(
|
groups_with_perms = get_groups_with_perms(
|
||||||
doc,
|
doc,
|
||||||
|
@ -36,7 +36,6 @@ from documents.models import CustomField
|
|||||||
from documents.models import CustomFieldInstance
|
from documents.models import CustomFieldInstance
|
||||||
from documents.models import Document
|
from documents.models import Document
|
||||||
from documents.models import DocumentType
|
from documents.models import DocumentType
|
||||||
from documents.models import Log
|
|
||||||
from documents.models import PaperlessTask
|
from documents.models import PaperlessTask
|
||||||
from documents.models import ShareLink
|
from documents.models import ShareLink
|
||||||
from documents.models import StoragePath
|
from documents.models import StoragePath
|
||||||
@ -761,12 +760,6 @@ class DocumentFilterSet(FilterSet):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class LogFilterSet(FilterSet):
|
|
||||||
class Meta:
|
|
||||||
model = Log
|
|
||||||
fields = {"level": INT_KWARGS, "created": DATE_KWARGS, "group": ID_KWARGS}
|
|
||||||
|
|
||||||
|
|
||||||
class ShareLinkFilterSet(FilterSet):
|
class ShareLinkFilterSet(FilterSet):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ShareLink
|
model = ShareLink
|
||||||
|
@ -5,6 +5,7 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import gnupg
|
import gnupg
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -34,16 +35,16 @@ class GnuPG:
|
|||||||
|
|
||||||
|
|
||||||
def move_documents_and_create_thumbnails(apps, schema_editor):
|
def move_documents_and_create_thumbnails(apps, schema_editor):
|
||||||
os.makedirs(
|
(Path(settings.MEDIA_ROOT) / "documents" / "originals").mkdir(
|
||||||
os.path.join(settings.MEDIA_ROOT, "documents", "originals"),
|
parents=True,
|
||||||
exist_ok=True,
|
exist_ok=True,
|
||||||
)
|
)
|
||||||
os.makedirs(
|
(Path(settings.MEDIA_ROOT) / "documents" / "thumbnails").mkdir(
|
||||||
os.path.join(settings.MEDIA_ROOT, "documents", "thumbnails"),
|
parents=True,
|
||||||
exist_ok=True,
|
exist_ok=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
documents = os.listdir(os.path.join(settings.MEDIA_ROOT, "documents"))
|
documents: list[str] = os.listdir(Path(settings.MEDIA_ROOT) / "documents")
|
||||||
|
|
||||||
if set(documents) == {"originals", "thumbnails"}:
|
if set(documents) == {"originals", "thumbnails"}:
|
||||||
return
|
return
|
||||||
@ -60,10 +61,7 @@ def move_documents_and_create_thumbnails(apps, schema_editor):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
Path(settings.SCRATCH_DIR).mkdir(parents=True, exists_ok=True)
|
||||||
os.makedirs(settings.SCRATCH_DIR)
|
|
||||||
except FileExistsError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for f in sorted(documents):
|
for f in sorted(documents):
|
||||||
if not f.endswith("gpg"):
|
if not f.endswith("gpg"):
|
||||||
@ -77,15 +75,14 @@ def move_documents_and_create_thumbnails(apps, schema_editor):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
thumb_temp = tempfile.mkdtemp(prefix="paperless", dir=settings.SCRATCH_DIR)
|
thumb_temp: str = tempfile.mkdtemp(prefix="paperless", dir=settings.SCRATCH_DIR)
|
||||||
orig_temp = tempfile.mkdtemp(prefix="paperless", dir=settings.SCRATCH_DIR)
|
orig_temp: str = tempfile.mkdtemp(prefix="paperless", dir=settings.SCRATCH_DIR)
|
||||||
|
|
||||||
orig_source = os.path.join(settings.MEDIA_ROOT, "documents", f)
|
orig_source: Path = Path(settings.MEDIA_ROOT) / "documents" / f
|
||||||
orig_target = os.path.join(orig_temp, f.replace(".gpg", ""))
|
orig_target: Path = Path(orig_temp) / f.replace(".gpg", "")
|
||||||
|
|
||||||
with open(orig_source, "rb") as encrypted:
|
with orig_source.open("rb") as encrypted, orig_target.open("wb") as unencrypted:
|
||||||
with open(orig_target, "wb") as unencrypted:
|
unencrypted.write(GnuPG.decrypted(encrypted))
|
||||||
unencrypted.write(GnuPG.decrypted(encrypted))
|
|
||||||
|
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
(
|
(
|
||||||
@ -95,27 +92,29 @@ def move_documents_and_create_thumbnails(apps, schema_editor):
|
|||||||
"-alpha",
|
"-alpha",
|
||||||
"remove",
|
"remove",
|
||||||
orig_target,
|
orig_target,
|
||||||
os.path.join(thumb_temp, "convert-%04d.png"),
|
Path(thumb_temp) / "convert-%04d.png",
|
||||||
),
|
),
|
||||||
).wait()
|
).wait()
|
||||||
|
|
||||||
thumb_source = os.path.join(thumb_temp, "convert-0000.png")
|
thumb_source: Path = Path(thumb_temp) / "convert-0000.png"
|
||||||
thumb_target = os.path.join(
|
thumb_target: Path = (
|
||||||
settings.MEDIA_ROOT,
|
Path(settings.MEDIA_ROOT)
|
||||||
"documents",
|
/ "documents"
|
||||||
"thumbnails",
|
/ "thumbnails"
|
||||||
re.sub(r"(\d+)\.\w+(\.gpg)", "\\1.png\\2", f),
|
/ re.sub(r"(\d+)\.\w+(\.gpg)", "\\1.png\\2", f)
|
||||||
)
|
)
|
||||||
with open(thumb_source, "rb") as unencrypted:
|
with (
|
||||||
with open(thumb_target, "wb") as encrypted:
|
thumb_source.open("rb") as unencrypted,
|
||||||
encrypted.write(GnuPG.encrypted(unencrypted))
|
thumb_target.open("wb") as encrypted,
|
||||||
|
):
|
||||||
|
encrypted.write(GnuPG.encrypted(unencrypted))
|
||||||
|
|
||||||
shutil.rmtree(thumb_temp)
|
shutil.rmtree(thumb_temp)
|
||||||
shutil.rmtree(orig_temp)
|
shutil.rmtree(orig_temp)
|
||||||
|
|
||||||
shutil.move(
|
shutil.move(
|
||||||
os.path.join(settings.MEDIA_ROOT, "documents", f),
|
Path(settings.MEDIA_ROOT) / "documents" / f,
|
||||||
os.path.join(settings.MEDIA_ROOT, "documents", "originals", f),
|
Path(settings.MEDIA_ROOT) / "documents" / "originals" / f,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Generated by Django 1.9.4 on 2016-03-28 19:09
|
# Generated by Django 1.9.4 on 2016-03-28 19:09
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
from pathlib import Path
|
||||||
|
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
import gnupg
|
import gnupg
|
||||||
@ -58,16 +58,16 @@ class Document:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def source_path(self):
|
def source_path(self):
|
||||||
return os.path.join(
|
return (
|
||||||
settings.MEDIA_ROOT,
|
Path(settings.MEDIA_ROOT)
|
||||||
"documents",
|
/ "documents"
|
||||||
"originals",
|
/ "originals"
|
||||||
f"{self.pk:07}.{self.file_type}.gpg",
|
/ f"{self.pk:07}.{self.file_type}.gpg"
|
||||||
)
|
).as_posix()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def source_file(self):
|
def source_file(self):
|
||||||
return open(self.source_path, "rb")
|
return Path(self.source_path).open("rb")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def file_name(self):
|
def file_name(self):
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Generated by Django 3.1.3 on 2020-11-20 11:21
|
# Generated by Django 3.1.3 on 2020-11-20 11:21
|
||||||
import os
|
from pathlib import Path
|
||||||
|
|
||||||
import magic
|
import magic
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -12,15 +12,15 @@ STORAGE_TYPE_UNENCRYPTED = "unencrypted"
|
|||||||
STORAGE_TYPE_GPG = "gpg"
|
STORAGE_TYPE_GPG = "gpg"
|
||||||
|
|
||||||
|
|
||||||
def source_path(self):
|
def source_path(self) -> Path:
|
||||||
if self.filename:
|
if self.filename:
|
||||||
fname = str(self.filename)
|
fname: str = str(self.filename)
|
||||||
else:
|
else:
|
||||||
fname = f"{self.pk:07}.{self.file_type}"
|
fname = f"{self.pk:07}.{self.file_type}"
|
||||||
if self.storage_type == STORAGE_TYPE_GPG:
|
if self.storage_type == STORAGE_TYPE_GPG:
|
||||||
fname += ".gpg"
|
fname += ".gpg"
|
||||||
|
|
||||||
return os.path.join(settings.ORIGINALS_DIR, fname)
|
return Path(settings.ORIGINALS_DIR) / fname
|
||||||
|
|
||||||
|
|
||||||
def add_mime_types(apps, schema_editor):
|
def add_mime_types(apps, schema_editor):
|
||||||
@ -28,24 +28,22 @@ def add_mime_types(apps, schema_editor):
|
|||||||
documents = Document.objects.all()
|
documents = Document.objects.all()
|
||||||
|
|
||||||
for d in documents:
|
for d in documents:
|
||||||
f = open(source_path(d), "rb")
|
with Path(source_path(d)).open("rb") as f:
|
||||||
if d.storage_type == STORAGE_TYPE_GPG:
|
if d.storage_type == STORAGE_TYPE_GPG:
|
||||||
data = GnuPG.decrypted(f)
|
data = GnuPG.decrypted(f)
|
||||||
else:
|
else:
|
||||||
data = f.read(1024)
|
data = f.read(1024)
|
||||||
|
|
||||||
d.mime_type = magic.from_buffer(data, mime=True)
|
d.mime_type = magic.from_buffer(data, mime=True)
|
||||||
d.save()
|
d.save()
|
||||||
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
|
|
||||||
def add_file_extensions(apps, schema_editor):
|
def add_file_extensions(apps, schema_editor):
|
||||||
Document = apps.get_model("documents", "Document")
|
Document = apps.get_model("documents", "Document")
|
||||||
documents = Document.objects.all()
|
documents = Document.objects.all()
|
||||||
|
|
||||||
for d in documents:
|
for d in documents:
|
||||||
d.file_type = os.path.splitext(d.filename)[1].strip(".")
|
d.file_type = Path(d.filename).suffix.lstrip(".")
|
||||||
d.save()
|
d.save()
|
||||||
|
|
||||||
|
|
||||||
|
15
src/documents/migrations/1064_delete_log.py
Normal file
15
src/documents/migrations/1064_delete_log.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-02-28 15:19
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("documents", "1063_paperlesstask_type_alter_paperlesstask_task_name_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name="Log",
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-03-01 18:10
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("documents", "1064_delete_log"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="workflowaction",
|
||||||
|
name="assign_custom_fields_values",
|
||||||
|
field=models.JSONField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional values to assign to the custom fields.",
|
||||||
|
null=True,
|
||||||
|
verbose_name="custom field values",
|
||||||
|
default=dict,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -1,12 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
from collections import OrderedDict
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
import dateutil.parser
|
|
||||||
import pathvalidate
|
import pathvalidate
|
||||||
from celery import states
|
from celery import states
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -320,7 +315,7 @@ class Document(SoftDeleteModel, ModelWithOwner):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def source_file(self):
|
def source_file(self):
|
||||||
return open(self.source_path, "rb")
|
return Path(self.source_path).open("rb")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_archive_version(self) -> bool:
|
def has_archive_version(self) -> bool:
|
||||||
@ -335,7 +330,7 @@ class Document(SoftDeleteModel, ModelWithOwner):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def archive_file(self):
|
def archive_file(self):
|
||||||
return open(self.archive_path, "rb")
|
return Path(self.archive_path).open("rb")
|
||||||
|
|
||||||
def get_public_filename(self, *, archive=False, counter=0, suffix=None) -> str:
|
def get_public_filename(self, *, archive=False, counter=0, suffix=None) -> str:
|
||||||
"""
|
"""
|
||||||
@ -372,43 +367,13 @@ class Document(SoftDeleteModel, ModelWithOwner):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def thumbnail_file(self):
|
def thumbnail_file(self):
|
||||||
return open(self.thumbnail_path, "rb")
|
return Path(self.thumbnail_path).open("rb")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def created_date(self):
|
def created_date(self):
|
||||||
return timezone.localdate(self.created)
|
return timezone.localdate(self.created)
|
||||||
|
|
||||||
|
|
||||||
class Log(models.Model):
|
|
||||||
LEVELS = (
|
|
||||||
(logging.DEBUG, _("debug")),
|
|
||||||
(logging.INFO, _("information")),
|
|
||||||
(logging.WARNING, _("warning")),
|
|
||||||
(logging.ERROR, _("error")),
|
|
||||||
(logging.CRITICAL, _("critical")),
|
|
||||||
)
|
|
||||||
|
|
||||||
group = models.UUIDField(_("group"), blank=True, null=True)
|
|
||||||
|
|
||||||
message = models.TextField(_("message"))
|
|
||||||
|
|
||||||
level = models.PositiveIntegerField(
|
|
||||||
_("level"),
|
|
||||||
choices=LEVELS,
|
|
||||||
default=logging.INFO,
|
|
||||||
)
|
|
||||||
|
|
||||||
created = models.DateTimeField(_("created"), auto_now_add=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ("-created",)
|
|
||||||
verbose_name = _("log")
|
|
||||||
verbose_name_plural = _("logs")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
|
|
||||||
class SavedView(ModelWithOwner):
|
class SavedView(ModelWithOwner):
|
||||||
class DisplayMode(models.TextChoices):
|
class DisplayMode(models.TextChoices):
|
||||||
TABLE = ("table", _("Table"))
|
TABLE = ("table", _("Table"))
|
||||||
@ -548,91 +513,6 @@ class SavedViewFilterRule(models.Model):
|
|||||||
return f"SavedViewFilterRule: {self.rule_type} : {self.value}"
|
return f"SavedViewFilterRule: {self.rule_type} : {self.value}"
|
||||||
|
|
||||||
|
|
||||||
# TODO: why is this in the models file?
|
|
||||||
# TODO: how about, what is this and where is it documented?
|
|
||||||
# It appears to parsing JSON from an environment variable to get a title and date from
|
|
||||||
# the filename, if possible, as a higher priority than either document filename or
|
|
||||||
# content parsing
|
|
||||||
class FileInfo:
|
|
||||||
REGEXES = OrderedDict(
|
|
||||||
[
|
|
||||||
(
|
|
||||||
"created-title",
|
|
||||||
re.compile(
|
|
||||||
r"^(?P<created>\d{8}(\d{6})?Z) - (?P<title>.*)$",
|
|
||||||
flags=re.IGNORECASE,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("title", re.compile(r"(?P<title>.*)$", flags=re.IGNORECASE)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
created=None,
|
|
||||||
correspondent=None,
|
|
||||||
title=None,
|
|
||||||
tags=(),
|
|
||||||
extension=None,
|
|
||||||
):
|
|
||||||
self.created = created
|
|
||||||
self.title = title
|
|
||||||
self.extension = extension
|
|
||||||
self.correspondent = correspondent
|
|
||||||
self.tags = tags
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _get_created(cls, created):
|
|
||||||
try:
|
|
||||||
return dateutil.parser.parse(f"{created[:-1]:0<14}Z")
|
|
||||||
except ValueError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _get_title(cls, title):
|
|
||||||
return title
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _mangle_property(cls, properties, name):
|
|
||||||
if name in properties:
|
|
||||||
properties[name] = getattr(cls, f"_get_{name}")(properties[name])
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_filename(cls, filename) -> "FileInfo":
|
|
||||||
# Mutate filename in-place before parsing its components
|
|
||||||
# by applying at most one of the configured transformations.
|
|
||||||
for pattern, repl in settings.FILENAME_PARSE_TRANSFORMS:
|
|
||||||
(filename, count) = pattern.subn(repl, filename)
|
|
||||||
if count:
|
|
||||||
break
|
|
||||||
|
|
||||||
# do this after the transforms so that the transforms can do whatever
|
|
||||||
# with the file extension.
|
|
||||||
filename_no_ext = os.path.splitext(filename)[0]
|
|
||||||
|
|
||||||
if filename_no_ext == filename and filename.startswith("."):
|
|
||||||
# This is a very special case where there is no text before the
|
|
||||||
# file type.
|
|
||||||
# TODO: this should be handled better. The ext is not removed
|
|
||||||
# because usually, files like '.pdf' are just hidden files
|
|
||||||
# with the name pdf, but in our case, its more likely that
|
|
||||||
# there's just no name to begin with.
|
|
||||||
filename = ""
|
|
||||||
# This isn't too bad either, since we'll just not match anything
|
|
||||||
# and return an empty title. TODO: actually, this is kinda bad.
|
|
||||||
else:
|
|
||||||
filename = filename_no_ext
|
|
||||||
|
|
||||||
# Parse filename components.
|
|
||||||
for regex in cls.REGEXES.values():
|
|
||||||
m = regex.match(filename)
|
|
||||||
if m:
|
|
||||||
properties = m.groupdict()
|
|
||||||
cls._mangle_property(properties, "created")
|
|
||||||
cls._mangle_property(properties, "title")
|
|
||||||
return cls(**properties)
|
|
||||||
|
|
||||||
|
|
||||||
# Extending User Model Using a One-To-One Link
|
# Extending User Model Using a One-To-One Link
|
||||||
class UiSettings(models.Model):
|
class UiSettings(models.Model):
|
||||||
user = models.OneToOneField(
|
user = models.OneToOneField(
|
||||||
@ -1391,6 +1271,16 @@ class WorkflowAction(models.Model):
|
|||||||
verbose_name=_("assign these custom fields"),
|
verbose_name=_("assign these custom fields"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assign_custom_fields_values = models.JSONField(
|
||||||
|
_("custom field values"),
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text=_(
|
||||||
|
"Optional values to assign to the custom fields.",
|
||||||
|
),
|
||||||
|
default=dict,
|
||||||
|
)
|
||||||
|
|
||||||
remove_tags = models.ManyToManyField(
|
remove_tags = models.ManyToManyField(
|
||||||
Tag,
|
Tag,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
@ -1446,6 +1446,11 @@ class BulkEditSerializer(
|
|||||||
raise serializers.ValidationError("delete_originals must be a boolean")
|
raise serializers.ValidationError("delete_originals must be a boolean")
|
||||||
else:
|
else:
|
||||||
parameters["delete_originals"] = False
|
parameters["delete_originals"] = False
|
||||||
|
if "archive_fallback" in parameters:
|
||||||
|
if not isinstance(parameters["archive_fallback"], bool):
|
||||||
|
raise serializers.ValidationError("archive_fallback must be a boolean")
|
||||||
|
else:
|
||||||
|
parameters["archive_fallback"] = False
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
method = attrs["method"]
|
method = attrs["method"]
|
||||||
@ -2018,6 +2023,7 @@ class WorkflowActionSerializer(serializers.ModelSerializer):
|
|||||||
"assign_change_users",
|
"assign_change_users",
|
||||||
"assign_change_groups",
|
"assign_change_groups",
|
||||||
"assign_custom_fields",
|
"assign_custom_fields",
|
||||||
|
"assign_custom_fields_values",
|
||||||
"remove_all_tags",
|
"remove_all_tags",
|
||||||
"remove_tags",
|
"remove_tags",
|
||||||
"remove_all_correspondents",
|
"remove_all_correspondents",
|
||||||
|
@ -632,7 +632,7 @@ def send_webhook(
|
|||||||
else:
|
else:
|
||||||
httpx.post(
|
httpx.post(
|
||||||
url,
|
url,
|
||||||
data=data,
|
content=data,
|
||||||
files=files,
|
files=files,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
).raise_for_status()
|
).raise_for_status()
|
||||||
@ -770,23 +770,40 @@ def run_workflows(
|
|||||||
if action.assign_custom_fields.exists():
|
if action.assign_custom_fields.exists():
|
||||||
if not use_overrides:
|
if not use_overrides:
|
||||||
for field in action.assign_custom_fields.all():
|
for field in action.assign_custom_fields.all():
|
||||||
if not CustomFieldInstance.objects.filter(
|
value_field_name = CustomFieldInstance.get_value_field_name(
|
||||||
|
data_type=field.data_type,
|
||||||
|
)
|
||||||
|
args = {
|
||||||
|
value_field_name: action.assign_custom_fields_values.get(
|
||||||
|
str(field.pk),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
# for some reason update_or_create doesn't work here
|
||||||
|
instance = CustomFieldInstance.objects.filter(
|
||||||
field=field,
|
field=field,
|
||||||
document=document,
|
document=document,
|
||||||
).exists():
|
).first()
|
||||||
# can be triggered on existing docs, so only add the field if it doesn't already exist
|
if instance:
|
||||||
|
setattr(instance, value_field_name, args[value_field_name])
|
||||||
|
instance.save()
|
||||||
|
else:
|
||||||
CustomFieldInstance.objects.create(
|
CustomFieldInstance.objects.create(
|
||||||
|
**args,
|
||||||
field=field,
|
field=field,
|
||||||
document=document,
|
document=document,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
overrides.custom_field_ids = list(
|
if overrides.custom_fields is None:
|
||||||
set(
|
overrides.custom_fields = {}
|
||||||
(overrides.custom_field_ids or [])
|
overrides.custom_fields.update(
|
||||||
+ list(
|
{
|
||||||
action.assign_custom_fields.values_list("pk", flat=True),
|
field.pk: action.assign_custom_fields_values.get(
|
||||||
),
|
str(field.pk),
|
||||||
),
|
None,
|
||||||
|
)
|
||||||
|
for field in action.assign_custom_fields.all()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def removal_action():
|
def removal_action():
|
||||||
@ -944,18 +961,18 @@ def run_workflows(
|
|||||||
if not use_overrides:
|
if not use_overrides:
|
||||||
CustomFieldInstance.objects.filter(document=document).delete()
|
CustomFieldInstance.objects.filter(document=document).delete()
|
||||||
else:
|
else:
|
||||||
overrides.custom_field_ids = None
|
overrides.custom_fields = None
|
||||||
elif action.remove_custom_fields.exists():
|
elif action.remove_custom_fields.exists():
|
||||||
if not use_overrides:
|
if not use_overrides:
|
||||||
CustomFieldInstance.objects.filter(
|
CustomFieldInstance.objects.filter(
|
||||||
field__in=action.remove_custom_fields.all(),
|
field__in=action.remove_custom_fields.all(),
|
||||||
document=document,
|
document=document,
|
||||||
).delete()
|
).delete()
|
||||||
elif overrides.custom_field_ids:
|
elif overrides.custom_fields:
|
||||||
for field in action.remove_custom_fields.filter(
|
for field in action.remove_custom_fields.filter(
|
||||||
pk__in=overrides.custom_field_ids,
|
pk__in=overrides.custom_fields.keys(),
|
||||||
):
|
):
|
||||||
overrides.custom_field_ids.remove(field.pk)
|
overrides.custom_fields.pop(field.pk, None)
|
||||||
|
|
||||||
def email_action():
|
def email_action():
|
||||||
if not settings.EMAIL_ENABLED:
|
if not settings.EMAIL_ENABLED:
|
||||||
|
@ -272,7 +272,7 @@ def update_document_content_maybe_archive_file(document_id):
|
|||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
oldDocument = Document.objects.get(pk=document.pk)
|
oldDocument = Document.objects.get(pk=document.pk)
|
||||||
if parser.get_archive_path():
|
if parser.get_archive_path():
|
||||||
with open(parser.get_archive_path(), "rb") as f:
|
with Path(parser.get_archive_path()).open("rb") as f:
|
||||||
checksum = hashlib.md5(f.read()).hexdigest()
|
checksum = hashlib.md5(f.read()).hexdigest()
|
||||||
# I'm going to save first so that in case the file move
|
# I'm going to save first so that in case the file move
|
||||||
# fails, the database is rolled back.
|
# fails, the database is rolled back.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
from pathlib import Path
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@ -136,10 +136,7 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase):
|
|||||||
THEN:
|
THEN:
|
||||||
- old app_logo file is deleted
|
- old app_logo file is deleted
|
||||||
"""
|
"""
|
||||||
with open(
|
with (Path(__file__).parent / "samples" / "simple.jpg").open("rb") as f:
|
||||||
os.path.join(os.path.dirname(__file__), "samples", "simple.jpg"),
|
|
||||||
"rb",
|
|
||||||
) as f:
|
|
||||||
self.client.patch(
|
self.client.patch(
|
||||||
f"{self.ENDPOINT}1/",
|
f"{self.ENDPOINT}1/",
|
||||||
{
|
{
|
||||||
@ -148,15 +145,12 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase):
|
|||||||
)
|
)
|
||||||
config = ApplicationConfiguration.objects.first()
|
config = ApplicationConfiguration.objects.first()
|
||||||
old_logo = config.app_logo
|
old_logo = config.app_logo
|
||||||
self.assertTrue(os.path.exists(old_logo.path))
|
self.assertTrue(Path(old_logo.path).exists())
|
||||||
with open(
|
with (Path(__file__).parent / "samples" / "simple.png").open("rb") as f:
|
||||||
os.path.join(os.path.dirname(__file__), "samples", "simple.png"),
|
|
||||||
"rb",
|
|
||||||
) as f:
|
|
||||||
self.client.patch(
|
self.client.patch(
|
||||||
f"{self.ENDPOINT}1/",
|
f"{self.ENDPOINT}1/",
|
||||||
{
|
{
|
||||||
"app_logo": f,
|
"app_logo": f,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertFalse(os.path.exists(old_logo.path))
|
self.assertFalse(Path(old_logo.path).exists())
|
||||||
|
@ -28,6 +28,7 @@ from documents.caching import CACHE_50_MINUTES
|
|||||||
from documents.caching import CLASSIFIER_HASH_KEY
|
from documents.caching import CLASSIFIER_HASH_KEY
|
||||||
from documents.caching import CLASSIFIER_MODIFIED_KEY
|
from documents.caching import CLASSIFIER_MODIFIED_KEY
|
||||||
from documents.caching import CLASSIFIER_VERSION_KEY
|
from documents.caching import CLASSIFIER_VERSION_KEY
|
||||||
|
from documents.data_models import DocumentSource
|
||||||
from documents.models import Correspondent
|
from documents.models import Correspondent
|
||||||
from documents.models import CustomField
|
from documents.models import CustomField
|
||||||
from documents.models import CustomFieldInstance
|
from documents.models import CustomFieldInstance
|
||||||
@ -39,7 +40,10 @@ from documents.models import SavedView
|
|||||||
from documents.models import ShareLink
|
from documents.models import ShareLink
|
||||||
from documents.models import StoragePath
|
from documents.models import StoragePath
|
||||||
from documents.models import Tag
|
from documents.models import Tag
|
||||||
|
from documents.models import Workflow
|
||||||
|
from documents.models import WorkflowAction
|
||||||
from documents.models import WorkflowTrigger
|
from documents.models import WorkflowTrigger
|
||||||
|
from documents.signals.handlers import run_workflows
|
||||||
from documents.tests.utils import DirectoriesMixin
|
from documents.tests.utils import DirectoriesMixin
|
||||||
from documents.tests.utils import DocumentConsumeDelayMixin
|
from documents.tests.utils import DocumentConsumeDelayMixin
|
||||||
|
|
||||||
@ -1362,7 +1366,69 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
|||||||
|
|
||||||
self.assertEqual(input_doc.original_file.name, "simple.pdf")
|
self.assertEqual(input_doc.original_file.name, "simple.pdf")
|
||||||
self.assertEqual(overrides.filename, "simple.pdf")
|
self.assertEqual(overrides.filename, "simple.pdf")
|
||||||
self.assertEqual(overrides.custom_field_ids, [custom_field.id])
|
self.assertEqual(overrides.custom_fields, {custom_field.id: None})
|
||||||
|
|
||||||
|
def test_upload_with_custom_fields_and_workflow(self):
|
||||||
|
"""
|
||||||
|
GIVEN: A document with a source file
|
||||||
|
WHEN: Upload the document with custom fields and a workflow
|
||||||
|
THEN: Metadata is set correctly, mimicking what happens in the real consumer plugin
|
||||||
|
"""
|
||||||
|
self.consume_file_mock.return_value = celery.result.AsyncResult(
|
||||||
|
id=str(uuid.uuid4()),
|
||||||
|
)
|
||||||
|
|
||||||
|
cf = CustomField.objects.create(
|
||||||
|
name="stringfield",
|
||||||
|
data_type=CustomField.FieldDataType.STRING,
|
||||||
|
)
|
||||||
|
cf2 = CustomField.objects.create(
|
||||||
|
name="intfield",
|
||||||
|
data_type=CustomField.FieldDataType.INT,
|
||||||
|
)
|
||||||
|
|
||||||
|
trigger1 = WorkflowTrigger.objects.create(
|
||||||
|
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
||||||
|
sources=f"{DocumentSource.ApiUpload},{DocumentSource.ConsumeFolder},{DocumentSource.MailFetch}",
|
||||||
|
)
|
||||||
|
action1 = WorkflowAction.objects.create(
|
||||||
|
assign_title="Doc title",
|
||||||
|
)
|
||||||
|
action1.assign_custom_fields.add(cf2)
|
||||||
|
action1.assign_custom_fields_values = {cf2.id: 123}
|
||||||
|
action1.save()
|
||||||
|
|
||||||
|
w1 = Workflow.objects.create(
|
||||||
|
name="Workflow 1",
|
||||||
|
order=0,
|
||||||
|
)
|
||||||
|
w1.triggers.add(trigger1)
|
||||||
|
w1.actions.add(action1)
|
||||||
|
w1.save()
|
||||||
|
|
||||||
|
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
|
||||||
|
response = self.client.post(
|
||||||
|
"/api/documents/post_document/",
|
||||||
|
{
|
||||||
|
"document": f,
|
||||||
|
"custom_fields": [cf.id],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
self.consume_file_mock.assert_called_once()
|
||||||
|
|
||||||
|
input_doc, overrides = self.get_last_consume_delay_call_args()
|
||||||
|
|
||||||
|
new_overrides, msg = run_workflows(
|
||||||
|
trigger_type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
||||||
|
document=input_doc,
|
||||||
|
logging_group=None,
|
||||||
|
overrides=overrides,
|
||||||
|
)
|
||||||
|
overrides.update(new_overrides)
|
||||||
|
self.assertEqual(overrides.custom_fields, {cf.id: None, cf2.id: 123})
|
||||||
|
|
||||||
def test_upload_with_webui_source(self):
|
def test_upload_with_webui_source(self):
|
||||||
"""
|
"""
|
||||||
|
@ -514,12 +514,23 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
Path(__file__).parent / "samples" / "simple.jpg",
|
Path(__file__).parent / "samples" / "simple.jpg",
|
||||||
img_doc,
|
img_doc,
|
||||||
)
|
)
|
||||||
|
img_doc_archive = self.dirs.archive_dir / "sample_image.pdf"
|
||||||
|
shutil.copy(
|
||||||
|
Path(__file__).parent
|
||||||
|
/ "samples"
|
||||||
|
/ "documents"
|
||||||
|
/ "originals"
|
||||||
|
/ "0000001.pdf",
|
||||||
|
img_doc_archive,
|
||||||
|
)
|
||||||
self.img_doc = Document.objects.create(
|
self.img_doc = Document.objects.create(
|
||||||
checksum="D",
|
checksum="D",
|
||||||
title="D",
|
title="D",
|
||||||
filename=img_doc,
|
filename=img_doc,
|
||||||
mime_type="image/jpeg",
|
mime_type="image/jpeg",
|
||||||
)
|
)
|
||||||
|
self.img_doc.archive_filename = img_doc_archive
|
||||||
|
self.img_doc.save()
|
||||||
|
|
||||||
@mock.patch("documents.tasks.consume_file.s")
|
@mock.patch("documents.tasks.consume_file.s")
|
||||||
def test_merge(self, mock_consume_file):
|
def test_merge(self, mock_consume_file):
|
||||||
@ -605,6 +616,32 @@ class TestPDFActions(DirectoriesMixin, TestCase):
|
|||||||
doc_ids,
|
doc_ids,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@mock.patch("documents.tasks.consume_file.s")
|
||||||
|
def test_merge_with_archive_fallback(self, mock_consume_file):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Existing documents
|
||||||
|
WHEN:
|
||||||
|
- Merge action is called with 2 documents, one of which is an image and archive_fallback is set to True
|
||||||
|
THEN:
|
||||||
|
- Image document should be included
|
||||||
|
"""
|
||||||
|
doc_ids = [self.doc2.id, self.img_doc.id]
|
||||||
|
|
||||||
|
result = bulk_edit.merge(doc_ids, archive_fallback=True)
|
||||||
|
self.assertEqual(result, "OK")
|
||||||
|
|
||||||
|
expected_filename = (
|
||||||
|
f"{'_'.join([str(doc_id) for doc_id in doc_ids])[:100]}_merged.pdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_consume_file.assert_called()
|
||||||
|
consume_file_args, _ = mock_consume_file.call_args
|
||||||
|
self.assertEqual(
|
||||||
|
Path(consume_file_args[0].original_file).name,
|
||||||
|
expected_filename,
|
||||||
|
)
|
||||||
|
|
||||||
@mock.patch("documents.tasks.consume_file.delay")
|
@mock.patch("documents.tasks.consume_file.delay")
|
||||||
@mock.patch("pikepdf.open")
|
@mock.patch("pikepdf.open")
|
||||||
def test_merge_with_errors(self, mock_open_pdf, mock_consume_file):
|
def test_merge_with_errors(self, mock_open_pdf, mock_consume_file):
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -617,7 +616,7 @@ class TestClassifier(DirectoriesMixin, TestCase):
|
|||||||
self.assertListEqual(self.classifier.predict_tags(doc2.content), [])
|
self.assertListEqual(self.classifier.predict_tags(doc2.content), [])
|
||||||
|
|
||||||
def test_load_classifier_not_exists(self):
|
def test_load_classifier_not_exists(self):
|
||||||
self.assertFalse(os.path.exists(settings.MODEL_FILE))
|
self.assertFalse(Path(settings.MODEL_FILE).exists())
|
||||||
self.assertIsNone(load_classifier())
|
self.assertIsNone(load_classifier())
|
||||||
|
|
||||||
@mock.patch("documents.classifier.DocumentClassifier.load")
|
@mock.patch("documents.classifier.DocumentClassifier.load")
|
||||||
@ -632,7 +631,7 @@ class TestClassifier(DirectoriesMixin, TestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
@override_settings(
|
@override_settings(
|
||||||
MODEL_FILE=os.path.join(os.path.dirname(__file__), "data", "model.pickle"),
|
MODEL_FILE=(Path(__file__).parent / "data" / "model.pickle").as_posix(),
|
||||||
)
|
)
|
||||||
@pytest.mark.skip(
|
@pytest.mark.skip(
|
||||||
reason="Disabled caching due to high memory usage - need to investigate.",
|
reason="Disabled caching due to high memory usage - need to investigate.",
|
||||||
@ -648,24 +647,24 @@ class TestClassifier(DirectoriesMixin, TestCase):
|
|||||||
@mock.patch("documents.classifier.DocumentClassifier.load")
|
@mock.patch("documents.classifier.DocumentClassifier.load")
|
||||||
def test_load_classifier_incompatible_version(self, load):
|
def test_load_classifier_incompatible_version(self, load):
|
||||||
Path(settings.MODEL_FILE).touch()
|
Path(settings.MODEL_FILE).touch()
|
||||||
self.assertTrue(os.path.exists(settings.MODEL_FILE))
|
self.assertTrue(Path(settings.MODEL_FILE).exists())
|
||||||
|
|
||||||
load.side_effect = IncompatibleClassifierVersionError("Dummy Error")
|
load.side_effect = IncompatibleClassifierVersionError("Dummy Error")
|
||||||
self.assertIsNone(load_classifier())
|
self.assertIsNone(load_classifier())
|
||||||
self.assertFalse(os.path.exists(settings.MODEL_FILE))
|
self.assertFalse(Path(settings.MODEL_FILE).exists())
|
||||||
|
|
||||||
@mock.patch("documents.classifier.DocumentClassifier.load")
|
@mock.patch("documents.classifier.DocumentClassifier.load")
|
||||||
def test_load_classifier_os_error(self, load):
|
def test_load_classifier_os_error(self, load):
|
||||||
Path(settings.MODEL_FILE).touch()
|
Path(settings.MODEL_FILE).touch()
|
||||||
self.assertTrue(os.path.exists(settings.MODEL_FILE))
|
self.assertTrue(Path(settings.MODEL_FILE).exists())
|
||||||
|
|
||||||
load.side_effect = OSError()
|
load.side_effect = OSError()
|
||||||
self.assertIsNone(load_classifier())
|
self.assertIsNone(load_classifier())
|
||||||
self.assertTrue(os.path.exists(settings.MODEL_FILE))
|
self.assertTrue(Path(settings.MODEL_FILE).exists())
|
||||||
|
|
||||||
def test_load_old_classifier_version(self):
|
def test_load_old_classifier_version(self):
|
||||||
shutil.copy(
|
shutil.copy(
|
||||||
os.path.join(os.path.dirname(__file__), "data", "v1.17.4.model.pickle"),
|
Path(__file__).parent / "data" / "v1.17.4.model.pickle",
|
||||||
self.dirs.scratch_dir,
|
self.dirs.scratch_dir,
|
||||||
)
|
)
|
||||||
with override_settings(
|
with override_settings(
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import shutil
|
import shutil
|
||||||
import stat
|
import stat
|
||||||
import tempfile
|
import tempfile
|
||||||
import zoneinfo
|
import zoneinfo
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest import TestCase as UnittestTestCase
|
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
@ -26,7 +24,6 @@ from documents.models import Correspondent
|
|||||||
from documents.models import CustomField
|
from documents.models import CustomField
|
||||||
from documents.models import Document
|
from documents.models import Document
|
||||||
from documents.models import DocumentType
|
from documents.models import DocumentType
|
||||||
from documents.models import FileInfo
|
|
||||||
from documents.models import StoragePath
|
from documents.models import StoragePath
|
||||||
from documents.models import Tag
|
from documents.models import Tag
|
||||||
from documents.parsers import DocumentParser
|
from documents.parsers import DocumentParser
|
||||||
@ -40,143 +37,6 @@ from paperless_mail.models import MailRule
|
|||||||
from paperless_mail.parsers import MailDocumentParser
|
from paperless_mail.parsers import MailDocumentParser
|
||||||
|
|
||||||
|
|
||||||
class TestAttributes(UnittestTestCase):
|
|
||||||
TAGS = ("tag1", "tag2", "tag3")
|
|
||||||
|
|
||||||
def _test_guess_attributes_from_name(self, filename, sender, title, tags):
|
|
||||||
file_info = FileInfo.from_filename(filename)
|
|
||||||
|
|
||||||
if sender:
|
|
||||||
self.assertEqual(file_info.correspondent.name, sender, filename)
|
|
||||||
else:
|
|
||||||
self.assertIsNone(file_info.correspondent, filename)
|
|
||||||
|
|
||||||
self.assertEqual(file_info.title, title, filename)
|
|
||||||
|
|
||||||
self.assertEqual(tuple(t.name for t in file_info.tags), tags, filename)
|
|
||||||
|
|
||||||
def test_guess_attributes_from_name_when_title_starts_with_dash(self):
|
|
||||||
self._test_guess_attributes_from_name(
|
|
||||||
"- weird but should not break.pdf",
|
|
||||||
None,
|
|
||||||
"- weird but should not break",
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_guess_attributes_from_name_when_title_ends_with_dash(self):
|
|
||||||
self._test_guess_attributes_from_name(
|
|
||||||
"weird but should not break -.pdf",
|
|
||||||
None,
|
|
||||||
"weird but should not break -",
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestFieldPermutations(TestCase):
|
|
||||||
valid_dates = (
|
|
||||||
"20150102030405Z",
|
|
||||||
"20150102Z",
|
|
||||||
)
|
|
||||||
valid_correspondents = ["timmy", "Dr. McWheelie", "Dash Gor-don", "o Θεpμaoτής", ""]
|
|
||||||
valid_titles = ["title", "Title w Spaces", "Title a-dash", "Tίτλoς", ""]
|
|
||||||
valid_tags = ["tag", "tig,tag", "tag1,tag2,tag-3"]
|
|
||||||
|
|
||||||
def _test_guessed_attributes(
|
|
||||||
self,
|
|
||||||
filename,
|
|
||||||
created=None,
|
|
||||||
correspondent=None,
|
|
||||||
title=None,
|
|
||||||
tags=None,
|
|
||||||
):
|
|
||||||
info = FileInfo.from_filename(filename)
|
|
||||||
|
|
||||||
# Created
|
|
||||||
if created is None:
|
|
||||||
self.assertIsNone(info.created, filename)
|
|
||||||
else:
|
|
||||||
self.assertEqual(info.created.year, int(created[:4]), filename)
|
|
||||||
self.assertEqual(info.created.month, int(created[4:6]), filename)
|
|
||||||
self.assertEqual(info.created.day, int(created[6:8]), filename)
|
|
||||||
|
|
||||||
# Correspondent
|
|
||||||
if correspondent:
|
|
||||||
self.assertEqual(info.correspondent.name, correspondent, filename)
|
|
||||||
else:
|
|
||||||
self.assertEqual(info.correspondent, None, filename)
|
|
||||||
|
|
||||||
# Title
|
|
||||||
self.assertEqual(info.title, title, filename)
|
|
||||||
|
|
||||||
# Tags
|
|
||||||
if tags is None:
|
|
||||||
self.assertEqual(info.tags, (), filename)
|
|
||||||
else:
|
|
||||||
self.assertEqual([t.name for t in info.tags], tags.split(","), filename)
|
|
||||||
|
|
||||||
def test_just_title(self):
|
|
||||||
template = "{title}.pdf"
|
|
||||||
for title in self.valid_titles:
|
|
||||||
spec = dict(title=title)
|
|
||||||
filename = template.format(**spec)
|
|
||||||
self._test_guessed_attributes(filename, **spec)
|
|
||||||
|
|
||||||
def test_created_and_title(self):
|
|
||||||
template = "{created} - {title}.pdf"
|
|
||||||
|
|
||||||
for created in self.valid_dates:
|
|
||||||
for title in self.valid_titles:
|
|
||||||
spec = {"created": created, "title": title}
|
|
||||||
self._test_guessed_attributes(template.format(**spec), **spec)
|
|
||||||
|
|
||||||
def test_invalid_date_format(self):
|
|
||||||
info = FileInfo.from_filename("06112017Z - title.pdf")
|
|
||||||
self.assertEqual(info.title, "title")
|
|
||||||
self.assertIsNone(info.created)
|
|
||||||
|
|
||||||
def test_filename_parse_transforms(self):
|
|
||||||
filename = "tag1,tag2_20190908_180610_0001.pdf"
|
|
||||||
all_patt = re.compile("^.*$")
|
|
||||||
none_patt = re.compile("$a")
|
|
||||||
re.compile("^([a-z0-9,]+)_(\\d{8})_(\\d{6})_([0-9]+)\\.")
|
|
||||||
|
|
||||||
# No transformations configured (= default)
|
|
||||||
info = FileInfo.from_filename(filename)
|
|
||||||
self.assertEqual(info.title, "tag1,tag2_20190908_180610_0001")
|
|
||||||
self.assertEqual(info.tags, ())
|
|
||||||
self.assertIsNone(info.created)
|
|
||||||
|
|
||||||
# Pattern doesn't match (filename unaltered)
|
|
||||||
with self.settings(FILENAME_PARSE_TRANSFORMS=[(none_patt, "none.gif")]):
|
|
||||||
info = FileInfo.from_filename(filename)
|
|
||||||
self.assertEqual(info.title, "tag1,tag2_20190908_180610_0001")
|
|
||||||
|
|
||||||
# Simple transformation (match all)
|
|
||||||
with self.settings(FILENAME_PARSE_TRANSFORMS=[(all_patt, "all.gif")]):
|
|
||||||
info = FileInfo.from_filename(filename)
|
|
||||||
self.assertEqual(info.title, "all")
|
|
||||||
|
|
||||||
# Multiple transformations configured (first pattern matches)
|
|
||||||
with self.settings(
|
|
||||||
FILENAME_PARSE_TRANSFORMS=[
|
|
||||||
(all_patt, "all.gif"),
|
|
||||||
(all_patt, "anotherall.gif"),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
info = FileInfo.from_filename(filename)
|
|
||||||
self.assertEqual(info.title, "all")
|
|
||||||
|
|
||||||
# Multiple transformations configured (second pattern matches)
|
|
||||||
with self.settings(
|
|
||||||
FILENAME_PARSE_TRANSFORMS=[
|
|
||||||
(none_patt, "none.gif"),
|
|
||||||
(all_patt, "anotherall.gif"),
|
|
||||||
],
|
|
||||||
):
|
|
||||||
info = FileInfo.from_filename(filename)
|
|
||||||
self.assertEqual(info.title, "anotherall")
|
|
||||||
|
|
||||||
|
|
||||||
class _BaseTestParser(DocumentParser):
|
class _BaseTestParser(DocumentParser):
|
||||||
def get_settings(self):
|
def get_settings(self):
|
||||||
"""
|
"""
|
||||||
@ -548,7 +408,9 @@ class TestConsumer(
|
|||||||
|
|
||||||
with self.get_consumer(
|
with self.get_consumer(
|
||||||
self.get_test_file(),
|
self.get_test_file(),
|
||||||
DocumentMetadataOverrides(custom_field_ids=[cf1.id, cf3.id]),
|
DocumentMetadataOverrides(
|
||||||
|
custom_fields={cf1.id: "value1", cf3.id: "http://example.com"},
|
||||||
|
),
|
||||||
) as consumer:
|
) as consumer:
|
||||||
consumer.run()
|
consumer.run()
|
||||||
|
|
||||||
@ -560,6 +422,11 @@ class TestConsumer(
|
|||||||
self.assertIn(cf1, fields_used)
|
self.assertIn(cf1, fields_used)
|
||||||
self.assertNotIn(cf2, fields_used)
|
self.assertNotIn(cf2, fields_used)
|
||||||
self.assertIn(cf3, fields_used)
|
self.assertIn(cf3, fields_used)
|
||||||
|
self.assertEqual(document.custom_fields.get(field=cf1).value, "value1")
|
||||||
|
self.assertEqual(
|
||||||
|
document.custom_fields.get(field=cf3).value,
|
||||||
|
"http://example.com",
|
||||||
|
)
|
||||||
self._assert_first_last_send_progress()
|
self._assert_first_last_send_progress()
|
||||||
|
|
||||||
def testOverrideAsn(self):
|
def testOverrideAsn(self):
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import os
|
|
||||||
import shutil
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
@ -22,7 +22,7 @@ class TestMakeThumbnails(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
filename="test.pdf",
|
filename="test.pdf",
|
||||||
)
|
)
|
||||||
shutil.copy(
|
shutil.copy(
|
||||||
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
Path(__file__).parent / "samples" / "simple.pdf",
|
||||||
self.d1.source_path,
|
self.d1.source_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ class TestMakeThumbnails(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
filename="test2.pdf",
|
filename="test2.pdf",
|
||||||
)
|
)
|
||||||
shutil.copy(
|
shutil.copy(
|
||||||
os.path.join(os.path.dirname(__file__), "samples", "simple.pdf"),
|
Path(__file__).parent / "samples" / "simple.pdf",
|
||||||
self.d2.source_path,
|
self.d2.source_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ class TestMakeThumbnails(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
filename="test3.pdf",
|
filename="test3.pdf",
|
||||||
)
|
)
|
||||||
shutil.copy(
|
shutil.copy(
|
||||||
os.path.join(os.path.dirname(__file__), "samples", "password-is-test.pdf"),
|
Path(__file__).parent / "samples" / "password-is-test.pdf",
|
||||||
self.d3.source_path,
|
self.d3.source_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import os
|
|
||||||
import shutil
|
import shutil
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -88,18 +87,18 @@ class TestClassifier(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
|
|
||||||
tasks.train_classifier()
|
tasks.train_classifier()
|
||||||
self.assertIsFile(settings.MODEL_FILE)
|
self.assertIsFile(settings.MODEL_FILE)
|
||||||
mtime = os.stat(settings.MODEL_FILE).st_mtime
|
mtime = Path(settings.MODEL_FILE).stat().st_mtime
|
||||||
|
|
||||||
tasks.train_classifier()
|
tasks.train_classifier()
|
||||||
self.assertIsFile(settings.MODEL_FILE)
|
self.assertIsFile(settings.MODEL_FILE)
|
||||||
mtime2 = os.stat(settings.MODEL_FILE).st_mtime
|
mtime2 = Path(settings.MODEL_FILE).stat().st_mtime
|
||||||
self.assertEqual(mtime, mtime2)
|
self.assertEqual(mtime, mtime2)
|
||||||
|
|
||||||
doc.content = "test2"
|
doc.content = "test2"
|
||||||
doc.save()
|
doc.save()
|
||||||
tasks.train_classifier()
|
tasks.train_classifier()
|
||||||
self.assertIsFile(settings.MODEL_FILE)
|
self.assertIsFile(settings.MODEL_FILE)
|
||||||
mtime3 = os.stat(settings.MODEL_FILE).st_mtime
|
mtime3 = Path(settings.MODEL_FILE).stat().st_mtime
|
||||||
self.assertNotEqual(mtime2, mtime3)
|
self.assertNotEqual(mtime2, mtime3)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import os
|
|
||||||
import tempfile
|
import tempfile
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
@ -107,12 +107,12 @@ class TestViews(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
content = b"This is a test"
|
content = b"This is a test"
|
||||||
|
|
||||||
with open(filename, "wb") as f:
|
with Path(filename).open("wb") as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
doc = Document.objects.create(
|
doc = Document.objects.create(
|
||||||
title="none",
|
title="none",
|
||||||
filename=os.path.basename(filename),
|
filename=Path(filename).name,
|
||||||
mime_type="application/pdf",
|
mime_type="application/pdf",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -133,6 +133,9 @@ class TestWorkflows(
|
|||||||
action.assign_change_groups.add(self.group1.pk)
|
action.assign_change_groups.add(self.group1.pk)
|
||||||
action.assign_custom_fields.add(self.cf1.pk)
|
action.assign_custom_fields.add(self.cf1.pk)
|
||||||
action.assign_custom_fields.add(self.cf2.pk)
|
action.assign_custom_fields.add(self.cf2.pk)
|
||||||
|
action.assign_custom_fields_values = {
|
||||||
|
self.cf2.pk: 42,
|
||||||
|
}
|
||||||
action.save()
|
action.save()
|
||||||
w = Workflow.objects.create(
|
w = Workflow.objects.create(
|
||||||
name="Workflow 1",
|
name="Workflow 1",
|
||||||
@ -209,6 +212,10 @@ class TestWorkflows(
|
|||||||
list(document.custom_fields.all().values_list("field", flat=True)),
|
list(document.custom_fields.all().values_list("field", flat=True)),
|
||||||
[self.cf1.pk, self.cf2.pk],
|
[self.cf1.pk, self.cf2.pk],
|
||||||
)
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
document.custom_fields.get(field=self.cf2.pk).value,
|
||||||
|
42,
|
||||||
|
)
|
||||||
|
|
||||||
info = cm.output[0]
|
info = cm.output[0]
|
||||||
expected_str = f"Document matched {trigger} from {w}"
|
expected_str = f"Document matched {trigger} from {w}"
|
||||||
@ -1215,11 +1222,11 @@ class TestWorkflows(
|
|||||||
def test_document_updated_workflow_existing_custom_field(self):
|
def test_document_updated_workflow_existing_custom_field(self):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
- Existing workflow with UPDATED trigger and action that adds a custom field
|
- Existing workflow with UPDATED trigger and action that assigns a custom field with a value
|
||||||
WHEN:
|
WHEN:
|
||||||
- Document is updated that already contains the field
|
- Document is updated that already contains the field
|
||||||
THEN:
|
THEN:
|
||||||
- Document update succeeds without trying to re-create the field
|
- Document update succeeds and updates the field
|
||||||
"""
|
"""
|
||||||
trigger = WorkflowTrigger.objects.create(
|
trigger = WorkflowTrigger.objects.create(
|
||||||
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
|
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
|
||||||
@ -1227,6 +1234,8 @@ class TestWorkflows(
|
|||||||
)
|
)
|
||||||
action = WorkflowAction.objects.create()
|
action = WorkflowAction.objects.create()
|
||||||
action.assign_custom_fields.add(self.cf1)
|
action.assign_custom_fields.add(self.cf1)
|
||||||
|
action.assign_custom_fields_values = {self.cf1.pk: "new value"}
|
||||||
|
action.save()
|
||||||
w = Workflow.objects.create(
|
w = Workflow.objects.create(
|
||||||
name="Workflow 1",
|
name="Workflow 1",
|
||||||
order=0,
|
order=0,
|
||||||
@ -1251,6 +1260,9 @@ class TestWorkflows(
|
|||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
doc.refresh_from_db()
|
||||||
|
self.assertEqual(doc.custom_fields.get(field=self.cf1).value, "new value")
|
||||||
|
|
||||||
def test_document_updated_workflow_merge_permissions(self):
|
def test_document_updated_workflow_merge_permissions(self):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
@ -2603,7 +2615,7 @@ class TestWorkflows(
|
|||||||
|
|
||||||
mock_post.assert_called_once_with(
|
mock_post.assert_called_once_with(
|
||||||
"http://paperless-ngx.com",
|
"http://paperless-ngx.com",
|
||||||
data="Test message",
|
content="Test message",
|
||||||
headers={},
|
headers={},
|
||||||
files=None,
|
files=None,
|
||||||
)
|
)
|
||||||
|
@ -1471,7 +1471,10 @@ class PostDocumentView(GenericAPIView):
|
|||||||
created=created,
|
created=created,
|
||||||
asn=archive_serial_number,
|
asn=archive_serial_number,
|
||||||
owner_id=request.user.id,
|
owner_id=request.user.id,
|
||||||
custom_field_ids=custom_field_ids,
|
# TODO: set values
|
||||||
|
custom_fields={cf_id: None for cf_id in custom_field_ids}
|
||||||
|
if custom_field_ids
|
||||||
|
else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
async_task = consume_file.delay(
|
async_task = consume_file.delay(
|
||||||
|
@ -2,7 +2,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: paperless-ngx\n"
|
"Project-Id-Version: paperless-ngx\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-02-25 11:07-0800\n"
|
"POT-Creation-Date: 2025-03-01 21:03-0800\n"
|
||||||
"PO-Revision-Date: 2022-02-17 04:17\n"
|
"PO-Revision-Date: 2022-02-17 04:17\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: English\n"
|
"Language-Team: English\n"
|
||||||
@ -21,39 +21,39 @@ msgstr ""
|
|||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:370
|
#: documents/filters.py:375
|
||||||
msgid "Value must be valid JSON."
|
msgid "Value must be valid JSON."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:389
|
#: documents/filters.py:394
|
||||||
msgid "Invalid custom field query expression"
|
msgid "Invalid custom field query expression"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:399
|
#: documents/filters.py:404
|
||||||
msgid "Invalid expression list. Must be nonempty."
|
msgid "Invalid expression list. Must be nonempty."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:420
|
#: documents/filters.py:425
|
||||||
msgid "Invalid logical operator {op!r}"
|
msgid "Invalid logical operator {op!r}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:434
|
#: documents/filters.py:439
|
||||||
msgid "Maximum number of query conditions exceeded."
|
msgid "Maximum number of query conditions exceeded."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:499
|
#: documents/filters.py:504
|
||||||
msgid "{name!r} is not a valid custom field."
|
msgid "{name!r} is not a valid custom field."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:536
|
#: documents/filters.py:541
|
||||||
msgid "{data_type} does not support query expr {expr!r}."
|
msgid "{data_type} does not support query expr {expr!r}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:644
|
#: documents/filters.py:649
|
||||||
msgid "Maximum nesting depth exceeded."
|
msgid "Maximum nesting depth exceeded."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/filters.py:829
|
#: documents/filters.py:834
|
||||||
msgid "Custom field not found"
|
msgid "Custom field not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ msgstr ""
|
|||||||
msgid "Automatic"
|
msgid "Automatic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:67 documents/models.py:433 documents/models.py:1526
|
#: documents/models.py:67 documents/models.py:433 documents/models.py:1536
|
||||||
#: paperless_mail/models.py:23 paperless_mail/models.py:143
|
#: paperless_mail/models.py:23 paperless_mail/models.py:143
|
||||||
msgid "name"
|
msgid "name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -256,7 +256,7 @@ msgid "The position of this document in your physical document archive."
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:295 documents/models.py:761 documents/models.py:815
|
#: documents/models.py:295 documents/models.py:761 documents/models.py:815
|
||||||
#: documents/models.py:1569
|
#: documents/models.py:1579
|
||||||
msgid "document"
|
msgid "document"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -1088,141 +1088,149 @@ msgstr ""
|
|||||||
msgid "assign these custom fields"
|
msgid "assign these custom fields"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1398
|
#: documents/models.py:1395
|
||||||
|
msgid "custom field values"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/models.py:1399
|
||||||
|
msgid "Optional values to assign to the custom fields."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: documents/models.py:1408
|
||||||
msgid "remove these tag(s)"
|
msgid "remove these tag(s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1403
|
#: documents/models.py:1413
|
||||||
msgid "remove all tags"
|
msgid "remove all tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1410
|
#: documents/models.py:1420
|
||||||
msgid "remove these document type(s)"
|
msgid "remove these document type(s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1415
|
#: documents/models.py:1425
|
||||||
msgid "remove all document types"
|
msgid "remove all document types"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1422
|
#: documents/models.py:1432
|
||||||
msgid "remove these correspondent(s)"
|
msgid "remove these correspondent(s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1427
|
#: documents/models.py:1437
|
||||||
msgid "remove all correspondents"
|
msgid "remove all correspondents"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1434
|
#: documents/models.py:1444
|
||||||
msgid "remove these storage path(s)"
|
msgid "remove these storage path(s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1439
|
#: documents/models.py:1449
|
||||||
msgid "remove all storage paths"
|
msgid "remove all storage paths"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1446
|
#: documents/models.py:1456
|
||||||
msgid "remove these owner(s)"
|
msgid "remove these owner(s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1451
|
#: documents/models.py:1461
|
||||||
msgid "remove all owners"
|
msgid "remove all owners"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1458
|
#: documents/models.py:1468
|
||||||
msgid "remove view permissions for these users"
|
msgid "remove view permissions for these users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1465
|
#: documents/models.py:1475
|
||||||
msgid "remove view permissions for these groups"
|
msgid "remove view permissions for these groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1472
|
#: documents/models.py:1482
|
||||||
msgid "remove change permissions for these users"
|
msgid "remove change permissions for these users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1479
|
#: documents/models.py:1489
|
||||||
msgid "remove change permissions for these groups"
|
msgid "remove change permissions for these groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1484
|
#: documents/models.py:1494
|
||||||
msgid "remove all permissions"
|
msgid "remove all permissions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1491
|
#: documents/models.py:1501
|
||||||
msgid "remove these custom fields"
|
msgid "remove these custom fields"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1496
|
#: documents/models.py:1506
|
||||||
msgid "remove all custom fields"
|
msgid "remove all custom fields"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1505
|
#: documents/models.py:1515
|
||||||
msgid "email"
|
msgid "email"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1514
|
#: documents/models.py:1524
|
||||||
msgid "webhook"
|
msgid "webhook"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1518
|
#: documents/models.py:1528
|
||||||
msgid "workflow action"
|
msgid "workflow action"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1519
|
#: documents/models.py:1529
|
||||||
msgid "workflow actions"
|
msgid "workflow actions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1528 paperless_mail/models.py:145
|
#: documents/models.py:1538 paperless_mail/models.py:145
|
||||||
msgid "order"
|
msgid "order"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1534
|
#: documents/models.py:1544
|
||||||
msgid "triggers"
|
msgid "triggers"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1541
|
#: documents/models.py:1551
|
||||||
msgid "actions"
|
msgid "actions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1544 paperless_mail/models.py:154
|
#: documents/models.py:1554 paperless_mail/models.py:154
|
||||||
msgid "enabled"
|
msgid "enabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1555
|
#: documents/models.py:1565
|
||||||
msgid "workflow"
|
msgid "workflow"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1559
|
#: documents/models.py:1569
|
||||||
msgid "workflow trigger type"
|
msgid "workflow trigger type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1573
|
#: documents/models.py:1583
|
||||||
msgid "date run"
|
msgid "date run"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1579
|
#: documents/models.py:1589
|
||||||
msgid "workflow run"
|
msgid "workflow run"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/models.py:1580
|
#: documents/models.py:1590
|
||||||
msgid "workflow runs"
|
msgid "workflow runs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:128
|
#: documents/serialisers.py:134
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Invalid regular expression: %(error)s"
|
msgid "Invalid regular expression: %(error)s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:554
|
#: documents/serialisers.py:560
|
||||||
msgid "Invalid color."
|
msgid "Invalid color."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:1570
|
#: documents/serialisers.py:1576
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "File type %(type)s not supported"
|
msgid "File type %(type)s not supported"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: documents/serialisers.py:1659
|
#: documents/serialisers.py:1665
|
||||||
msgid "Invalid variable detected."
|
msgid "Invalid variable detected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -1463,7 +1471,7 @@ msgstr ""
|
|||||||
msgid "Unable to parse URI {value}"
|
msgid "Unable to parse URI {value}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/apps.py:10
|
#: paperless/apps.py:11
|
||||||
msgid "Paperless"
|
msgid "Paperless"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -1611,139 +1619,139 @@ msgstr ""
|
|||||||
msgid "paperless application settings"
|
msgid "paperless application settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:721
|
#: paperless/settings.py:724
|
||||||
msgid "English (US)"
|
msgid "English (US)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:722
|
#: paperless/settings.py:725
|
||||||
msgid "Arabic"
|
msgid "Arabic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:723
|
#: paperless/settings.py:726
|
||||||
msgid "Afrikaans"
|
msgid "Afrikaans"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:724
|
#: paperless/settings.py:727
|
||||||
msgid "Belarusian"
|
msgid "Belarusian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:725
|
#: paperless/settings.py:728
|
||||||
msgid "Bulgarian"
|
msgid "Bulgarian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:726
|
#: paperless/settings.py:729
|
||||||
msgid "Catalan"
|
msgid "Catalan"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:727
|
#: paperless/settings.py:730
|
||||||
msgid "Czech"
|
msgid "Czech"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:728
|
#: paperless/settings.py:731
|
||||||
msgid "Danish"
|
msgid "Danish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:729
|
#: paperless/settings.py:732
|
||||||
msgid "German"
|
msgid "German"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:730
|
#: paperless/settings.py:733
|
||||||
msgid "Greek"
|
msgid "Greek"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:731
|
#: paperless/settings.py:734
|
||||||
msgid "English (GB)"
|
msgid "English (GB)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:732
|
#: paperless/settings.py:735
|
||||||
msgid "Spanish"
|
msgid "Spanish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:733
|
#: paperless/settings.py:736
|
||||||
msgid "Finnish"
|
msgid "Finnish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:734
|
#: paperless/settings.py:737
|
||||||
msgid "French"
|
msgid "French"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:735
|
#: paperless/settings.py:738
|
||||||
msgid "Hungarian"
|
msgid "Hungarian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:736
|
#: paperless/settings.py:739
|
||||||
msgid "Italian"
|
msgid "Italian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:737
|
#: paperless/settings.py:740
|
||||||
msgid "Japanese"
|
msgid "Japanese"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:738
|
#: paperless/settings.py:741
|
||||||
msgid "Korean"
|
msgid "Korean"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:739
|
#: paperless/settings.py:742
|
||||||
msgid "Luxembourgish"
|
msgid "Luxembourgish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:740
|
#: paperless/settings.py:743
|
||||||
msgid "Norwegian"
|
msgid "Norwegian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:741
|
#: paperless/settings.py:744
|
||||||
msgid "Dutch"
|
msgid "Dutch"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:742
|
#: paperless/settings.py:745
|
||||||
msgid "Polish"
|
msgid "Polish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:743
|
#: paperless/settings.py:746
|
||||||
msgid "Portuguese (Brazil)"
|
msgid "Portuguese (Brazil)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:744
|
#: paperless/settings.py:747
|
||||||
msgid "Portuguese"
|
msgid "Portuguese"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:745
|
#: paperless/settings.py:748
|
||||||
msgid "Romanian"
|
msgid "Romanian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:746
|
#: paperless/settings.py:749
|
||||||
msgid "Russian"
|
msgid "Russian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:747
|
#: paperless/settings.py:750
|
||||||
msgid "Slovak"
|
msgid "Slovak"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:748
|
#: paperless/settings.py:751
|
||||||
msgid "Slovenian"
|
msgid "Slovenian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:749
|
#: paperless/settings.py:752
|
||||||
msgid "Serbian"
|
msgid "Serbian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:750
|
#: paperless/settings.py:753
|
||||||
msgid "Swedish"
|
msgid "Swedish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:751
|
#: paperless/settings.py:754
|
||||||
msgid "Turkish"
|
msgid "Turkish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:752
|
#: paperless/settings.py:755
|
||||||
msgid "Ukrainian"
|
msgid "Ukrainian"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:753
|
#: paperless/settings.py:756
|
||||||
msgid "Chinese Simplified"
|
msgid "Chinese Simplified"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: paperless/settings.py:754
|
#: paperless/settings.py:757
|
||||||
msgid "Chinese Traditional"
|
msgid "Chinese Traditional"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import json
|
|||||||
import math
|
import math
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import tempfile
|
import tempfile
|
||||||
from os import PathLike
|
from os import PathLike
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -1089,11 +1088,6 @@ FILENAME_DATE_ORDER = os.getenv("PAPERLESS_FILENAME_DATE_ORDER")
|
|||||||
# fewer dates shown.
|
# fewer dates shown.
|
||||||
NUMBER_OF_SUGGESTED_DATES = __get_int("PAPERLESS_NUMBER_OF_SUGGESTED_DATES", 3)
|
NUMBER_OF_SUGGESTED_DATES = __get_int("PAPERLESS_NUMBER_OF_SUGGESTED_DATES", 3)
|
||||||
|
|
||||||
# Transformations applied before filename parsing
|
|
||||||
FILENAME_PARSE_TRANSFORMS = []
|
|
||||||
for t in json.loads(os.getenv("PAPERLESS_FILENAME_PARSE_TRANSFORMS", "[]")):
|
|
||||||
FILENAME_PARSE_TRANSFORMS.append((re.compile(t["pattern"]), t["repl"]))
|
|
||||||
|
|
||||||
# Specify the filename format for out files
|
# Specify the filename format for out files
|
||||||
FILENAME_FORMAT = os.getenv("PAPERLESS_FILENAME_FORMAT")
|
FILENAME_FORMAT = os.getenv("PAPERLESS_FILENAME_FORMAT")
|
||||||
|
|
||||||
|
@ -38,9 +38,9 @@ class TestChecks(DirectoriesMixin, TestCase):
|
|||||||
self.assertTrue(msg.msg.endswith("is set but doesn't exist."))
|
self.assertTrue(msg.msg.endswith("is set but doesn't exist."))
|
||||||
|
|
||||||
def test_paths_check_no_access(self):
|
def test_paths_check_no_access(self):
|
||||||
os.chmod(self.dirs.data_dir, 0o000)
|
Path(self.dirs.data_dir).chmod(0o000)
|
||||||
os.chmod(self.dirs.media_dir, 0o000)
|
Path(self.dirs.media_dir).chmod(0o000)
|
||||||
os.chmod(self.dirs.consumption_dir, 0o000)
|
Path(self.dirs.consumption_dir).chmod(0o000)
|
||||||
|
|
||||||
self.addCleanup(os.chmod, self.dirs.data_dir, 0o777)
|
self.addCleanup(os.chmod, self.dirs.data_dir, 0o777)
|
||||||
self.addCleanup(os.chmod, self.dirs.media_dir, 0o777)
|
self.addCleanup(os.chmod, self.dirs.media_dir, 0o777)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import os
|
from pathlib import Path
|
||||||
|
|
||||||
from allauth.account import views as allauth_account_views
|
from allauth.account import views as allauth_account_views
|
||||||
from allauth.mfa.base import views as allauth_mfa_views
|
from allauth.mfa.base import views as allauth_mfa_views
|
||||||
@ -270,7 +270,7 @@ urlpatterns = [
|
|||||||
re_path(
|
re_path(
|
||||||
r"^logo(?P<path>.*)$",
|
r"^logo(?P<path>.*)$",
|
||||||
serve,
|
serve,
|
||||||
kwargs={"document_root": os.path.join(settings.MEDIA_ROOT, "logo")},
|
kwargs={"document_root": Path(settings.MEDIA_ROOT) / "logo"},
|
||||||
),
|
),
|
||||||
# allauth
|
# allauth
|
||||||
path(
|
path(
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import abc
|
import abc
|
||||||
import os
|
|
||||||
from email import message_from_bytes
|
from email import message_from_bytes
|
||||||
from email import policy
|
from email import policy
|
||||||
from email.message import Message
|
from email.message import Message
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from gnupg import GPG
|
from gnupg import GPG
|
||||||
@ -50,7 +50,7 @@ class MailMessageDecryptor(MailMessagePreprocessor, LoggingMixin):
|
|||||||
return False
|
return False
|
||||||
if settings.EMAIL_GNUPG_HOME is None:
|
if settings.EMAIL_GNUPG_HOME is None:
|
||||||
return True
|
return True
|
||||||
return os.path.isdir(settings.EMAIL_GNUPG_HOME)
|
return Path(settings.EMAIL_GNUPG_HOME).is_dir()
|
||||||
|
|
||||||
def run(self, message: MailMessage) -> MailMessage:
|
def run(self, message: MailMessage) -> MailMessage:
|
||||||
if not hasattr(message, "obj"):
|
if not hasattr(message, "obj"):
|
||||||
|
@ -159,7 +159,7 @@ class RasterisedDocumentParser(DocumentParser):
|
|||||||
# the whole text, so do not utilize it in that case
|
# the whole text, so do not utilize it in that case
|
||||||
if (
|
if (
|
||||||
sidecar_file is not None
|
sidecar_file is not None
|
||||||
and os.path.isfile(sidecar_file)
|
and sidecar_file.is_file()
|
||||||
and self.settings.mode != "redo"
|
and self.settings.mode != "redo"
|
||||||
):
|
):
|
||||||
text = self.read_file_handle_unicode_errors(sidecar_file)
|
text = self.read_file_handle_unicode_errors(sidecar_file)
|
||||||
@ -174,7 +174,7 @@ class RasterisedDocumentParser(DocumentParser):
|
|||||||
|
|
||||||
# no success with the sidecar file, try PDF
|
# no success with the sidecar file, try PDF
|
||||||
|
|
||||||
if not os.path.isfile(pdf_file):
|
if not Path(pdf_file).is_file():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -368,8 +368,8 @@ class RasterisedDocumentParser(DocumentParser):
|
|||||||
from ocrmypdf import SubprocessOutputError
|
from ocrmypdf import SubprocessOutputError
|
||||||
from ocrmypdf.exceptions import DigitalSignatureError
|
from ocrmypdf.exceptions import DigitalSignatureError
|
||||||
|
|
||||||
archive_path = Path(os.path.join(self.tempdir, "archive.pdf"))
|
archive_path = Path(self.tempdir) / "archive.pdf"
|
||||||
sidecar_file = Path(os.path.join(self.tempdir, "sidecar.txt"))
|
sidecar_file = Path(self.tempdir) / "sidecar.txt"
|
||||||
|
|
||||||
args = self.construct_ocrmypdf_parameters(
|
args = self.construct_ocrmypdf_parameters(
|
||||||
document_path,
|
document_path,
|
||||||
@ -412,12 +412,8 @@ class RasterisedDocumentParser(DocumentParser):
|
|||||||
f"Attempting force OCR to get the text.",
|
f"Attempting force OCR to get the text.",
|
||||||
)
|
)
|
||||||
|
|
||||||
archive_path_fallback = Path(
|
archive_path_fallback = Path(self.tempdir) / "archive-fallback.pdf"
|
||||||
os.path.join(self.tempdir, "archive-fallback.pdf"),
|
sidecar_file_fallback = Path(self.tempdir) / "sidecar-fallback.txt"
|
||||||
)
|
|
||||||
sidecar_file_fallback = Path(
|
|
||||||
os.path.join(self.tempdir, "sidecar-fallback.txt"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Attempt to run OCR with safe settings.
|
# Attempt to run OCR with safe settings.
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ class TestTikaParserAgainstServer:
|
|||||||
== "This is an DOCX test document, also made September 14, 2022"
|
== "This is an DOCX test document, also made September 14, 2022"
|
||||||
)
|
)
|
||||||
assert tika_parser.archive_path is not None
|
assert tika_parser.archive_path is not None
|
||||||
with open(tika_parser.archive_path, "rb") as f:
|
with Path(tika_parser.archive_path).open("rb") as f:
|
||||||
assert b"PDF-" in f.read()[:10]
|
assert b"PDF-" in f.read()[:10]
|
||||||
|
|
||||||
# self.assertEqual(tika_parser.date, datetime.datetime(2022, 9, 14))
|
# self.assertEqual(tika_parser.date, datetime.datetime(2022, 9, 14))
|
||||||
@ -104,7 +104,7 @@ class TestTikaParserAgainstServer:
|
|||||||
in tika_parser.text
|
in tika_parser.text
|
||||||
)
|
)
|
||||||
assert tika_parser.archive_path is not None
|
assert tika_parser.archive_path is not None
|
||||||
with open(tika_parser.archive_path, "rb") as f:
|
with Path(tika_parser.archive_path).open("rb") as f:
|
||||||
assert b"PDF-" in f.read()[:10]
|
assert b"PDF-" in f.read()[:10]
|
||||||
|
|
||||||
def test_tika_fails_multi_part(
|
def test_tika_fails_multi_part(
|
||||||
@ -130,5 +130,5 @@ class TestTikaParserAgainstServer:
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert tika_parser.archive_path is not None
|
assert tika_parser.archive_path is not None
|
||||||
with open(tika_parser.archive_path, "rb") as f:
|
with Path(tika_parser.archive_path).open("rb") as f:
|
||||||
assert b"PDF-" in f.read()[:10]
|
assert b"PDF-" in f.read()[:10]
|
||||||
|
@ -38,7 +38,7 @@ class TestTikaParser:
|
|||||||
|
|
||||||
assert tika_parser.text == "the content"
|
assert tika_parser.text == "the content"
|
||||||
assert tika_parser.archive_path is not None
|
assert tika_parser.archive_path is not None
|
||||||
with open(tika_parser.archive_path, "rb") as f:
|
with Path(tika_parser.archive_path).open("rb") as f:
|
||||||
assert f.read() == b"PDF document"
|
assert f.read() == b"PDF document"
|
||||||
|
|
||||||
assert tika_parser.date == datetime.datetime(
|
assert tika_parser.date == datetime.datetime(
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
[tool:pytest]
|
|
||||||
DJANGO_SETTINGS_MODULE = paperless.settings
|
|
||||||
addopts = --pythonwarnings=all --cov --cov-report=html --cov-report=xml --numprocesses auto --maxprocesses=16 --quiet --durations=50
|
|
||||||
env =
|
|
||||||
PAPERLESS_DISABLE_DBHANDLER=true
|
|
||||||
PAPERLESS_CACHE_BACKEND=django.core.cache.backends.locmem.LocMemCache
|
|
||||||
norecursedirs = locale/*
|
|
||||||
|
|
||||||
[coverage:run]
|
|
||||||
source =
|
|
||||||
./
|
|
||||||
omit =
|
|
||||||
*/tests/*
|
|
||||||
manage.py
|
|
||||||
paperless/workers.py
|
|
||||||
paperless/wsgi.py
|
|
||||||
paperless/auth.py
|
|
||||||
|
|
||||||
[coverage:report]
|
|
||||||
exclude_also =
|
|
||||||
if settings.AUDIT_LOG_ENABLED:
|
|
||||||
if AUDIT_LOG_ENABLED:
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
|
|
||||||
[mypy]
|
|
||||||
plugins = mypy_django_plugin.main, mypy_drf_plugin.main, numpy.typing.mypy_plugin
|
|
||||||
check_untyped_defs = true
|
|
||||||
disallow_any_generics = true
|
|
||||||
disallow_incomplete_defs = true
|
|
||||||
disallow_untyped_defs = true
|
|
||||||
warn_redundant_casts = true
|
|
||||||
warn_unused_ignores = true
|
|
||||||
|
|
||||||
[mypy.plugins.django-stubs]
|
|
||||||
django_settings_module = "paperless.settings"
|
|
@ -6,7 +6,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
Granian(
|
Granian(
|
||||||
"paperless.asgi:application",
|
"paperless.asgi:application",
|
||||||
interface=Interfaces.ASGI,
|
interface=Interfaces.ASGINL,
|
||||||
address=os.getenv("GRANIAN_HOST") or os.getenv("PAPERLESS_BIND_ADDR", "::"),
|
address=os.getenv("GRANIAN_HOST") or os.getenv("PAPERLESS_BIND_ADDR", "::"),
|
||||||
port=int(os.getenv("GRANIAN_PORT") or os.getenv("PAPERLESS_PORT") or 8000),
|
port=int(os.getenv("GRANIAN_PORT") or os.getenv("PAPERLESS_PORT") or 8000),
|
||||||
workers=int(
|
workers=int(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user