mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-03-06 09:16:23 +00:00
Compare commits
8 Commits
feature-mi
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c2d5483c2 | ||
|
|
815e598218 | ||
|
|
a5a267fe49 | ||
|
|
24a2cfd957 | ||
|
|
7cf2ef6398 | ||
|
|
df03207eef | ||
|
|
fa998ecd49 | ||
|
|
1e21bcd26e |
18
.codecov.yml
18
.codecov.yml
@@ -14,10 +14,6 @@ component_management:
|
|||||||
# https://docs.codecov.com/docs/carryforward-flags
|
# https://docs.codecov.com/docs/carryforward-flags
|
||||||
flags:
|
flags:
|
||||||
# Backend Python versions
|
# Backend Python versions
|
||||||
backend-python-3.10:
|
|
||||||
paths:
|
|
||||||
- src/**
|
|
||||||
carryforward: true
|
|
||||||
backend-python-3.11:
|
backend-python-3.11:
|
||||||
paths:
|
paths:
|
||||||
- src/**
|
- src/**
|
||||||
@@ -26,6 +22,14 @@ flags:
|
|||||||
paths:
|
paths:
|
||||||
- src/**
|
- src/**
|
||||||
carryforward: true
|
carryforward: true
|
||||||
|
backend-python-3.13:
|
||||||
|
paths:
|
||||||
|
- src/**
|
||||||
|
carryforward: true
|
||||||
|
backend-python-3.14:
|
||||||
|
paths:
|
||||||
|
- src/**
|
||||||
|
carryforward: true
|
||||||
# Frontend (shards merge into single flag)
|
# Frontend (shards merge into single flag)
|
||||||
frontend-node-24.x:
|
frontend-node-24.x:
|
||||||
paths:
|
paths:
|
||||||
@@ -41,9 +45,10 @@ coverage:
|
|||||||
project:
|
project:
|
||||||
backend:
|
backend:
|
||||||
flags:
|
flags:
|
||||||
- backend-python-3.10
|
|
||||||
- backend-python-3.11
|
- backend-python-3.11
|
||||||
- backend-python-3.12
|
- backend-python-3.12
|
||||||
|
- backend-python-3.13
|
||||||
|
- backend-python-3.14
|
||||||
paths:
|
paths:
|
||||||
- src/**
|
- src/**
|
||||||
# https://docs.codecov.com/docs/commit-status#threshold
|
# https://docs.codecov.com/docs/commit-status#threshold
|
||||||
@@ -59,9 +64,10 @@ coverage:
|
|||||||
patch:
|
patch:
|
||||||
backend:
|
backend:
|
||||||
flags:
|
flags:
|
||||||
- backend-python-3.10
|
|
||||||
- backend-python-3.11
|
- backend-python-3.11
|
||||||
- backend-python-3.12
|
- backend-python-3.12
|
||||||
|
- backend-python-3.13
|
||||||
|
- backend-python-3.14
|
||||||
paths:
|
paths:
|
||||||
- src/**
|
- src/**
|
||||||
target: 100%
|
target: 100%
|
||||||
|
|||||||
2
.github/workflows/ci-backend.yml
vendored
2
.github/workflows/ci-backend.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['3.10', '3.11', '3.12']
|
python-version: ['3.11', '3.12', '3.13', '3.14']
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
2
.github/workflows/ci-frontend.yml
vendored
2
.github/workflows/ci-frontend.yml
vendored
@@ -164,6 +164,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6.0.2
|
uses: actions/checkout@v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.2.0
|
uses: pnpm/action-setup@v4.2.0
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ If you want to implement something big:
|
|||||||
|
|
||||||
## Python
|
## Python
|
||||||
|
|
||||||
Paperless supports python 3.10 - 3.12 at this time. We format Python code with [ruff](https://docs.astral.sh/ruff/formatter/).
|
Paperless-ngx currently supports Python 3.11, 3.12, 3.13, and 3.14. As a policy, we aim to support at least the three most recent Python versions, and drop support for versions as they reach end-of-life. Older versions may be supported if dependencies permit, but this is not guaranteed.
|
||||||
|
|
||||||
|
We format Python code with [ruff](https://docs.astral.sh/ruff/formatter/).
|
||||||
|
|
||||||
## Branches
|
## Branches
|
||||||
|
|
||||||
|
|||||||
@@ -262,6 +262,10 @@ your files differently, you can do that by adjusting the
|
|||||||
or using [storage paths (see below)](#storage-paths). Paperless adds the
|
or using [storage paths (see below)](#storage-paths). Paperless adds the
|
||||||
correct file extension e.g. `.pdf`, `.jpg` automatically.
|
correct file extension e.g. `.pdf`, `.jpg` automatically.
|
||||||
|
|
||||||
|
When a document has file versions, each version uses the same naming rules and
|
||||||
|
storage path resolution as any other document file, with an added version suffix
|
||||||
|
such as `_v1`, `_v2`, etc.
|
||||||
|
|
||||||
This variable allows you to configure the filename (folders are allowed)
|
This variable allows you to configure the filename (folders are allowed)
|
||||||
using placeholders. For example, configuring this to
|
using placeholders. For example, configuring this to
|
||||||
|
|
||||||
@@ -353,6 +357,8 @@ If paperless detects that two documents share the same filename,
|
|||||||
paperless will automatically append `_01`, `_02`, etc to the filename.
|
paperless will automatically append `_01`, `_02`, etc to the filename.
|
||||||
This happens if all the placeholders in a filename evaluate to the same
|
This happens if all the placeholders in a filename evaluate to the same
|
||||||
value.
|
value.
|
||||||
|
For versioned files, this counter is appended after the version suffix
|
||||||
|
(for example `statement_v2_01.pdf`).
|
||||||
|
|
||||||
If there are any errors in the placeholders included in `PAPERLESS_FILENAME_FORMAT`,
|
If there are any errors in the placeholders included in `PAPERLESS_FILENAME_FORMAT`,
|
||||||
paperless will fall back to using the default naming scheme instead.
|
paperless will fall back to using the default naming scheme instead.
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ to enable polling and disable inotify. See [here](configuration.md#polling).
|
|||||||
#### Prerequisites
|
#### Prerequisites
|
||||||
|
|
||||||
- Paperless runs on Linux only, Windows is not supported.
|
- Paperless runs on Linux only, Windows is not supported.
|
||||||
- Python 3 is required with versions 3.10 - 3.12 currently supported. Newer versions may work, but some dependencies may not be fully compatible.
|
- Python 3.11, 3.12, 3.13, or 3.14 is required. As a policy, Paperless-ngx aims to support at least the three most recent Python versions and drops support for versions as they reach end-of-life. Newer versions may work, but some dependencies may not be fully compatible.
|
||||||
|
|
||||||
#### Installation
|
#### Installation
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ Think of versions as **file history** for a document.
|
|||||||
|
|
||||||
- Versions track the underlying file and extracted text content (OCR/text).
|
- Versions track the underlying file and extracted text content (OCR/text).
|
||||||
- Metadata such as tags, correspondent, document type, storage path and custom fields stay on the "root" document.
|
- Metadata such as tags, correspondent, document type, storage path and custom fields stay on the "root" document.
|
||||||
|
- Version files follow normal filename formatting (including storage paths) and add a `_vN` suffix (for example `_v1`, `_v2`).
|
||||||
- By default, search and document content use the latest version.
|
- By default, search and document content use the latest version.
|
||||||
- In document detail, selecting a version switches the preview, file metadata and content (and download etc buttons) to that version.
|
- In document detail, selecting a version switches the preview, file metadata and content (and download etc buttons) to that version.
|
||||||
- Deleting a non-root version keeps metadata and falls back to the latest remaining version.
|
- Deleting a non-root version keeps metadata and falls back to the latest remaining version.
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ name = "paperless-ngx"
|
|||||||
version = "2.20.10"
|
version = "2.20.10"
|
||||||
description = "A community-supported supercharged document management system: scan, index and archive all your physical documents"
|
description = "A community-supported supercharged document management system: scan, index and archive all your physical documents"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.11"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
"Programming Language :: Python :: 3.10",
|
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
"Programming Language :: Python :: 3.13",
|
"Programming Language :: Python :: 3.13",
|
||||||
@@ -177,7 +176,7 @@ torch = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
target-version = "py310"
|
target-version = "py311"
|
||||||
line-length = 88
|
line-length = 88
|
||||||
src = [
|
src = [
|
||||||
"src",
|
"src",
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"ignorePatterns": [
|
|
||||||
"projects/**/*",
|
|
||||||
"/src/app/components/common/pdf-viewer/**"
|
|
||||||
],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"*.ts"
|
|
||||||
],
|
|
||||||
"parserOptions": {
|
|
||||||
"project": [
|
|
||||||
"tsconfig.json"
|
|
||||||
],
|
|
||||||
"createDefaultProgram": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"plugin:@angular-eslint/recommended",
|
|
||||||
"plugin:@angular-eslint/template/process-inline-templates"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"@angular-eslint/directive-selector": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"type": "attribute",
|
|
||||||
"prefix": "pngx",
|
|
||||||
"style": "camelCase"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"@angular-eslint/component-selector": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"type": "element",
|
|
||||||
"prefix": "pngx",
|
|
||||||
"style": "kebab-case"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"*.html"
|
|
||||||
],
|
|
||||||
"extends": [
|
|
||||||
"plugin:@angular-eslint/template/recommended"
|
|
||||||
],
|
|
||||||
"rules": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
58
src-ui/eslint.config.js
Normal file
58
src-ui/eslint.config.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
const angularEslintPlugin = require('@angular-eslint/eslint-plugin')
|
||||||
|
const angularTemplatePlugin = require('@angular-eslint/eslint-plugin-template')
|
||||||
|
const angularTemplateParser = require('@angular-eslint/template-parser')
|
||||||
|
const tsParser = require('@typescript-eslint/parser')
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
ignores: ['projects/**/*', 'src/app/components/common/pdf-viewer/**'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
parserOptions: {
|
||||||
|
project: ['tsconfig.json'],
|
||||||
|
createDefaultProgram: true,
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'@angular-eslint': angularEslintPlugin,
|
||||||
|
'@angular-eslint/template': angularTemplatePlugin,
|
||||||
|
},
|
||||||
|
processor: '@angular-eslint/template/extract-inline-html',
|
||||||
|
rules: {
|
||||||
|
...angularEslintPlugin.configs.recommended.rules,
|
||||||
|
'@angular-eslint/directive-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'attribute',
|
||||||
|
prefix: 'pngx',
|
||||||
|
style: 'camelCase',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@angular-eslint/component-selector': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
prefix: 'pngx',
|
||||||
|
style: 'kebab-case',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.html'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: angularTemplateParser,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'@angular-eslint/template': angularTemplatePlugin,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...angularTemplatePlugin.configs.recommended.rules,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -44,11 +44,11 @@
|
|||||||
"@angular-builders/jest": "^21.0.3",
|
"@angular-builders/jest": "^21.0.3",
|
||||||
"@angular-devkit/core": "^21.2.0",
|
"@angular-devkit/core": "^21.2.0",
|
||||||
"@angular-devkit/schematics": "^21.2.0",
|
"@angular-devkit/schematics": "^21.2.0",
|
||||||
"@angular-eslint/builder": "21.2.0",
|
"@angular-eslint/builder": "21.3.0",
|
||||||
"@angular-eslint/eslint-plugin": "21.2.0",
|
"@angular-eslint/eslint-plugin": "21.3.0",
|
||||||
"@angular-eslint/eslint-plugin-template": "21.2.0",
|
"@angular-eslint/eslint-plugin-template": "21.3.0",
|
||||||
"@angular-eslint/schematics": "21.2.0",
|
"@angular-eslint/schematics": "21.3.0",
|
||||||
"@angular-eslint/template-parser": "21.2.0",
|
"@angular-eslint/template-parser": "21.3.0",
|
||||||
"@angular/build": "^21.2.0",
|
"@angular/build": "^21.2.0",
|
||||||
"@angular/cli": "~21.2.0",
|
"@angular/cli": "~21.2.0",
|
||||||
"@angular/compiler-cli": "~21.2.0",
|
"@angular/compiler-cli": "~21.2.0",
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
||||||
"@typescript-eslint/parser": "^8.54.0",
|
"@typescript-eslint/parser": "^8.54.0",
|
||||||
"@typescript-eslint/utils": "^8.54.0",
|
"@typescript-eslint/utils": "^8.54.0",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^10.0.2",
|
||||||
"jest": "30.2.0",
|
"jest": "30.2.0",
|
||||||
"jest-environment-jsdom": "^30.2.0",
|
"jest-environment-jsdom": "^30.2.0",
|
||||||
"jest-junit": "^16.0.0",
|
"jest-junit": "^16.0.0",
|
||||||
|
|||||||
315
src-ui/pnpm-lock.yaml
generated
315
src-ui/pnpm-lock.yaml
generated
@@ -103,20 +103,20 @@ importers:
|
|||||||
specifier: ^21.2.0
|
specifier: ^21.2.0
|
||||||
version: 21.2.0(chokidar@5.0.0)
|
version: 21.2.0(chokidar@5.0.0)
|
||||||
'@angular-eslint/builder':
|
'@angular-eslint/builder':
|
||||||
specifier: 21.2.0
|
specifier: 21.3.0
|
||||||
version: 21.2.0(@angular/cli@21.2.0(@types/node@25.3.3)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
version: 21.3.0(@angular/cli@21.2.0(@types/node@25.3.3)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@angular-eslint/eslint-plugin':
|
'@angular-eslint/eslint-plugin':
|
||||||
specifier: 21.2.0
|
specifier: 21.3.0
|
||||||
version: 21.2.0(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
version: 21.3.0(@typescript-eslint/utils@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@angular-eslint/eslint-plugin-template':
|
'@angular-eslint/eslint-plugin-template':
|
||||||
specifier: 21.2.0
|
specifier: 21.3.0
|
||||||
version: 21.2.0(@angular-eslint/template-parser@21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.54.0)(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
version: 21.3.0(@angular-eslint/template-parser@21.3.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.54.0)(@typescript-eslint/utils@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@angular-eslint/schematics':
|
'@angular-eslint/schematics':
|
||||||
specifier: 21.2.0
|
specifier: 21.3.0
|
||||||
version: 21.2.0(@angular-eslint/template-parser@21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@angular/cli@21.2.0(@types/node@25.3.3)(chokidar@5.0.0))(@typescript-eslint/types@8.54.0)(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(chokidar@5.0.0)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
version: 21.3.0(@angular-eslint/template-parser@21.3.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(@angular/cli@21.2.0(@types/node@25.3.3)(chokidar@5.0.0))(@typescript-eslint/types@8.54.0)(@typescript-eslint/utils@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(chokidar@5.0.0)(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@angular-eslint/template-parser':
|
'@angular-eslint/template-parser':
|
||||||
specifier: 21.2.0
|
specifier: 21.3.0
|
||||||
version: 21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
version: 21.3.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@angular/build':
|
'@angular/build':
|
||||||
specifier: ^21.2.0
|
specifier: ^21.2.0
|
||||||
version: 21.2.0(@angular/compiler-cli@21.2.0(@angular/compiler@21.2.0)(typescript@5.9.3))(@angular/compiler@21.2.0)(@angular/core@21.2.0(@angular/compiler@21.2.0)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/localize@21.2.0(@angular/compiler-cli@21.2.0(@angular/compiler@21.2.0)(typescript@5.9.3))(@angular/compiler@21.2.0))(@angular/platform-browser@21.2.0(@angular/common@21.2.0(@angular/core@21.2.0(@angular/compiler@21.2.0)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.0(@angular/compiler@21.2.0)(rxjs@7.8.2)(zone.js@0.16.1)))(@types/node@25.3.3)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(postcss@8.5.6)(terser@5.44.1)(tslib@2.8.1)(typescript@5.9.3)(yaml@2.7.0)
|
version: 21.2.0(@angular/compiler-cli@21.2.0(@angular/compiler@21.2.0)(typescript@5.9.3))(@angular/compiler@21.2.0)(@angular/core@21.2.0(@angular/compiler@21.2.0)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/localize@21.2.0(@angular/compiler-cli@21.2.0(@angular/compiler@21.2.0)(typescript@5.9.3))(@angular/compiler@21.2.0))(@angular/platform-browser@21.2.0(@angular/common@21.2.0(@angular/core@21.2.0(@angular/compiler@21.2.0)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.0(@angular/compiler@21.2.0)(rxjs@7.8.2)(zone.js@0.16.1)))(@types/node@25.3.3)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(postcss@8.5.6)(terser@5.44.1)(tslib@2.8.1)(typescript@5.9.3)(yaml@2.7.0)
|
||||||
@@ -140,16 +140,16 @@ importers:
|
|||||||
version: 25.3.3
|
version: 25.3.3
|
||||||
'@typescript-eslint/eslint-plugin':
|
'@typescript-eslint/eslint-plugin':
|
||||||
specifier: ^8.54.0
|
specifier: ^8.54.0
|
||||||
version: 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
version: 8.54.0(@typescript-eslint/parser@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@typescript-eslint/parser':
|
'@typescript-eslint/parser':
|
||||||
specifier: ^8.54.0
|
specifier: ^8.54.0
|
||||||
version: 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
version: 8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@typescript-eslint/utils':
|
'@typescript-eslint/utils':
|
||||||
specifier: ^8.54.0
|
specifier: ^8.54.0
|
||||||
version: 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
version: 8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.39.2
|
specifier: ^10.0.2
|
||||||
version: 9.39.2(jiti@2.6.1)
|
version: 10.0.2(jiti@2.6.1)
|
||||||
jest:
|
jest:
|
||||||
specifier: 30.2.0
|
specifier: 30.2.0
|
||||||
version: 30.2.0(@types/node@25.3.3)(ts-node@10.9.2(@types/node@25.3.3)(typescript@5.9.3))
|
version: 30.2.0(@types/node@25.3.3)(ts-node@10.9.2(@types/node@25.3.3)(typescript@5.9.3))
|
||||||
@@ -364,48 +364,48 @@ packages:
|
|||||||
resolution: {integrity: sha512-3kn3FI5v7BQ7Zct6raek+WgvyDwOJ8wElbyC903GxMQCDBRGGcevhHvTAIHhknihEsrgplzPhTlWeMbk1JfdFg==}
|
resolution: {integrity: sha512-3kn3FI5v7BQ7Zct6raek+WgvyDwOJ8wElbyC903GxMQCDBRGGcevhHvTAIHhknihEsrgplzPhTlWeMbk1JfdFg==}
|
||||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
|
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
|
||||||
|
|
||||||
'@angular-eslint/builder@21.2.0':
|
'@angular-eslint/builder@21.3.0':
|
||||||
resolution: {integrity: sha512-wcp3J9cbrDwSeI/o1D/DSvMQa8zpKjc5WhRGTx33omhWijCfiVNEAiBLWiEx5Sb/dWcoX8yFNWY5jSgFVy9Sjw==}
|
resolution: {integrity: sha512-26QUUouei52biUFAlJSrWNAU9tuF2miKwd8uHdxWwCF31xz+OxC5+NfudWvt1AFaYow7gWueX1QX3rNNtSPDrg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@angular/cli': '>= 21.0.0 < 22.0.0'
|
'@angular/cli': '>= 21.0.0 < 22.0.0'
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '*'
|
typescript: '*'
|
||||||
|
|
||||||
'@angular-eslint/bundled-angular-compiler@21.2.0':
|
'@angular-eslint/bundled-angular-compiler@21.3.0':
|
||||||
resolution: {integrity: sha512-J0DWL+j6t9ItFIyIADvzHGqwDA1qfVJ9bx+oTmJ/Hlo7cUpIRoXpcTXpug0CEEABFH0RfDu6PDG2b0FoZ1+7bg==}
|
resolution: {integrity: sha512-l521I24J9gJxyMbRkrM24Tc7W8J8BP+TDAmVs2nT8+lXbS3kg8QpWBRtd+hNUgq6o+vt+lKBkytnEfu8OiqeRg==}
|
||||||
|
|
||||||
'@angular-eslint/eslint-plugin-template@21.2.0':
|
'@angular-eslint/eslint-plugin-template@21.3.0':
|
||||||
resolution: {integrity: sha512-lJ13Dj0DjR6YiceQR0sRbyWzSzOQ6uZPwK9CJUF3wuZjYAUvL1D61zaU9QrVLtf89NVOxv+dYZHDdu3IDeIqbA==}
|
resolution: {integrity: sha512-lVixd/KypPWgA/5/pUOhJV9MTcaHjYZEqyOi+IiLk+h+maGxn6/s6Ot+20n+XGS85zAgOY+qUw6EEQ11hoojIQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@angular-eslint/template-parser': 21.2.0
|
'@angular-eslint/template-parser': 21.3.0
|
||||||
'@typescript-eslint/types': ^7.11.0 || ^8.0.0
|
'@typescript-eslint/types': ^7.11.0 || ^8.0.0
|
||||||
'@typescript-eslint/utils': ^7.11.0 || ^8.0.0
|
'@typescript-eslint/utils': ^7.11.0 || ^8.0.0
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '*'
|
typescript: '*'
|
||||||
|
|
||||||
'@angular-eslint/eslint-plugin@21.2.0':
|
'@angular-eslint/eslint-plugin@21.3.0':
|
||||||
resolution: {integrity: sha512-X2Qn2viDsjm91CEMxNrxDH3qkKpp6un0C1F1BW2p/m9J4AUVfOcXwWz9UpHFSHTRQ+YlTJbiH1ZwwAPeKhFaxA==}
|
resolution: {integrity: sha512-Whf/AUUBekOlfSJRS78m76YGrBQAZ3waXE7oOdlW5xEQvn8jBDN9EGuNnjg/syZzvzjK4ZpYC4g1XYXrc+fQIg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@typescript-eslint/utils': ^7.11.0 || ^8.0.0
|
'@typescript-eslint/utils': ^7.11.0 || ^8.0.0
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '*'
|
typescript: '*'
|
||||||
|
|
||||||
'@angular-eslint/schematics@21.2.0':
|
'@angular-eslint/schematics@21.3.0':
|
||||||
resolution: {integrity: sha512-WtT4fPKIUQ/hswy+l2GF/rKOdD+42L3fUzzcwRzNutQbe2tU9SimoSOAsay/ylWEuhIOQTs7ysPB8fUgFQoLpA==}
|
resolution: {integrity: sha512-8deU/zVY9f8k8kAQQ9PL130ox2VlrZw3fMxgsPNAY5tjQ0xk0J2YVSszYHhcqdMGG1J01IsxIjvQaJ4pFfEmMw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@angular/cli': '>= 21.0.0 < 22.0.0'
|
'@angular/cli': '>= 21.0.0 < 22.0.0'
|
||||||
|
|
||||||
'@angular-eslint/template-parser@21.2.0':
|
'@angular-eslint/template-parser@21.3.0':
|
||||||
resolution: {integrity: sha512-TCb3qYOC/uXKZCo56cJ6N9sHeWdFhyVqrbbYfFjTi09081T6jllgHDZL5Ms7gOMNY8KywWGGbhxwvzeA0RwTgA==}
|
resolution: {integrity: sha512-ysyou1zAY6M6rSZNdIcYKGd4nk6TCapamyFNB3ivmTlVZ0O35TS9o/rJ0aUttuHgDp+Ysgs3ql+LA746PXgCyQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '*'
|
typescript: '*'
|
||||||
|
|
||||||
'@angular-eslint/utils@21.2.0':
|
'@angular-eslint/utils@21.3.0':
|
||||||
resolution: {integrity: sha512-E19/hkuvHoNFvctBkmEiGWpy2bbC6cgbr3GNVrn2nGtbI4jnwnDFCGHv50I4LBfvj0PA9E6TWe73ejJ5qoMJWQ==}
|
resolution: {integrity: sha512-oNigH6w3l+owTMboj/uFG0tHOy43uH8BpQRtBOQL1/s2+5in/BJ2Fjobv3SyizxTgeJ1FhRefbkT8GmVjK7jAA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@typescript-eslint/utils': ^7.11.0 || ^8.0.0
|
'@typescript-eslint/utils': ^7.11.0 || ^8.0.0
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
typescript: '*'
|
typescript: '*'
|
||||||
|
|
||||||
'@angular/build@21.1.2':
|
'@angular/build@21.1.2':
|
||||||
@@ -1579,33 +1579,25 @@ packages:
|
|||||||
resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
|
resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
|
||||||
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
||||||
|
|
||||||
'@eslint/config-array@0.21.1':
|
'@eslint/config-array@0.23.2':
|
||||||
resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==}
|
resolution: {integrity: sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
'@eslint/config-helpers@0.4.2':
|
'@eslint/config-helpers@0.5.2':
|
||||||
resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==}
|
resolution: {integrity: sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
'@eslint/core@0.17.0':
|
'@eslint/core@1.1.0':
|
||||||
resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
|
resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
'@eslint/eslintrc@3.3.3':
|
'@eslint/object-schema@3.0.2':
|
||||||
resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==}
|
resolution: {integrity: sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
'@eslint/js@9.39.2':
|
'@eslint/plugin-kit@0.6.0':
|
||||||
resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==}
|
resolution: {integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
'@eslint/object-schema@2.1.7':
|
|
||||||
resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==}
|
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
|
||||||
|
|
||||||
'@eslint/plugin-kit@0.4.1':
|
|
||||||
resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
|
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
|
||||||
|
|
||||||
'@fastify/busboy@2.1.1':
|
'@fastify/busboy@2.1.1':
|
||||||
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
|
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
|
||||||
@@ -3324,8 +3316,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
ajv: ^8.8.2
|
ajv: ^8.8.2
|
||||||
|
|
||||||
ajv@6.12.6:
|
ajv@6.14.0:
|
||||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
|
||||||
|
|
||||||
ajv@8.17.1:
|
ajv@8.17.1:
|
||||||
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
|
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
|
||||||
@@ -4024,12 +4016,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
|
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
|
||||||
engines: {node: '>=8.0.0'}
|
engines: {node: '>=8.0.0'}
|
||||||
|
|
||||||
eslint-scope@8.4.0:
|
eslint-scope@9.1.1:
|
||||||
resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
|
resolution: {integrity: sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
|
||||||
|
|
||||||
eslint-scope@9.1.0:
|
|
||||||
resolution: {integrity: sha512-CkWE42hOJsNj9FJRaoMX9waUFYhqY4jmyLFdAdzZr6VaCg3ynLYx4WnOdkaIifGfH4gsUcBTn4OZbHXkpLD0FQ==}
|
|
||||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
eslint-visitor-keys@3.4.3:
|
eslint-visitor-keys@3.4.3:
|
||||||
@@ -4040,9 +4028,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
|
resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
eslint@9.39.2:
|
eslint-visitor-keys@5.0.1:
|
||||||
resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==}
|
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
|
eslint@10.0.2:
|
||||||
|
resolution: {integrity: sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
jiti: '*'
|
jiti: '*'
|
||||||
@@ -4050,9 +4042,9 @@ packages:
|
|||||||
jiti:
|
jiti:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
espree@10.4.0:
|
espree@11.1.1:
|
||||||
resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
|
resolution: {integrity: sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
esprima@4.0.1:
|
esprima@4.0.1:
|
||||||
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
||||||
@@ -4316,10 +4308,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||||
|
|
||||||
globals@14.0.0:
|
|
||||||
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
gopd@1.2.0:
|
gopd@1.2.0:
|
||||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -4974,9 +4962,6 @@ packages:
|
|||||||
lodash.memoize@4.1.2:
|
lodash.memoize@4.1.2:
|
||||||
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
|
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
|
||||||
|
|
||||||
lodash.merge@4.6.2:
|
|
||||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
|
||||||
|
|
||||||
lodash@4.17.23:
|
lodash@4.17.23:
|
||||||
resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==}
|
resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==}
|
||||||
|
|
||||||
@@ -5110,9 +5095,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
|
resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
|
|
||||||
minimatch@3.1.2:
|
|
||||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
|
||||||
|
|
||||||
minimatch@3.1.5:
|
minimatch@3.1.5:
|
||||||
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
|
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
|
||||||
|
|
||||||
@@ -7123,48 +7105,48 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- chokidar
|
- chokidar
|
||||||
|
|
||||||
'@angular-eslint/builder@21.2.0(@angular/cli@21.2.0(@types/node@25.3.3)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
'@angular-eslint/builder@21.3.0(@angular/cli@21.2.0(@types/node@25.3.3)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@angular-devkit/architect': 0.2101.2(chokidar@5.0.0)
|
'@angular-devkit/architect': 0.2102.0(chokidar@5.0.0)
|
||||||
'@angular-devkit/core': 21.2.0(chokidar@5.0.0)
|
'@angular-devkit/core': 21.2.0(chokidar@5.0.0)
|
||||||
'@angular/cli': 21.2.0(@types/node@25.3.3)(chokidar@5.0.0)
|
'@angular/cli': 21.2.0(@types/node@25.3.3)(chokidar@5.0.0)
|
||||||
eslint: 9.39.2(jiti@2.6.1)
|
eslint: 10.0.2(jiti@2.6.1)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- chokidar
|
- chokidar
|
||||||
|
|
||||||
'@angular-eslint/bundled-angular-compiler@21.2.0': {}
|
'@angular-eslint/bundled-angular-compiler@21.3.0': {}
|
||||||
|
|
||||||
'@angular-eslint/eslint-plugin-template@21.2.0(@angular-eslint/template-parser@21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.54.0)(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
'@angular-eslint/eslint-plugin-template@21.3.0(@angular-eslint/template-parser@21.3.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.54.0)(@typescript-eslint/utils@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@angular-eslint/bundled-angular-compiler': 21.2.0
|
'@angular-eslint/bundled-angular-compiler': 21.3.0
|
||||||
'@angular-eslint/template-parser': 21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
'@angular-eslint/template-parser': 21.3.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@angular-eslint/utils': 21.2.0(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
'@angular-eslint/utils': 21.3.0(@typescript-eslint/utils@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@typescript-eslint/types': 8.54.0
|
'@typescript-eslint/types': 8.54.0
|
||||||
'@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
aria-query: 5.3.2
|
aria-query: 5.3.2
|
||||||
axobject-query: 4.1.0
|
axobject-query: 4.1.0
|
||||||
eslint: 9.39.2(jiti@2.6.1)
|
eslint: 10.0.2(jiti@2.6.1)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
'@angular-eslint/eslint-plugin@21.2.0(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
'@angular-eslint/eslint-plugin@21.3.0(@typescript-eslint/utils@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@angular-eslint/bundled-angular-compiler': 21.2.0
|
'@angular-eslint/bundled-angular-compiler': 21.3.0
|
||||||
'@angular-eslint/utils': 21.2.0(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
'@angular-eslint/utils': 21.3.0(@typescript-eslint/utils@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
eslint: 9.39.2(jiti@2.6.1)
|
eslint: 10.0.2(jiti@2.6.1)
|
||||||
ts-api-utils: 2.4.0(typescript@5.9.3)
|
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
'@angular-eslint/schematics@21.2.0(@angular-eslint/template-parser@21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@angular/cli@21.2.0(@types/node@25.3.3)(chokidar@5.0.0))(@typescript-eslint/types@8.54.0)(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(chokidar@5.0.0)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
'@angular-eslint/schematics@21.3.0(@angular-eslint/template-parser@21.3.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(@angular/cli@21.2.0(@types/node@25.3.3)(chokidar@5.0.0))(@typescript-eslint/types@8.54.0)(@typescript-eslint/utils@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(chokidar@5.0.0)(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@angular-devkit/core': 21.2.0(chokidar@5.0.0)
|
'@angular-devkit/core': 21.2.0(chokidar@5.0.0)
|
||||||
'@angular-devkit/schematics': 21.2.0(chokidar@5.0.0)
|
'@angular-devkit/schematics': 21.2.0(chokidar@5.0.0)
|
||||||
'@angular-eslint/eslint-plugin': 21.2.0(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
'@angular-eslint/eslint-plugin': 21.3.0(@typescript-eslint/utils@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@angular-eslint/eslint-plugin-template': 21.2.0(@angular-eslint/template-parser@21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.54.0)(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
'@angular-eslint/eslint-plugin-template': 21.3.0(@angular-eslint/template-parser@21.3.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.54.0)(@typescript-eslint/utils@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@angular/cli': 21.2.0(@types/node@25.3.3)(chokidar@5.0.0)
|
'@angular/cli': 21.2.0(@types/node@25.3.3)(chokidar@5.0.0)
|
||||||
ignore: 7.0.5
|
ignore: 7.0.5
|
||||||
semver: 7.7.3
|
semver: 7.7.4
|
||||||
strip-json-comments: 3.1.1
|
strip-json-comments: 3.1.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@angular-eslint/template-parser'
|
- '@angular-eslint/template-parser'
|
||||||
@@ -7174,18 +7156,18 @@ snapshots:
|
|||||||
- eslint
|
- eslint
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
'@angular-eslint/template-parser@21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
'@angular-eslint/template-parser@21.3.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@angular-eslint/bundled-angular-compiler': 21.2.0
|
'@angular-eslint/bundled-angular-compiler': 21.3.0
|
||||||
eslint: 9.39.2(jiti@2.6.1)
|
eslint: 10.0.2(jiti@2.6.1)
|
||||||
eslint-scope: 9.1.0
|
eslint-scope: 9.1.1
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
'@angular-eslint/utils@21.2.0(@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
'@angular-eslint/utils@21.3.0(@typescript-eslint/utils@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@angular-eslint/bundled-angular-compiler': 21.2.0
|
'@angular-eslint/bundled-angular-compiler': 21.3.0
|
||||||
'@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
eslint: 9.39.2(jiti@2.6.1)
|
eslint: 10.0.2(jiti@2.6.1)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
'@angular/build@21.1.2(@angular/compiler-cli@21.2.0(@angular/compiler@21.2.0)(typescript@5.9.3))(@angular/compiler@21.2.0)(@angular/core@21.2.0(@angular/compiler@21.2.0)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/localize@21.2.0(@angular/compiler-cli@21.2.0(@angular/compiler@21.2.0)(typescript@5.9.3))(@angular/compiler@21.2.0))(@angular/platform-browser@21.2.0(@angular/common@21.2.0(@angular/core@21.2.0(@angular/compiler@21.2.0)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.0(@angular/compiler@21.2.0)(rxjs@7.8.2)(zone.js@0.16.1)))(@types/node@25.3.3)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(postcss@8.5.6)(terser@5.44.1)(tslib@2.8.1)(typescript@5.9.3)(yaml@2.7.0)':
|
'@angular/build@21.1.2(@angular/compiler-cli@21.2.0(@angular/compiler@21.2.0)(typescript@5.9.3))(@angular/compiler@21.2.0)(@angular/core@21.2.0(@angular/compiler@21.2.0)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/localize@21.2.0(@angular/compiler-cli@21.2.0(@angular/compiler@21.2.0)(typescript@5.9.3))(@angular/compiler@21.2.0))(@angular/platform-browser@21.2.0(@angular/common@21.2.0(@angular/core@21.2.0(@angular/compiler@21.2.0)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.0(@angular/compiler@21.2.0)(rxjs@7.8.2)(zone.js@0.16.1)))(@types/node@25.3.3)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(postcss@8.5.6)(terser@5.44.1)(tslib@2.8.1)(typescript@5.9.3)(yaml@2.7.0)':
|
||||||
@@ -8422,50 +8404,34 @@ snapshots:
|
|||||||
'@esbuild/win32-x64@0.27.3':
|
'@esbuild/win32-x64@0.27.3':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))':
|
'@eslint-community/eslint-utils@4.9.1(eslint@10.0.2(jiti@2.6.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.39.2(jiti@2.6.1)
|
eslint: 10.0.2(jiti@2.6.1)
|
||||||
eslint-visitor-keys: 3.4.3
|
eslint-visitor-keys: 3.4.3
|
||||||
|
|
||||||
'@eslint-community/regexpp@4.12.2': {}
|
'@eslint-community/regexpp@4.12.2': {}
|
||||||
|
|
||||||
'@eslint/config-array@0.21.1':
|
'@eslint/config-array@0.23.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint/object-schema': 2.1.7
|
'@eslint/object-schema': 3.0.2
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
minimatch: 3.1.2
|
minimatch: 10.2.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@eslint/config-helpers@0.4.2':
|
'@eslint/config-helpers@0.5.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint/core': 0.17.0
|
'@eslint/core': 1.1.0
|
||||||
|
|
||||||
'@eslint/core@0.17.0':
|
'@eslint/core@1.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/json-schema': 7.0.15
|
'@types/json-schema': 7.0.15
|
||||||
|
|
||||||
'@eslint/eslintrc@3.3.3':
|
'@eslint/object-schema@3.0.2': {}
|
||||||
|
|
||||||
|
'@eslint/plugin-kit@0.6.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 6.12.6
|
'@eslint/core': 1.1.0
|
||||||
debug: 4.4.3
|
|
||||||
espree: 10.4.0
|
|
||||||
globals: 14.0.0
|
|
||||||
ignore: 5.3.2
|
|
||||||
import-fresh: 3.3.1
|
|
||||||
js-yaml: 4.1.1
|
|
||||||
minimatch: 3.1.2
|
|
||||||
strip-json-comments: 3.1.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
'@eslint/js@9.39.2': {}
|
|
||||||
|
|
||||||
'@eslint/object-schema@2.1.7': {}
|
|
||||||
|
|
||||||
'@eslint/plugin-kit@0.4.1':
|
|
||||||
dependencies:
|
|
||||||
'@eslint/core': 0.17.0
|
|
||||||
levn: 0.4.1
|
levn: 0.4.1
|
||||||
|
|
||||||
'@fastify/busboy@2.1.1': {}
|
'@fastify/busboy@2.1.1': {}
|
||||||
@@ -9844,15 +9810,15 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/yargs-parser': 21.0.3
|
'@types/yargs-parser': 21.0.3
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
'@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.2
|
'@eslint-community/regexpp': 4.12.2
|
||||||
'@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/parser': 8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@typescript-eslint/scope-manager': 8.54.0
|
'@typescript-eslint/scope-manager': 8.54.0
|
||||||
'@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/type-utils': 8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.54.0
|
'@typescript-eslint/visitor-keys': 8.54.0
|
||||||
eslint: 9.39.2(jiti@2.6.1)
|
eslint: 10.0.2(jiti@2.6.1)
|
||||||
ignore: 7.0.5
|
ignore: 7.0.5
|
||||||
natural-compare: 1.4.0
|
natural-compare: 1.4.0
|
||||||
ts-api-utils: 2.4.0(typescript@5.9.3)
|
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||||
@@ -9860,14 +9826,14 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
'@typescript-eslint/parser@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 8.54.0
|
'@typescript-eslint/scope-manager': 8.54.0
|
||||||
'@typescript-eslint/types': 8.54.0
|
'@typescript-eslint/types': 8.54.0
|
||||||
'@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.54.0
|
'@typescript-eslint/visitor-keys': 8.54.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 9.39.2(jiti@2.6.1)
|
eslint: 10.0.2(jiti@2.6.1)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -9890,13 +9856,13 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
'@typescript-eslint/type-utils@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.54.0
|
'@typescript-eslint/types': 8.54.0
|
||||||
'@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3)
|
||||||
'@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 9.39.2(jiti@2.6.1)
|
eslint: 10.0.2(jiti@2.6.1)
|
||||||
ts-api-utils: 2.4.0(typescript@5.9.3)
|
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -9919,13 +9885,13 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
'@typescript-eslint/utils@8.54.0(eslint@10.0.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
|
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
|
||||||
'@typescript-eslint/scope-manager': 8.54.0
|
'@typescript-eslint/scope-manager': 8.54.0
|
||||||
'@typescript-eslint/types': 8.54.0
|
'@typescript-eslint/types': 8.54.0
|
||||||
'@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3)
|
||||||
eslint: 9.39.2(jiti@2.6.1)
|
eslint: 10.0.2(jiti@2.6.1)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -10136,7 +10102,7 @@ snapshots:
|
|||||||
ajv: 8.18.0
|
ajv: 8.18.0
|
||||||
fast-deep-equal: 3.1.3
|
fast-deep-equal: 3.1.3
|
||||||
|
|
||||||
ajv@6.12.6:
|
ajv@6.14.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-deep-equal: 3.1.3
|
fast-deep-equal: 3.1.3
|
||||||
fast-json-stable-stringify: 2.1.0
|
fast-json-stable-stringify: 2.1.0
|
||||||
@@ -10905,12 +10871,7 @@ snapshots:
|
|||||||
esrecurse: 4.3.0
|
esrecurse: 4.3.0
|
||||||
estraverse: 4.3.0
|
estraverse: 4.3.0
|
||||||
|
|
||||||
eslint-scope@8.4.0:
|
eslint-scope@9.1.1:
|
||||||
dependencies:
|
|
||||||
esrecurse: 4.3.0
|
|
||||||
estraverse: 5.3.0
|
|
||||||
|
|
||||||
eslint-scope@9.1.0:
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/esrecurse': 4.3.1
|
'@types/esrecurse': 4.3.1
|
||||||
'@types/estree': 1.0.8
|
'@types/estree': 1.0.8
|
||||||
@@ -10921,28 +10882,27 @@ snapshots:
|
|||||||
|
|
||||||
eslint-visitor-keys@4.2.1: {}
|
eslint-visitor-keys@4.2.1: {}
|
||||||
|
|
||||||
eslint@9.39.2(jiti@2.6.1):
|
eslint-visitor-keys@5.0.1: {}
|
||||||
|
|
||||||
|
eslint@10.0.2(jiti@2.6.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
|
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.2(jiti@2.6.1))
|
||||||
'@eslint-community/regexpp': 4.12.2
|
'@eslint-community/regexpp': 4.12.2
|
||||||
'@eslint/config-array': 0.21.1
|
'@eslint/config-array': 0.23.2
|
||||||
'@eslint/config-helpers': 0.4.2
|
'@eslint/config-helpers': 0.5.2
|
||||||
'@eslint/core': 0.17.0
|
'@eslint/core': 1.1.0
|
||||||
'@eslint/eslintrc': 3.3.3
|
'@eslint/plugin-kit': 0.6.0
|
||||||
'@eslint/js': 9.39.2
|
|
||||||
'@eslint/plugin-kit': 0.4.1
|
|
||||||
'@humanfs/node': 0.16.7
|
'@humanfs/node': 0.16.7
|
||||||
'@humanwhocodes/module-importer': 1.0.1
|
'@humanwhocodes/module-importer': 1.0.1
|
||||||
'@humanwhocodes/retry': 0.4.3
|
'@humanwhocodes/retry': 0.4.3
|
||||||
'@types/estree': 1.0.8
|
'@types/estree': 1.0.8
|
||||||
ajv: 6.12.6
|
ajv: 6.14.0
|
||||||
chalk: 4.1.2
|
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
escape-string-regexp: 4.0.0
|
escape-string-regexp: 4.0.0
|
||||||
eslint-scope: 8.4.0
|
eslint-scope: 9.1.1
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 5.0.1
|
||||||
espree: 10.4.0
|
espree: 11.1.1
|
||||||
esquery: 1.7.0
|
esquery: 1.7.0
|
||||||
esutils: 2.0.3
|
esutils: 2.0.3
|
||||||
fast-deep-equal: 3.1.3
|
fast-deep-equal: 3.1.3
|
||||||
@@ -10953,8 +10913,7 @@ snapshots:
|
|||||||
imurmurhash: 0.1.4
|
imurmurhash: 0.1.4
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
json-stable-stringify-without-jsonify: 1.0.1
|
json-stable-stringify-without-jsonify: 1.0.1
|
||||||
lodash.merge: 4.6.2
|
minimatch: 10.2.4
|
||||||
minimatch: 3.1.2
|
|
||||||
natural-compare: 1.4.0
|
natural-compare: 1.4.0
|
||||||
optionator: 0.9.4
|
optionator: 0.9.4
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@@ -10962,11 +10921,11 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
espree@10.4.0:
|
espree@11.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.16.0
|
acorn: 8.16.0
|
||||||
acorn-jsx: 5.3.2(acorn@8.16.0)
|
acorn-jsx: 5.3.2(acorn@8.16.0)
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 5.0.1
|
||||||
|
|
||||||
esprima@4.0.1: {}
|
esprima@4.0.1: {}
|
||||||
|
|
||||||
@@ -11284,8 +11243,6 @@ snapshots:
|
|||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
path-is-absolute: 1.0.1
|
path-is-absolute: 1.0.1
|
||||||
|
|
||||||
globals@14.0.0: {}
|
|
||||||
|
|
||||||
gopd@1.2.0: {}
|
gopd@1.2.0: {}
|
||||||
|
|
||||||
graceful-fs@4.2.11: {}
|
graceful-fs@4.2.11: {}
|
||||||
@@ -12177,8 +12134,6 @@ snapshots:
|
|||||||
|
|
||||||
lodash.memoize@4.1.2: {}
|
lodash.memoize@4.1.2: {}
|
||||||
|
|
||||||
lodash.merge@4.6.2: {}
|
|
||||||
|
|
||||||
lodash@4.17.23: {}
|
lodash@4.17.23: {}
|
||||||
|
|
||||||
log-symbols@7.0.1:
|
log-symbols@7.0.1:
|
||||||
@@ -12314,10 +12269,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 5.0.4
|
brace-expansion: 5.0.4
|
||||||
|
|
||||||
minimatch@3.1.2:
|
|
||||||
dependencies:
|
|
||||||
brace-expansion: 1.1.12
|
|
||||||
|
|
||||||
minimatch@3.1.5:
|
minimatch@3.1.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 1.1.12
|
brace-expansion: 1.1.12
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
from datetime import UTC
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import timezone
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -139,7 +139,7 @@ def thumbnail_last_modified(request: Any, pk: int) -> datetime | None:
|
|||||||
# No cache, get the timestamp and cache the datetime
|
# No cache, get the timestamp and cache the datetime
|
||||||
last_modified = datetime.fromtimestamp(
|
last_modified = datetime.fromtimestamp(
|
||||||
doc.thumbnail_path.stat().st_mtime,
|
doc.thumbnail_path.stat().st_mtime,
|
||||||
tz=timezone.utc,
|
tz=UTC,
|
||||||
)
|
)
|
||||||
cache.set(doc_key, last_modified, CACHE_50_MINUTES)
|
cache.set(doc_key, last_modified, CACHE_50_MINUTES)
|
||||||
return last_modified
|
return last_modified
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import datetime
|
|||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from enum import Enum
|
from enum import StrEnum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Final
|
from typing import Final
|
||||||
@@ -11,6 +11,7 @@ import magic
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.db.models import Max
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from filelock import FileLock
|
from filelock import FileLock
|
||||||
@@ -82,7 +83,7 @@ class ConsumerError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ConsumerStatusShortMessage(str, Enum):
|
class ConsumerStatusShortMessage(StrEnum):
|
||||||
DOCUMENT_ALREADY_EXISTS = "document_already_exists"
|
DOCUMENT_ALREADY_EXISTS = "document_already_exists"
|
||||||
DOCUMENT_ALREADY_EXISTS_IN_TRASH = "document_already_exists_in_trash"
|
DOCUMENT_ALREADY_EXISTS_IN_TRASH = "document_already_exists_in_trash"
|
||||||
ASN_ALREADY_EXISTS = "asn_already_exists"
|
ASN_ALREADY_EXISTS = "asn_already_exists"
|
||||||
@@ -124,22 +125,6 @@ class ConsumerPluginMixin:
|
|||||||
|
|
||||||
self.filename = self.metadata.filename or self.input_doc.original_file.name
|
self.filename = self.metadata.filename or self.input_doc.original_file.name
|
||||||
|
|
||||||
if input_doc.root_document_id:
|
|
||||||
self.log.debug(
|
|
||||||
f"Document root document id: {input_doc.root_document_id}",
|
|
||||||
)
|
|
||||||
root_document = Document.objects.get(pk=input_doc.root_document_id)
|
|
||||||
version_index = Document.objects.filter(root_document=root_document).count()
|
|
||||||
filename_path = Path(self.filename)
|
|
||||||
if filename_path.suffix:
|
|
||||||
self.filename = str(
|
|
||||||
filename_path.with_name(
|
|
||||||
f"{filename_path.stem}_v{version_index}{filename_path.suffix}",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.filename = f"{self.filename}_v{version_index}"
|
|
||||||
|
|
||||||
def _send_progress(
|
def _send_progress(
|
||||||
self,
|
self,
|
||||||
current_progress: int,
|
current_progress: int,
|
||||||
@@ -185,7 +170,7 @@ class ConsumerPlugin(
|
|||||||
):
|
):
|
||||||
logging_name = LOGGING_NAME
|
logging_name = LOGGING_NAME
|
||||||
|
|
||||||
def _clone_root_into_version(
|
def _create_version_from_root(
|
||||||
self,
|
self,
|
||||||
root_doc: Document,
|
root_doc: Document,
|
||||||
*,
|
*,
|
||||||
@@ -194,30 +179,38 @@ class ConsumerPlugin(
|
|||||||
mime_type: str,
|
mime_type: str,
|
||||||
) -> Document:
|
) -> Document:
|
||||||
self.log.debug("Saving record for updated version to database")
|
self.log.debug("Saving record for updated version to database")
|
||||||
version_doc = Document.objects.get(pk=root_doc.pk)
|
root_doc_frozen = Document.objects.select_for_update().get(pk=root_doc.pk)
|
||||||
setattr(version_doc, "pk", None)
|
next_version_index = (
|
||||||
version_doc.root_document = root_doc
|
Document.global_objects.filter(
|
||||||
|
root_document_id=root_doc_frozen.pk,
|
||||||
|
).aggregate(
|
||||||
|
max_index=Max("version_index"),
|
||||||
|
)["max_index"]
|
||||||
|
or 0
|
||||||
|
)
|
||||||
file_for_checksum = (
|
file_for_checksum = (
|
||||||
self.unmodified_original
|
self.unmodified_original
|
||||||
if self.unmodified_original is not None
|
if self.unmodified_original is not None
|
||||||
else self.working_copy
|
else self.working_copy
|
||||||
)
|
)
|
||||||
version_doc.checksum = hashlib.md5(
|
version_doc = Document(
|
||||||
file_for_checksum.read_bytes(),
|
root_document=root_doc_frozen,
|
||||||
).hexdigest()
|
version_index=next_version_index + 1,
|
||||||
version_doc.content = text or ""
|
checksum=hashlib.md5(
|
||||||
version_doc.page_count = page_count
|
file_for_checksum.read_bytes(),
|
||||||
version_doc.mime_type = mime_type
|
).hexdigest(),
|
||||||
version_doc.original_filename = self.filename
|
content=text or "",
|
||||||
version_doc.storage_path = root_doc.storage_path
|
page_count=page_count,
|
||||||
# Clear unique file path fields so they can be generated uniquely later
|
mime_type=mime_type,
|
||||||
version_doc.filename = None
|
original_filename=self.filename,
|
||||||
version_doc.archive_filename = None
|
owner_id=root_doc_frozen.owner_id,
|
||||||
version_doc.archive_checksum = None
|
created=root_doc_frozen.created,
|
||||||
|
title=root_doc_frozen.title,
|
||||||
|
added=timezone.now(),
|
||||||
|
modified=timezone.now(),
|
||||||
|
)
|
||||||
if self.metadata.version_label is not None:
|
if self.metadata.version_label is not None:
|
||||||
version_doc.version_label = self.metadata.version_label
|
version_doc.version_label = self.metadata.version_label
|
||||||
version_doc.added = timezone.now()
|
|
||||||
version_doc.modified = timezone.now()
|
|
||||||
return version_doc
|
return version_doc
|
||||||
|
|
||||||
def run_pre_consume_script(self) -> None:
|
def run_pre_consume_script(self) -> None:
|
||||||
@@ -543,7 +536,7 @@ class ConsumerPlugin(
|
|||||||
root_doc = Document.objects.get(
|
root_doc = Document.objects.get(
|
||||||
pk=self.input_doc.root_document_id,
|
pk=self.input_doc.root_document_id,
|
||||||
)
|
)
|
||||||
original_document = self._clone_root_into_version(
|
original_document = self._create_version_from_root(
|
||||||
root_doc,
|
root_doc,
|
||||||
text=text,
|
text=text,
|
||||||
page_count=page_count,
|
page_count=page_count,
|
||||||
|
|||||||
@@ -129,12 +129,19 @@ def generate_filename(
|
|||||||
archive_filename=False,
|
archive_filename=False,
|
||||||
use_format=True,
|
use_format=True,
|
||||||
) -> Path:
|
) -> Path:
|
||||||
|
# version docs use the root document for formatting, just with a suffix
|
||||||
|
context_doc = doc if doc.root_document_id is None else doc.root_document
|
||||||
|
version_suffix = (
|
||||||
|
f"_v{doc.version_index}"
|
||||||
|
if doc.root_document_id is not None and doc.version_index is not None
|
||||||
|
else ""
|
||||||
|
)
|
||||||
base_path: Path | None = None
|
base_path: Path | None = None
|
||||||
|
|
||||||
# Determine the source of the format string
|
# Determine the source of the format string
|
||||||
if use_format:
|
if use_format:
|
||||||
if doc.storage_path is not None:
|
if context_doc.storage_path is not None:
|
||||||
filename_format = doc.storage_path.path
|
filename_format = context_doc.storage_path.path
|
||||||
elif settings.FILENAME_FORMAT is not None:
|
elif settings.FILENAME_FORMAT is not None:
|
||||||
# Maybe convert old to new style
|
# Maybe convert old to new style
|
||||||
filename_format = convert_format_str_to_template_format(
|
filename_format = convert_format_str_to_template_format(
|
||||||
@@ -147,7 +154,7 @@ def generate_filename(
|
|||||||
|
|
||||||
# If we have one, render it
|
# If we have one, render it
|
||||||
if filename_format is not None:
|
if filename_format is not None:
|
||||||
rendered_path: str | None = format_filename(doc, filename_format)
|
rendered_path: str | None = format_filename(context_doc, filename_format)
|
||||||
if rendered_path:
|
if rendered_path:
|
||||||
base_path = Path(rendered_path)
|
base_path = Path(rendered_path)
|
||||||
|
|
||||||
@@ -161,7 +168,7 @@ def generate_filename(
|
|||||||
base_filename = base_path.name
|
base_filename = base_path.name
|
||||||
|
|
||||||
# Build the final filename with counter and filetype
|
# Build the final filename with counter and filetype
|
||||||
final_filename = f"{base_filename}{counter_str}{filetype_str}"
|
final_filename = f"{base_filename}{version_suffix}{counter_str}{filetype_str}"
|
||||||
|
|
||||||
# If we have a directory component, include it
|
# If we have a directory component, include it
|
||||||
if str(directory) != ".":
|
if str(directory) != ".":
|
||||||
@@ -170,7 +177,9 @@ def generate_filename(
|
|||||||
full_path = Path(final_filename)
|
full_path = Path(final_filename)
|
||||||
else:
|
else:
|
||||||
# No template, use document ID
|
# No template, use document ID
|
||||||
final_filename = f"{doc.pk:07}{counter_str}{filetype_str}"
|
final_filename = (
|
||||||
|
f"{context_doc.pk:07}{version_suffix}{counter_str}{filetype_str}"
|
||||||
|
)
|
||||||
full_path = Path(final_filename)
|
full_path = Path(final_filename)
|
||||||
|
|
||||||
return full_path
|
return full_path
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import math
|
|||||||
import re
|
import re
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
from datetime import UTC
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import time
|
from datetime import time
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from datetime import timezone
|
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
@@ -437,7 +437,7 @@ class ManualResults:
|
|||||||
class LocalDateParser(English):
|
class LocalDateParser(English):
|
||||||
def reverse_timezone_offset(self, d):
|
def reverse_timezone_offset(self, d):
|
||||||
return (d.replace(tzinfo=django_timezone.get_current_timezone())).astimezone(
|
return (d.replace(tzinfo=django_timezone.get_current_timezone())).astimezone(
|
||||||
timezone.utc,
|
UTC,
|
||||||
)
|
)
|
||||||
|
|
||||||
def date_from(self, *args, **kwargs):
|
def date_from(self, *args, **kwargs):
|
||||||
@@ -641,8 +641,8 @@ def rewrite_natural_date_keywords(query_string: str) -> str:
|
|||||||
end = datetime(local_now.year - 1, 12, 31, 23, 59, 59, tzinfo=tz)
|
end = datetime(local_now.year - 1, 12, 31, 23, 59, 59, tzinfo=tz)
|
||||||
|
|
||||||
# Convert to UTC and format
|
# Convert to UTC and format
|
||||||
start_str = start.astimezone(timezone.utc).strftime("%Y%m%d%H%M%S")
|
start_str = start.astimezone(UTC).strftime("%Y%m%d%H%M%S")
|
||||||
end_str = end.astimezone(timezone.utc).strftime("%Y%m%d%H%M%S")
|
end_str = end.astimezone(UTC).strftime("%Y%m%d%H%M%S")
|
||||||
return f"{field}:[{start_str} TO {end_str}]"
|
return f"{field}:[{start_str} TO {end_str}]"
|
||||||
|
|
||||||
return re.sub(pattern, repl, query_string, flags=re.IGNORECASE)
|
return re.sub(pattern, repl, query_string, flags=re.IGNORECASE)
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Generated by Django 5.2.11 on 2026-03-02 17:48
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("documents", "0015_savedview_visibility_to_ui_settings"),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="document",
|
||||||
|
name="version_index",
|
||||||
|
field=models.PositiveIntegerField(
|
||||||
|
blank=True,
|
||||||
|
db_index=True,
|
||||||
|
help_text="Index of this version within the root document.",
|
||||||
|
null=True,
|
||||||
|
verbose_name="version index",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="document",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
condition=models.Q(
|
||||||
|
("root_document__isnull", False),
|
||||||
|
("version_index__isnull", False),
|
||||||
|
),
|
||||||
|
fields=("root_document", "version_index"),
|
||||||
|
name="documents_document_root_version_index_uniq",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -319,6 +319,14 @@ class Document(SoftDeleteModel, ModelWithOwner): # type: ignore[django-manager-
|
|||||||
verbose_name=_("root document for this version"),
|
verbose_name=_("root document for this version"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
version_index = models.PositiveIntegerField(
|
||||||
|
_("version index"),
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
db_index=True,
|
||||||
|
help_text=_("Index of this version within the root document."),
|
||||||
|
)
|
||||||
|
|
||||||
version_label = models.CharField(
|
version_label = models.CharField(
|
||||||
_("version label"),
|
_("version label"),
|
||||||
max_length=64,
|
max_length=64,
|
||||||
@@ -331,6 +339,16 @@ class Document(SoftDeleteModel, ModelWithOwner): # type: ignore[django-manager-
|
|||||||
ordering = ("-created",)
|
ordering = ("-created",)
|
||||||
verbose_name = _("document")
|
verbose_name = _("document")
|
||||||
verbose_name_plural = _("documents")
|
verbose_name_plural = _("documents")
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["root_document", "version_index"],
|
||||||
|
condition=models.Q(
|
||||||
|
root_document__isnull=False,
|
||||||
|
version_index__isnull=False,
|
||||||
|
),
|
||||||
|
name="documents_document_root_version_index_uniq",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
created = self.created.isoformat()
|
created = self.created.isoformat()
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ from abc import abstractmethod
|
|||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
|
from typing import Self
|
||||||
try:
|
|
||||||
from typing import Self
|
|
||||||
except ImportError:
|
|
||||||
from typing_extensions import Self
|
|
||||||
|
|
||||||
import dateparser
|
import dateparser
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ if TYPE_CHECKING:
|
|||||||
from channels_redis.pubsub import RedisPubSubChannelLayer
|
from channels_redis.pubsub import RedisPubSubChannelLayer
|
||||||
|
|
||||||
|
|
||||||
class ProgressStatusOptions(str, enum.Enum):
|
class ProgressStatusOptions(enum.StrEnum):
|
||||||
STARTED = "STARTED"
|
STARTED = "STARTED"
|
||||||
WORKING = "WORKING"
|
WORKING = "WORKING"
|
||||||
SUCCESS = "SUCCESS"
|
SUCCESS = "SUCCESS"
|
||||||
|
|||||||
@@ -620,6 +620,16 @@ def update_filename_and_move_files(
|
|||||||
root=settings.ARCHIVE_DIR,
|
root=settings.ARCHIVE_DIR,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Keep version files in sync with root
|
||||||
|
if instance.root_document_id is None:
|
||||||
|
for version_doc in Document.objects.filter(root_document_id=instance.pk).only(
|
||||||
|
"pk",
|
||||||
|
):
|
||||||
|
update_filename_and_move_files(
|
||||||
|
Document,
|
||||||
|
version_doc,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def process_cf_select_update(custom_field: CustomField) -> None:
|
def process_cf_select_update(custom_field: CustomField) -> None:
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ def base_config() -> DateParserConfig:
|
|||||||
12,
|
12,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
),
|
),
|
||||||
filename_date_order="YMD",
|
filename_date_order="YMD",
|
||||||
content_date_order="DMY",
|
content_date_order="DMY",
|
||||||
@@ -45,7 +45,7 @@ def config_with_ignore_dates() -> DateParserConfig:
|
|||||||
12,
|
12,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
),
|
),
|
||||||
filename_date_order="DMY",
|
filename_date_order="DMY",
|
||||||
content_date_order="MDY",
|
content_date_order="MDY",
|
||||||
|
|||||||
@@ -101,50 +101,50 @@ class TestFilterDate:
|
|||||||
[
|
[
|
||||||
# Valid Dates
|
# Valid Dates
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(2024, 1, 10, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 1, 10, tzinfo=datetime.UTC),
|
||||||
datetime.datetime(2024, 1, 10, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 1, 10, tzinfo=datetime.UTC),
|
||||||
id="valid_past_date",
|
id="valid_past_date",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(2024, 1, 15, 12, 0, 0, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 1, 15, 12, 0, 0, tzinfo=datetime.UTC),
|
||||||
datetime.datetime(2024, 1, 15, 12, 0, 0, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 1, 15, 12, 0, 0, tzinfo=datetime.UTC),
|
||||||
id="exactly_at_reference",
|
id="exactly_at_reference",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(1901, 1, 1, tzinfo=datetime.timezone.utc),
|
datetime.datetime(1901, 1, 1, tzinfo=datetime.UTC),
|
||||||
datetime.datetime(1901, 1, 1, tzinfo=datetime.timezone.utc),
|
datetime.datetime(1901, 1, 1, tzinfo=datetime.UTC),
|
||||||
id="year_1901_valid",
|
id="year_1901_valid",
|
||||||
),
|
),
|
||||||
# Date is > reference_time
|
# Date is > reference_time
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(2024, 1, 16, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 1, 16, tzinfo=datetime.UTC),
|
||||||
None,
|
None,
|
||||||
id="future_date_day_after",
|
id="future_date_day_after",
|
||||||
),
|
),
|
||||||
# date.date() in ignore_dates
|
# date.date() in ignore_dates
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=datetime.UTC),
|
||||||
None,
|
None,
|
||||||
id="ignored_date_midnight_jan1",
|
id="ignored_date_midnight_jan1",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(2024, 1, 1, 10, 30, 0, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 1, 1, 10, 30, 0, tzinfo=datetime.UTC),
|
||||||
None,
|
None,
|
||||||
id="ignored_date_midday_jan1",
|
id="ignored_date_midday_jan1",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(2024, 12, 25, 15, 0, 0, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2024, 12, 25, 15, 0, 0, tzinfo=datetime.UTC),
|
||||||
None,
|
None,
|
||||||
id="ignored_date_dec25_future",
|
id="ignored_date_dec25_future",
|
||||||
),
|
),
|
||||||
# date.year <= 1900
|
# date.year <= 1900
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(1899, 12, 31, tzinfo=datetime.timezone.utc),
|
datetime.datetime(1899, 12, 31, tzinfo=datetime.UTC),
|
||||||
None,
|
None,
|
||||||
id="year_1899",
|
id="year_1899",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
datetime.datetime(1900, 1, 1, tzinfo=datetime.timezone.utc),
|
datetime.datetime(1900, 1, 1, tzinfo=datetime.UTC),
|
||||||
None,
|
None,
|
||||||
id="year_1900_boundary",
|
id="year_1900_boundary",
|
||||||
),
|
),
|
||||||
@@ -176,7 +176,7 @@ class TestFilterDate:
|
|||||||
1,
|
1,
|
||||||
12,
|
12,
|
||||||
0,
|
0,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
)
|
)
|
||||||
another_ignored = datetime.datetime(
|
another_ignored = datetime.datetime(
|
||||||
2024,
|
2024,
|
||||||
@@ -184,7 +184,7 @@ class TestFilterDate:
|
|||||||
25,
|
25,
|
||||||
15,
|
15,
|
||||||
30,
|
30,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
)
|
)
|
||||||
allowed_date = datetime.datetime(
|
allowed_date = datetime.datetime(
|
||||||
2024,
|
2024,
|
||||||
@@ -192,7 +192,7 @@ class TestFilterDate:
|
|||||||
2,
|
2,
|
||||||
12,
|
12,
|
||||||
0,
|
0,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert parser._filter_date(ignored_date) is None
|
assert parser._filter_date(ignored_date) is None
|
||||||
@@ -204,7 +204,7 @@ class TestFilterDate:
|
|||||||
regex_parser: RegexDateParserPlugin,
|
regex_parser: RegexDateParserPlugin,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Should work with timezone-aware datetimes."""
|
"""Should work with timezone-aware datetimes."""
|
||||||
date_utc = datetime.datetime(2024, 1, 10, 12, 0, tzinfo=datetime.timezone.utc)
|
date_utc = datetime.datetime(2024, 1, 10, 12, 0, tzinfo=datetime.UTC)
|
||||||
|
|
||||||
result = regex_parser._filter_date(date_utc)
|
result = regex_parser._filter_date(date_utc)
|
||||||
|
|
||||||
@@ -221,8 +221,8 @@ class TestRegexDateParser:
|
|||||||
"report-2023-12-25.txt",
|
"report-2023-12-25.txt",
|
||||||
"Event recorded on 25/12/2022.",
|
"Event recorded on 25/12/2022.",
|
||||||
[
|
[
|
||||||
datetime.datetime(2023, 12, 25, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2023, 12, 25, tzinfo=datetime.UTC),
|
||||||
datetime.datetime(2022, 12, 25, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2022, 12, 25, tzinfo=datetime.UTC),
|
||||||
],
|
],
|
||||||
id="filename-y-m-d_and_content-d-m-y",
|
id="filename-y-m-d_and_content-d-m-y",
|
||||||
),
|
),
|
||||||
@@ -230,8 +230,8 @@ class TestRegexDateParser:
|
|||||||
"img_2023.01.02.jpg",
|
"img_2023.01.02.jpg",
|
||||||
"Taken on 01/02/2023",
|
"Taken on 01/02/2023",
|
||||||
[
|
[
|
||||||
datetime.datetime(2023, 1, 2, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2023, 1, 2, tzinfo=datetime.UTC),
|
||||||
datetime.datetime(2023, 2, 1, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2023, 2, 1, tzinfo=datetime.UTC),
|
||||||
],
|
],
|
||||||
id="ambiguous-dates-respect-orders",
|
id="ambiguous-dates-respect-orders",
|
||||||
),
|
),
|
||||||
@@ -239,7 +239,7 @@ class TestRegexDateParser:
|
|||||||
"notes.txt",
|
"notes.txt",
|
||||||
"bad date 99/99/9999 and 25/12/2022",
|
"bad date 99/99/9999 and 25/12/2022",
|
||||||
[
|
[
|
||||||
datetime.datetime(2022, 12, 25, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2022, 12, 25, tzinfo=datetime.UTC),
|
||||||
],
|
],
|
||||||
id="parse-exception-skips-bad-and-yields-good",
|
id="parse-exception-skips-bad-and-yields-good",
|
||||||
),
|
),
|
||||||
@@ -275,24 +275,24 @@ class TestRegexDateParser:
|
|||||||
or "2023.12.25" in date_string
|
or "2023.12.25" in date_string
|
||||||
or "2023-12-25" in date_string
|
or "2023-12-25" in date_string
|
||||||
):
|
):
|
||||||
return datetime.datetime(2023, 12, 25, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 12, 25, tzinfo=datetime.UTC)
|
||||||
|
|
||||||
# content DMY 25/12/2022
|
# content DMY 25/12/2022
|
||||||
if "25/12/2022" in date_string or "25-12-2022" in date_string:
|
if "25/12/2022" in date_string or "25-12-2022" in date_string:
|
||||||
return datetime.datetime(2022, 12, 25, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2022, 12, 25, tzinfo=datetime.UTC)
|
||||||
|
|
||||||
# filename YMD 2023.01.02
|
# filename YMD 2023.01.02
|
||||||
if "2023.01.02" in date_string or "2023-01-02" in date_string:
|
if "2023.01.02" in date_string or "2023-01-02" in date_string:
|
||||||
return datetime.datetime(2023, 1, 2, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 1, 2, tzinfo=datetime.UTC)
|
||||||
|
|
||||||
# ambiguous 01/02/2023 -> respect DATE_ORDER setting
|
# ambiguous 01/02/2023 -> respect DATE_ORDER setting
|
||||||
if "01/02/2023" in date_string:
|
if "01/02/2023" in date_string:
|
||||||
if date_order == "DMY":
|
if date_order == "DMY":
|
||||||
return datetime.datetime(2023, 2, 1, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 2, 1, tzinfo=datetime.UTC)
|
||||||
if date_order == "YMD":
|
if date_order == "YMD":
|
||||||
return datetime.datetime(2023, 1, 2, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 1, 2, tzinfo=datetime.UTC)
|
||||||
# fallback
|
# fallback
|
||||||
return datetime.datetime(2023, 2, 1, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 2, 1, tzinfo=datetime.UTC)
|
||||||
|
|
||||||
# simulate parse failure for malformed input
|
# simulate parse failure for malformed input
|
||||||
if "99/99/9999" in date_string or "bad date" in date_string:
|
if "99/99/9999" in date_string or "bad date" in date_string:
|
||||||
@@ -328,7 +328,7 @@ class TestRegexDateParser:
|
|||||||
12,
|
12,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
),
|
),
|
||||||
filename_date_order="YMD",
|
filename_date_order="YMD",
|
||||||
content_date_order="DMY",
|
content_date_order="DMY",
|
||||||
@@ -344,13 +344,13 @@ class TestRegexDateParser:
|
|||||||
) -> datetime.datetime | None:
|
) -> datetime.datetime | None:
|
||||||
if "10/12/2023" in date_string or "10-12-2023" in date_string:
|
if "10/12/2023" in date_string or "10-12-2023" in date_string:
|
||||||
# ignored date
|
# ignored date
|
||||||
return datetime.datetime(2023, 12, 10, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 12, 10, tzinfo=datetime.UTC)
|
||||||
if "01/02/2024" in date_string or "01-02-2024" in date_string:
|
if "01/02/2024" in date_string or "01-02-2024" in date_string:
|
||||||
# future relative to reference_time -> filtered
|
# future relative to reference_time -> filtered
|
||||||
return datetime.datetime(2024, 2, 1, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2024, 2, 1, tzinfo=datetime.UTC)
|
||||||
if "05/01/2023" in date_string or "05-01-2023" in date_string:
|
if "05/01/2023" in date_string or "05-01-2023" in date_string:
|
||||||
# valid
|
# valid
|
||||||
return datetime.datetime(2023, 1, 5, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 1, 5, tzinfo=datetime.UTC)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
mocker.patch(target, side_effect=fake_parse)
|
mocker.patch(target, side_effect=fake_parse)
|
||||||
@@ -358,7 +358,7 @@ class TestRegexDateParser:
|
|||||||
content = "Ignored: 10/12/2023, Future: 01/02/2024, Keep: 05/01/2023"
|
content = "Ignored: 10/12/2023, Future: 01/02/2024, Keep: 05/01/2023"
|
||||||
results = list(parser.parse("whatever.txt", content))
|
results = list(parser.parse("whatever.txt", content))
|
||||||
|
|
||||||
assert results == [datetime.datetime(2023, 1, 5, tzinfo=datetime.timezone.utc)]
|
assert results == [datetime.datetime(2023, 1, 5, tzinfo=datetime.UTC)]
|
||||||
|
|
||||||
def test_parse_handles_no_matches_and_returns_empty_list(
|
def test_parse_handles_no_matches_and_returns_empty_list(
|
||||||
self,
|
self,
|
||||||
@@ -392,7 +392,7 @@ class TestRegexDateParser:
|
|||||||
12,
|
12,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
),
|
),
|
||||||
filename_date_order=None,
|
filename_date_order=None,
|
||||||
content_date_order="DMY",
|
content_date_order="DMY",
|
||||||
@@ -409,9 +409,9 @@ class TestRegexDateParser:
|
|||||||
) -> datetime.datetime | None:
|
) -> datetime.datetime | None:
|
||||||
# return distinct datetimes so we can tell which source was parsed
|
# return distinct datetimes so we can tell which source was parsed
|
||||||
if "25/12/2022" in date_string:
|
if "25/12/2022" in date_string:
|
||||||
return datetime.datetime(2022, 12, 25, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2022, 12, 25, tzinfo=datetime.UTC)
|
||||||
if "2023-12-25" in date_string:
|
if "2023-12-25" in date_string:
|
||||||
return datetime.datetime(2023, 12, 25, tzinfo=datetime.timezone.utc)
|
return datetime.datetime(2023, 12, 25, tzinfo=datetime.UTC)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
mock = mocker.patch(target, side_effect=fake_parse)
|
mock = mocker.patch(target, side_effect=fake_parse)
|
||||||
@@ -429,5 +429,5 @@ class TestRegexDateParser:
|
|||||||
assert "25/12/2022" in called_date_string
|
assert "25/12/2022" in called_date_string
|
||||||
# And the parser should have yielded the corresponding datetime
|
# And the parser should have yielded the corresponding datetime
|
||||||
assert results == [
|
assert results == [
|
||||||
datetime.datetime(2022, 12, 25, tzinfo=datetime.timezone.utc),
|
datetime.datetime(2022, 12, 25, tzinfo=datetime.UTC),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -726,6 +726,14 @@ class TestConsumer(
|
|||||||
self.assertIsNotNone(root_doc)
|
self.assertIsNotNone(root_doc)
|
||||||
assert root_doc is not None
|
assert root_doc is not None
|
||||||
|
|
||||||
|
root_storage_path = StoragePath.objects.create(
|
||||||
|
name="version-root-path",
|
||||||
|
path="root/{{title}}",
|
||||||
|
)
|
||||||
|
root_doc.storage_path = root_storage_path
|
||||||
|
root_doc.archive_serial_number = 42
|
||||||
|
root_doc.save()
|
||||||
|
|
||||||
actor = User.objects.create_user(
|
actor = User.objects.create_user(
|
||||||
username="actor",
|
username="actor",
|
||||||
email="actor@example.com",
|
email="actor@example.com",
|
||||||
@@ -762,7 +770,7 @@ class TestConsumer(
|
|||||||
)
|
)
|
||||||
consumer.setup()
|
consumer.setup()
|
||||||
try:
|
try:
|
||||||
self.assertTrue(consumer.filename.endswith("_v0.pdf"))
|
self.assertEqual(consumer.filename, version_file.name)
|
||||||
consumer.run()
|
consumer.run()
|
||||||
finally:
|
finally:
|
||||||
consumer.cleanup()
|
consumer.cleanup()
|
||||||
@@ -772,8 +780,10 @@ class TestConsumer(
|
|||||||
version = versions.first()
|
version = versions.first()
|
||||||
assert version is not None
|
assert version is not None
|
||||||
assert version.original_filename is not None
|
assert version.original_filename is not None
|
||||||
|
self.assertEqual(version.version_index, 1)
|
||||||
self.assertEqual(version.version_label, "v2")
|
self.assertEqual(version.version_label, "v2")
|
||||||
self.assertTrue(version.original_filename.endswith("_v0.pdf"))
|
self.assertIsNone(version.archive_serial_number)
|
||||||
|
self.assertEqual(version.original_filename, version_file.name)
|
||||||
self.assertTrue(bool(version.content))
|
self.assertTrue(bool(version.content))
|
||||||
|
|
||||||
@override_settings(AUDIT_LOG_ENABLED=True)
|
@override_settings(AUDIT_LOG_ENABLED=True)
|
||||||
@@ -822,7 +832,7 @@ class TestConsumer(
|
|||||||
)
|
)
|
||||||
consumer.setup()
|
consumer.setup()
|
||||||
try:
|
try:
|
||||||
self.assertEqual(consumer.filename, "valid_pdf_version-upload_v0")
|
self.assertEqual(consumer.filename, "valid_pdf_version-upload")
|
||||||
consumer.run()
|
consumer.run()
|
||||||
finally:
|
finally:
|
||||||
consumer.cleanup()
|
consumer.cleanup()
|
||||||
@@ -832,9 +842,67 @@ class TestConsumer(
|
|||||||
)
|
)
|
||||||
self.assertIsNotNone(version)
|
self.assertIsNotNone(version)
|
||||||
assert version is not None
|
assert version is not None
|
||||||
self.assertEqual(version.original_filename, "valid_pdf_version-upload_v0")
|
self.assertEqual(version.version_index, 1)
|
||||||
|
self.assertEqual(version.original_filename, "valid_pdf_version-upload")
|
||||||
self.assertTrue(bool(version.content))
|
self.assertTrue(bool(version.content))
|
||||||
|
|
||||||
|
@override_settings(AUDIT_LOG_ENABLED=True)
|
||||||
|
@mock.patch("documents.consumer.load_classifier")
|
||||||
|
def test_consume_version_index_monotonic_after_version_deletion(self, m) -> None:
|
||||||
|
m.return_value = MagicMock()
|
||||||
|
|
||||||
|
with self.get_consumer(self.get_test_file()) as consumer:
|
||||||
|
consumer.run()
|
||||||
|
|
||||||
|
root_doc = Document.objects.first()
|
||||||
|
self.assertIsNotNone(root_doc)
|
||||||
|
assert root_doc is not None
|
||||||
|
|
||||||
|
def consume_version(version_file: Path) -> Document:
|
||||||
|
status = DummyProgressManager(version_file.name, None)
|
||||||
|
overrides = DocumentMetadataOverrides()
|
||||||
|
doc = ConsumableDocument(
|
||||||
|
DocumentSource.ApiUpload,
|
||||||
|
original_file=version_file,
|
||||||
|
root_document_id=root_doc.pk,
|
||||||
|
)
|
||||||
|
preflight = ConsumerPreflightPlugin(
|
||||||
|
doc,
|
||||||
|
overrides,
|
||||||
|
status, # type: ignore[arg-type]
|
||||||
|
self.dirs.scratch_dir,
|
||||||
|
"task-id",
|
||||||
|
)
|
||||||
|
preflight.setup()
|
||||||
|
preflight.run()
|
||||||
|
|
||||||
|
consumer = ConsumerPlugin(
|
||||||
|
doc,
|
||||||
|
overrides,
|
||||||
|
status, # type: ignore[arg-type]
|
||||||
|
self.dirs.scratch_dir,
|
||||||
|
"task-id",
|
||||||
|
)
|
||||||
|
consumer.setup()
|
||||||
|
try:
|
||||||
|
consumer.run()
|
||||||
|
finally:
|
||||||
|
consumer.cleanup()
|
||||||
|
|
||||||
|
version = (
|
||||||
|
Document.objects.filter(root_document=root_doc).order_by("-id").first()
|
||||||
|
)
|
||||||
|
assert version is not None
|
||||||
|
return version
|
||||||
|
|
||||||
|
v1 = consume_version(self.get_test_file2())
|
||||||
|
self.assertEqual(v1.version_index, 1)
|
||||||
|
v1.delete()
|
||||||
|
|
||||||
|
# The next version should have version_index 2, even though version_index 1 was deleted
|
||||||
|
v2 = consume_version(self.get_test_file())
|
||||||
|
self.assertEqual(v2.version_index, 2)
|
||||||
|
|
||||||
@mock.patch("documents.consumer.load_classifier")
|
@mock.patch("documents.consumer.load_classifier")
|
||||||
def testClassifyDocument(self, m) -> None:
|
def testClassifyDocument(self, m) -> None:
|
||||||
correspondent = Correspondent.objects.create(
|
correspondent = Correspondent.objects.create(
|
||||||
|
|||||||
@@ -77,6 +77,58 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
settings.ORIGINALS_DIR / "test" / "test.pdf",
|
settings.ORIGINALS_DIR / "test" / "test.pdf",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@override_settings(FILENAME_FORMAT=None)
|
||||||
|
def test_root_storage_path_change_updates_version_files(self) -> None:
|
||||||
|
old_storage_path = StoragePath.objects.create(
|
||||||
|
name="old-path",
|
||||||
|
path="old/{{title}}",
|
||||||
|
)
|
||||||
|
new_storage_path = StoragePath.objects.create(
|
||||||
|
name="new-path",
|
||||||
|
path="new/{{title}}",
|
||||||
|
)
|
||||||
|
|
||||||
|
root_doc = Document.objects.create(
|
||||||
|
title="rootdoc",
|
||||||
|
mime_type="application/pdf",
|
||||||
|
checksum="root-checksum",
|
||||||
|
storage_path=old_storage_path,
|
||||||
|
)
|
||||||
|
version_doc = Document.objects.create(
|
||||||
|
title="version-title",
|
||||||
|
mime_type="application/pdf",
|
||||||
|
checksum="version-checksum",
|
||||||
|
root_document=root_doc,
|
||||||
|
version_index=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
Document.objects.filter(pk=root_doc.pk).update(
|
||||||
|
filename=generate_filename(root_doc),
|
||||||
|
)
|
||||||
|
Document.objects.filter(pk=version_doc.pk).update(
|
||||||
|
filename=generate_filename(version_doc),
|
||||||
|
)
|
||||||
|
root_doc.refresh_from_db()
|
||||||
|
version_doc.refresh_from_db()
|
||||||
|
|
||||||
|
create_source_path_directory(root_doc.source_path)
|
||||||
|
Path(root_doc.source_path).touch()
|
||||||
|
create_source_path_directory(version_doc.source_path)
|
||||||
|
Path(version_doc.source_path).touch()
|
||||||
|
|
||||||
|
root_doc.storage_path = new_storage_path
|
||||||
|
root_doc.save()
|
||||||
|
|
||||||
|
root_doc.refresh_from_db()
|
||||||
|
version_doc.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(root_doc.filename, "new/rootdoc.pdf")
|
||||||
|
self.assertEqual(version_doc.filename, "new/rootdoc_v1.pdf")
|
||||||
|
self.assertIsFile(root_doc.source_path)
|
||||||
|
self.assertIsFile(version_doc.source_path)
|
||||||
|
self.assertIsNotFile(settings.ORIGINALS_DIR / "old" / "rootdoc.pdf")
|
||||||
|
self.assertIsNotFile(settings.ORIGINALS_DIR / "old" / "rootdoc_v1.pdf")
|
||||||
|
|
||||||
@override_settings(FILENAME_FORMAT="{correspondent}/{correspondent}")
|
@override_settings(FILENAME_FORMAT="{correspondent}/{correspondent}")
|
||||||
def test_file_renaming_missing_permissions(self) -> None:
|
def test_file_renaming_missing_permissions(self) -> None:
|
||||||
document = Document()
|
document = Document()
|
||||||
@@ -336,7 +388,11 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
|||||||
added=d1,
|
added=d1,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(generate_filename(doc1), Path("1232-01-09.pdf"))
|
# Account for 3.14 padding changes
|
||||||
|
expected_year: str = d1.strftime("%Y")
|
||||||
|
expected_filename: Path = Path(f"{expected_year}-01-09.pdf")
|
||||||
|
|
||||||
|
self.assertEqual(generate_filename(doc1), expected_filename)
|
||||||
|
|
||||||
doc1.added = timezone.make_aware(datetime.datetime(2020, 11, 16, 1, 1, 1))
|
doc1.added = timezone.make_aware(datetime.datetime(2020, 11, 16, 1, 1, 1))
|
||||||
|
|
||||||
@@ -1222,6 +1278,94 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
|
|||||||
Path("logs.pdf"),
|
Path("logs.pdf"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@override_settings(FILENAME_FORMAT="{title}")
|
||||||
|
def test_version_index_suffix_for_template_filename(self) -> None:
|
||||||
|
root_doc = Document.objects.create(
|
||||||
|
title="the_doc",
|
||||||
|
mime_type="application/pdf",
|
||||||
|
checksum="root-checksum",
|
||||||
|
)
|
||||||
|
version_doc = Document.objects.create(
|
||||||
|
title="the_doc",
|
||||||
|
mime_type="application/pdf",
|
||||||
|
checksum="version-checksum",
|
||||||
|
root_document=root_doc,
|
||||||
|
version_index=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(generate_filename(version_doc), Path("the_doc_v1.pdf"))
|
||||||
|
self.assertEqual(
|
||||||
|
generate_filename(version_doc, counter=1),
|
||||||
|
Path("the_doc_v1_01.pdf"),
|
||||||
|
)
|
||||||
|
|
||||||
|
@override_settings(FILENAME_FORMAT=None)
|
||||||
|
def test_version_index_suffix_for_default_filename(self) -> None:
|
||||||
|
root_doc = Document.objects.create(
|
||||||
|
title="root",
|
||||||
|
mime_type="text/plain",
|
||||||
|
checksum="root-checksum",
|
||||||
|
)
|
||||||
|
version_doc = Document.objects.create(
|
||||||
|
title="root",
|
||||||
|
mime_type="text/plain",
|
||||||
|
checksum="version-checksum",
|
||||||
|
root_document=root_doc,
|
||||||
|
version_index=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
generate_filename(version_doc),
|
||||||
|
Path(f"{root_doc.pk:07d}_v2.txt"),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
generate_filename(version_doc, archive_filename=True),
|
||||||
|
Path(f"{root_doc.pk:07d}_v2.pdf"),
|
||||||
|
)
|
||||||
|
|
||||||
|
@override_settings(FILENAME_FORMAT="{original_name}")
|
||||||
|
def test_version_index_suffix_with_original_name_placeholder(self) -> None:
|
||||||
|
root_doc = Document.objects.create(
|
||||||
|
title="root",
|
||||||
|
mime_type="application/pdf",
|
||||||
|
checksum="root-checksum",
|
||||||
|
original_filename="root-upload.pdf",
|
||||||
|
)
|
||||||
|
version_doc = Document.objects.create(
|
||||||
|
title="root",
|
||||||
|
mime_type="application/pdf",
|
||||||
|
checksum="version-checksum",
|
||||||
|
root_document=root_doc,
|
||||||
|
version_index=1,
|
||||||
|
original_filename="version-upload.pdf",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(generate_filename(version_doc), Path("root-upload_v1.pdf"))
|
||||||
|
|
||||||
|
def test_version_index_suffix_with_storage_path(self) -> None:
|
||||||
|
storage_path = StoragePath.objects.create(
|
||||||
|
name="vtest",
|
||||||
|
path="folder/{{title}}",
|
||||||
|
)
|
||||||
|
root_doc = Document.objects.create(
|
||||||
|
title="storage_doc",
|
||||||
|
mime_type="application/pdf",
|
||||||
|
checksum="root-checksum",
|
||||||
|
storage_path=storage_path,
|
||||||
|
)
|
||||||
|
version_doc = Document.objects.create(
|
||||||
|
title="version_title_should_not_be_used",
|
||||||
|
mime_type="application/pdf",
|
||||||
|
checksum="version-checksum",
|
||||||
|
root_document=root_doc,
|
||||||
|
version_index=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
generate_filename(version_doc),
|
||||||
|
Path("folder/storage_doc_v3.pdf"),
|
||||||
|
)
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
FILENAME_FORMAT="XX{correspondent}/{title}",
|
FILENAME_FORMAT="XX{correspondent}/{title}",
|
||||||
FILENAME_FORMAT_REMOVE_NONE=True,
|
FILENAME_FORMAT_REMOVE_NONE=True,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class TestDateLocalization:
|
|||||||
14,
|
14,
|
||||||
30,
|
30,
|
||||||
5,
|
5,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
)
|
)
|
||||||
|
|
||||||
TEST_DATETIME_STRING: str = "2023-10-26T14:30:05+00:00"
|
TEST_DATETIME_STRING: str = "2023-10-26T14:30:05+00:00"
|
||||||
|
|||||||
@@ -4698,7 +4698,7 @@ class TestDateWorkflowLocalization(
|
|||||||
14,
|
14,
|
||||||
30,
|
30,
|
||||||
5,
|
5,
|
||||||
tzinfo=datetime.timezone.utc,
|
tzinfo=datetime.UTC,
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import StrEnum
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
|||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
|
||||||
|
|
||||||
class VersionResolutionError(str, Enum):
|
class VersionResolutionError(StrEnum):
|
||||||
INVALID = "invalid"
|
INVALID = "invalid"
|
||||||
NOT_FOUND = "not_found"
|
NOT_FOUND = "not_found"
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user