Compare commits

..
Author SHA1 Message Date
stumpylog 08ea8b90a4 Refactor: split views.py into documents/views/ module
documents/views.py suffers the same problem as serialisers.py: a
single 5,395-line file covering every REST resource in the app.
Restructures it into a package, one module per domain area, matching
the same layout used for documents/serialisers/.

paperless/urls.py and paperless_mail/views.py are updated to import
from the new submodules. Test files that imported or mock.patch'd
documents.views or documents.serialisers symbols directly are updated
to point at the correct submodule for both splits. The mypy and
pyrefly baselines are updated to reference the new file paths, and a
stale comment in chat_qa.j2 pointing at the old location of
_get_llm_output_language is corrected.
2026-10-03 14:34:19 -07:00
stumpylog 12fa696c73 Refactor: extract serialisers.py into a structured module
documents/serialisers.py had grown into a single file with every
serialiser for the app, making it hard to navigate and putting
unrelated domains behind one import path.

Moves serialisers into documents/serialisers/ as a package, grouping
them into logical per-domain files instead. paperless_mail/serialisers.py
is updated to import from the new submodules.
2026-10-03 14:03:33 -07:00
274 changed files with 48272 additions and 57513 deletions

No files matched your search

@@ -66,7 +66,7 @@ services:
command: command:
- "gotenberg" - "gotenberg"
- "--chromium-disable-javascript=true" - "--chromium-disable-javascript=true"
- "--chromium-allow-list=^file:///tmp/.*" - "--chromium-allow-list=file:///tmp/.*"
tika: tika:
image: docker.io/apache/tika:3.3.1.0 image: docker.io/apache/tika:3.3.1.0
restart: unless-stopped restart: unless-stopped
+1 -11
View File
@@ -31,19 +31,9 @@ body:
value: | value: |
#### 🤖 Use of AI tools #### 🤖 Use of AI tools
If this report was generated, in whole or in part, by an AI tool or agent you **must** say so using the field below. Failing to clearly disclose this is a violation of our [Code of Conduct](https://github.com/paperless-ngx/paperless-ngx/blob/main/CODE_OF_CONDUCT.md). If this report was generated, in whole or in part, by an AI tool or agent you **must** clearly say so in the description below. Failing to clearly disclose this is a violation of our [Code of Conduct](https://github.com/paperless-ngx/paperless-ngx/blob/main/CODE_OF_CONDUCT.md).
Reports written by an AI agent must describe the _observed behavior only_: what happened, how to reproduce it, logs etc. Do **not** include code analysis, speculation about the root cause, or suggest code fixes. Reports written by an AI agent must describe the _observed behavior only_: what happened, how to reproduce it, logs etc. Do **not** include code analysis, speculation about the root cause, or suggest code fixes.
- type: dropdown
id: ai-usage
attributes:
label: AI usage
description: Was any part of this report written by an AI tool or agent?
options:
- No AI tools were used
- Yes, AI tools helped write this report
validations:
required: true
- type: textarea - type: textarea
id: description id: description
attributes: attributes:
+3 -3
View File
@@ -80,7 +80,7 @@ jobs:
needs: changes needs: changes
if: needs.changes.outputs.backend_changed == 'true' if: needs.changes.outputs.backend_changed == 'true'
name: "Python ${{ matrix.python-version }}" name: "Python ${{ matrix.python-version }}"
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
contents: read contents: read
strategy: strategy:
@@ -114,7 +114,7 @@ jobs:
packages: unpaper tesseract-ocr imagemagick ghostscript poppler-utils packages: unpaper tesseract-ocr imagemagick ghostscript poppler-utils
- name: Configure ImageMagick - name: Configure ImageMagick
run: | run: |
sudo cp docker/rootfs/etc/ImageMagick-6/paperless-policy.xml /etc/ImageMagick-7/policy.xml sudo cp docker/rootfs/etc/ImageMagick-6/paperless-policy.xml /etc/ImageMagick-6/policy.xml
- name: Install Python dependencies - name: Install Python dependencies
env: env:
PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }} PYTHON_VERSION: ${{ steps.setup-python.outputs.python-version }}
@@ -158,7 +158,7 @@ jobs:
needs: changes needs: changes
if: needs.changes.outputs.backend_changed == 'true' if: needs.changes.outputs.backend_changed == 'true'
name: Check project typing name: Check project typing
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
contents: read contents: read
env: env:
+3 -3
View File
@@ -24,10 +24,10 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- runner: ubuntu-26.04 - runner: ubuntu-24.04
arch: amd64 arch: amd64
platform: linux/amd64 platform: linux/amd64
- runner: ubuntu-26.04-arm - runner: ubuntu-24.04-arm
arch: arm64 arch: arm64
platform: linux/arm64 platform: linux/arm64
runs-on: ${{ matrix.runner }} runs-on: ${{ matrix.runner }}
@@ -163,7 +163,7 @@ jobs:
archive: false archive: false
merge-and-push: merge-and-push:
name: Merge and Push Manifest name: Merge and Push Manifest
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
needs: build-arch needs: build-arch
if: needs.build-arch.outputs.should-push == 'true' if: needs.build-arch.outputs.should-push == 'true'
environment: image-publishing environment: image-publishing
+2 -2
View File
@@ -65,7 +65,7 @@ jobs:
needs: changes needs: changes
if: needs.changes.outputs.docs_changed == 'true' if: needs.changes.outputs.docs_changed == 'true'
name: Build Documentation name: Build Documentation
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
- name: Checkout - name: Checkout
@@ -102,7 +102,7 @@ jobs:
name: Deploy Documentation name: Deploy Documentation
needs: [changes, build] needs: [changes, build]
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.changes.outputs.docs_changed == 'true' if: github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.changes.outputs.docs_changed == 'true'
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
pages: write pages: write
id-token: write id-token: write
+6 -6
View File
@@ -72,7 +72,7 @@ jobs:
needs: changes needs: changes
if: needs.changes.outputs.frontend_changed == 'true' if: needs.changes.outputs.frontend_changed == 'true'
name: Install Dependencies name: Install Dependencies
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
contents: read contents: read
steps: steps:
@@ -104,7 +104,7 @@ jobs:
name: Lint name: Lint
needs: [changes, install-dependencies] needs: [changes, install-dependencies]
if: needs.changes.outputs.frontend_changed == 'true' if: needs.changes.outputs.frontend_changed == 'true'
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
contents: read contents: read
steps: steps:
@@ -137,7 +137,7 @@ jobs:
name: "Unit Tests (${{ matrix.shard-index }}/${{ matrix.shard-count }})" name: "Unit Tests (${{ matrix.shard-index }}/${{ matrix.shard-count }})"
needs: [changes, install-dependencies] needs: [changes, install-dependencies]
if: needs.changes.outputs.frontend_changed == 'true' if: needs.changes.outputs.frontend_changed == 'true'
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
contents: read contents: read
strategy: strategy:
@@ -188,10 +188,10 @@ jobs:
name: E2E Tests name: E2E Tests
needs: [changes, install-dependencies] needs: [changes, install-dependencies]
if: needs.changes.outputs.frontend_changed == 'true' if: needs.changes.outputs.frontend_changed == 'true'
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
contents: read contents: read
container: mcr.microsoft.com/playwright:v1.62.1-resolute container: mcr.microsoft.com/playwright:v1.62.1-noble
env: env:
PLAYWRIGHT_BROWSERS_PATH: /ms-playwright PLAYWRIGHT_BROWSERS_PATH: /ms-playwright
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
@@ -246,7 +246,7 @@ jobs:
name: Frontend Build name: Frontend Build
needs: [changes, unit-tests, e2e-tests] needs: [changes, unit-tests, e2e-tests]
if: needs.changes.outputs.frontend_changed == 'true' if: needs.changes.outputs.frontend_changed == 'true'
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
contents: read contents: read
steps: steps:
+5 -5
View File
@@ -14,7 +14,7 @@ permissions: {}
jobs: jobs:
wait-for-docker: wait-for-docker:
name: Wait for Docker Build name: Wait for Docker Build
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
checks: read checks: read
statuses: read statuses: read
@@ -30,7 +30,7 @@ jobs:
build-release: build-release:
name: Build Release name: Build Release
needs: wait-for-docker needs: wait-for-docker
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
contents: read contents: read
steps: steps:
@@ -73,7 +73,7 @@ jobs:
timeout-minutes: 12 timeout-minutes: 12
uses: $/.github/actions/apt-install uses: $/.github/actions/apt-install
with: with:
packages: gettext libleptonica6 packages: gettext liblept5
# ---- Build Documentation ---- # ---- Build Documentation ----
- name: Build documentation - name: Build documentation
env: env:
@@ -145,7 +145,7 @@ jobs:
publish-release: publish-release:
name: Publish Release name: Publish Release
needs: build-release needs: build-release
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
contents: write contents: write
pull-requests: write pull-requests: write
@@ -197,7 +197,7 @@ jobs:
name: Append Changelog name: Append Changelog
needs: publish-release needs: publish-release
if: needs.publish-release.outputs.prerelease == 'false' if: needs.publish-release.outputs.prerelease == 'false'
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
contents: write contents: write
pull-requests: write pull-requests: write
+2 -2
View File
@@ -15,7 +15,7 @@ permissions:
jobs: jobs:
zizmor: zizmor:
name: Run zizmor name: Run zizmor
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
contents: read contents: read
actions: read actions: read
@@ -29,7 +29,7 @@ jobs:
uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4 uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
semgrep: semgrep:
name: Semgrep CE name: Semgrep CE
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
container: container:
image: semgrep/semgrep:1.155.0@sha256:cc869c685dcc0fe497c86258da9f205397d8108e56d21a86082ea4886e52784d image: semgrep/semgrep:1.155.0@sha256:cc869c685dcc0fe497c86258da9f205397d8108e56d21a86082ea4886e52784d
if: github.actor != 'dependabot[bot]' if: github.actor != 'dependabot[bot]'
+2 -2
View File
@@ -17,7 +17,7 @@ jobs:
cleanup-images: cleanup-images:
name: Cleanup Image Tags for ${{ matrix.primary-name }} name: Cleanup Image Tags for ${{ matrix.primary-name }}
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
environment: registry-maintenance environment: registry-maintenance
strategy: strategy:
fail-fast: false fail-fast: false
@@ -42,7 +42,7 @@ jobs:
cleanup-untagged-images: cleanup-untagged-images:
name: Cleanup Untagged Images Tags for ${{ matrix.primary-name }} name: Cleanup Untagged Images Tags for ${{ matrix.primary-name }}
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
needs: needs:
- cleanup-images - cleanup-images
environment: registry-maintenance environment: registry-maintenance
+1 -1
View File
@@ -21,7 +21,7 @@ on:
jobs: jobs:
analyze: analyze:
name: Analyze name: Analyze
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
actions: read actions: read
contents: read contents: read
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
synchronize-with-crowdin: synchronize-with-crowdin:
name: Crowdin Sync name: Crowdin Sync
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
environment: translation-sync environment: translation-sync
steps: steps:
- name: Checkout - name: Checkout
+8 -25
View File
@@ -7,7 +7,7 @@ jobs:
# Note: peakoss/anti-slop does not support the `issues` event yet (all of its # Note: peakoss/anti-slop does not support the `issues` event yet (all of its
# issue inputs are still commented out upstream), so the checks that the PR Bot # issue inputs are still commented out upstream), so the checks that the PR Bot
# workflow gets from the action are implemented manually here. # workflow gets from the action are implemented manually here.
runs-on: ubuntu-slim runs-on: ubuntu-latest
permissions: permissions:
issues: write issues: write
steps: steps:
@@ -43,12 +43,6 @@ jobs:
const haystack = `${issue.title}\n${issue.body ?? ''}`; const haystack = `${issue.title}\n${issue.body ?? ''}`;
const honeypot = haystack.toUpperCase().includes('ASLOP-PR-VERIFY'); const honeypot = haystack.toUpperCase().includes('ASLOP-PR-VERIFY');
// The form's required "AI usage" dropdown renders as a `### AI usage` section. An agent
// that follows the template trips the honeypot even when it discloses, so only treat
// the honeypot as undisclosed AI use when the reporter did not answer "Yes" here.
const aiUsage = (issue.body ?? '').match(/^###\s+AI usage\s*\n+\s*(.+?)\s*$/m)?.[1] ?? '';
const disclosedAI = aiUsage.toLowerCase().startsWith('yes');
// Issues opened through the form always get the template's default labels. GitHub // Issues opened through the form always get the template's default labels. GitHub
// applies them a second or two *after* creation, so re-read them instead of trusting // applies them a second or two *after* creation, so re-read them instead of trusting
// the webhook payload, and retry before concluding that there are none. // the webhook payload, and retry before concluding that there are none.
@@ -61,30 +55,19 @@ jobs:
if (labels.some((label) => templateLabels.includes(label))) break; if (labels.some((label) => templateLabels.includes(label))) break;
} }
const bypassedTemplate = !labels.some((label) => templateLabels.includes(label)); const bypassedTemplate = !labels.some((label) => templateLabels.includes(label));
core.info( core.info(`Labels: [${labels.join(', ')}], honeypot: ${honeypot}, bypassed template: ${bypassedTemplate}`);
`Labels: [${labels.join(', ')}], honeypot: ${honeypot}, AI usage: "${aiUsage}", ` +
`bypassed template: ${bypassedTemplate}`
);
if (!bypassedTemplate && (disclosedAI || !honeypot)) { if (!honeypot && !bypassedTemplate) {
if (disclosedAI) { core.info('No slop signals found');
core.info('AI use disclosed, labeling only');
await github.rest.issues.addLabels({ ...common, labels: ['ai'] });
} else {
core.info('No slop signals found');
}
return; return;
} }
// The honeypot is only ever tripped deliberately, so that message can name the cause.
// A missing template label only tells us the issue did not come from the form, which // A missing template label only tells us the issue did not come from the form, which
// is reason enough to close it, but not proof of how it was written. It takes priority: // is reason enough to close it, but not proof of how it was written.
// a hand-built body can carry the honeypot *and* a disclosure, and the form message is const body = honeypot
// the only one that is accurate for both. Reaching the honeypot message means the issue
// came from the form without disclosing AI usage, so that message can name the cause.
const body = honeypot && !bypassedTemplate
? 'This issue was automatically closed because it contains a marker that is only visible to ' + ? 'This issue was automatically closed because it contains a marker that is only visible to ' +
'automated tools, which indicates it was generated by an AI agent, but the "AI usage" field does not ' + 'automated tools, which indicates it was generated by an AI agent without being disclosed as such.\n\n' +
'disclose this.\n\n' +
`Please see our [contributing guidelines](${contributing}) and [Code of Conduct](${codeOfConduct}). ` + `Please see our [contributing guidelines](${contributing}) and [Code of Conduct](${codeOfConduct}). ` +
'You are welcome to open a new issue that describes the problem you observed in your own words.' 'You are welcome to open a new issue that describes the problem you observed in your own words.'
: 'This issue was automatically closed because it was not opened using our bug report form. ' + : 'This issue was automatically closed because it was not opened using our bug report form. ' +
+12 -2
View File
@@ -4,7 +4,7 @@ on:
types: [opened] types: [opened]
jobs: jobs:
Anti-slop: Anti-slop:
runs-on: ubuntu-slim runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
issues: read issues: read
@@ -24,7 +24,7 @@ jobs:
ASLOP-PR-VERIFY ASLOP-PR-VERIFY
pr-bot: pr-bot:
name: Automated PR Bot name: Automated PR Bot
runs-on: ubuntu-slim runs-on: ubuntu-latest
# Runs after Anti-slop so the welcome comment can see whether the PR was closed # Runs after Anti-slop so the welcome comment can see whether the PR was closed
# instead of racing it. Still runs if that job fails, so labeling is not lost. # instead of racing it. Still runs if that job fails, so labeling is not lost.
needs: Anti-slop needs: Anti-slop
@@ -38,6 +38,16 @@ jobs:
uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0 uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Label by size
uses: Gascon1/pr-size-labeler@deff8ed00a76639a7c0f197525bafa3350ba4c36 # v1.3.0
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
xs_label: 'small-change'
xs_diff: '9'
s_label: 'non-trivial'
s_diff: '99999'
fail_if_xl: 'false'
excluded_files: /\.lock$/ /\.txt$/ ^src-ui/pnpm-lock\.yaml$ ^src-ui/messages\.xlf$ ^src/locale/en_US/LC_MESSAGES/django\.po$
- name: Label by PR title - name: Label by PR title
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with: with:
+1 -1
View File
@@ -12,7 +12,7 @@ permissions:
jobs: jobs:
pr_opened_or_reopened: pr_opened_or_reopened:
name: pr_opened_or_reopened name: pr_opened_or_reopened
runs-on: ubuntu-slim runs-on: ubuntu-24.04
permissions: permissions:
# write permission is required for autolabeler # write permission is required for autolabeler
pull-requests: write pull-requests: write
+5 -5
View File
@@ -9,7 +9,7 @@ jobs:
stale: stale:
name: 'Stale' name: 'Stale'
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
issues: write issues: write
pull-requests: write pull-requests: write
@@ -34,7 +34,7 @@ jobs:
lock-threads: lock-threads:
name: 'Lock Old Threads' name: 'Lock Old Threads'
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-26.04 runs-on: ubuntu-24.04
permissions: permissions:
issues: write issues: write
pull-requests: write pull-requests: write
@@ -58,7 +58,7 @@ jobs:
close-answered-discussions: close-answered-discussions:
name: 'Close Answered Discussions' name: 'Close Answered Discussions'
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-slim runs-on: ubuntu-24.04
permissions: permissions:
discussions: write discussions: write
steps: steps:
@@ -117,7 +117,7 @@ jobs:
close-outdated-discussions: close-outdated-discussions:
name: 'Close Outdated Discussions' name: 'Close Outdated Discussions'
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-slim runs-on: ubuntu-24.04
permissions: permissions:
discussions: write discussions: write
steps: steps:
@@ -211,7 +211,7 @@ jobs:
close-unsupported-feature-requests: close-unsupported-feature-requests:
name: 'Close Unsupported Feature Requests' name: 'Close Unsupported Feature Requests'
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-slim runs-on: ubuntu-24.04
permissions: permissions:
discussions: write discussions: write
steps: steps:
+1 -1
View File
@@ -8,7 +8,7 @@ env:
jobs: jobs:
generate-translate-strings: generate-translate-strings:
name: Generate Translation Strings name: Generate Translation Strings
runs-on: ubuntu-26.04 runs-on: ubuntu-latest
environment: translation-sync environment: translation-sync
permissions: permissions:
contents: write contents: write
+351 -351
View File
@@ -410,180 +410,179 @@ src/documents/search/_query.py:0: error: Argument 3 to "Term" has incompatible t
src/documents/search/_query.py:0: error: Argument 3 to "Term" has incompatible type "**dict[str, int | None]"; expected "bool" [arg-type] src/documents/search/_query.py:0: error: Argument 3 to "Term" has incompatible type "**dict[str, int | None]"; expected "bool" [arg-type]
src/documents/search/_query.py:0: error: Unexpected keyword argument "children" for "Node" [call-arg] src/documents/search/_query.py:0: error: Unexpected keyword argument "children" for "Node" [call-arg]
src/documents/search/_schema.py:0: error: Redundant cast to "Path" [redundant-cast] src/documents/search/_schema.py:0: error: Redundant cast to "Path" [redundant-cast]
src/documents/search/_schema.py:0: error: Unexpected keyword argument "fast" for "add_json_field" of "SchemaBuilder" [call-arg] src/documents/serialisers/base.py:0: error: "type[_MT?]" has no attribute "_meta" [attr-defined]
src/documents/serialisers.py:0: error: "type[_MT?]" has no attribute "_meta" [attr-defined] src/documents/serialisers/base.py:0: error: "type[_MT?]" has no attribute "objects" [attr-defined]
src/documents/serialisers.py:0: error: "type[_MT?]" has no attribute "objects" [attr-defined] src/documents/serialisers/base.py:0: error: "type[_MT?]" has no attribute "objects" [attr-defined]
src/documents/serialisers.py:0: error: "type[_MT?]" has no attribute "objects" [attr-defined] src/documents/serialisers/base.py:0: error: "type[_MT?]" has no attribute "objects" [attr-defined]
src/documents/serialisers.py:0: error: "type[_MT?]" has no attribute "objects" [attr-defined] src/documents/serialisers/base.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Argument "choices" to "ChoiceField" has incompatible type "type[FieldDataType]"; expected "Sequence[Any]" [arg-type] src/documents/serialisers/base.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Argument "default" to "MultipleChoiceField" has incompatible type "set[DocumentSource]"; expected "set[str | int] | set[str] | set[int] | Callable[[], set[str | int] | set[str] | set[int]] | _Empty | None" [arg-type] src/documents/serialisers/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Argument 1 of "get_value" is incompatible with supertype "rest_framework.fields.Field"; supertype defines the argument type as "Mapping[str, Any]" [override] src/documents/serialisers/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Argument 1 of "update" is incompatible with supertype "rest_framework.serializers.BaseSerializer"; supertype defines the argument type as "dict[str, Any]" [override] src/documents/serialisers/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Argument 1 to "__get__" of "BaseManager" has incompatible type "PrimaryKeyRelatedField[Any]"; expected "Options[Any] | None" [arg-type] src/documents/serialisers/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Argument 1 to "get_value_field_name" of "CustomFieldInstance" has incompatible type "str"; expected "FieldDataType" [arg-type] src/documents/serialisers/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Argument 1 to "int" has incompatible type "Any | Collection[str]"; expected "str | Buffer | SupportsInt | SupportsIndex" [arg-type] src/documents/serialisers/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Argument 1 to "list" has incompatible type "QuerySet[Document, TypedDict({'id': int, 'title': str, 'deleted_at': datetime | None})]"; expected "Iterable[dict[str, Any]]" [arg-type] src/documents/serialisers/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Expected iterable as variadic argument [misc] src/documents/serialisers/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Item "Field[Any, Any, Any, Any]" of "Field[Any, Any, Any, Any] | None" has no attribute "get_shared_object_pks" [union-attr]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Item "None" of "Any | None" has no attribute "name" [union-attr]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Item "None" of "Any | None" has no attribute "pk" [union-attr]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Item "None" of "Field[Any, Any, Any, Any] | None" has no attribute "context" [union-attr]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Item "None" of "Field[Any, Any, Any, Any] | None" has no attribute "get_shared_object_pks" [union-attr]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Item "list[str]" of "Any | list[str]" has no attribute "values_list" [union-attr]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Missing type arguments for generic type "Iterable" [type-arg]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Missing type arguments for generic type "dict" [type-arg]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Need type annotation for "permissions_dict" [var-annotated]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/base.py:0: error: Unsupported target for indexed assignment ("Mapping[str, Any] | Any") [index]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Incompatible types in assignment (expression has type "type[Correspondent]", variable has type "type[Tag] | None") [assignment]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Incompatible types in assignment (expression has type "type[DocumentType]", variable has type "type[Tag] | None") [assignment]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Incompatible types in assignment (expression has type "type[StoragePath]", variable has type "type[Tag] | None") [assignment]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/bulk_edit.py:0: error: Missing type arguments for generic type "dict" [type-arg]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Argument 1 of "update" is incompatible with supertype "rest_framework.serializers.BaseSerializer"; supertype defines the argument type as "dict[str, Any]" [override]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Incompatible types in assignment (expression has type "type[SearchResultListSerializer]", base class "Meta" defined the type as "type[OwnedObjectListSerializer]") [assignment]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Item "Field[Any, Any, Any, Any]" of "Field[Any, Any, Any, Any] | None" has no attribute "fetch_documents" [union-attr]
src/documents/serialisers.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/serialisers/documents.py:0: error: Item "Field[Any, Any, Any, Any]" of "Field[Any, Any, Any, Any] | None" has no attribute "get_shared_object_pks" [union-attr]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/documents.py:0: error: Item "None" of "Field[Any, Any, Any, Any] | None" has no attribute "context" [union-attr]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/documents.py:0: error: Item "None" of "Field[Any, Any, Any, Any] | None" has no attribute "context" [union-attr]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/documents.py:0: error: Item "None" of "Field[Any, Any, Any, Any] | None" has no attribute "fetch_documents" [union-attr]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/documents.py:0: error: Item "None" of "Field[Any, Any, Any, Any] | None" has no attribute "get_shared_object_pks" [union-attr]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/documents.py:0: error: Skipping analyzing "auditlog.context": module is installed, but missing library stubs or py.typed marker [import-untyped]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/documents.py:0: error: Unsupported target for indexed assignment ("Mapping[str, Any] | Any") [index]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/documents.py:0: error: Unsupported target for indexed assignment ("Mapping[str, Any] | Any") [index]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/documents.py:0: error: Value of type "Any | None" is not indexable [index]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Argument "choices" to "ChoiceField" has incompatible type "type[FieldDataType]"; expected "Sequence[Any]" [arg-type]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Argument 1 of "get_value" is incompatible with supertype "rest_framework.fields.Field"; supertype defines the argument type as "Mapping[str, Any]" [override]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Argument 1 to "__get__" of "BaseManager" has incompatible type "PrimaryKeyRelatedField[Any]"; expected "Options[Any] | None" [arg-type]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Argument 1 to "get_value_field_name" of "CustomFieldInstance" has incompatible type "str"; expected "FieldDataType" [arg-type]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Expected iterable as variadic argument [misc]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Incompatible types in assignment (expression has type "PrimaryKeyRelatedField[Tag]", base class "Field" defined the type as "BaseSerializer[Any]") [assignment]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Incompatible types in assignment (expression has type "Sequence[str] | tuple[Lower]", variable has type "Sequence[str] | None") [assignment]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Item "None" of "CustomField | None" has no attribute "name" [union-attr]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Missing type arguments for generic type "Field" [type-arg]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Missing type arguments for generic type "dict" [type-arg]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Need type annotation for "document" [var-annotated]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Unsupported target for indexed assignment ("Mapping[str, Any]") [index]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/metadata.py:0: error: Value of type "Any | None" is not indexable [index]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/saved_views.py:0: error: Argument 1 to "int" has incompatible type "Any | Collection[str]"; expected "str | Buffer | SupportsInt | SupportsIndex" [arg-type]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/saved_views.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/saved_views.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/serialisers/saved_views.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Incompatible types in assignment (expression has type "PrimaryKeyRelatedField[Tag]", base class "Field" defined the type as "BaseSerializer[Any]") [assignment] src/documents/serialisers/saved_views.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Incompatible types in assignment (expression has type "Sequence[str] | tuple[Lower]", variable has type "Sequence[str] | None") [assignment] src/documents/serialisers/saved_views.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Incompatible types in assignment (expression has type "WorkflowActionWebhookSerializer", variable has type "WorkflowActionEmailSerializer") [assignment] src/documents/serialisers/saved_views.py:0: error: Value of type "Match[str] | None" is not indexable [index]
src/documents/serialisers.py:0: error: Incompatible types in assignment (expression has type "type[Correspondent]", variable has type "type[Tag] | None") [assignment] src/documents/serialisers/sharing.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Incompatible types in assignment (expression has type "type[DocumentType]", variable has type "type[Tag] | None") [assignment] src/documents/serialisers/sharing.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Incompatible types in assignment (expression has type "type[SearchResultListSerializer]", base class "Meta" defined the type as "type[OwnedObjectListSerializer]") [assignment] src/documents/serialisers/sharing.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Incompatible types in assignment (expression has type "type[StoragePath]", variable has type "type[Tag] | None") [assignment] src/documents/serialisers/sharing.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Item "Field[Any, Any, Any, Any]" of "Field[Any, Any, Any, Any] | None" has no attribute "fetch_documents" [union-attr] src/documents/serialisers/sharing.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Item "Field[Any, Any, Any, Any]" of "Field[Any, Any, Any, Any] | None" has no attribute "get_shared_object_pks" [union-attr] src/documents/serialisers/sharing.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Item "Field[Any, Any, Any, Any]" of "Field[Any, Any, Any, Any] | None" has no attribute "get_shared_object_pks" [union-attr] src/documents/serialisers/sharing.py:0: error: Need type annotation for "documents" [var-annotated]
src/documents/serialisers.py:0: error: Item "None" of "Any | None" has no attribute "name" [union-attr] src/documents/serialisers/system.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Item "None" of "Any | None" has no attribute "pk" [union-attr] src/documents/serialisers/system.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Item "None" of "CustomField | None" has no attribute "name" [union-attr] src/documents/serialisers/tasks.py:0: error: Argument 1 to "list" has incompatible type "QuerySet[Document, TypedDict({'id': int, 'title': str, 'deleted_at': datetime | None})]"; expected "Iterable[dict[str, Any]]" [arg-type]
src/documents/serialisers.py:0: error: Item "None" of "Field[Any, Any, Any, Any] | None" has no attribute "context" [union-attr] src/documents/serialisers/tasks.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Item "None" of "Field[Any, Any, Any, Any] | None" has no attribute "context" [union-attr] src/documents/serialisers/tasks.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Item "None" of "Field[Any, Any, Any, Any] | None" has no attribute "context" [union-attr] src/documents/serialisers/tasks.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Item "None" of "Field[Any, Any, Any, Any] | None" has no attribute "fetch_documents" [union-attr] src/documents/serialisers/tasks.py:0: error: No overload variant of "get" of "dict" matches argument types "str", "str" [call-overload]
src/documents/serialisers.py:0: error: Item "None" of "Field[Any, Any, Any, Any] | None" has no attribute "get_shared_object_pks" [union-attr] src/documents/serialisers/tasks.py:0: error: No overload variant of "get" of "dict" matches argument types "str", "str" [call-overload]
src/documents/serialisers.py:0: error: Item "None" of "Field[Any, Any, Any, Any] | None" has no attribute "get_shared_object_pks" [union-attr] src/documents/serialisers/tasks.py:0: error: No overload variant of "get" of "dict" matches argument types "str", "str" [call-overload]
src/documents/serialisers.py:0: error: Item "list[str]" of "Any | list[str]" has no attribute "values_list" [union-attr] src/documents/serialisers/upload.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Missing type arguments for generic type "Field" [type-arg] src/documents/serialisers/upload.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Missing type arguments for generic type "Iterable" [type-arg] src/documents/serialisers/upload.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Missing type arguments for generic type "dict" [type-arg] src/documents/serialisers/upload.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Missing type arguments for generic type "dict" [type-arg] src/documents/serialisers/upload.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Missing type arguments for generic type "dict" [type-arg] src/documents/serialisers/upload.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Need type annotation for "document" [var-annotated] src/documents/serialisers/upload.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Need type annotation for "documents" [var-annotated] src/documents/serialisers/workflows.py:0: error: Argument "default" to "MultipleChoiceField" has incompatible type "set[DocumentSource]"; expected "set[str | int] | set[str] | set[int] | Callable[[], set[str | int] | set[str] | set[int]] | _Empty | None" [arg-type]
src/documents/serialisers.py:0: error: Need type annotation for "permissions_dict" [var-annotated] src/documents/serialisers/workflows.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: No overload variant of "get" of "dict" matches argument types "str", "str" [call-overload] src/documents/serialisers/workflows.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: No overload variant of "get" of "dict" matches argument types "str", "str" [call-overload] src/documents/serialisers/workflows.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: No overload variant of "get" of "dict" matches argument types "str", "str" [call-overload] src/documents/serialisers/workflows.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Skipping analyzing "auditlog.context": module is installed, but missing library stubs or py.typed marker [import-untyped] src/documents/serialisers/workflows.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Unsupported target for indexed assignment ("Mapping[str, Any] | Any") [index] src/documents/serialisers/workflows.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/serialisers.py:0: error: Unsupported target for indexed assignment ("Mapping[str, Any] | Any") [index] src/documents/serialisers/workflows.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Unsupported target for indexed assignment ("Mapping[str, Any] | Any") [index] src/documents/serialisers/workflows.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Unsupported target for indexed assignment ("Mapping[str, Any]") [index] src/documents/serialisers/workflows.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Value of type "Any | None" is not indexable [index] src/documents/serialisers/workflows.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/serialisers.py:0: error: Value of type "Any | None" is not indexable [index] src/documents/serialisers/workflows.py:0: error: Incompatible types in assignment (expression has type "WorkflowActionWebhookSerializer", variable has type "WorkflowActionEmailSerializer") [assignment]
src/documents/serialisers.py:0: error: Value of type "Match[str] | None" is not indexable [index]
src/documents/signals/handlers.py:0: error: "BaseDatabaseWrapper" has no attribute "close_pool" [attr-defined] src/documents/signals/handlers.py:0: error: "BaseDatabaseWrapper" has no attribute "close_pool" [attr-defined]
src/documents/signals/handlers.py:0: error: "BaseDatabaseWrapper" has no attribute "close_pool" [attr-defined] src/documents/signals/handlers.py:0: error: "BaseDatabaseWrapper" has no attribute "close_pool" [attr-defined]
src/documents/signals/handlers.py:0: error: "Task[[int, int], Any]" has no attribute "delay_on_commit" [attr-defined] src/documents/signals/handlers.py:0: error: "Task[[int, int], Any]" has no attribute "delay_on_commit" [attr-defined]
@@ -1525,180 +1524,181 @@ src/documents/utils.py:0: error: Incompatible types in assignment (expression ha
src/documents/utils.py:0: error: Missing type arguments for generic type "CompletedProcess" [type-arg] src/documents/utils.py:0: error: Missing type arguments for generic type "CompletedProcess" [type-arg]
src/documents/validators.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/validators.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/versioning.py:0: error: Missing type arguments for generic type "Prefetch" [type-arg] src/documents/versioning.py:0: error: Missing type arguments for generic type "Prefetch" [type-arg]
src/documents/views.py:0: error: "BulkPermissionMixin" has no attribute "filter_queryset" [attr-defined] src/documents/views/base.py:0: error: "BulkPermissionMixin" has no attribute "filter_queryset" [attr-defined]
src/documents/views.py:0: error: "BulkPermissionMixin" has no attribute "get_queryset" [attr-defined] src/documents/views/base.py:0: error: "BulkPermissionMixin" has no attribute "get_queryset" [attr-defined]
src/documents/views.py:0: error: "BulkPermissionMixin" has no attribute "queryset" [attr-defined] src/documents/views/base.py:0: error: "BulkPermissionMixin" has no attribute "queryset" [attr-defined]
src/documents/views.py:0: error: "BulkPermissionMixin" has no attribute "queryset" [attr-defined] src/documents/views/base.py:0: error: "BulkPermissionMixin" has no attribute "queryset" [attr-defined]
src/documents/views.py:0: error: "Sequence[_SupportsHasPermission]" has no attribute "append" [attr-defined] src/documents/views/base.py:0: error: "get_serializer_context" undefined in superclass [misc]
src/documents/views.py:0: error: "get_serializer_context" undefined in superclass [misc] src/documents/views/base.py:0: error: "type[Model]" has no attribute "objects" [attr-defined]
src/documents/views.py:0: error: "object" has no attribute "apply_async" [attr-defined] src/documents/views/base.py:0: error: Argument "user" to "_has_document_permissions" of "DocumentOperationPermissionMixin" has incompatible type "User | AnonymousUser"; expected "User" [arg-type]
src/documents/views.py:0: error: "type[DocumentFilterSet]" has no attribute "declared_filters" [attr-defined] src/documents/views/base.py:0: error: Argument "user" to "_resolve_document_ids" of "DocumentSelectionMixin" has incompatible type "User | AnonymousUser"; expected "User" [arg-type]
src/documents/views.py:0: error: "type[Model]" has no attribute "objects" [attr-defined] src/documents/views/base.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Argument "filename" to "FileResponse" has incompatible type "str | Any | None"; expected "str" [arg-type] src/documents/views/base.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Argument "path" to "EmailAttachment" has incompatible type "Path | None"; expected "Path" [arg-type] src/documents/views/base.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Argument "user" to "_has_document_permissions" of "DocumentOperationPermissionMixin" has incompatible type "User | AnonymousUser"; expected "User" [arg-type] src/documents/views/base.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Argument "user" to "_has_document_permissions" of "DocumentOperationPermissionMixin" has incompatible type "User | AnonymousUser"; expected "User" [arg-type] src/documents/views/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Argument "user" to "_resolve_document_ids" of "DocumentSelectionMixin" has incompatible type "User | AnonymousUser"; expected "User" [arg-type] src/documents/views/base.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Argument "user" to "_resolve_document_ids" of "DocumentSelectionMixin" has incompatible type "User | AnonymousUser"; expected "User" [arg-type] src/documents/views/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/views.py:0: error: Argument 1 to "TaskSummarySerializer" has incompatible type "QuerySet[PaperlessTask@AnnotatedWith[TypedDict({'total_count': int, 'pending_count': int, 'success_count': int, 'failure_count': int, 'avg_duration_seconds': Any, 'avg_wait_time_seconds': Any, 'last_run': Any, 'last_success': Any, 'last_failure': Any})], TypedDict({'task_type': str, 'total_count': Any, 'pending_count': Any, 'success_count': Any, 'failure_count': Any, 'avg_duration_seconds': Any, 'avg_wait_time_seconds': Any, 'last_run': Any, 'last_success': Any, 'last_failure': Any})]"; expected "dict[str, Any] | None" [arg-type] src/documents/views/base.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/views.py:0: error: Argument 1 to "int" has incompatible type "Any | Collection[str]"; expected "str | Buffer | SupportsInt | SupportsIndex" [arg-type] src/documents/views/base.py:0: error: Missing type arguments for generic type "list" [type-arg]
src/documents/views.py:0: error: Argument 1 to "int" has incompatible type "Any | Collection[str]"; expected "str | Buffer | SupportsInt | SupportsIndex" [arg-type] src/documents/views/bulk_edit.py:0: error: Argument "user" to "_has_document_permissions" of "DocumentOperationPermissionMixin" has incompatible type "User | AnonymousUser"; expected "User" [arg-type]
src/documents/views.py:0: error: Argument 1 to "paginate_queryset" of "GenericAPIView" has incompatible type "TantivyRelevanceList"; expected "QuerySet[Any, _MT_co] | Sequence[_MT_co]" [arg-type] src/documents/views/bulk_edit.py:0: error: Argument "user" to "_resolve_document_ids" of "DocumentSelectionMixin" has incompatible type "User | AnonymousUser"; expected "User" [arg-type]
src/documents/views.py:0: error: Argument 1 to "permitted_document_ids" has incompatible type "User | AnonymousUser"; expected "User | None" [arg-type] src/documents/views/bulk_edit.py:0: error: Argument 1 to "int" has incompatible type "Any | Collection[str]"; expected "str | Buffer | SupportsInt | SupportsIndex" [arg-type]
src/documents/views.py:0: error: Argument 1 to "permitted_object_ids" has incompatible type "User | AnonymousUser"; expected "User | None" [arg-type] src/documents/views/bulk_edit.py:0: error: Argument 1 to "permitted_object_ids" has incompatible type "User | AnonymousUser"; expected "User | None" [arg-type]
src/documents/views.py:0: error: Argument 2 to "match_correspondents" has incompatible type "DocumentClassifier | None"; expected "DocumentClassifier" [arg-type] src/documents/views/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Argument 2 to "match_document_types" has incompatible type "DocumentClassifier | None"; expected "DocumentClassifier" [arg-type] src/documents/views/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Argument 2 to "match_storage_paths" has incompatible type "DocumentClassifier | None"; expected "DocumentClassifier" [arg-type] src/documents/views/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Argument 2 to "match_tags" has incompatible type "DocumentClassifier | None"; expected "DocumentClassifier" [arg-type] src/documents/views/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Argument 3 to "autocomplete" of "TantivyBackend" has incompatible type "User | AnonymousUser | None"; expected "AbstractUser | None" [arg-type] src/documents/views/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Dict entry 0 has incompatible type "str": "bool"; expected "str": "str" [dict-item] src/documents/views/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/bulk_edit.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/bulk_edit.py:0: error: Incompatible types in assignment (expression has type "type[ArchiveOnlyStrategy]", variable has type "type[OriginalAndArchiveStrategy]") [assignment]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/bulk_edit.py:0: error: Incompatible types in assignment (expression has type "type[OriginalsOnlyStrategy]", variable has type "type[OriginalAndArchiveStrategy]") [assignment]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/bulk_edit.py:0: error: Skipping analyzing "auditlog.models": module is installed, but missing library stubs or py.typed marker [import-untyped]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/chat.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/documents.py:0: error: "type[DocumentFilterSet]" has no attribute "declared_filters" [attr-defined]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Argument "path" to "EmailAttachment" has incompatible type "Path | None"; expected "Path" [arg-type]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Argument 1 to "paginate_queryset" of "GenericAPIView" has incompatible type "TantivyRelevanceList"; expected "QuerySet[Any, _MT_co] | Sequence[_MT_co]" [arg-type]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Argument 2 to "match_correspondents" has incompatible type "DocumentClassifier | None"; expected "DocumentClassifier" [arg-type]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Argument 2 to "match_document_types" has incompatible type "DocumentClassifier | None"; expected "DocumentClassifier" [arg-type]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Argument 2 to "match_storage_paths" has incompatible type "DocumentClassifier | None"; expected "DocumentClassifier" [arg-type]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Argument 2 to "match_tags" has incompatible type "DocumentClassifier | None"; expected "DocumentClassifier" [arg-type]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Incompatible types in assignment (expression has type "ClassificationSuggestions", variable has type "dict[Any, Any]") [assignment]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Item "BasePagination" of "BasePagination | None" has no attribute "get_page_size" [union-attr]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Item "BasePagination" of "BasePagination | None" has no attribute "page_size" [union-attr]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Item "None" of "BasePagination | None" has no attribute "get_page_size" [union-attr]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Item "None" of "BasePagination | None" has no attribute "page_size" [union-attr]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Missing positional argument "request" in call to "email_documents" [call-arg]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Missing type arguments for generic type "list" [type-arg]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Missing type arguments for generic type "list" [type-arg]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Missing type arguments for generic type "list" [type-arg]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Need type annotation for "doc" [var-annotated]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Need type annotation for "doc" [var-annotated]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: No overload variant of "prefetch_related" of "QuerySet" matches argument type "list[object]" [call-overload]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Skipping analyzing "auditlog.models": module is installed, but missing library stubs or py.typed marker [import-untyped]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Skipping analyzing "langdetect": module is installed, but missing library stubs or py.typed marker [import-untyped]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/documents.py:0: error: Unsupported operand types for + ("None" and "int") [operator]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/index.py:0: error: Argument "filename" to "FileResponse" has incompatible type "str | Any | None"; expected "str" [arg-type]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/index.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/index.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/index.py:0: error: Item "AnonymousUser" of "User | AnonymousUser" has no attribute "get_full_name" [union-attr]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/index.py:0: error: Item "None" of "Any | None" has no attribute "get" [union-attr]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/index.py:0: error: Item "None" of "Any | None" has no attribute "get" [union-attr]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/index.py:0: error: Item "None" of "ApplicationConfiguration | None" has no attribute "app_logo" [union-attr]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/logs.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/logs.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/metadata.py:0: error: Argument 1 to "int" has incompatible type "Any | Collection[str]"; expected "str | Buffer | SupportsInt | SupportsIndex" [arg-type]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/metadata.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/metadata.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/metadata.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/metadata.py:0: error: Incompatible types in assignment (expression has type "tuple[type[IsAuthenticated], type[ViewDocumentsPermissions]]", variable has type "tuple[type[IsAuthenticated], type[PaperlessObjectPermissions]]") [assignment]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/metadata.py:0: error: Need type annotation for "children_map" (hint: "children_map: dict[<type>, <type>] = ...") [var-annotated]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/search.py:0: error: Argument 3 to "autocomplete" of "TantivyBackend" has incompatible type "User | AnonymousUser | None"; expected "AbstractUser | None" [arg-type]
src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views/search.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/views/search.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/views/search.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/views/search.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/views/search.py:0: error: Incompatible types in assignment (expression has type "QuerySet[Document, Document]", variable has type "list[Any]") [assignment]
src/documents/views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/views/search.py:0: error: Need type annotation for "docs" (hint: "docs: list[<type>] = ...") [var-annotated]
src/documents/views.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/views/search.py:0: error: Value of type "Iterable[Any]" is not indexable [index]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "Any | None", variable has type "dict[Any, Any]") [assignment] src/documents/views/search.py:0: error: Value of type "Iterable[Any]" is not indexable [index]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "ClassificationSuggestions", variable has type "dict[Any, Any]") [assignment] src/documents/views/search.py:0: error: Value of type "Iterable[Any]" is not indexable [index]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "QuerySet[Document, Document]", variable has type "list[Any]") [assignment] src/documents/views/search.py:0: error: Value of type "Iterable[Any]" is not indexable [index]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "bool", target has type "dict[str, str]") [assignment] src/documents/views/search.py:0: error: Value of type "Iterable[Any]" is not indexable [index]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "bool", target has type "dict[str, str]") [assignment] src/documents/views/search.py:0: error: Value of type "Iterable[Any]" is not indexable [index]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "bool", target has type "dict[str, str]") [assignment] src/documents/views/search.py:0: error: Value of type "Iterable[Any]" is not indexable [index]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "int", target has type "dict[str, str]") [assignment] src/documents/views/search.py:0: error: Value of type "Iterable[CustomField]" is not indexable [index]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "str | None", target has type "dict[str, str]") [assignment] src/documents/views/search.py:0: error: Value of type "Iterable[Group]" is not indexable [index]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "str | None", target has type "dict[str, str]") [assignment] src/documents/views/search.py:0: error: Value of type "Iterable[User]" is not indexable [index]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "str", target has type "dict[str, str]") [assignment] src/documents/views/search.py:0: error: Value of type "Iterable[Workflow]" is not indexable [index]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "str", target has type "dict[str, str]") [assignment] src/documents/views/sharing.py:0: error: "Sequence[_SupportsHasPermission]" has no attribute "append" [attr-defined]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "str", target has type "dict[str, str]") [assignment] src/documents/views/sharing.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "str", target has type "dict[str, str]") [assignment] src/documents/views/sharing.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "str", target has type "dict[str, str]") [assignment] src/documents/views/sharing.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "tuple[type[IsAuthenticated], type[ViewDocumentsPermissions]]", variable has type "tuple[type[IsAuthenticated], type[PaperlessObjectPermissions]]") [assignment] src/documents/views/sharing.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "type[ArchiveOnlyStrategy]", variable has type "type[OriginalAndArchiveStrategy]") [assignment] src/documents/views/sharing.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "type[DocumentSerializer]", variable has type "type[TrashSerializer]") [assignment] src/documents/views/sharing.py:0: error: Missing type arguments for generic type "GenericViewSet" [type-arg]
src/documents/views.py:0: error: Incompatible types in assignment (expression has type "type[OriginalsOnlyStrategy]", variable has type "type[OriginalAndArchiveStrategy]") [assignment] src/documents/views/sharing.py:0: error: Need type annotation for "authentication_classes" (hint: "authentication_classes: list[<type>] = ...") [var-annotated]
src/documents/views.py:0: error: Item "AnonymousUser" of "User | AnonymousUser" has no attribute "get_full_name" [union-attr] src/documents/views/sharing.py:0: error: Need type annotation for "permission_classes" (hint: "permission_classes: list[<type>] = ...") [var-annotated]
src/documents/views.py:0: error: Item "BasePagination" of "BasePagination | None" has no attribute "get_page_size" [union-attr] src/documents/views/system.py:0: error: Argument 1 to "permitted_document_ids" has incompatible type "User | AnonymousUser"; expected "User | None" [arg-type]
src/documents/views.py:0: error: Item "BasePagination" of "BasePagination | None" has no attribute "page_size" [union-attr] src/documents/views/system.py:0: error: Dict entry 0 has incompatible type "str": "bool"; expected "str": "str" [dict-item]
src/documents/views.py:0: error: Item "None" of "Any | None" has no attribute "get" [union-attr] src/documents/views/system.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Item "None" of "Any | None" has no attribute "get" [union-attr] src/documents/views/system.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Item "None" of "ApplicationConfiguration | None" has no attribute "app_logo" [union-attr] src/documents/views/system.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Item "None" of "BasePagination | None" has no attribute "get_page_size" [union-attr] src/documents/views/system.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Item "None" of "BasePagination | None" has no attribute "page_size" [union-attr] src/documents/views/system.py:0: error: Incompatible types in assignment (expression has type "Any | None", variable has type "dict[Any, Any]") [assignment]
src/documents/views.py:0: error: Missing positional argument "request" in call to "email_documents" [call-arg] src/documents/views/system.py:0: error: Incompatible types in assignment (expression has type "bool", target has type "dict[str, str]") [assignment]
src/documents/views.py:0: error: Missing type arguments for generic type "GenericViewSet" [type-arg] src/documents/views/system.py:0: error: Incompatible types in assignment (expression has type "bool", target has type "dict[str, str]") [assignment]
src/documents/views.py:0: error: Missing type arguments for generic type "list" [type-arg] src/documents/views/system.py:0: error: Incompatible types in assignment (expression has type "bool", target has type "dict[str, str]") [assignment]
src/documents/views.py:0: error: Missing type arguments for generic type "list" [type-arg] src/documents/views/system.py:0: error: Incompatible types in assignment (expression has type "int", target has type "dict[str, str]") [assignment]
src/documents/views.py:0: error: Missing type arguments for generic type "list" [type-arg] src/documents/views/system.py:0: error: Incompatible types in assignment (expression has type "str | None", target has type "dict[str, str]") [assignment]
src/documents/views.py:0: error: Missing type arguments for generic type "list" [type-arg] src/documents/views/system.py:0: error: Incompatible types in assignment (expression has type "str | None", target has type "dict[str, str]") [assignment]
src/documents/views.py:0: error: Need type annotation for "authentication_classes" (hint: "authentication_classes: list[<type>] = ...") [var-annotated] src/documents/views/system.py:0: error: Incompatible types in assignment (expression has type "str", target has type "dict[str, str]") [assignment]
src/documents/views.py:0: error: Need type annotation for "children_map" (hint: "children_map: dict[<type>, <type>] = ...") [var-annotated] src/documents/views/system.py:0: error: Incompatible types in assignment (expression has type "str", target has type "dict[str, str]") [assignment]
src/documents/views.py:0: error: Need type annotation for "doc" [var-annotated] src/documents/views/system.py:0: error: Incompatible types in assignment (expression has type "str", target has type "dict[str, str]") [assignment]
src/documents/views.py:0: error: Need type annotation for "doc" [var-annotated] src/documents/views/system.py:0: error: Incompatible types in assignment (expression has type "str", target has type "dict[str, str]") [assignment]
src/documents/views.py:0: error: Need type annotation for "docs" (hint: "docs: list[<type>] = ...") [var-annotated] src/documents/views/system.py:0: error: Incompatible types in assignment (expression has type "str", target has type "dict[str, str]") [assignment]
src/documents/views.py:0: error: Need type annotation for "permission_classes" (hint: "permission_classes: list[<type>] = ...") [var-annotated] src/documents/views/system.py:0: error: Incompatible types in assignment (expression has type "type[DocumentSerializer]", variable has type "type[TrashSerializer]") [assignment]
src/documents/views.py:0: error: Need type annotation for "ui_settings" (hint: "ui_settings: dict[<type>, <type>] = ...") [var-annotated] src/documents/views/system.py:0: error: Need type annotation for "ui_settings" (hint: "ui_settings: dict[<type>, <type>] = ...") [var-annotated]
src/documents/views.py:0: error: No overload variant of "prefetch_related" of "QuerySet" matches argument type "list[object]" [call-overload] src/documents/views/tasks.py:0: error: "object" has no attribute "apply_async" [attr-defined]
src/documents/views.py:0: error: Skipping analyzing "auditlog.models": module is installed, but missing library stubs or py.typed marker [import-untyped] src/documents/views/tasks.py:0: error: Argument 1 to "TaskSummarySerializer" has incompatible type "QuerySet[PaperlessTask@AnnotatedWith[TypedDict({'total_count': int, 'pending_count': int, 'success_count': int, 'failure_count': int, 'avg_duration_seconds': Any, 'avg_wait_time_seconds': Any, 'last_run': Any, 'last_success': Any, 'last_failure': Any})], TypedDict({'task_type': str, 'total_count': Any, 'pending_count': Any, 'success_count': Any, 'failure_count': Any, 'avg_duration_seconds': Any, 'avg_wait_time_seconds': Any, 'last_run': Any, 'last_success': Any, 'last_failure': Any})]"; expected "dict[str, Any] | None" [arg-type]
src/documents/views.py:0: error: Skipping analyzing "langdetect": module is installed, but missing library stubs or py.typed marker [import-untyped] src/documents/views/tasks.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Unsupported operand types for + ("None" and "int") [operator] src/documents/views/tasks.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Value of type "Iterable[Any]" is not indexable [index] src/documents/views/tasks.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Value of type "Iterable[Any]" is not indexable [index] src/documents/views/tasks.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Value of type "Iterable[Any]" is not indexable [index] src/documents/views/tasks.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/views.py:0: error: Value of type "Iterable[Any]" is not indexable [index] src/documents/views/tasks.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Value of type "Iterable[Any]" is not indexable [index] src/documents/views/tasks.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Value of type "Iterable[Any]" is not indexable [index] src/documents/views/tasks.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Value of type "Iterable[Any]" is not indexable [index] src/documents/views/tasks.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Value of type "Iterable[CustomField]" is not indexable [index] src/documents/views/tasks.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Value of type "Iterable[Group]" is not indexable [index] src/documents/views/tasks.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Value of type "Iterable[User]" is not indexable [index] src/documents/views/upload.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views.py:0: error: Value of type "Iterable[Workflow]" is not indexable [index] src/documents/views/workflows.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/views/workflows.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/workflows/actions.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/workflows/actions.py:0: error: Function is missing a return type annotation [no-untyped-def]
src/documents/workflows/actions.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/workflows/actions.py:0: error: Function is missing a type annotation [no-untyped-def]
src/documents/workflows/actions.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def] src/documents/workflows/actions.py:0: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
@@ -2145,9 +2145,9 @@ src/paperless_ai/ai_classifier.py:0: error: Argument 1 to "parse_ai_response" ha
src/paperless_ai/ai_classifier.py:0: error: Incompatible types in assignment (expression has type "Any | None", variable has type "str") [assignment] src/paperless_ai/ai_classifier.py:0: error: Incompatible types in assignment (expression has type "Any | None", variable has type "str") [assignment]
src/paperless_ai/ai_classifier.py:0: error: Missing type arguments for generic type "dict" [type-arg] src/paperless_ai/ai_classifier.py:0: error: Missing type arguments for generic type "dict" [type-arg]
src/paperless_ai/ai_classifier.py:0: error: Missing type arguments for generic type "dict" [type-arg] src/paperless_ai/ai_classifier.py:0: error: Missing type arguments for generic type "dict" [type-arg]
src/paperless_ai/ai_classifier.py:0: error: TypedDict key must be a string literal; expected one of ("correspondents", "dates", "document_types", "storage_paths", "tags", ...) [literal-required] src/paperless_ai/ai_classifier.py:0: error: TypedDict key must be a string literal; expected one of ("title", "tags", "correspondents", "document_types", "storage_paths", ...) [literal-required]
src/paperless_ai/ai_classifier.py:0: error: TypedDict key must be a string literal; expected one of ("correspondents", "dates", "document_types", "storage_paths", "tags", ...) [literal-required] src/paperless_ai/ai_classifier.py:0: error: TypedDict key must be a string literal; expected one of ("title", "tags", "correspondents", "document_types", "storage_paths", ...) [literal-required]
src/paperless_ai/ai_classifier.py:0: error: TypedDict key must be a string literal; expected one of ("correspondents", "dates", "document_types", "storage_paths", "tags", ...) [literal-required] src/paperless_ai/ai_classifier.py:0: error: TypedDict key must be a string literal; expected one of ("title", "tags", "correspondents", "document_types", "storage_paths", ...) [literal-required]
src/paperless_ai/base_model.py:0: error: Invalid index type "str | None" for "dict[str, FieldInfo]"; expected type "str" [index] src/paperless_ai/base_model.py:0: error: Invalid index type "str | None" for "dict[str, FieldInfo]"; expected type "str" [index]
src/paperless_ai/chat.py:0: error: Dict entry 1 has incompatible type "str": "str | Any | None"; expected "str": "int | str" [dict-item] src/paperless_ai/chat.py:0: error: Dict entry 1 has incompatible type "str": "str | Any | None"; expected "str": "int | str" [dict-item]
src/paperless_ai/chat.py:0: error: Function is missing a return type annotation [no-untyped-def] src/paperless_ai/chat.py:0: error: Function is missing a return type annotation [no-untyped-def]
+357 -364
View File
@@ -896,240 +896,233 @@
"concise_description": "Argument `FieldRef | None` is not assignable to parameter `field` with type `FieldRef` in function `whoosh_compat.ast.Fuzzy.__init__`", "concise_description": "Argument `FieldRef | None` is not assignable to parameter `field` with type `FieldRef` in function `whoosh_compat.ast.Fuzzy.__init__`",
"severity": "error" "severity": "error"
}, },
{
"column": 17,
"path": "src/documents/search/_schema.py",
"name": "unexpected-keyword",
"concise_description": "Unexpected keyword argument `fast` in function `tantivy.tantivy.SchemaBuilder.add_json_field`",
"severity": "error"
},
{ {
"column": 9, "column": 9,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/base.py",
"name": "bad-override-param-name", "name": "bad-override-param-name",
"concise_description": "Class member `MatchingModelSerializer.validate` overrides parent class `ModelSerializer` in an inconsistent manner", "concise_description": "Class member `MatchingModelSerializer.validate` overrides parent class `ModelSerializer` in an inconsistent manner",
"severity": "error" "severity": "error"
}, },
{ {
"column": 62, "column": 62,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/base.py",
"name": "unsupported-operation", "name": "unsupported-operation",
"concise_description": "Cannot set item in `dict[str, QuerySet[User, User]]`", "concise_description": "Cannot set item in `dict[str, QuerySet[User, User]]`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 17, "column": 17,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/base.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of class `list` has no attribute `values_list`", "concise_description": "Object of class `list` has no attribute `values_list`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 9, "column": 9,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/base.py",
"name": "bad-override-param-name", "name": "bad-override-param-name",
"concise_description": "Class member `OwnedObjectListSerializer.to_representation` overrides parent class `ListSerializer` in an inconsistent manner", "concise_description": "Class member `OwnedObjectListSerializer.to_representation` overrides parent class `ListSerializer` in an inconsistent manner",
"severity": "error" "severity": "error"
}, },
{ {
"column": 9, "column": 9,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/base.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of class `NoneType` has no attribute `context`", "concise_description": "Object of class `NoneType` has no attribute `context`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 51, "column": 51,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/base.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of type `BaseSerializer[Unknown] | Field[Unknown, Unknown, Unknown, Unknown] | None` has no attribute `get_shared_object_pks`", "concise_description": "Object of type `BaseSerializer[Unknown] | Field[Unknown, Unknown, Unknown, Unknown] | None` has no attribute `get_shared_object_pks`",
"severity": "error" "severity": "error"
}, },
{
"column": 14,
"path": "src/documents/serialisers.py",
"name": "bad-assignment",
"concise_description": "`ManyRelatedField | RelatedField[@_, @_, Any]` is not assignable to attribute `parent` with type `BaseSerializer[Unknown]`",
"severity": "error"
},
{
"column": 17,
"path": "src/documents/serialisers.py",
"name": "unsupported-operation",
"concise_description": "Cannot set item in `Mapping[str, Any]`",
"severity": "error"
},
{
"column": 9,
"path": "src/documents/serialisers.py",
"name": "bad-override-param-name",
"concise_description": "Class member `TagSerializer.validate` overrides parent class `MatchingModelSerializer` in an inconsistent manner",
"severity": "error"
},
{
"column": 17,
"path": "src/documents/serialisers.py",
"name": "bad-argument-type",
"concise_description": "Argument `type[CustomField.FieldDataType]` is not assignable to parameter `choices` with type `Sequence[Any]` in function `rest_framework.fields.ChoiceField.__init__`",
"severity": "error"
},
{ {
"column": 28, "column": 28,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/base.py",
"name": "bad-assignment", "name": "bad-assignment",
"concise_description": "`Unknown | None` is not assignable to attribute `method_name` with type `str`", "concise_description": "`Unknown | None` is not assignable to attribute `method_name` with type `str`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 13, "column": 7,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/bulk_edit.py",
"name": "bad-argument-type", "name": "invalid-inheritance",
"concise_description": "Argument `str` is not assignable to parameter `data_type` with type `CustomField.FieldDataType` in function `documents.models.CustomFieldInstance.get_value_field_name`", "concise_description": "Class `BulkEditSerializer` has inconsistent type arguments for base class `Field`: `Field[Any, Any, Any, dict[str, Any]]` and `Field[Any, Any, Any, dict[str, list[int]]]`",
"severity": "error"
},
{
"column": 9,
"path": "src/documents/serialisers.py",
"name": "bad-override-param-name",
"concise_description": "Class member `CustomFieldInstanceSerializer.get_value` overrides parent class `ModelSerializer` in an inconsistent manner",
"severity": "error"
},
{
"column": 9,
"path": "src/documents/serialisers.py",
"name": "bad-override-param-name",
"concise_description": "Class member `CustomFieldInstanceSerializer.validate` overrides parent class `ModelSerializer` in an inconsistent manner",
"severity": "error"
},
{
"column": 34,
"path": "src/documents/serialisers.py",
"name": "unsupported-operation",
"concise_description": "`None` is not subscriptable",
"severity": "error" "severity": "error"
}, },
{ {
"column": 30, "column": 30,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/documents.py",
"name": "unsupported-operation", "name": "unsupported-operation",
"concise_description": "`None` is not subscriptable", "concise_description": "`None` is not subscriptable",
"severity": "error" "severity": "error"
}, },
{ {
"column": 9, "column": 9,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/documents.py",
"name": "bad-override-param-name", "name": "bad-override-param-name",
"concise_description": "Class member `SearchResultListSerializer.to_representation` overrides parent class `ListSerializer` in an inconsistent manner", "concise_description": "Class member `SearchResultListSerializer.to_representation` overrides parent class `ListSerializer` in an inconsistent manner",
"severity": "error" "severity": "error"
}, },
{ {
"column": 21, "column": 21,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/documents.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of type `BaseSerializer[Unknown] | Field[Unknown, Unknown, Unknown, Unknown] | None` has no attribute `fetch_documents`", "concise_description": "Object of type `BaseSerializer[Unknown] | Field[Unknown, Unknown, Unknown, Unknown] | None` has no attribute `fetch_documents`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 9, "column": 9,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/documents.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of class `NoneType` has no attribute `context`", "concise_description": "Object of class `NoneType` has no attribute `context`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 9, "column": 9,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/documents.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of class `NoneType` has no attribute `context`", "concise_description": "Object of class `NoneType` has no attribute `context`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 51, "column": 51,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/documents.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of type `BaseSerializer[Unknown] | Field[Unknown, Unknown, Unknown, Unknown] | None` has no attribute `get_shared_object_pks`", "concise_description": "Object of type `BaseSerializer[Unknown] | Field[Unknown, Unknown, Unknown, Unknown] | None` has no attribute `get_shared_object_pks`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 9, "column": 9,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/documents.py",
"name": "bad-override-param-name", "name": "bad-override-param-name",
"concise_description": "Class member `SearchResultSerializer.to_representation` overrides parent class `DocumentSerializer` in an inconsistent manner", "concise_description": "Class member `SearchResultSerializer.to_representation` overrides parent class `DocumentSerializer` in an inconsistent manner",
"severity": "error" "severity": "error"
}, },
{ {
"column": 9, "column": 9,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/documents.py",
"name": "bad-override-mutable-attribute", "name": "bad-override-mutable-attribute",
"concise_description": "Class member `Meta.list_serializer_class` overrides parent class `Meta` in an inconsistent manner", "concise_description": "Class member `Meta.list_serializer_class` overrides parent class `Meta` in an inconsistent manner",
"severity": "error" "severity": "error"
}, },
{
"column": 14,
"path": "src/documents/serialisers/metadata.py",
"name": "bad-assignment",
"concise_description": "`ManyRelatedField | RelatedField[@_, @_, Any]` is not assignable to attribute `parent` with type `BaseSerializer[Unknown]`",
"severity": "error"
},
{
"column": 17,
"path": "src/documents/serialisers/metadata.py",
"name": "unsupported-operation",
"concise_description": "Cannot set item in `Mapping[str, Any]`",
"severity": "error"
},
{
"column": 9,
"path": "src/documents/serialisers/metadata.py",
"name": "bad-override-param-name",
"concise_description": "Class member `TagSerializer.validate` overrides parent class `MatchingModelSerializer` in an inconsistent manner",
"severity": "error"
},
{
"column": 17,
"path": "src/documents/serialisers/metadata.py",
"name": "bad-argument-type",
"concise_description": "Argument `type[CustomField.FieldDataType]` is not assignable to parameter `choices` with type `Sequence[Any]` in function `rest_framework.fields.ChoiceField.__init__`",
"severity": "error"
},
{
"column": 13,
"path": "src/documents/serialisers/metadata.py",
"name": "bad-argument-type",
"concise_description": "Argument `str` is not assignable to parameter `data_type` with type `CustomField.FieldDataType` in function `documents.models.CustomFieldInstance.get_value_field_name`",
"severity": "error"
},
{
"column": 9,
"path": "src/documents/serialisers/metadata.py",
"name": "bad-override-param-name",
"concise_description": "Class member `CustomFieldInstanceSerializer.get_value` overrides parent class `ModelSerializer` in an inconsistent manner",
"severity": "error"
},
{
"column": 9,
"path": "src/documents/serialisers/metadata.py",
"name": "bad-override-param-name",
"concise_description": "Class member `CustomFieldInstanceSerializer.validate` overrides parent class `ModelSerializer` in an inconsistent manner",
"severity": "error"
},
{
"column": 34,
"path": "src/documents/serialisers/metadata.py",
"name": "unsupported-operation",
"concise_description": "`None` is not subscriptable",
"severity": "error"
},
{ {
"column": 21, "column": 21,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/saved_views.py",
"name": "unsupported-operation", "name": "unsupported-operation",
"concise_description": "`Literal[SavedView.DisplayFields.CUSTOM_FIELD]` is not subscriptable", "concise_description": "`Literal[SavedView.DisplayFields.CUSTOM_FIELD]` is not subscriptable",
"severity": "error" "severity": "error"
}, },
{ {
"column": 36, "column": 36,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/saved_views.py",
"name": "unsupported-operation", "name": "unsupported-operation",
"concise_description": "`None` is not subscriptable", "concise_description": "`None` is not subscriptable",
"severity": "error" "severity": "error"
}, },
{
"column": 7,
"path": "src/documents/serialisers.py",
"name": "invalid-inheritance",
"concise_description": "Class `BulkEditSerializer` has inconsistent type arguments for base class `Field`: `Field[Any, Any, Any, dict[str, Any]]` and `Field[Any, Any, Any, dict[str, list[int]]]`",
"severity": "error"
},
{ {
"column": 73, "column": 73,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/sharing.py",
"name": "unbound-name", "name": "unbound-name",
"concise_description": "`address` is uninitialized", "concise_description": "`address` is uninitialized",
"severity": "error" "severity": "error"
}, },
{ {
"column": 9, "column": 9,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/system.py",
"name": "bad-override", "name": "bad-override",
"concise_description": "Class member `UiSettingsViewSerializer.create` overrides parent class `ModelSerializer` in an inconsistent manner", "concise_description": "Class member `UiSettingsViewSerializer.create` overrides parent class `ModelSerializer` in an inconsistent manner",
"severity": "error" "severity": "error"
}, },
{ {
"column": 46, "column": 46,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/tasks.py",
"name": "no-matching-overload", "name": "no-matching-overload",
"concise_description": "No matching overload found for function `dict.get` called with arguments: (str, str)", "concise_description": "No matching overload found for function `dict.get` called with arguments: (str, str)",
"severity": "error" "severity": "error"
}, },
{ {
"column": 38, "column": 38,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/tasks.py",
"name": "no-matching-overload", "name": "no-matching-overload",
"concise_description": "No matching overload found for function `dict.get` called with arguments: (str, str)", "concise_description": "No matching overload found for function `dict.get` called with arguments: (str, str)",
"severity": "error" "severity": "error"
}, },
{ {
"column": 51, "column": 51,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/tasks.py",
"name": "no-matching-overload", "name": "no-matching-overload",
"concise_description": "No matching overload found for function `dict.get` called with arguments: (str, Literal['manual_task'])", "concise_description": "No matching overload found for function `dict.get` called with arguments: (str, Literal['manual_task'])",
"severity": "error" "severity": "error"
}, },
{ {
"column": 52, "column": 52,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/workflows.py",
"name": "unbound-name", "name": "unbound-name",
"concise_description": "`triggers` may be uninitialized", "concise_description": "`triggers` may be uninitialized",
"severity": "error" "severity": "error"
}, },
{ {
"column": 62, "column": 62,
"path": "src/documents/serialisers.py", "path": "src/documents/serialisers/workflows.py",
"name": "unbound-name", "name": "unbound-name",
"concise_description": "`actions` may be uninitialized", "concise_description": "`actions` may be uninitialized",
"severity": "error" "severity": "error"
@@ -3619,389 +3612,389 @@
"concise_description": "`float | int | None` is not assignable to attribute `MAX_IMAGE_PIXELS` with type `int | None`", "concise_description": "`float | int | None` is not assignable to attribute `MAX_IMAGE_PIXELS` with type `int | None`",
"severity": "error" "severity": "error"
}, },
{
"column": 31,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of class `AbstractBaseUser` has no attribute `username`",
"severity": "error"
},
{
"column": 32,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of type `AbstractBaseUser | AnonymousUser` has no attribute `get_full_name`",
"severity": "error"
},
{ {
"column": 17, "column": 17,
"path": "src/documents/views.py", "path": "src/documents/views/base.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of class `BulkPermissionMixin` has no attribute `queryset`", "concise_description": "Object of class `BulkPermissionMixin` has no attribute `queryset`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 19, "column": 19,
"path": "src/documents/views.py", "path": "src/documents/views/base.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of class `object` has no attribute `get_serializer_context`", "concise_description": "Object of class `object` has no attribute `get_serializer_context`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 24, "column": 24,
"path": "src/documents/views.py", "path": "src/documents/views/base.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of class `BulkPermissionMixin` has no attribute `filter_queryset`", "concise_description": "Object of class `BulkPermissionMixin` has no attribute `filter_queryset`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 45, "column": 45,
"path": "src/documents/views.py", "path": "src/documents/views/base.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of class `BulkPermissionMixin` has no attribute `get_queryset`", "concise_description": "Object of class `BulkPermissionMixin` has no attribute `get_queryset`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 22, "column": 22,
"path": "src/documents/views.py", "path": "src/documents/views/base.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of class `BulkPermissionMixin` has no attribute `queryset`", "concise_description": "Object of class `BulkPermissionMixin` has no attribute `queryset`",
"severity": "error" "severity": "error"
}, },
{
"column": 29,
"path": "src/documents/views.py",
"name": "bad-argument-type",
"concise_description": "Argument `Self@CorrespondentViewSet` is not assignable to parameter `instance` with type `Options[Any] | None` in function `django.db.models.manager.BaseManager.__get__`",
"severity": "error"
},
{
"column": 29,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of class `NoneType` has no attribute `annotate`",
"severity": "error"
},
{
"column": 25,
"path": "src/documents/views.py",
"name": "bad-argument-type",
"concise_description": "Argument `Self@CorrespondentViewSet` is not assignable to parameter `instance` with type `Options[Any] | None` in function `django.db.models.manager.BaseManager.__get__`",
"severity": "error"
},
{
"column": 25,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of class `NoneType` has no attribute `annotate`",
"severity": "error"
},
{
"column": 48,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of class `Correspondent` has no attribute `document_count`",
"severity": "error"
},
{
"column": 48,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of class `DocumentType` has no attribute `document_count`",
"severity": "error"
},
{
"column": 48,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of class `StoragePath` has no attribute `document_count`",
"severity": "error"
},
{
"column": 28,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Class `DocumentFilterSet` has no class attribute `declared_filters`",
"severity": "error"
},
{
"column": 9,
"path": "src/documents/views.py",
"name": "bad-override",
"concise_description": "Class member `DocumentViewSet.destroy` overrides parent class `DestroyModelMixin` in an inconsistent manner",
"severity": "error"
},
{
"column": 37,
"path": "src/documents/views.py",
"name": "bad-argument-type",
"concise_description": "Argument `list[MetadataEntry] | list[Unknown] | None` is not assignable to parameter `original_metadata` with type `list[Unknown]` in function `documents.caching.set_metadata_cache`",
"severity": "error"
},
{
"column": 57,
"path": "src/documents/views.py",
"name": "bad-argument-type",
"concise_description": "Argument `DocumentClassifier | None` is not assignable to parameter `classifier` with type `DocumentClassifier` in function `documents.matching.match_correspondents`",
"severity": "error"
},
{
"column": 52,
"path": "src/documents/views.py",
"name": "bad-argument-type",
"concise_description": "Argument `DocumentClassifier | None` is not assignable to parameter `classifier` with type `DocumentClassifier` in function `documents.matching.match_tags`",
"severity": "error"
},
{
"column": 59,
"path": "src/documents/views.py",
"name": "bad-argument-type",
"concise_description": "Argument `DocumentClassifier | None` is not assignable to parameter `classifier` with type `DocumentClassifier` in function `documents.matching.match_document_types`",
"severity": "error"
},
{
"column": 58,
"path": "src/documents/views.py",
"name": "bad-argument-type",
"concise_description": "Argument `DocumentClassifier | None` is not assignable to parameter `classifier` with type `DocumentClassifier` in function `documents.matching.match_storage_paths`",
"severity": "error"
},
{ {
"column": 17, "column": 17,
"path": "src/documents/views.py", "path": "src/documents/views/base.py",
"name": "bad-argument-type",
"concise_description": "Argument `ClassificationSuggestions` is not assignable to parameter `suggestions` with type `dict[Unknown, Unknown]` in function `documents.caching.set_llm_suggestions_cache`",
"severity": "error"
},
{
"column": 5,
"path": "src/documents/views.py",
"name": "bad-specialization",
"concise_description": "`(self: Self@DocumentViewSet, request: Unknown, pk: Unknown | None = None) -> HttpResponseForbidden | Response | None` is not assignable to upper bound `(...) -> HttpResponseBase` of type variable `_View`",
"severity": "error"
},
{
"column": 36,
"path": "src/documents/views.py",
"name": "missing-argument",
"concise_description": "Missing argument `request` in function `DocumentViewSet.email_documents`",
"severity": "error"
},
{
"column": 9,
"path": "src/documents/views.py",
"name": "bad-override",
"concise_description": "Class member `UnifiedSearchViewSet.list` overrides parent class `DocumentViewSet` in an inconsistent manner",
"severity": "error"
},
{
"column": 17,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of type `BasePagination | None` has no attribute `get_page_size`",
"severity": "error"
},
{
"column": 58,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of type `BasePagination | None` has no attribute `page_size`",
"severity": "error"
},
{
"column": 13,
"path": "src/documents/views.py",
"name": "not-iterable",
"concise_description": "Type `None` is not iterable",
"severity": "error"
},
{
"column": 43,
"path": "src/documents/views.py",
"name": "bad-argument-type",
"concise_description": "Argument `TantivyRelevanceList` is not assignable to parameter `queryset` with type `QuerySet[Any, @_] | Sequence[@_]` in function `rest_framework.generics.GenericAPIView.paginate_queryset`",
"severity": "error"
},
{
"column": 25,
"path": "src/documents/views.py",
"name": "unsupported-operation",
"concise_description": "`+` is not supported between `None` and `Literal[1]`",
"severity": "error"
},
{
"column": 17,
"path": "src/documents/views.py",
"name": "not-iterable", "name": "not-iterable",
"concise_description": "Type `None` is not iterable", "concise_description": "Type `None` is not iterable",
"severity": "error" "severity": "error"
}, },
{ {
"column": 18, "column": 18,
"path": "src/documents/views.py", "path": "src/documents/views/base.py",
"name": "bad-argument-type", "name": "bad-argument-type",
"concise_description": "Argument `AbstractBaseUser | AnonymousUser` is not assignable to parameter `user` with type `User` in function `DocumentSelectionMixin._resolve_document_ids`", "concise_description": "Argument `AbstractBaseUser | AnonymousUser` is not assignable to parameter `user` with type `User` in function `DocumentSelectionMixin._resolve_document_ids`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 18, "column": 18,
"path": "src/documents/views.py", "path": "src/documents/views/base.py",
"name": "bad-argument-type", "name": "bad-argument-type",
"concise_description": "Argument `AbstractBaseUser | AnonymousUser` is not assignable to parameter `user` with type `User` in function `DocumentOperationPermissionMixin._has_document_permissions`", "concise_description": "Argument `AbstractBaseUser | AnonymousUser` is not assignable to parameter `user` with type `User` in function `DocumentOperationPermissionMixin._has_document_permissions`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 18, "column": 18,
"path": "src/documents/views.py", "path": "src/documents/views/bulk_edit.py",
"name": "bad-argument-type", "name": "bad-argument-type",
"concise_description": "Argument `AbstractBaseUser | AnonymousUser` is not assignable to parameter `user` with type `User` in function `DocumentSelectionMixin._resolve_document_ids`", "concise_description": "Argument `AbstractBaseUser | AnonymousUser` is not assignable to parameter `user` with type `User` in function `documents.views.base.DocumentSelectionMixin._resolve_document_ids`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 18, "column": 18,
"path": "src/documents/views.py", "path": "src/documents/views/bulk_edit.py",
"name": "bad-argument-type", "name": "bad-argument-type",
"concise_description": "Argument `AbstractBaseUser | AnonymousUser` is not assignable to parameter `user` with type `User` in function `DocumentOperationPermissionMixin._has_document_permissions`", "concise_description": "Argument `AbstractBaseUser | AnonymousUser` is not assignable to parameter `user` with type `User` in function `documents.views.base.DocumentOperationPermissionMixin._has_document_permissions`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 33, "column": 33,
"path": "src/documents/views.py", "path": "src/documents/views/bulk_edit.py",
"name": "unbound-name", "name": "unbound-name",
"concise_description": "`old_values` may be uninitialized", "concise_description": "`old_values` may be uninitialized",
"severity": "error" "severity": "error"
}, },
{
"column": 52,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of class `Correspondent` has no attribute `document_count`",
"severity": "error"
},
{
"column": 52,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of class `Tag` has no attribute `document_count`",
"severity": "error"
},
{
"column": 52,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of class `DocumentType` has no attribute `document_count`",
"severity": "error"
},
{
"column": 52,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of class `StoragePath` has no attribute `document_count`",
"severity": "error"
},
{
"column": 52,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of class `CustomField` has no attribute `document_count`",
"severity": "error"
},
{
"column": 65,
"path": "src/documents/views.py",
"name": "bad-argument-type",
"concise_description": "Argument `AbstractBaseUser | AnonymousUser | None` is not assignable to parameter `user` with type `AbstractUser | None` in function `documents.search._backend.TantivyBackend.autocomplete`",
"severity": "error"
},
{
"column": 9,
"path": "src/documents/views.py",
"name": "bad-override",
"concise_description": "Class member `TasksViewSet.get_serializer_class` overrides parent class `ReadOnlyModelViewSet` in an inconsistent manner",
"severity": "error"
},
{
"column": 12,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of class `AbstractBaseUser` has no attribute `is_staff`",
"severity": "error"
},
{
"column": 44,
"path": "src/documents/views.py",
"name": "bad-argument-type",
"concise_description": "Argument `QuerySet[PaperlessTask, dict[str, Any]]` is not assignable to parameter `instance` with type `dict[str, Any] | None` in function `rest_framework.serializers.BaseSerializer.__init__`",
"severity": "error"
},
{
"column": 13,
"path": "src/documents/views.py",
"name": "missing-attribute",
"concise_description": "Object of class `Sequence` has no attribute `append`",
"severity": "error"
},
{ {
"column": 24, "column": 24,
"path": "src/documents/views.py", "path": "src/documents/views/bulk_edit.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of class `BaseSerializer` has no attribute `get_object_class`", "concise_description": "Object of class `BaseSerializer` has no attribute `get_object_class`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 45, "column": 45,
"path": "src/documents/views.py", "path": "src/documents/views/bulk_edit.py",
"name": "bad-argument-type", "name": "bad-argument-type",
"concise_description": "Argument `AbstractBaseUser | AnonymousUser` is not assignable to parameter `user` with type `User | None` in function `documents.permissions.permitted_object_ids`", "concise_description": "Argument `AbstractBaseUser | AnonymousUser` is not assignable to parameter `user` with type `User | None` in function `documents.permissions.permitted_object_ids`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 16, "column": 16,
"path": "src/documents/views.py", "path": "src/documents/views/bulk_edit.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of class `AbstractBaseUser` has no attribute `is_superuser`", "concise_description": "Object of class `AbstractBaseUser` has no attribute `is_superuser`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 17, "column": 17,
"path": "src/documents/views.py", "path": "src/documents/views/bulk_edit.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of class `AbstractBaseUser` has no attribute `has_perm`", "concise_description": "Object of class `AbstractBaseUser` has no attribute `has_perm`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 9, "column": 48,
"path": "src/documents/views.py", "path": "src/documents/views/documents.py",
"name": "bad-override", "name": "missing-attribute",
"concise_description": "Class member `WorkflowTriggerViewSet.partial_update` overrides parent class `ModelViewSet` in an inconsistent manner", "concise_description": "Object of class `Correspondent` has no attribute `document_count`",
"severity": "error"
},
{
"column": 48,
"path": "src/documents/views/documents.py",
"name": "missing-attribute",
"concise_description": "Object of class `DocumentType` has no attribute `document_count`",
"severity": "error"
},
{
"column": 48,
"path": "src/documents/views/documents.py",
"name": "missing-attribute",
"concise_description": "Object of class `StoragePath` has no attribute `document_count`",
"severity": "error"
},
{
"column": 28,
"path": "src/documents/views/documents.py",
"name": "missing-attribute",
"concise_description": "Class `DocumentFilterSet` has no class attribute `declared_filters`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 9, "column": 9,
"path": "src/documents/views.py", "path": "src/documents/views/documents.py",
"name": "bad-override", "name": "bad-override",
"concise_description": "Class member `WorkflowActionViewSet.partial_update` overrides parent class `ModelViewSet` in an inconsistent manner", "concise_description": "Class member `DocumentViewSet.destroy` overrides parent class `DestroyModelMixin` in an inconsistent manner",
"severity": "error"
},
{
"column": 37,
"path": "src/documents/views/documents.py",
"name": "bad-argument-type",
"concise_description": "Argument `list[MetadataEntry] | list[Unknown] | None` is not assignable to parameter `original_metadata` with type `list[Unknown]` in function `documents.caching.set_metadata_cache`",
"severity": "error"
},
{
"column": 57,
"path": "src/documents/views/documents.py",
"name": "bad-argument-type",
"concise_description": "Argument `DocumentClassifier | None` is not assignable to parameter `classifier` with type `DocumentClassifier` in function `documents.matching.match_correspondents`",
"severity": "error"
},
{
"column": 52,
"path": "src/documents/views/documents.py",
"name": "bad-argument-type",
"concise_description": "Argument `DocumentClassifier | None` is not assignable to parameter `classifier` with type `DocumentClassifier` in function `documents.matching.match_tags`",
"severity": "error"
},
{
"column": 59,
"path": "src/documents/views/documents.py",
"name": "bad-argument-type",
"concise_description": "Argument `DocumentClassifier | None` is not assignable to parameter `classifier` with type `DocumentClassifier` in function `documents.matching.match_document_types`",
"severity": "error"
},
{
"column": 58,
"path": "src/documents/views/documents.py",
"name": "bad-argument-type",
"concise_description": "Argument `DocumentClassifier | None` is not assignable to parameter `classifier` with type `DocumentClassifier` in function `documents.matching.match_storage_paths`",
"severity": "error"
},
{
"column": 17,
"path": "src/documents/views/documents.py",
"name": "bad-argument-type",
"concise_description": "Argument `ClassificationSuggestions` is not assignable to parameter `suggestions` with type `dict[Unknown, Unknown]` in function `documents.caching.set_llm_suggestions_cache`",
"severity": "error"
},
{
"column": 5,
"path": "src/documents/views/documents.py",
"name": "bad-specialization",
"concise_description": "`(self: Self@DocumentViewSet, request: Unknown, pk: Unknown | None = None) -> HttpResponseForbidden | Response | None` is not assignable to upper bound `(...) -> HttpResponseBase` of type variable `_View`",
"severity": "error"
},
{
"column": 36,
"path": "src/documents/views/documents.py",
"name": "missing-argument",
"concise_description": "Missing argument `request` in function `DocumentViewSet.email_documents`",
"severity": "error"
},
{
"column": 9,
"path": "src/documents/views/documents.py",
"name": "bad-override",
"concise_description": "Class member `UnifiedSearchViewSet.list` overrides parent class `DocumentViewSet` in an inconsistent manner",
"severity": "error"
},
{
"column": 17,
"path": "src/documents/views/documents.py",
"name": "missing-attribute",
"concise_description": "Object of type `BasePagination | None` has no attribute `get_page_size`",
"severity": "error"
},
{
"column": 58,
"path": "src/documents/views/documents.py",
"name": "missing-attribute",
"concise_description": "Object of type `BasePagination | None` has no attribute `page_size`",
"severity": "error"
},
{
"column": 13,
"path": "src/documents/views/documents.py",
"name": "not-iterable",
"concise_description": "Type `None` is not iterable",
"severity": "error"
},
{
"column": 43,
"path": "src/documents/views/documents.py",
"name": "bad-argument-type",
"concise_description": "Argument `TantivyRelevanceList` is not assignable to parameter `queryset` with type `QuerySet[Any, @_] | Sequence[@_]` in function `rest_framework.generics.GenericAPIView.paginate_queryset`",
"severity": "error"
},
{
"column": 25,
"path": "src/documents/views/documents.py",
"name": "unsupported-operation",
"concise_description": "`+` is not supported between `None` and `Literal[1]`",
"severity": "error"
},
{
"column": 31,
"path": "src/documents/views/index.py",
"name": "missing-attribute",
"concise_description": "Object of class `AbstractBaseUser` has no attribute `username`",
"severity": "error"
},
{
"column": 32,
"path": "src/documents/views/index.py",
"name": "missing-attribute",
"concise_description": "Object of type `AbstractBaseUser | AnonymousUser` has no attribute `get_full_name`",
"severity": "error"
},
{
"column": 16,
"path": "src/documents/views/index.py",
"name": "missing-attribute",
"concise_description": "Object of class `NoneType` has no attribute `app_logo`",
"severity": "error"
},
{
"column": 29,
"path": "src/documents/views/metadata.py",
"name": "bad-argument-type",
"concise_description": "Argument `Self@CorrespondentViewSet` is not assignable to parameter `instance` with type `Options[Any] | None` in function `django.db.models.manager.BaseManager.__get__`",
"severity": "error"
},
{
"column": 29,
"path": "src/documents/views/metadata.py",
"name": "missing-attribute",
"concise_description": "Object of class `NoneType` has no attribute `annotate`",
"severity": "error"
},
{
"column": 25,
"path": "src/documents/views/metadata.py",
"name": "bad-argument-type",
"concise_description": "Argument `Self@CorrespondentViewSet` is not assignable to parameter `instance` with type `Options[Any] | None` in function `django.db.models.manager.BaseManager.__get__`",
"severity": "error"
},
{
"column": 25,
"path": "src/documents/views/metadata.py",
"name": "missing-attribute",
"concise_description": "Object of class `NoneType` has no attribute `annotate`",
"severity": "error"
},
{
"column": 52,
"path": "src/documents/views/search.py",
"name": "missing-attribute",
"concise_description": "Object of class `Correspondent` has no attribute `document_count`",
"severity": "error"
},
{
"column": 52,
"path": "src/documents/views/search.py",
"name": "missing-attribute",
"concise_description": "Object of class `Tag` has no attribute `document_count`",
"severity": "error"
},
{
"column": 52,
"path": "src/documents/views/search.py",
"name": "missing-attribute",
"concise_description": "Object of class `DocumentType` has no attribute `document_count`",
"severity": "error"
},
{
"column": 52,
"path": "src/documents/views/search.py",
"name": "missing-attribute",
"concise_description": "Object of class `StoragePath` has no attribute `document_count`",
"severity": "error"
},
{
"column": 52,
"path": "src/documents/views/search.py",
"name": "missing-attribute",
"concise_description": "Object of class `CustomField` has no attribute `document_count`",
"severity": "error"
},
{
"column": 65,
"path": "src/documents/views/search.py",
"name": "bad-argument-type",
"concise_description": "Argument `AbstractBaseUser | AnonymousUser | None` is not assignable to parameter `user` with type `AbstractUser | None` in function `documents.search._backend.TantivyBackend.autocomplete`",
"severity": "error"
},
{
"column": 13,
"path": "src/documents/views/sharing.py",
"name": "missing-attribute",
"concise_description": "Object of class `Sequence` has no attribute `append`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 40, "column": 40,
"path": "src/documents/views.py", "path": "src/documents/views/system.py",
"name": "unbound-name", "name": "unbound-name",
"concise_description": "`all_migrations` may be uninitialized", "concise_description": "`all_migrations` may be uninitialized",
"severity": "error" "severity": "error"
}, },
{ {
"column": 17, "column": 17,
"path": "src/documents/views.py", "path": "src/documents/views/system.py",
"name": "bad-argument-type", "name": "bad-argument-type",
"concise_description": "Argument `AbstractBaseUser | AnonymousUser` is not assignable to parameter `user` with type `User | None` in function `documents.permissions.permitted_document_ids`", "concise_description": "Argument `AbstractBaseUser | AnonymousUser` is not assignable to parameter `user` with type `User | None` in function `documents.permissions.permitted_document_ids`",
"severity": "error" "severity": "error"
}, },
{ {
"column": 16, "column": 9,
"path": "src/documents/views.py", "path": "src/documents/views/tasks.py",
"name": "bad-override",
"concise_description": "Class member `TasksViewSet.get_serializer_class` overrides parent class `ReadOnlyModelViewSet` in an inconsistent manner",
"severity": "error"
},
{
"column": 12,
"path": "src/documents/views/tasks.py",
"name": "missing-attribute", "name": "missing-attribute",
"concise_description": "Object of class `NoneType` has no attribute `app_logo`", "concise_description": "Object of class `AbstractBaseUser` has no attribute `is_staff`",
"severity": "error"
},
{
"column": 44,
"path": "src/documents/views/tasks.py",
"name": "bad-argument-type",
"concise_description": "Argument `QuerySet[PaperlessTask, dict[str, Any]]` is not assignable to parameter `instance` with type `dict[str, Any] | None` in function `rest_framework.serializers.BaseSerializer.__init__`",
"severity": "error"
},
{
"column": 9,
"path": "src/documents/views/workflows.py",
"name": "bad-override",
"concise_description": "Class member `WorkflowTriggerViewSet.partial_update` overrides parent class `ModelViewSet` in an inconsistent manner",
"severity": "error"
},
{
"column": 9,
"path": "src/documents/views/workflows.py",
"name": "bad-override",
"concise_description": "Class member `WorkflowActionViewSet.partial_update` overrides parent class `ModelViewSet` in an inconsistent manner",
"severity": "error" "severity": "error"
}, },
{ {
+13 -4
View File
@@ -43,17 +43,26 @@ Once you have submitted a **P**ull **R**equest it will be reviewed, approved, an
Important: Pull requests that implement a new feature or enhancement _should almost always target an existing feature request_ with evidence of community interest and discussion. This is in order to balance the work of implementing and maintaining new features / enhancements. Instead of opening a PR which does not meet this requirement, please open a feature request instead, to gather feedback from both users and the project maintainers. Important: Pull requests that implement a new feature or enhancement _should almost always target an existing feature request_ with evidence of community interest and discussion. This is in order to balance the work of implementing and maintaining new features / enhancements. Instead of opening a PR which does not meet this requirement, please open a feature request instead, to gather feedback from both users and the project maintainers.
## PR Review Process ## Non-Trivial Requests
Our community review process for PRs is the following: PRs deemed `non-trivial` will go through a stricter review process before being merged into `dev`. This is to ensure code quality and complete functionality (free of side effects).
Examples of `non-trivial` PRs might include:
- Additional features
- Large changes to many distinct files
- Breaking or deprecation of existing features
Our community review process for `non-trivial` PRs is the following:
1. Must pass usual automated code tests and formatting checks. 1. Must pass usual automated code tests and formatting checks.
2. The PR will be assigned and pinged to the appropriately experienced team (i.e. @paperless-ngx/backend for backend changes). 2. The PR will be assigned and pinged to the appropriately experienced team (i.e. @paperless-ngx/backend for backend changes).
3. Development team will check and test code manually (possibly over several days). 3. Development team will check and test code manually (possibly over several days).
- You may be asked to make changes or rebase. - You may be asked to make changes or rebase.
4. One or more members of the team will approve and finally merge the request into `dev` 🎉. - The team may ask for additional testing done by @paperless-ngx/test
4. **At least two** members of the team will approve and finally merge the request into `dev` 🎉.
This process might be slow as community members have different schedules and time to dedicate to the Paperless project. However it ensures community code reviews are thorough and of high quality. This process might be slow as community members have different schedules and time to dedicate to the Paperless project. However it ensures community code reviews are as brilliantly thorough as they once were with @jonaswinkler.
# Use of AI Tools # Use of AI Tools
+8 -4
View File
@@ -21,7 +21,7 @@ Paperless-ngx is a document management system that transforms your physical docu
Paperless-ngx is the official successor to the original [Paperless](https://github.com/the-paperless-project/paperless) & [Paperless-ng](https://github.com/jonaswinkler/paperless-ng) projects and is designed to distribute the responsibility of advancing and supporting the project among a team of people. [Consider joining us!](#community-support) Paperless-ngx is the official successor to the original [Paperless](https://github.com/the-paperless-project/paperless) & [Paperless-ng](https://github.com/jonaswinkler/paperless-ng) projects and is designed to distribute the responsibility of advancing and supporting the project among a team of people. [Consider joining us!](#community-support)
Thanks to the generous folks at [Fyra Stack](https://fyrastack.com), a demo is available at [demo.paperless-ngx.com](https://demo.paperless-ngx.com) using login `demo` / `demo`. _Note: demo content is reset frequently and confidential information should not be uploaded._ Thanks to the generous folks at [DigitalOcean](https://m.do.co/c/8d70b916d462), a demo is available at [demo.paperless-ngx.com](https://demo.paperless-ngx.com) using login `demo` / `demo`. _Note: demo content is reset frequently and confidential information should not be uploaded._
- [Features](#features) - [Features](#features)
- [Getting started](#getting-started) - [Getting started](#getting-started)
@@ -33,9 +33,13 @@ Thanks to the generous folks at [Fyra Stack](https://fyrastack.com), a demo is a
- [Related Projects](#related-projects) - [Related Projects](#related-projects)
- [Important Note](#important-note) - [Important Note](#important-note)
<p align="right">Demo hosting provided by:<br/> <p align="right">This project is supported by:<br/>
<a href="https://fyrastack.com"> <a href="https://m.do.co/c/8d70b916d462" style="padding-top: 4px; display: block;">
<img src="docs/assets/sponsors/fyra-stack.svg" alt="" width="28px" align="absmiddle"> Fyra Stack <picture>
<source media="(prefers-color-scheme: dark)" srcset="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_white.svg" width="140px">
<source media="(prefers-color-scheme: light)" srcset="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="140px">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_black_.svg" width="140px">
</picture>
</a> </a>
</p> </p>
+1 -1
View File
@@ -14,7 +14,7 @@ services:
command: command:
- "gotenberg" - "gotenberg"
- "--chromium-disable-javascript=true" - "--chromium-disable-javascript=true"
- "--chromium-allow-list=^file:///tmp/.*" - "--chromium-allow-list=file:///tmp/.*"
- "--log-level=warn" - "--log-level=warn"
- "--log-format=text" - "--log-format=text"
tika: tika:
@@ -40,6 +40,7 @@ services:
volumes: volumes:
- dbdata:/var/lib/mysql - dbdata:/var/lib/mysql
environment: environment:
MARIADB_HOST: paperless
MARIADB_DATABASE: paperless MARIADB_DATABASE: paperless
MARIADB_USER: paperless MARIADB_USER: paperless
MARIADB_PASSWORD: paperless MARIADB_PASSWORD: paperless
@@ -78,7 +79,7 @@ services:
command: command:
- "gotenberg" - "gotenberg"
- "--chromium-disable-javascript=true" - "--chromium-disable-javascript=true"
- "--chromium-allow-list=^file:///tmp/.*" - "--chromium-allow-list=file:///tmp/.*"
tika: tika:
image: docker.io/apache/tika:3.3.1.0 image: docker.io/apache/tika:3.3.1.0
restart: unless-stopped restart: unless-stopped
@@ -36,6 +36,7 @@ services:
volumes: volumes:
- dbdata:/var/lib/mysql - dbdata:/var/lib/mysql
environment: environment:
MARIADB_HOST: paperless
MARIADB_DATABASE: paperless MARIADB_DATABASE: paperless
MARIADB_USER: paperless MARIADB_USER: paperless
MARIADB_PASSWORD: paperless MARIADB_PASSWORD: paperless
@@ -74,7 +74,7 @@ services:
command: command:
- "gotenberg" - "gotenberg"
- "--chromium-disable-javascript=true" - "--chromium-disable-javascript=true"
- "--chromium-allow-list=^file:///tmp/.*" - "--chromium-allow-list=file:///tmp/.*"
tika: tika:
image: docker.io/apache/tika:3.3.1.0 image: docker.io/apache/tika:3.3.1.0
restart: unless-stopped restart: unless-stopped
@@ -63,7 +63,7 @@ services:
command: command:
- "gotenberg" - "gotenberg"
- "--chromium-disable-javascript=true" - "--chromium-disable-javascript=true"
- "--chromium-allow-list=^file:///tmp/.*" - "--chromium-allow-list=file:///tmp/.*"
tika: tika:
image: docker.io/apache/tika:3.3.1.0 image: docker.io/apache/tika:3.3.1.0
restart: unless-stopped restart: unless-stopped
-13
View File
@@ -1010,19 +1010,6 @@ documents to both separate and categorize them in a single operation.
**Example:** A 6-page scan with TAG:invoice on page 3 and TAG:receipt on page 5 will create **Example:** A 6-page scan with TAG:invoice on page 3 and TAG:receipt on page 5 will create
three documents: pages 1-2 (no tags), pages 3-4 (tagged "invoice"), and pages 5-6 (tagged "receipt"). three documents: pages 1-2 (no tags), pages 3-4 (tagged "invoice"), and pages 5-6 (tagged "receipt").
### Barcode Contents {#barcode-contents}
By default, Paperless only uses barcodes for splitting, ASNs and tags. With
[`PAPERLESS_CONSUMER_STORE_BARCODE_VALUES`](configuration.md#PAPERLESS_CONSUMER_STORE_BARCODE_VALUES)
enabled, it stores the content of every barcode with the document, e.g. payment codes or QR codes.
- Barcodes are listed on the **Metadata** tab with page, type and content, and can be copied.
- The API returns them in the `barcodes` field of `/api/documents/{id}/metadata/`.
- They can be [searched](usage.md#searching-barcodes), e.g. `barcodes:DE89370400440532013000`.
- Only the first [`PAPERLESS_CONSUMER_BARCODE_MAX_PAGES`](configuration.md#PAPERLESS_CONSUMER_BARCODE_MAX_PAGES)
pages are scanned. Reprocessing reads the barcodes of existing documents.
- Each version keeps its own barcodes, and the newest version's are shown and searched.
## Automatic collation of double-sided documents {#collate} ## Automatic collation of double-sided documents {#collate}
!!! note !!! note
-29
View File
@@ -101,32 +101,3 @@ h4 code {
border-top: .05rem solid var(--md-default-fg-color--lightest); border-top: .05rem solid var(--md-default-fg-color--lightest);
} }
} }
.md-typeset .demo-sponsor {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: .35rem;
margin-top: -1rem;
margin-bottom: 2rem;
color: var(--md-default-fg-color--light);
font-size: .65rem;
}
.md-typeset .demo-sponsor a {
display: inline-flex;
align-items: center;
gap: .25rem;
color: inherit;
font-weight: 600;
}
.md-typeset .demo-sponsor a:hover {
color: var(--md-accent-fg-color);
}
.md-typeset .demo-sponsor img {
width: 1rem;
height: 1rem;
flex-shrink: 0;
}
-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="666.667" height="666.667" viewBox="0 0 666.667 666.667"><g stroke-width="1.333"><path fill="#fef400" d="m332.66668 418.66668h-84.66667v-85.33334-85.33333h84.66667 84.66667v-85.33334-85.333334h85.33333 85.33334v85.333334 85.33334h-85.33334-85.33333v85.33333 85.33334z"/><path fill="#fe6565" d="m248.00001 589.33335h-170.666674v-170.66667-170.66667h85.333334 85.33334v-85.33334-85.333334h84.66667 84.66667v85.333334 85.33334h85.33333 85.33334v85.33333 85.33334h-84.66667-84.66667v85.33334 85.33333zm84.66667-170.66667h84.66667v-85.33334-85.33333h-84.66667-84.66667v85.33333 85.33334z"/></g></svg>

Before

Width:  |  Height:  |  Size: 641 B

-174
View File
@@ -1,179 +1,5 @@
# Changelog # Changelog
## paperless-ngx 3.3.1
### Bug Fixes
- Fix: use a root document's effective content for the LLM index and pr… [@stumpylog](https://github.com/stumpylog) ([#14423](https://github.com/paperless-ngx/paperless-ngx/pull/14423))
- Fix: index document versions as their root document [@stumpylog](https://github.com/stumpylog) ([#14422](https://github.com/paperless-ngx/paperless-ngx/pull/14422))
- Fix: authorize document versions by their root in the single-object permission check [@stumpylog](https://github.com/stumpylog) ([#14421](https://github.com/paperless-ngx/paperless-ngx/pull/14421))
- Fix: Provide better error messages to users for invalid JSON [@stumpylog](https://github.com/stumpylog) ([#14419](https://github.com/paperless-ngx/paperless-ngx/pull/14419))
- Fix: authorize document versions by their root and speed up permission id sets [@stumpylog](https://github.com/stumpylog) ([#14396](https://github.com/paperless-ngx/paperless-ngx/pull/14396))
- Chore: update the db options warning [@shamoon](https://github.com/shamoon) ([#14398](https://github.com/paperless-ngx/paperless-ngx/pull/14398))
- Fix: bulk reprocess latest version for root documents [@shamoon](https://github.com/shamoon) ([#14385](https://github.com/paperless-ngx/paperless-ngx/pull/14385))
- Fix: consistently use root doc for version action permissions [@shamoon](https://github.com/shamoon) ([#14384](https://github.com/paperless-ngx/paperless-ngx/pull/14384))
- Fix: Don't attempt to index created dates which are not representable in the FTS index [@stumpylog](https://github.com/stumpylog) ([#14390](https://github.com/paperless-ngx/paperless-ngx/pull/14390))
- Fix: retry a search index rebuild that was interrupted [@stumpylog](https://github.com/stumpylog) ([#14379](https://github.com/paperless-ngx/paperless-ngx/pull/14379))
### Documentation
- Chore: silence gotenberg startup warning [@slankes](https://github.com/slankes) ([#14405](https://github.com/paperless-ngx/paperless-ngx/pull/14405))
- Documentation: remove MARIADB\_HOST from MariaDB compose files [@shamoon](https://github.com/shamoon) ([#14378](https://github.com/paperless-ngx/paperless-ngx/pull/14378))
- Documentation: Updates the troubleshooting with 2 common database collation issues [@stumpylog](https://github.com/stumpylog) ([#14370](https://github.com/paperless-ngx/paperless-ngx/pull/14370))
### Maintenance
- Chore: Upgrade runners to Ubuntu 26.04 [@stumpylog](https://github.com/stumpylog) ([#14347](https://github.com/paperless-ngx/paperless-ngx/pull/14347))
### Dependencies
- Chore(deps): Bump the npm\_and\_yarn group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14417](https://github.com/paperless-ngx/paperless-ngx/pull/14417))
### All App Changes
<details>
<summary>12 changes</summary>
- Chore: share the authorizing owner rule between the id sets and the trash filter [@stumpylog](https://github.com/stumpylog) ([#14425](https://github.com/paperless-ngx/paperless-ngx/pull/14425))
- Fix: use a root document's effective content for the LLM index and pr… [@stumpylog](https://github.com/stumpylog) ([#14423](https://github.com/paperless-ngx/paperless-ngx/pull/14423))
- Fix: index document versions as their root document [@stumpylog](https://github.com/stumpylog) ([#14422](https://github.com/paperless-ngx/paperless-ngx/pull/14422))
- Fix: authorize document versions by their root in the single-object permission check [@stumpylog](https://github.com/stumpylog) ([#14421](https://github.com/paperless-ngx/paperless-ngx/pull/14421))
- Fix: Provide better error messages to users for invalid JSON [@stumpylog](https://github.com/stumpylog) ([#14419](https://github.com/paperless-ngx/paperless-ngx/pull/14419))
- Chore(deps): Bump the npm\_and\_yarn group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14417](https://github.com/paperless-ngx/paperless-ngx/pull/14417))
- Fix: authorize document versions by their root and speed up permission id sets [@stumpylog](https://github.com/stumpylog) ([#14396](https://github.com/paperless-ngx/paperless-ngx/pull/14396))
- Chore: update the db options warning [@shamoon](https://github.com/shamoon) ([#14398](https://github.com/paperless-ngx/paperless-ngx/pull/14398))
- Fix: bulk reprocess latest version for root documents [@shamoon](https://github.com/shamoon) ([#14385](https://github.com/paperless-ngx/paperless-ngx/pull/14385))
- Fix: consistently use root doc for version action permissions [@shamoon](https://github.com/shamoon) ([#14384](https://github.com/paperless-ngx/paperless-ngx/pull/14384))
- Fix: Don't attempt to index created dates which are not representable in the FTS index [@stumpylog](https://github.com/stumpylog) ([#14390](https://github.com/paperless-ngx/paperless-ngx/pull/14390))
- Fix: retry a search index rebuild that was interrupted [@stumpylog](https://github.com/stumpylog) ([#14379](https://github.com/paperless-ngx/paperless-ngx/pull/14379))
</details>
## paperless-ngx 3.3.0
### Features / Enhancements
- Enhancement: more control over suggestion requests [@shamoon](https://github.com/shamoon) ([#14258](https://github.com/paperless-ngx/paperless-ngx/pull/14258))
- Chorehancement: set manifest CORS for credentials [@shamoon](https://github.com/shamoon) ([#14307](https://github.com/paperless-ngx/paperless-ngx/pull/14307))
- Enhancement: include Django admin with 2FA [@shamoon](https://github.com/shamoon) ([#14270](https://github.com/paperless-ngx/paperless-ngx/pull/14270))
- Feature: propagate resolved secrets to interactive container shells [@stumpylog](https://github.com/stumpylog) ([#14254](https://github.com/paperless-ngx/paperless-ngx/pull/14254))
- Enhancement: support separate embedding API key [@furkanural](https://github.com/furkanural) ([#14067](https://github.com/paperless-ngx/paperless-ngx/pull/14067))
- Enhancement: support passthrough extra params for LLMs [@shamoon](https://github.com/shamoon) ([#14202](https://github.com/paperless-ngx/paperless-ngx/pull/14202))
- Feature: store barcode contents, list and search them [@jurassicparkicecream](https://github.com/jurassicparkicecream) ([#14276](https://github.com/paperless-ngx/paperless-ngx/pull/14276))
### Bug Fixes
- Fix: Set the ProcessedMail owner based on the rule owner in all cases [@stumpylog](https://github.com/stumpylog) ([#14356](https://github.com/paperless-ngx/paperless-ngx/pull/14356))
- Fix: Ensure log rotation settings are converted to integers [@stumpylog](https://github.com/stumpylog) ([#14343](https://github.com/paperless-ngx/paperless-ngx/pull/14343))
- Fix: Wrap apt calls into a retry so we can ideally jump a slow mirror [@stumpylog](https://github.com/stumpylog) ([#14344](https://github.com/paperless-ngx/paperless-ngx/pull/14344))
- Fix: ship pdf.js CMaps so CJK documents render in the viewer [@MrOggy85](https://github.com/MrOggy85) ([#14318](https://github.com/paperless-ngx/paperless-ngx/pull/14318))
- Fix: use version page\_count for versioned document [@shamoon](https://github.com/shamoon) ([#14280](https://github.com/paperless-ngx/paperless-ngx/pull/14280))
- Fix: allow pointer events for pdf links in pngx viewer [@shamoon](https://github.com/shamoon) ([#14264](https://github.com/paperless-ngx/paperless-ngx/pull/14264))
- Fix: During a move to the trash directory, only attempt to copy metadata [@stumpylog](https://github.com/stumpylog) ([#14250](https://github.com/paperless-ngx/paperless-ngx/pull/14250))
- Fix: convert file mtime to the configured time zone directly [@stumpylog](https://github.com/stumpylog) ([#14249](https://github.com/paperless-ngx/paperless-ngx/pull/14249))
- Fix: ensure documentDeleted subscription is discarded [@shamoon](https://github.com/shamoon) ([#14247](https://github.com/paperless-ngx/paperless-ngx/pull/14247))
- Chore: Fix bugs in the test suite [@stumpylog](https://github.com/stumpylog) ([#14244](https://github.com/paperless-ngx/paperless-ngx/pull/14244))
- Fix: ensure bulk operations are checked against version root [@shamoon](https://github.com/shamoon) ([#14246](https://github.com/paperless-ngx/paperless-ngx/pull/14246))
- Fix: indexing after document-added workflows signal [@shamoon](https://github.com/shamoon) ([#14242](https://github.com/paperless-ngx/paperless-ngx/pull/14242))
- Fix: Record full tag and custom field lists in bulk edit audit log [@stumpylog](https://github.com/stumpylog) ([#14236](https://github.com/paperless-ngx/paperless-ngx/pull/14236))
- Chore: update pikepdf for ocrmypdf requirement [@shamoon](https://github.com/shamoon) ([#14235](https://github.com/paperless-ngx/paperless-ngx/pull/14235))
- Fix: handle legacy bulk edit split page range with missing page\_count [@shamoon](https://github.com/shamoon) ([#14212](https://github.com/paperless-ngx/paperless-ngx/pull/14212))
- Fix: ignore invalid EXIF orientation when generating image archives [@zhzy0077](https://github.com/zhzy0077) ([#14203](https://github.com/paperless-ngx/paperless-ngx/pull/14203))
### Documentation
- Documentation: correct duplicates info [@shamoon](https://github.com/shamoon) ([#14243](https://github.com/paperless-ngx/paperless-ngx/pull/14243))
### Maintenance
- Chore(deps): Bump the actions group across 1 directory with 4 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14332](https://github.com/paperless-ngx/paperless-ngx/pull/14332))
- Fix: Wrap apt calls into a retry so we can ideally jump a slow mirror [@stumpylog](https://github.com/stumpylog) ([#14344](https://github.com/paperless-ngx/paperless-ngx/pull/14344))
- Chore(deps): Bump the actions group across 1 directory with 10 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14301](https://github.com/paperless-ngx/paperless-ngx/pull/14301))
### Dependencies
<details>
<summary>27 changes</summary>
- Chore(deps): Bump django-filter from 25.2 to 26.1 @[dependabot[bot]](https://github.com/apps/dependabot) ([#14337](https://github.com/paperless-ngx/paperless-ngx/pull/14337))
- Chore(deps): Bump the utilities-patch group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14338](https://github.com/paperless-ngx/paperless-ngx/pull/14338))
- Chore(deps): Bump the pre-commit-dependencies group across 1 directory with 3 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14351](https://github.com/paperless-ngx/paperless-ngx/pull/14351))
- Chore(deps-dev): Bump types-channels from 4.3.0.20260408 to 4.3.0.20260518 @[dependabot[bot]](https://github.com/apps/dependabot) ([#14335](https://github.com/paperless-ngx/paperless-ngx/pull/14335))
- docker(deps): Bump astral-sh/uv from 0.12.20-python3.14-trixie-slim to 0.12.23-python3.14-trixie-slim @[dependabot[bot]](https://github.com/apps/dependabot) ([#14327](https://github.com/paperless-ngx/paperless-ngx/pull/14327))
- Chore(deps): Bump the actions group across 1 directory with 4 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14332](https://github.com/paperless-ngx/paperless-ngx/pull/14332))
- Chore(deps): Bump the uv group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14325](https://github.com/paperless-ngx/paperless-ngx/pull/14325))
- Chore(deps): Bump the frontend-angular-dependencies group across 1 directory with 13 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14329](https://github.com/paperless-ngx/paperless-ngx/pull/14329))
- Chore(deps-dev): Bump prettier from 3.9.8 to 3.9.9 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#14331](https://github.com/paperless-ngx/paperless-ngx/pull/14331))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group across 1 directory with 3 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14330](https://github.com/paperless-ngx/paperless-ngx/pull/14330))
- Chore(deps-dev): Bump zensical from 0.0.64 to 0.0.65 in the development group @[dependabot[bot]](https://github.com/apps/dependabot) ([#14326](https://github.com/paperless-ngx/paperless-ngx/pull/14326))
- Chore(deps): Bump the uv group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14314](https://github.com/paperless-ngx/paperless-ngx/pull/14314))
- Chore(deps): Bump the utilities-minor group across 1 directory with 7 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14305](https://github.com/paperless-ngx/paperless-ngx/pull/14305))
- docker-compose(deps): bump greenmail/standalone from 2.1.13 to 2.1.14 in /docker/compose @[dependabot[bot]](https://github.com/apps/dependabot) ([#14281](https://github.com/paperless-ngx/paperless-ngx/pull/14281))
- docker(deps): Bump astral-sh/uv from 0.12.16-python3.14-trixie-slim to 0.12.20-python3.14-trixie-slim @[dependabot[bot]](https://github.com/apps/dependabot) ([#14282](https://github.com/paperless-ngx/paperless-ngx/pull/14282))
- Chore(deps): Bump the pre-commit-dependencies group across 1 directory with 3 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14283](https://github.com/paperless-ngx/paperless-ngx/pull/14283))
- Chore(deps): Bump the utilities-patch group across 1 directory with 6 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14297](https://github.com/paperless-ngx/paperless-ngx/pull/14297))
- Chore(deps): Bump the actions group across 1 directory with 10 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14301](https://github.com/paperless-ngx/paperless-ngx/pull/14301))
- Chore(deps-dev): Bump the frontend-jest-dependencies group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14286](https://github.com/paperless-ngx/paperless-ngx/pull/14286))
- Chore(deps-dev): Bump eslint from 10.10.0 to 10.11.0 in /src-ui in the frontend-eslint-dependencies group across 1 directory @[dependabot[bot]](https://github.com/apps/dependabot) ([#14287](https://github.com/paperless-ngx/paperless-ngx/pull/14287))
- Chore(deps-dev): Bump @types/node from 26.5.0 to 26.6.2 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#14288](https://github.com/paperless-ngx/paperless-ngx/pull/14288))
- Chore(deps-dev): Bump prettier from 3.9.6 to 3.9.8 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#14289](https://github.com/paperless-ngx/paperless-ngx/pull/14289))
- Chore(deps): Bump the frontend-angular-dependencies group across 1 directory with 10 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14285](https://github.com/paperless-ngx/paperless-ngx/pull/14285))
- Chore: replace bleach with turbohtml [@gaborbernat](https://github.com/gaborbernat) ([#14269](https://github.com/paperless-ngx/paperless-ngx/pull/14269))
- Chore(deps): Bump autobahn from 25.12.2 to 26.7.1 in the uv group across 1 directory @[dependabot[bot]](https://github.com/apps/dependabot) ([#14231](https://github.com/paperless-ngx/paperless-ngx/pull/14231))
- Chore: update pikepdf for ocrmypdf requirement [@shamoon](https://github.com/shamoon) ([#14235](https://github.com/paperless-ngx/paperless-ngx/pull/14235))
- Chore(deps): Bump the pre-commit-dependencies group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14133](https://github.com/paperless-ngx/paperless-ngx/pull/14133))
</details>
### All App Changes
<details>
<summary>41 changes</summary>
- Feature: store barcode contents, list and search them [@jurassicparkicecream](https://github.com/jurassicparkicecream) ([#14276](https://github.com/paperless-ngx/paperless-ngx/pull/14276))
- Fix: Set the ProcessedMail owner based on the rule owner in all cases [@stumpylog](https://github.com/stumpylog) ([#14356](https://github.com/paperless-ngx/paperless-ngx/pull/14356))
- Chore(deps): Bump django-filter from 25.2 to 26.1 @[dependabot[bot]](https://github.com/apps/dependabot) ([#14337](https://github.com/paperless-ngx/paperless-ngx/pull/14337))
- Chore(deps): Bump the utilities-patch group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14338](https://github.com/paperless-ngx/paperless-ngx/pull/14338))
- Chore(deps-dev): Bump types-channels from 4.3.0.20260408 to 4.3.0.20260518 @[dependabot[bot]](https://github.com/apps/dependabot) ([#14335](https://github.com/paperless-ngx/paperless-ngx/pull/14335))
- Chore(deps): Bump the uv group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14325](https://github.com/paperless-ngx/paperless-ngx/pull/14325))
- Fix: Ensure log rotation settings are converted to integers [@stumpylog](https://github.com/stumpylog) ([#14343](https://github.com/paperless-ngx/paperless-ngx/pull/14343))
- Chore(deps): Bump the frontend-angular-dependencies group across 1 directory with 13 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14329](https://github.com/paperless-ngx/paperless-ngx/pull/14329))
- Chore(deps-dev): Bump prettier from 3.9.8 to 3.9.9 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#14331](https://github.com/paperless-ngx/paperless-ngx/pull/14331))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group across 1 directory with 3 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14330](https://github.com/paperless-ngx/paperless-ngx/pull/14330))
- Fix: ship pdf.js CMaps so CJK documents render in the viewer [@MrOggy85](https://github.com/MrOggy85) ([#14318](https://github.com/paperless-ngx/paperless-ngx/pull/14318))
- Chore(deps-dev): Bump zensical from 0.0.64 to 0.0.65 in the development group @[dependabot[bot]](https://github.com/apps/dependabot) ([#14326](https://github.com/paperless-ngx/paperless-ngx/pull/14326))
- Enhancement: more control over suggestion requests [@shamoon](https://github.com/shamoon) ([#14258](https://github.com/paperless-ngx/paperless-ngx/pull/14258))
- Chore(deps): Bump the uv group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14314](https://github.com/paperless-ngx/paperless-ngx/pull/14314))
- Chore: anchor admin url pattern [@shamoon](https://github.com/shamoon) ([#14316](https://github.com/paperless-ngx/paperless-ngx/pull/14316))
- Chorehancement: set manifest CORS for credentials [@shamoon](https://github.com/shamoon) ([#14307](https://github.com/paperless-ngx/paperless-ngx/pull/14307))
- Chore(deps): Bump the utilities-minor group across 1 directory with 7 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14305](https://github.com/paperless-ngx/paperless-ngx/pull/14305))
- Chore(deps): Bump the utilities-patch group across 1 directory with 6 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14297](https://github.com/paperless-ngx/paperless-ngx/pull/14297))
- Chore(deps-dev): Bump the frontend-jest-dependencies group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14286](https://github.com/paperless-ngx/paperless-ngx/pull/14286))
- Chore(deps-dev): Bump eslint from 10.10.0 to 10.11.0 in /src-ui in the frontend-eslint-dependencies group across 1 directory @[dependabot[bot]](https://github.com/apps/dependabot) ([#14287](https://github.com/paperless-ngx/paperless-ngx/pull/14287))
- Chore(deps-dev): Bump @types/node from 26.5.0 to 26.6.2 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#14288](https://github.com/paperless-ngx/paperless-ngx/pull/14288))
- Chore(deps-dev): Bump prettier from 3.9.6 to 3.9.8 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#14289](https://github.com/paperless-ngx/paperless-ngx/pull/14289))
- Chore(deps): Bump the frontend-angular-dependencies group across 1 directory with 10 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#14285](https://github.com/paperless-ngx/paperless-ngx/pull/14285))
- Fix: use version page\_count for versioned document [@shamoon](https://github.com/shamoon) ([#14280](https://github.com/paperless-ngx/paperless-ngx/pull/14280))
- Chore: replace bleach with turbohtml [@gaborbernat](https://github.com/gaborbernat) ([#14269](https://github.com/paperless-ngx/paperless-ngx/pull/14269))
- Enhancement: include Django admin with 2FA [@shamoon](https://github.com/shamoon) ([#14270](https://github.com/paperless-ngx/paperless-ngx/pull/14270))
- Fix: allow pointer events for pdf links in pngx viewer [@shamoon](https://github.com/shamoon) ([#14264](https://github.com/paperless-ngx/paperless-ngx/pull/14264))
- Feature: propagate resolved secrets to interactive container shells [@stumpylog](https://github.com/stumpylog) ([#14254](https://github.com/paperless-ngx/paperless-ngx/pull/14254))
- Fix: During a move to the trash directory, only attempt to copy metadata [@stumpylog](https://github.com/stumpylog) ([#14250](https://github.com/paperless-ngx/paperless-ngx/pull/14250))
- Fix: convert file mtime to the configured time zone directly [@stumpylog](https://github.com/stumpylog) ([#14249](https://github.com/paperless-ngx/paperless-ngx/pull/14249))
- Fix: ensure documentDeleted subscription is discarded [@shamoon](https://github.com/shamoon) ([#14247](https://github.com/paperless-ngx/paperless-ngx/pull/14247))
- Chore: Fix bugs in the test suite [@stumpylog](https://github.com/stumpylog) ([#14244](https://github.com/paperless-ngx/paperless-ngx/pull/14244))
- Fix: ensure bulk operations are checked against version root [@shamoon](https://github.com/shamoon) ([#14246](https://github.com/paperless-ngx/paperless-ngx/pull/14246))
- Enhancement: support separate embedding API key [@furkanural](https://github.com/furkanural) ([#14067](https://github.com/paperless-ngx/paperless-ngx/pull/14067))
- Enhancement: support passthrough extra params for LLMs [@shamoon](https://github.com/shamoon) ([#14202](https://github.com/paperless-ngx/paperless-ngx/pull/14202))
- Chore(deps): Bump autobahn from 25.12.2 to 26.7.1 in the uv group across 1 directory @[dependabot[bot]](https://github.com/apps/dependabot) ([#14231](https://github.com/paperless-ngx/paperless-ngx/pull/14231))
- Fix: indexing after document-added workflows signal [@shamoon](https://github.com/shamoon) ([#14242](https://github.com/paperless-ngx/paperless-ngx/pull/14242))
- Fix: Record full tag and custom field lists in bulk edit audit log [@stumpylog](https://github.com/stumpylog) ([#14236](https://github.com/paperless-ngx/paperless-ngx/pull/14236))
- Chore: update pikepdf for ocrmypdf requirement [@shamoon](https://github.com/shamoon) ([#14235](https://github.com/paperless-ngx/paperless-ngx/pull/14235))
- Fix: handle legacy bulk edit split page range with missing page\_count [@shamoon](https://github.com/shamoon) ([#14212](https://github.com/paperless-ngx/paperless-ngx/pull/14212))
- Fix: ignore invalid EXIF orientation when generating image archives [@zhzy0077](https://github.com/zhzy0077) ([#14203](https://github.com/paperless-ngx/paperless-ngx/pull/14203))
</details>
## paperless-ngx 3.2.1 ## paperless-ngx 3.2.1
### Bug Fixes ### Bug Fixes
-7
View File
@@ -1796,13 +1796,6 @@ assigns or creates tags if a properly formatted barcode is detected.
Defaults to false. Defaults to false.
#### [`PAPERLESS_CONSUMER_STORE_BARCODE_VALUES=<bool>`](#PAPERLESS_CONSUMER_STORE_BARCODE_VALUES) {#PAPERLESS_CONSUMER_STORE_BARCODE_VALUES}
: Stores the content of every barcode found during consumption, see
[Barcode Contents](advanced_usage.md#barcode-contents).
Defaults to false.
## Audit Trail ## Audit Trail
#### [`PAPERLESS_AUDIT_LOG_ENABLED=<bool>`](#PAPERLESS_AUDIT_LOG_ENABLED) {#PAPERLESS_AUDIT_LOG_ENABLED} #### [`PAPERLESS_AUDIT_LOG_ENABLED=<bool>`](#PAPERLESS_AUDIT_LOG_ENABLED) {#PAPERLESS_AUDIT_LOG_ENABLED}
+1 -3
View File
@@ -76,9 +76,7 @@ is not supported by any of the available parsers.
**A:** Not by default. As of v3, a file whose contents match an existing document is still **A:** Not by default. As of v3, a file whose contents match an existing document is still
consumed, and the duplicate is flagged in the UI — open the document and check the consumed, and the duplicate is flagged in the UI — open the document and check the
**Duplicates** tab to review documents that share the same content, or filter the document **Duplicates** tab to review documents that share the same content. If you prefer the old
list by **Duplicates** to find all of them (see
[Duplicate documents](usage.md#duplicate-documents)). If you prefer the old
behavior of rejecting duplicates during consumption, set behavior of rejecting duplicates during consumption, set
[`PAPERLESS_CONSUMER_DELETE_DUPLICATES`](configuration.md#PAPERLESS_CONSUMER_DELETE_DUPLICATES) [`PAPERLESS_CONSUMER_DELETE_DUPLICATES`](configuration.md#PAPERLESS_CONSUMER_DELETE_DUPLICATES)
to `true`. to `true`.
+4 -4
View File
@@ -12,10 +12,10 @@ physical documents into a searchable online archive so you can keep, well, _less
[Get started](setup.md){ .md-button .md-button--primary .index-callout } [Get started](setup.md){ .md-button .md-button--primary .index-callout }
[Demo](https://demo.paperless-ngx.com){ .md-button .md-button--secondary target=\_blank } [Demo](https://demo.paperless-ngx.com){ .md-button .md-button--secondary target=\_blank }
<div class="demo-sponsor"> <div style="display: flex; justify-content: end; margin-top: -1.5rem;">
<span>Demo hosting by</span> <a href="https://m.do.co/c/8d70b916d462" target="_blank">
<a href="https://fyrastack.com" target="_blank" rel="noopener"> <img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_white.svg#only-dark" class="no-lightbox" width="150px">
<img src="assets/sponsors/fyra-stack.svg" alt="" class="no-lightbox" width="20" height="20"> Fyra Stack <img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_black.svg#only-light" class="no-lightbox" width="150px">
</a> </a>
</div> </div>
+1 -60
View File
@@ -129,7 +129,7 @@ change in the `docker-compose.yml` file:
command: command:
- 'gotenberg' - 'gotenberg'
- '--chromium-disable-javascript=true' - '--chromium-disable-javascript=true'
- '--chromium-allow-list=^file:///tmp/.*' - '--chromium-allow-list=file:///tmp/.*'
- '--api-timeout=60s' - '--api-timeout=60s'
``` ```
@@ -272,65 +272,6 @@ This error can occur in installations which have upgraded from a version of Pape
$ python3 manage.py convert_mariadb_uuid $ python3 manage.py convert_mariadb_uuid
``` ```
## MariaDB/MySQL error "Illegal mix of collations"
Consumption or other operations fail with an error like:
```
(1267, "Illegal mix of collations (utf8mb4_general_ci,IMPLICIT) and
(utf8mb4_unicode_ci,IMPLICIT) for operation '='")
```
This happens when the tables in your database do not all use the same
collation. It is most often seen on databases that existed before a
MariaDB/MySQL upgrade: older tables keep their original collation, while
tables created afterwards use the new server default.
To work around it, set the collation your existing tables use (the one in the error
that is not `utf8mb4_unicode_ci`) with
[`PAPERLESS_DB_OPTIONS`](configuration.md#PAPERLESS_DB_OPTIONS):
```bash
PAPERLESS_DB_OPTIONS="collation=utf8mb4_general_ci"
```
To fix it permanently, back up your database, then convert the database and
each table to a single collation and remove the override:
```sql
ALTER DATABASE paperless CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE <table_name> CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
## PostgreSQL warns about a "collation version mismatch"
The PostgreSQL log shows a warning like:
```
WARNING: database "paperless" has a collation version mismatch
DETAIL: The database was created using collation version 2.36, but the operating system provides version 2.41.
HINT: Rebuild all objects in this database that use the default collation and run ALTER DATABASE paperless REFRESH COLLATION VERSION, or build PostgreSQL with the right library version.
```
This comes from PostgreSQL, not Paperless-ngx. The `glibc` version that PostgreSQL
runs against changed, and the existing database was created with an older one. With
Docker, `glibc` comes from the PostgreSQL image's Debian base, not the host, so this
commonly happens when a new image is pulled after its base Debian release changed.
If PostgreSQL is installed directly on a host, it uses the host's `glibc` instead.
The warning is not an error and Paperless-ngx keeps working, but the database's text
indexes may be built with outdated sorting rules. To resolve it, back up your
database, then connect to it (for example with `psql -U paperless -d paperless`
inside the database container) and run:
```sql
REINDEX DATABASE paperless;
ALTER DATABASE paperless REFRESH COLLATION VERSION;
```
To avoid this in the future, pin your PostgreSQL image to a specific Debian release,
for example `postgres:18-trixie`, rather than `postgres:18`.
## Platform-Specific Deployment Troubleshooting ## Platform-Specific Deployment Troubleshooting
A user-maintained wiki page is available to help troubleshoot issues that may arise when trying to deploy Paperless-ngx on specific platforms, for example SELinux. Please see [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Platform%E2%80%90Specific-Troubleshooting). A user-maintained wiki page is available to help troubleshoot issues that may arise when trying to deploy Paperless-ngx on specific platforms, for example SELinux. Please see [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Platform%E2%80%90Specific-Troubleshooting).
+8 -21
View File
@@ -299,18 +299,19 @@ for details.
### Duplicate documents ### Duplicate documents
By default, Paperless-ngx **does not reject duplicates**. If you consume a file whose By default, Paperless-ngx **does not reject duplicates**. If you consume a file whose
contents match an existing document (same original or archive checksum), the new copy is contents exactly match an existing document (same checksum), the new copy is still
still consumed and a warning is logged. consumed and a warning is logged. The task entry for the upload also flags that a
duplicate was detected and links to the existing document(s).
When a document has duplicates, a **Duplicates** tab appears on its detail page, listing To review duplicates, open a document and switch to the **Duplicates** tab on the
the other documents you can view that share the same content (including any in the trash). document detail page. It lists other documents that share the same content, including any
To find all documents with duplicates, choose **Duplicates** in the document list's text that are in the trash (shown with a badge), and links to each so you can decide which to
filter dropdown, or use `has_duplicates=true` in the REST API. keep.
If you would rather reject duplicates at consumption time (the pre-v3 behavior), set If you would rather reject duplicates at consumption time (the pre-v3 behavior), set
[`PAPERLESS_CONSUMER_DELETE_DUPLICATES`](configuration.md#PAPERLESS_CONSUMER_DELETE_DUPLICATES) [`PAPERLESS_CONSUMER_DELETE_DUPLICATES`](configuration.md#PAPERLESS_CONSUMER_DELETE_DUPLICATES)
to `true`. The duplicate file is then deleted instead of consumed, and the task fails with to `true`. The duplicate file is then deleted instead of consumed, and the task fails with
a "Document already exists" message linking to the existing document. a "document already exists" message.
## Document Suggestions ## Document Suggestions
@@ -1060,20 +1061,6 @@ notes.user:alice notes.note:insurance
The bare `notes:` prefix is shorthand for `notes.note:`. The bare `notes:` prefix is shorthand for `notes.note:`.
#### Searching barcodes
If [barcode contents are stored](advanced_usage.md#barcode-contents), they can be searched by
content or type, but only with a field name:
```
barcodes.value:DE89370400440532013000
barcodes.format:qrcode
barcodes:wifi barcodes:guest
```
`barcodes:` is shorthand for `barcodes.value:`. Separators are stripped, so each part of e.g.
`WIFI:S:Guest;P:secret;;` can be searched on its own.
All of these can be combined. Syntax not described here may not work as expected, and an unknown field name is searched as ordinary text. All of these can be combined. Syntax not described here may not work as expected, and an unknown field name is searched as ordinary text.
!!! note !!! note
+4 -4
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "paperless-ngx" name = "paperless-ngx"
version = "3.3.1" version = "3.2.1"
description = """\ description = """\
A community-supported supercharged document management system: scan, index and archive all your physical documents\ A community-supported supercharged document management system: scan, index and archive all your physical documents\
""" """
@@ -33,7 +33,7 @@ dependencies = [
"django-compression-middleware~=0.5.0", "django-compression-middleware~=0.5.0",
"django-cors-headers~=4.9.0", "django-cors-headers~=4.9.0",
"django-extensions~=4.1", "django-extensions~=4.1",
"django-filter>=25.1,<27", "django-filter~=25.1",
"django-guardian>=3.3.3,<3.6", "django-guardian>=3.3.3,<3.6",
"django-multiselectfield~=1.0.1", "django-multiselectfield~=1.0.1",
"django-rich~=2.2.0", "django-rich~=2.2.0",
@@ -43,7 +43,7 @@ dependencies = [
"drf-spectacular~=0.30", "drf-spectacular~=0.30",
"drf-spectacular-sidecar>=2026.7.1,<2026.10", "drf-spectacular-sidecar>=2026.7.1,<2026.10",
"drf-writable-nested~=0.7.1", "drf-writable-nested~=0.7.1",
"filelock>=3.32,<4.1", "filelock~=3.32.0",
"flower>=2.0.1,<2.3", "flower>=2.0.1,<2.3",
"gotenberg-client[httpx]~=1.0", "gotenberg-client[httpx]~=1.0",
"httpx-oauth~=0.17", "httpx-oauth~=0.17",
@@ -90,7 +90,7 @@ postgres = [
"psycopg[c,pool]==3.3.4", "psycopg[c,pool]==3.3.4",
# Direct dependency for proper resolution of the pre-built wheels # Direct dependency for proper resolution of the pre-built wheels
"psycopg-c==3.3.4", "psycopg-c==3.3.4",
"psycopg-pool==3.3.3", "psycopg-pool==3.3.2",
] ]
webserver = [ webserver = [
"granian[uvloop]>=2.7,<2.9", "granian[uvloop]>=2.7,<2.9",
+1 -1
View File
@@ -2,5 +2,5 @@
docker run -p 5432:5432 -e POSTGRES_PASSWORD=password -v paperless_pgdata:/var/lib/postgresql/data -d postgres:15 docker run -p 5432:5432 -e POSTGRES_PASSWORD=password -v paperless_pgdata:/var/lib/postgresql/data -d postgres:15
docker run -d -p 6379:6379 redis:latest docker run -d -p 6379:6379 redis:latest
docker run -p 3000:3000 -d gotenberg/gotenberg:7.8 gotenberg --chromium-disable-javascript=true --chromium-allow-list="^file:///tmp/.*" docker run -p 3000:3000 -d gotenberg/gotenberg:7.8 gotenberg --chromium-disable-javascript=true --chromium-allow-list="file:///tmp/.*"
docker run -p 9998:9998 -d docker.io/apache/tika:latest docker run -p 9998:9998 -d docker.io/apache/tika:latest
-2
View File
@@ -15,8 +15,6 @@ module.exports = {
tsconfig: '<rootDir>/tsconfig.spec.json', tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$', stringifyContentPathRegex: '\\.(html|svg)$',
useESM: true, useESM: true,
// Disable the builder's deprecated override; tsconfig.spec.json enables isolation.
isolatedModules: false,
}, },
], ],
}, },
+217 -265
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "paperless-ngx-ui", "name": "paperless-ngx-ui",
"version": "3.3.1", "version": "3.2.1",
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
"ng": "ng", "ng": "ng",
@@ -102,34 +102,25 @@
</button> </button>
} }
@if (selectedSection !== TaskSection.All && totalTasks() > pageSize) { <ngb-pagination
<ngb-pagination [pageSize]="pageSize"
[pageSize]="pageSize" [collectionSize]="totalTasks()"
[collectionSize]="totalTasks()" [page]="page"
[page]="page" [maxSize]="5"
[maxSize]="5" [rotate]="true"
[rotate]="true" size="sm"
size="sm" aria-label="Tasks pagination"
aria-label="Tasks pagination" (pageChange)="setPage($event)">
(pageChange)="setPage($event)"> </ngb-pagination>
</ngb-pagination>
}
</div> </div>
<ng-template let-tasks="tasks" let-section="section" #tasksTemplate> <ng-template let-tasks="tasks" let-section="section" #tasksTemplate>
<div class="section-header mb-2"> <div class="section-header d-flex align-items-center justify-content-between mb-2">
<h5 class="mb-0">{{ sectionLabel(section) }}</h5> <div>
@let count = sectionCount(section); <h5 class="mb-0">{{ sectionLabel(section) }}</h5>
@let hasMore = selectedSection === TaskSection.All && count > tasks.length; <div class="small text-muted">
<div class="small text-muted"> <ng-container i18n>{tasks.length, plural, =1 {1 task} other {{{tasks.length}} tasks}}</ng-container>
@if (hasMore) { </div>
<ng-container i18n>{{tasks.length}} of {{count}} tasks</ng-container>
<button class="btn btn-link show-all p-0 ms-2 align-baseline" type="button" (click)="setSection(section)">
<ng-container i18n>Show all</ng-container><i-bs class="ms-1" width="1em" height="1em" name="arrow-right"></i-bs>
</button>
} @else {
<ng-container i18n>{count, plural, =1 {1 task} other {{{count}} tasks}}</ng-container>
}
</div> </div>
</div> </div>
@@ -178,9 +169,10 @@
<div>{{ taskDisplayName(task) }}</div> <div>{{ taskDisplayName(task) }}</div>
<div class="small text-muted"> <div class="small text-muted">
@if (taskShowsSeparateTypeLabel(task)) { @if (taskShowsSeparateTypeLabel(task)) {
<span class="me-2">{{ task.task_type_display }}</span> <span>{{ task.task_type_display }}</span>
<span class="mx-1">&bull;</span>
} }
<span class="text-nowrap"><i-bs class="me-1" width="1em" height="1em" name="box-arrow-in-right"></i-bs>{{ task.trigger_source_display }}</span> <span>{{ task.trigger_source_display }}</span>
</div> </div>
</td> </td>
<td class="d-none d-lg-table-cell created-col">{{ task.date_created | customDate:'short' }}</td> <td class="d-none d-lg-table-cell created-col">{{ task.date_created | customDate:'short' }}</td>
@@ -72,7 +72,3 @@ pre {
tbody tr:nth-last-child(2) td { tbody tr:nth-last-child(2) td {
border-bottom: none !important; border-bottom: none !important;
} }
.section-header .show-all {
font-size: inherit;
}
@@ -223,6 +223,16 @@ describe('TasksComponent', () => {
) )
.flush(paginatedTasks) .flush(paginatedTasks)
httpTestingController
.expectOne(
(req) =>
req.url === `${environment.apiBaseUrl}tasks/` &&
req.params.get('acknowledged') === 'false' &&
req.params.get('page_size') === '25' &&
req.params.get('page') === '1'
)
.flush(paginatedTasks)
httpTestingController httpTestingController
.expectOne( .expectOne(
(req) => (req) =>
@@ -231,30 +241,8 @@ describe('TasksComponent', () => {
!req.params.has('status') !req.params.has('status')
) )
.flush(sectionCountResponse) .flush(sectionCountResponse)
flushTaskLists(10)
}) })
// answers task list requests with the fixture tasks matching their status filter
function flushTaskLists(pageSize: number, count?: number) {
httpTestingController
.match(
(req) =>
req.url === `${environment.apiBaseUrl}tasks/` &&
req.params.get('page_size') === `${pageSize}`
)
.forEach((req) => {
const statuses = req.request.params.getAll('status')
const results = tasks.filter((task) => statuses.includes(task.status))
req.flush({ count: count ?? results.length, results })
})
}
function selectSection(section: TaskSection, count: number) {
component.setSection(section)
flushTaskLists(25, count)
}
it('should display task sections with counts', () => { it('should display task sections with counts', () => {
expect(component.selectedSection).toBe(TaskSection.All) expect(component.selectedSection).toBe(TaskSection.All)
expect(component.selectedTaskType).toBeNull() expect(component.selectedTaskType).toBeNull()
@@ -350,7 +338,6 @@ describe('TasksComponent', () => {
}) })
it('should render pagination controls next to the task filter', () => { it('should render pagination controls next to the task filter', () => {
selectSection(TaskSection.Completed, 30)
fixture.detectChanges() fixture.detectChanges()
const controls = fixture.debugElement.query(By.css('.task-controls')) const controls = fixture.debugElement.query(By.css('.task-controls'))
@@ -361,51 +348,6 @@ describe('TasksComponent', () => {
expect(pagination).not.toBeNull() expect(pagination).not.toBeNull()
}) })
it('should only paginate a section with more than one page of tasks', () => {
const pagination = () =>
fixture.debugElement.query(By.css('ngb-pagination'))
fixture.detectChanges()
expect(pagination()).toBeNull()
selectSection(TaskSection.Completed, 2)
fixture.detectChanges()
expect(pagination()).toBeNull()
})
it('should load the all tasks overview one section at a time', () => {
expect(component.tasksForSection(TaskSection.NeedsAttention)).toHaveLength(
2
)
expect(component.tasksForSection(TaskSection.InProgress)).toHaveLength(3)
expect(component.tasksForSection(TaskSection.Completed)).toHaveLength(2)
expect(component.totalTasks()).toBe(7)
})
it('should show section totals and link truncated sections to their full list', () => {
component.sectionCounts.update((counts) => ({
...counts,
[TaskSection.Completed]: 27,
}))
fixture.detectChanges()
const header = (label: string) =>
fixture.debugElement
.queryAll(By.css('.section-header'))
.find((h) => h.nativeElement.textContent.includes(label))
expect(header('Needs attention').nativeElement.textContent).toContain(
'2 tasks'
)
expect(header('Needs attention').query(By.css('button'))).toBeNull()
expect(header('Recently completed').nativeElement.textContent).toContain(
'2 of 27 tasks'
)
header('Recently completed').query(By.css('button')).nativeElement.click()
expect(component.selectedSection).toBe(TaskSection.Completed)
})
it('should apply the selected section to the server-side task query', () => { it('should apply the selected section to the server-side task query', () => {
component.setSection(TaskSection.NeedsAttention) component.setSection(TaskSection.NeedsAttention)
@@ -426,56 +368,55 @@ describe('TasksComponent', () => {
it('should apply task type and trigger source filters to the server-side task query', () => { it('should apply task type and trigger source filters to the server-side task query', () => {
component.setTaskType(PaperlessTaskType.SanityCheck) component.setTaskType(PaperlessTaskType.SanityCheck)
expect( httpTestingController
httpTestingController.match( .expectOne(
(request) => (request) =>
request.url === `${environment.apiBaseUrl}tasks/` && request.url === `${environment.apiBaseUrl}tasks/` &&
request.params.get('page_size') === '10' && request.params.get('page_size') === '25' &&
request.params.get('task_type') === PaperlessTaskType.SanityCheck request.params.get('task_type') === PaperlessTaskType.SanityCheck
) )
).toHaveLength(3) .flush({ count: 1, results: [tasks[6]] })
component.setTriggerSource(PaperlessTaskTriggerSource.System) component.setTriggerSource(PaperlessTaskTriggerSource.System)
expect( httpTestingController
httpTestingController.match( .expectOne(
(request) => (request) =>
request.url === `${environment.apiBaseUrl}tasks/` && request.url === `${environment.apiBaseUrl}tasks/` &&
request.params.get('page_size') === '10' && request.params.get('page_size') === '25' &&
request.params.get('task_type') === PaperlessTaskType.SanityCheck && request.params.get('task_type') === PaperlessTaskType.SanityCheck &&
request.params.get('trigger_source') === request.params.get('trigger_source') ===
PaperlessTaskTriggerSource.System PaperlessTaskTriggerSource.System
) )
).toHaveLength(3) .flush({ count: 1, results: [tasks[6]] })
}) })
it('should apply text filters to the server-side task query', () => { it('should apply text filters to the server-side task query', () => {
component.filterText = 'invoice' component.filterText = 'invoice'
jest.advanceTimersByTime(150) jest.advanceTimersByTime(150)
expect( httpTestingController
httpTestingController.match( .expectOne(
(request) => (request) =>
request.url === `${environment.apiBaseUrl}tasks/` && request.url === `${environment.apiBaseUrl}tasks/` &&
request.params.get('page_size') === '10' && request.params.get('page_size') === '25' &&
request.params.get('name') === 'invoice' request.params.get('name') === 'invoice'
) )
).toHaveLength(3) .flush({ count: 1, results: [tasks[0]] })
component.setFilterTarget(TaskFilterTargetID.Result) component.setFilterTarget(TaskFilterTargetID.Result)
expect( httpTestingController
httpTestingController.match( .expectOne(
(request) => (request) =>
request.url === `${environment.apiBaseUrl}tasks/` && request.url === `${environment.apiBaseUrl}tasks/` &&
request.params.get('page_size') === '10' && request.params.get('page_size') === '25' &&
request.params.get('result') === 'invoice' request.params.get('result') === 'invoice'
) )
).toHaveLength(3) .flush({ count: 0, results: [] })
}) })
it('should load a different task page when pagination changes', () => { it('should load a different task page when pagination changes', () => {
selectSection(TaskSection.Completed, 30)
component.setPage(2) component.setPage(2)
const pageTwoTasks = { const pageTwoTasks = {
@@ -499,7 +440,6 @@ describe('TasksComponent', () => {
}) })
it('should not replace section counts with current-page counts', () => { it('should not replace section counts with current-page counts', () => {
selectSection(TaskSection.Completed, 30)
component.setPage(2) component.setPage(2)
httpTestingController httpTestingController
@@ -515,21 +455,11 @@ describe('TasksComponent', () => {
results: [tasks[0]], results: [tasks[0]],
}) })
expect(component.sectionCount(TaskSection.All)).toBe(7)
expect(component.sectionCount(TaskSection.NeedsAttention)).toBe(2) expect(component.sectionCount(TaskSection.NeedsAttention)).toBe(2)
expect(component.sectionCount(TaskSection.InProgress)).toBe(3) expect(component.sectionCount(TaskSection.InProgress)).toBe(3)
expect(component.sectionCount(TaskSection.Completed)).toBe(2) expect(component.sectionCount(TaskSection.Completed)).toBe(2)
}) })
it('should stop loading when the overview fails to load', () => {
component.setSection(TaskSection.All)
httpTestingController
.match((req) => req.params.get('page_size') === '10')[0]
.flush('', { status: 500, statusText: 'Error' })
expect(component.loading()).toBe(false)
})
it('should expose stable task type options and disable empty ones', () => { it('should expose stable task type options and disable empty ones', () => {
expect(component.taskTypeOptions.map((option) => option.value)).toContain( expect(component.taskTypeOptions.map((option) => option.value)).toContain(
PaperlessTaskType.TrainClassifier PaperlessTaskType.TrainClassifier
@@ -15,9 +15,6 @@ import {
distinctUntilChanged, distinctUntilChanged,
filter, filter,
first, first,
forkJoin,
map,
Observable,
Subject, Subject,
takeUntil, takeUntil,
timer, timer,
@@ -28,7 +25,6 @@ import {
PaperlessTaskTriggerSource, PaperlessTaskTriggerSource,
PaperlessTaskType, PaperlessTaskType,
} from 'src/app/data/paperless-task' } from 'src/app/data/paperless-task'
import { Results } from 'src/app/data/results'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { TasksService } from 'src/app/services/tasks.service' import { TasksService } from 'src/app/services/tasks.service'
@@ -173,7 +169,6 @@ export class TasksComponent
public expandedTask: number public expandedTask: number
public autoRefreshEnabled: boolean = true public autoRefreshEnabled: boolean = true
public readonly pageSize = 25 public readonly pageSize = 25
public readonly overviewSize = 10
public page: number = 1 public page: number = 1
readonly totalTasks = signal(0) readonly totalTasks = signal(0)
readonly sectionCounts = signal<Record<TaskSection, number>>({ readonly sectionCounts = signal<Record<TaskSection, number>>({
@@ -489,6 +484,13 @@ export class TasksComponent
return this.sectionCounts()[section] return this.sectionCounts()[section]
} }
private setSectionCount(section: TaskSection, count: number) {
this.sectionCounts.update((counts) => ({
...counts,
[section]: count,
}))
}
sectionShowsResults(section: TaskSection): boolean { sectionShowsResults(section: TaskSection): boolean {
return section !== TaskSection.InProgress return section !== TaskSection.InProgress
} }
@@ -735,12 +737,21 @@ export class TasksComponent
this.reloadSectionCounts() this.reloadSectionCounts()
this.loading.set(true) this.loading.set(true)
this.listTasks() this.tasksService
.list(
this.page,
this.pageSize,
this.getParamsForSection(this.selectedSection)
)
.pipe(first(), takeUntil(this.unsubscribeNotifier)) .pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe({ .subscribe({
next: (result) => { next: (result) => {
this.pagedTasks.set(result.results) this.pagedTasks.set(result.results)
this.totalTasks.set(result.count) this.totalTasks.set(result.count)
this.setSectionCount(TaskSection.All, result.count)
if (this.selectedSection !== TaskSection.All) {
this.setSectionCount(this.selectedSection, result.count)
}
this.loading.set(false) this.loading.set(false)
if ( if (
this.page > 1 && this.page > 1 &&
@@ -756,30 +767,4 @@ export class TasksComponent
}, },
}) })
} }
private listTasks(): Observable<Results<PaperlessTask>> {
if (this.selectedSection !== TaskSection.All) {
return this.tasksService.list(
this.page,
this.pageSize,
this.getParamsForSection(this.selectedSection)
)
}
// All is an overview of each section to avoid splitting sections across pages
return forkJoin(
this.sections.map((section) =>
this.tasksService.list(
1,
this.overviewSize,
this.getParamsForSection(section)
)
)
).pipe(
map((results) => ({
count: results.reduce((sum, result) => sum + result.count, 0),
results: results.flatMap((result) => result.results),
}))
)
}
} }
@@ -154,31 +154,6 @@ describe('UsersAndGroupsComponent', () => {
jest.useRealTimers() jest.useRealTimers()
}) })
it('should logout current user under base href if password changed', () => {
jest.useFakeTimers()
const base = document.createElement('base')
base.href = '/paperless/'
document.head.appendChild(base)
completeSetup()
let modal: NgbModalRef
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
component.editUser(users[0])
const navSpy = jest
.spyOn(navUtils, 'setLocationHref')
.mockImplementation(() => {})
const editDialog = modal.componentInstance as UserEditDialogComponent
editDialog.passwordIsSet = true
settingsService.currentUser.set(users[0]) // simulate logged in as same user
editDialog.succeeded.emit(users[0])
fixture.detectChanges()
jest.advanceTimersByTime(2600)
expect(navSpy).toHaveBeenCalledWith(
`${window.location.origin}/paperless/accounts/logout/?next=/paperless/accounts/login/?next=/paperless/`
)
base.remove()
jest.useRealTimers()
})
it('should support edit / create group, show error if needed', () => { it('should support edit / create group, show error if needed', () => {
completeSetup() completeSetup()
let modal: NgbModalRef let modal: NgbModalRef
@@ -116,9 +116,8 @@ export class UsersAndGroupsComponent
$localize`Password has been changed, you will be logged out momentarily.` $localize`Password has been changed, you will be logged out momentarily.`
) )
setTimeout(() => { setTimeout(() => {
const basePath = new URL(document.baseURI).pathname
setLocationHref( setLocationHref(
`${window.location.origin}${basePath}accounts/logout/?next=${basePath}accounts/login/?next=${basePath}` `${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/`
) )
}, 2500) }, 2500)
} else { } else {
@@ -1,7 +1,5 @@
@if (!accordion) { @if (!accordion) {
@if (title?.length > 0) { <h5 i18n>Permissions</h5>
<h5>{{title}}</h5>
}
<ng-container [ngTemplateOutlet]="permissionsForm"></ng-container> <ng-container [ngTemplateOutlet]="permissionsForm"></ng-container>
} }
@if (accordion) { @if (accordion) {
@@ -59,9 +59,6 @@ export class PermissionsFormComponent
@Input() @Input()
accordion: boolean = false accordion: boolean = false
@Input()
title: string = $localize`Permissions`
form = new FormGroup({ form = new FormGroup({
owner: new FormControl(null), owner: new FormControl(null),
set_permissions: new FormGroup({ set_permissions: new FormGroup({
@@ -3,13 +3,13 @@
<h3 class="d-flex align-items-center mb-2 mb-md-1" style="line-height: 1.4"> <h3 class="d-flex align-items-center mb-2 mb-md-1" style="line-height: 1.4">
<span class="text-truncate">{{title()}}</span> <span class="text-truncate">{{title()}}</span>
@if (id()) { @if (id()) {
<button type="button" class="badge border-0 bg-primary text-primary-text-contrast ms-3 small fs-normal" (click)="copyID()" title="Copy ID" i18n-title> <span class="badge bg-primary text-primary-text-contrast ms-3 small fs-normal cursor-pointer" (click)="copyID()">
@if (copied()) { @if (copied()) {
<i-bs width="1em" height="1em" name="clipboard-check" class="me-1"></i-bs><ng-container i18n>Copied!</ng-container> <i-bs width="1em" height="1em" name="clipboard-check" class="me-1"></i-bs><ng-container i18n>Copied!</ng-container>
} @else { } @else {
ID: {{id()}} ID: {{id()}}
} }
</button> </span>
} }
@if (subTitle()) { @if (subTitle()) {
<span class="page-subtitle h6 mb-0 mt-1 fw-normal ms-md-3 text-truncate" style="line-height: 1.4">{{subTitle()}}</span> <span class="page-subtitle h6 mb-0 mt-1 fw-normal ms-md-3 text-truncate" style="line-height: 1.4">{{subTitle()}}</span>
@@ -43,13 +43,6 @@ describe('PageHeaderComponent', () => {
expect(titleSpy).toHaveBeenCalledWith(`Foo Bar - ${environment.appTitle}`) expect(titleSpy).toHaveBeenCalledWith(`Foo Bar - ${environment.appTitle}`)
}) })
it('should set html title to just the app title until a title is available', () => {
const titleSpy = jest.spyOn(titleService, 'setTitle')
fixture.componentRef.setInput('title', undefined)
fixture.detectChanges()
expect(titleSpy).toHaveBeenCalledWith(environment.appTitle)
})
it('should copy id to clipboard, reset after 3 seconds', () => { it('should copy id to clipboard, reset after 3 seconds', () => {
jest.useFakeTimers() jest.useFakeTimers()
fixture.componentRef.setInput('id', 42) fixture.componentRef.setInput('id', 42)
@@ -27,10 +27,7 @@ export class PageHeaderComponent {
constructor() { constructor() {
effect(() => { effect(() => {
const title = this.title() this.titleService.setTitle(`${this.title()} - ${environment.appTitle}`)
this.titleService.setTitle(
title ? `${title} - ${environment.appTitle}` : environment.appTitle
)
}) })
} }
@@ -218,38 +218,6 @@ describe('ProfileEditDialogComponent', () => {
jest.useRealTimers() jest.useRealTimers()
}) })
it('should logout under base href on save if password changed', () => {
jest.useFakeTimers()
const base = document.createElement('base')
base.href = '/paperless/'
document.head.appendChild(base)
const getSpy = jest.spyOn(profileService, 'get')
getSpy.mockReturnValue(of(profile))
const getProvidersSpy = jest.spyOn(
profileService,
'getSocialAccountProviders'
)
getProvidersSpy.mockReturnValue(of(socialAccountProviders))
component.ngOnInit()
component['newPassword'] = 'new*pass'
component.form.get('password').patchValue('new*pass')
component.form.get('password_confirm').patchValue('new*pass')
const updateSpy = jest.spyOn(profileService, 'update')
updateSpy.mockReturnValue(of(null))
const navSpy = jest
.spyOn(navUtils, 'setLocationHref')
.mockImplementation(() => {})
component.save()
expect(updateSpy).toHaveBeenCalled()
jest.advanceTimersByTime(2600)
expect(navSpy).toHaveBeenCalledWith(
`${window.location.origin}/paperless/accounts/logout/?next=/paperless/accounts/login/?next=/paperless/`
)
base.remove()
jest.useRealTimers()
})
it('should support auth token copy', () => { it('should support auth token copy', () => {
jest.useFakeTimers() jest.useFakeTimers()
const getSpy = jest.spyOn(profileService, 'get') const getSpy = jest.spyOn(profileService, 'get')
@@ -210,9 +210,8 @@ export class ProfileEditDialogComponent
$localize`Password has been changed, you will be logged out momentarily.` $localize`Password has been changed, you will be logged out momentarily.`
) )
setTimeout(() => { setTimeout(() => {
const basePath = new URL(document.baseURI).pathname
setLocationHref( setLocationHref(
`${window.location.origin}${basePath}accounts/logout/?next=${basePath}accounts/login/?next=${basePath}` `${window.location.origin}/accounts/logout/?next=/accounts/login/?next=/`
) )
}, 2500) }, 2500)
} }
@@ -1,6 +1,6 @@
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div class="btn-group"> <div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-primary" (click)="clickSuggest()" [disabled]="disabled() || loading()" [aria-label]="noSuggestions ? 'No suggestions' : 'Suggest'" i18n-aria-label> <button type="button" class="btn btn-sm btn-outline-primary" (click)="clickSuggest()" [disabled]="disabled() || loading() || (suggestions() && !aiEnabled())" [aria-label]="noSuggestions ? 'No suggestions' : 'Suggest'" i18n-aria-label>
@if (loading()) { @if (loading()) {
<div class="spinner-border spinner-border-sm" role="status"></div> <div class="spinner-border spinner-border-sm" role="status"></div>
} @else if (noSuggestions) { } @else if (noSuggestions) {
@@ -18,44 +18,46 @@
} }
</button> </button>
<div class="btn-group" ngbDropdown #dropdown="ngbDropdown" [popperOptions]="popperOptions"> @if (aiEnabled()) {
<button type="button" class="btn btn-sm btn-outline-primary" ngbDropdownToggle [disabled]="disabled() || loading() || !suggestions()" aria-expanded="false" aria-controls="suggestionsDropdown" aria-label="Suggestions dropdown"> <div class="btn-group" ngbDropdown #dropdown="ngbDropdown" [popperOptions]="popperOptions">
<span class="visually-hidden" i18n>Show suggestions</span> <button type="button" class="btn btn-sm btn-outline-primary" ngbDropdownToggle [disabled]="disabled() || loading() || !suggestions()" aria-expanded="false" aria-controls="suggestionsDropdown" aria-label="Suggestions dropdown">
</button> <span class="visually-hidden" i18n>Show suggestions</span>
</button>
<div ngbDropdownMenu aria-labelledby="suggestionsDropdown" class="shadow suggestions-dropdown"> <div ngbDropdownMenu aria-labelledby="suggestionsDropdown" class="shadow suggestions-dropdown">
<div class="list-group list-group-flush small pb-0"> <div class="list-group list-group-flush small pb-0">
@if (novelSuggestions === 0 && fieldSuggestions === 0) { @if (novelSuggestions === 0 && reusableSuggestions === 0) {
<div class="list-group-item text-muted fst-italic"> <div class="list-group-item text-muted fst-italic">
<small class="text-muted small fst-italic" i18n>No novel suggestions</small> <small class="text-muted small fst-italic" i18n>No novel suggestions</small>
</div> </div>
}
@if (suggestions()?.suggested_tags?.length > 0) {
<small class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="tags"></i-bs><ng-container i18n>Tags</ng-container></small>
@for (tag of suggestions().suggested_tags; track tag) {
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addTag.emit(tag)">{{ tag }}</button>
} }
} @if (suggestions()?.suggested_tags?.length > 0) {
@if (suggestions()?.suggested_document_types?.length > 0) { <small class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="tags"></i-bs><ng-container i18n>Tags</ng-container></small>
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="hash"></i-bs><ng-container i18n>Document Types</ng-container></div> @for (tag of suggestions().suggested_tags; track tag) {
@for (type of suggestions().suggested_document_types; track type) { <button type="button" class="list-group-item list-group-item-action bg-light" (click)="addTag.emit(tag)">{{ tag }}</button>
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addDocumentType.emit(type)">{{ type }}</button> }
} }
} @if (suggestions()?.suggested_document_types?.length > 0) {
@if (suggestions()?.suggested_correspondents?.length > 0) { <div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="hash"></i-bs><ng-container i18n>Document Types</ng-container></div>
<div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="person"></i-bs><ng-container i18n>Correspondents</ng-container></div> @for (type of suggestions().suggested_document_types; track type) {
@for (correspondent of suggestions().suggested_correspondents; track correspondent) { <button type="button" class="list-group-item list-group-item-action bg-light" (click)="addDocumentType.emit(type)">{{ type }}</button>
<button type="button" class="list-group-item list-group-item-action bg-light" (click)="addCorrespondent.emit(correspondent)">{{ correspondent }}</button> }
} }
} @if (suggestions()?.suggested_correspondents?.length > 0) {
@if (fieldSuggestions > 0) { <div class="list-group-item text-uppercase text-muted small"><i-bs class="me-2" name="person"></i-bs><ng-container i18n>Correspondents</ng-container></div>
<div class="list-group-item text-muted fst-italic"> @for (correspondent of suggestions().suggested_correspondents; track correspondent) {
<small class="text-muted small fst-italic" i18n>{fieldSuggestions, plural, =1 {1 suggestion available below} other {{{fieldSuggestions}} suggestions available below}}</small> <button type="button" class="list-group-item list-group-item-action bg-light" (click)="addCorrespondent.emit(correspondent)">{{ correspondent }}</button>
</div> }
} }
@if (reusableSuggestions > 0) {
<div class="list-group-item text-muted fst-italic">
<small class="text-muted small fst-italic" i18n>{reusableSuggestions, plural, =1 {1 existing value suggested below} other {{{reusableSuggestions}} existing values suggested below}}</small>
</div>
}
</div>
</div> </div>
</div> </div>
</div> }
</div> </div>
@if (aiEnabled()) { @if (aiEnabled()) {
@@ -84,7 +84,7 @@ describe('SuggestionsDropdownComponent', () => {
fixture.detectChanges() fixture.detectChanges()
expect(fixture.nativeElement.textContent).toContain( expect(fixture.nativeElement.textContent).toContain(
'2 suggestions available below' '2 existing values suggested below'
) )
expect(fixture.nativeElement.textContent).not.toContain( expect(fixture.nativeElement.textContent).not.toContain(
'No novel suggestions' 'No novel suggestions'
@@ -108,7 +108,7 @@ describe('SuggestionsDropdownComponent', () => {
expect(component.totalSuggestions).toBe(4) expect(component.totalSuggestions).toBe(4)
expect(fixture.nativeElement.textContent).toContain('Arbitration') expect(fixture.nativeElement.textContent).toContain('Arbitration')
expect(fixture.nativeElement.textContent).toContain( expect(fixture.nativeElement.textContent).toContain(
'2 suggestions available below' '2 existing values suggested below'
) )
}) })
@@ -125,46 +125,10 @@ describe('SuggestionsDropdownComponent', () => {
}) })
expect(component.novelSuggestions).toBe(0) expect(component.novelSuggestions).toBe(0)
expect(component.totalSuggestions).toBe(6) expect(component.totalSuggestions).toBe(5)
fixture.componentRef.setInput('appliedStoragePath', 7) fixture.componentRef.setInput('appliedStoragePath', 7)
expect(component.totalSuggestions).toBe(5) expect(component.totalSuggestions).toBe(4)
})
it('should count title and dates as field suggestions without calling them existing values', () => {
fixture.componentRef.setInput('aiEnabled', true)
fixture.componentRef.setInput('fetchedSources', [SuggestionSource.ML])
fixture.componentRef.setInput('suggestions', {
title: 'Suggested title',
dates: ['2026-01-04', '2026-02-01', '2026-03-01'],
correspondents: [1, 2, 3, 4],
document_types: [1, 2, 3, 4],
tags: [1, 2, 3, 4, 5, 6, 7, 8],
})
fixture.detectChanges()
component.clickSuggest()
fixture.detectChanges()
expect(component.reusableSuggestions).toBe(16)
expect(component.fieldSuggestions).toBe(20)
expect(component.totalSuggestions).toBe(20)
expect(fixture.nativeElement.textContent).toContain(
'20 suggestions available below'
)
expect(fixture.nativeElement.textContent).not.toContain('existing value')
})
it('should not count title or date suggestions matching the current values', () => {
fixture.componentRef.setInput('suggestions', {
title: 'Current title',
dates: ['2026-01-04', '2026-02-01'],
})
expect(component.fieldSuggestions).toBe(3)
fixture.componentRef.setInput('appliedTitle', 'Current title')
fixture.componentRef.setInput('appliedCreated', '2026-01-04')
expect(component.fieldSuggestions).toBe(1)
expect(component.totalSuggestions).toBe(1)
}) })
it('should show when a completed request returned no suggestions', () => { it('should show when a completed request returned no suggestions', () => {
@@ -181,28 +145,6 @@ describe('SuggestionsDropdownComponent', () => {
expect(fixture.nativeElement.textContent).toContain('No suggestions') expect(fixture.nativeElement.textContent).toContain('No suggestions')
}) })
it('should wait for all pending responses before showing the empty state', () => {
fixture.componentRef.setInput('aiEnabled', true)
fixture.componentRef.setInput('source', SuggestionSource.Both)
fixture.componentRef.setInput('fetchedSources', [SuggestionSource.ML])
fixture.componentRef.setInput('suggestions', { tags: [] })
fixture.componentRef.setInput('loading', true)
fixture.detectChanges()
expect(component.noSuggestions).toBeFalsy()
expect(fixture.nativeElement.textContent).not.toContain('No suggestions')
expect(
fixture.nativeElement.querySelector('[role="status"]')
).not.toBeNull()
fixture.componentRef.setInput('loading', false)
fixture.detectChanges()
expect(component.noSuggestions).toBeTruthy()
expect(fixture.nativeElement.textContent).toContain('No suggestions')
expect(fixture.nativeElement.querySelector('[role="status"]')).toBeNull()
})
it('should not show the empty state before a request or with suggestions', () => { it('should not show the empty state before a request or with suggestions', () => {
expect(component.noSuggestions).toBeFalsy() expect(component.noSuggestions).toBeFalsy()
@@ -251,37 +193,6 @@ describe('SuggestionsDropdownComponent', () => {
expect(fixture.nativeElement.textContent).toContain('No novel suggestions') expect(fixture.nativeElement.textContent).toContain('No novel suggestions')
}) })
it('should open ML date suggestions with AI disabled', () => {
const getSuggestions = jest.spyOn(component.getSuggestions, 'emit')
fixture.componentRef.setInput('aiEnabled', false)
fixture.componentRef.setInput('fetchedSources', [SuggestionSource.ML])
fixture.componentRef.setInput('suggestions', {
dates: ['2026-01-04', '2026-02-01'],
})
fixture.componentRef.setInput('appliedCreated', '2026-01-04')
fixture.detectChanges()
const button = fixture.nativeElement.querySelector('button')
expect(button.disabled).toBeFalsy()
expect(button.querySelector('.badge').textContent.trim()).toBe('1')
button.click()
fixture.detectChanges()
expect(component.dropdown.isOpen()).toBeTruthy()
expect(fixture.nativeElement.textContent).toContain(
'1 suggestion available below'
)
expect(getSuggestions).not.toHaveBeenCalled()
expect(
fixture.nativeElement.querySelector('button[title="Suggestion options"]')
).toBeNull()
button.click()
fixture.detectChanges()
expect(component.dropdown.isOpen()).toBeFalsy()
})
it('should fetch unfetched sources and show existing suggestions', () => { it('should fetch unfetched sources and show existing suggestions', () => {
jest.spyOn(component.getSuggestions, 'emit') jest.spyOn(component.getSuggestions, 'emit')
fixture.componentRef.setInput('aiEnabled', true) fixture.componentRef.setInput('aiEnabled', true)
@@ -34,8 +34,6 @@ export class SuggestionsDropdownComponent {
readonly appliedCorrespondent = input<number>(null) readonly appliedCorrespondent = input<number>(null)
readonly appliedDocumentType = input<number>(null) readonly appliedDocumentType = input<number>(null)
readonly appliedStoragePath = input<number>(null) readonly appliedStoragePath = input<number>(null)
readonly appliedTitle = input<string>(null)
readonly appliedCreated = input<string>(null)
@Output() @Output()
getSuggestions: EventEmitter<SuggestionSource> = new EventEmitter() getSuggestions: EventEmitter<SuggestionSource> = new EventEmitter()
@@ -53,7 +51,11 @@ export class SuggestionsDropdownComponent {
addCorrespondent: EventEmitter<string> = new EventEmitter() addCorrespondent: EventEmitter<string> = new EventEmitter()
public clickSuggest(): void { public clickSuggest(): void {
if (this.disabled() || this.loading()) { if (
this.disabled() ||
this.loading() ||
(this.suggestions() && !this.aiEnabled())
) {
return return
} }
@@ -126,28 +128,7 @@ export class SuggestionsDropdownComponent {
} }
get totalSuggestions(): number { get totalSuggestions(): number {
return this.novelSuggestions + this.fieldSuggestions return this.novelSuggestions + this.reusableSuggestions
}
get fieldSuggestions(): number {
return (
this.reusableSuggestions +
this.unappliedTitleSuggestions +
this.unappliedDateSuggestions
)
}
// hide a title or date suggestion equal to the current value
private get unappliedTitleSuggestions(): number {
const title = this.suggestions()?.title
return title && title !== this.appliedTitle() ? 1 : 0
}
private get unappliedDateSuggestions(): number {
const created = this.appliedCreated()
return (this.suggestions()?.dates ?? []).filter(
(date) => !created || date !== created
).length
} }
private countUnapplied(suggested: number[], applied: number[]): number { private countUnapplied(suggested: number[], applied: number[]): number {
@@ -158,7 +139,6 @@ export class SuggestionsDropdownComponent {
get noSuggestions(): boolean { get noSuggestions(): boolean {
const suggestions = this.suggestions() const suggestions = this.suggestions()
return ( return (
!this.loading() &&
suggestions != null && suggestions != null &&
!suggestions.title && !suggestions.title &&
!suggestions.tags?.length && !suggestions.tags?.length &&
@@ -1,46 +0,0 @@
<table class="table table-borderless align-baseline">
<thead>
<tr>
<th i18n>Page</th>
<th i18n>Type</th>
<th i18n>Content</th>
<th></th>
</tr>
</thead>
<tbody>
@for (barcode of barcodes(); track $index) {
<tr>
<td>{{ barcode.page }}</td>
<td class="text-nowrap">{{ barcode.format }}</td>
<td class="text-break">
@if (isLink(barcode.value)) {
<a
[href]="barcode.value"
target="_blank"
rel="noopener noreferrer nofollow"
>{{ barcode.value }}</a
>
} @else {
{{ barcode.value }}
}
</td>
<td class="text-end">
<button
type="button"
class="btn btn-sm btn-outline-primary"
(click)="copy($index)"
title="Copy content"
i18n-title
>
@if (copiedIndex() === $index) {
<i-bs name="clipboard-check"></i-bs>
} @else {
<i-bs name="clipboard"></i-bs>
}
<span class="visually-hidden" i18n>Copy content</span>
</button>
</td>
</tr>
}
</tbody>
</table>
@@ -1,73 +0,0 @@
import { Clipboard } from '@angular/cdk/clipboard'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { DocumentBarcodesComponent } from './document-barcodes.component'
const barcodes = [
{ page: 1, value: 'ASN00123', format: 'Code128' },
{ page: 2, value: 'https://example.com/invoice/4711', format: 'QRCode' },
{ page: 2, value: 'javascript:alert(1)', format: 'QRCode' },
]
describe('DocumentBarcodesComponent', () => {
let component: DocumentBarcodesComponent
let fixture: ComponentFixture<DocumentBarcodesComponent>
let clipboard: Clipboard
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [
DocumentBarcodesComponent,
NgxBootstrapIconsModule.pick(allIcons),
],
}).compileComponents()
fixture = TestBed.createComponent(DocumentBarcodesComponent)
component = fixture.componentInstance
clipboard = TestBed.inject(Clipboard)
fixture.componentRef.setInput('barcodes', barcodes)
fixture.detectChanges()
})
it('should display all barcodes', () => {
const rows = fixture.nativeElement.querySelectorAll('tbody tr')
expect(rows).toHaveLength(3)
expect(rows[0].textContent).toContain('ASN00123')
expect(rows[0].textContent).toContain('Code128')
})
it('should only link http(s) values', () => {
const links = fixture.nativeElement.querySelectorAll('tbody a')
expect(links).toHaveLength(1)
expect(links[0].getAttribute('href')).toEqual(
'https://example.com/invoice/4711'
)
expect(links[0].getAttribute('target')).toEqual('_blank')
})
it('should copy a value and show feedback', () => {
jest.useFakeTimers()
const copySpy = jest.spyOn(clipboard, 'copy').mockReturnValue(true)
const buttons = fixture.nativeElement.querySelectorAll('tbody button')
buttons[0].click()
fixture.detectChanges()
expect(copySpy).toHaveBeenCalledWith('ASN00123')
expect(component.copiedIndex()).toEqual(0)
expect(buttons[0].querySelector('i-bs').getAttribute('name')).toEqual(
'clipboard-check'
)
jest.advanceTimersByTime(3000)
fixture.detectChanges()
expect(component.copiedIndex()).toBeNull()
expect(buttons[0].querySelector('i-bs').getAttribute('name')).toEqual(
'clipboard'
)
jest.useRealTimers()
})
it('should not show feedback if copying failed', () => {
jest.spyOn(clipboard, 'copy').mockReturnValue(false)
component.copy(1)
expect(component.copiedIndex()).toBeNull()
})
})
@@ -1,38 +0,0 @@
import { Clipboard } from '@angular/cdk/clipboard'
import { Component, inject, input, OnDestroy, signal } from '@angular/core'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { DocumentBarcode } from 'src/app/data/document-barcode'
@Component({
selector: 'pngx-document-barcodes',
templateUrl: './document-barcodes.component.html',
imports: [NgxBootstrapIconsModule],
})
export class DocumentBarcodesComponent implements OnDestroy {
private readonly clipboard = inject(Clipboard)
readonly barcodes = input<DocumentBarcode[]>([])
readonly copiedIndex = signal<number>(null)
private copyTimeout: ReturnType<typeof setTimeout>
public isLink(value: string): boolean {
try {
const url = new URL(value.trim())
return ['http:', 'https:'].includes(url.protocol) && !!url.host
} catch {
return false
}
}
public copy(index: number) {
if (!this.clipboard.copy(this.barcodes()[index].value)) return
this.copiedIndex.set(index)
clearTimeout(this.copyTimeout)
this.copyTimeout = setTimeout(() => this.copiedIndex.set(null), 3000)
}
ngOnDestroy(): void {
clearTimeout(this.copyTimeout)
}
}
@@ -1,4 +1,25 @@
<pngx-page-header [title]="title()" [id]="documentId()"> <pngx-page-header [title]="title()" [id]="documentId()">
@if (archiveContentRenderType === ContentRenderType.PDF && !useNativePdfViewer) {
@if (previewNumPages()) {
<div class="input-group input-group-sm ms-2 d-none d-md-flex">
<div class="input-group-text" i18n>Page</div>
<input class="form-control flex-grow-0 w-auto" type="number" min="1" [max]="previewNumPages()" [ngModel]="previewCurrentPage()" (ngModelChange)="previewCurrentPage.set($event)" />
<div class="input-group-text" i18n>of {{previewNumPages()}}</div>
</div>
}
<div class="input-group input-group-sm me-md-5 d-none d-md-flex">
<button class="btn btn-outline-secondary" (click)="decreaseZoom()" i18n>-</button>
<select class="form-select" (change)="setZoom($event.target.value)" [ngModel]="currentZoom">
@for (setting of zoomSettings; track setting) {
<option [value]="setting">
{{ getZoomSettingTitle(setting) }}
</option>
}
</select>
<button class="btn btn-outline-secondary" (click)="increaseZoom()" i18n>+</button>
</div>
}
<button type="button" class="btn btn-sm btn-outline-danger me-md-4" (click)="delete()" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }" aria-label="Delete" i18n-aria-label> <button type="button" class="btn btn-sm btn-outline-danger me-md-4" (click)="delete()" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }" aria-label="Delete" i18n-aria-label>
<i-bs width="1.2em" height="1.2em" name="trash"></i-bs><span class="d-none d-lg-inline ps-1" i18n>Delete</span> <i-bs width="1.2em" height="1.2em" name="trash"></i-bs><span class="d-none d-lg-inline ps-1" i18n>Delete</span>
</button> </button>
@@ -13,7 +34,7 @@
(versionsUpdated)="onVersionsUpdated($event)" (versionsUpdated)="onVersionsUpdated($event)"
/> />
<div class="btn-group ms-4"> <div class="btn-group">
<button (click)="download()" class="btn btn-sm btn-outline-primary" [disabled]="downloading()" aria-label="Download" i18n-aria-label> <button (click)="download()" class="btn btn-sm btn-outline-primary" [disabled]="downloading()" aria-label="Download" i18n-aria-label>
@if (downloading()) { @if (downloading()) {
<div class="spinner-border spinner-border-sm" role="status"></div> <div class="spinner-border spinner-border-sm" role="status"></div>
@@ -73,7 +94,7 @@
<button class="btn btn-sm btn-outline-primary" id="sendDropdown" ngbDropdownToggle aria-label="Send" i18n-aria-label> <button class="btn btn-sm btn-outline-primary" id="sendDropdown" ngbDropdownToggle aria-label="Send" i18n-aria-label>
<i-bs name="send"></i-bs><div class="d-none d-sm-inline ms-1"><ng-container i18n>Send</ng-container></div> <i-bs name="send"></i-bs><div class="d-none d-sm-inline ms-1"><ng-container i18n>Send</ng-container></div>
</button> </button>
<div ngbDropdownMenu aria-labelledby="sendDropdown" class="shadow"> <div ngbDropdownMenu aria-labelledby="actionsDropdown" class="shadow">
<button ngbDropdownItem (click)="openShareLinks()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ShareLink }"> <button ngbDropdownItem (click)="openShareLinks()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ShareLink }">
<i-bs name="link" class="me-1"></i-bs><span i18n>Share Links</span> <i-bs name="link" class="me-1"></i-bs><span i18n>Share Links</span>
</button> </button>
@@ -92,8 +113,8 @@
<form [formGroup]='documentForm' (ngSubmit)="save()"> <form [formGroup]='documentForm' (ngSubmit)="save()">
<div class="btn-toolbar mb-1 pb-3 gap-2 row-gap-2 border-bottom"> <div class="btn-toolbar justify-content-end mb-1 pb-3 gap-2 row-gap-2 border-bottom">
<div class="btn-group"> <div class="btn-group me-auto">
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Close" (click)="close()"> <button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Close" (click)="close()">
<i-bs width="1.2em" height="1.2em" name="x"></i-bs> <i-bs width="1.2em" height="1.2em" name="x"></i-bs>
</button> </button>
@@ -106,30 +127,41 @@
</div> </div>
<ng-container *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }"> <ng-container *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
<div class="btn-group ms-3"> <div class="d-flex gap-2">
<pngx-suggestions-dropdown *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" <div class="btn-group">
[disabled]="!userCanEdit || suggestionsLoading()" <pngx-suggestions-dropdown *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }"
[loading]="suggestionsLoading()" [disabled]="!userCanEdit || suggestionsLoading()"
[suggestions]="suggestions()" [loading]="suggestionsLoading()"
[aiEnabled]="aiEnabled" [suggestions]="suggestions()"
[source]="suggestionSource" [aiEnabled]="aiEnabled"
[defaultSource]="defaultSuggestionSource" [source]="suggestionSource"
[fetchedSources]="fetchedSuggestionSources()" [defaultSource]="defaultSuggestionSource"
[appliedTags]="documentForm.value.tags" [fetchedSources]="fetchedSuggestionSources()"
[appliedCorrespondent]="documentForm.value.correspondent" [appliedTags]="documentForm.value.tags"
[appliedDocumentType]="documentForm.value.document_type" [appliedCorrespondent]="documentForm.value.correspondent"
[appliedStoragePath]="documentForm.value.storage_path" [appliedDocumentType]="documentForm.value.document_type"
[appliedTitle]="documentForm.value.title" [appliedStoragePath]="documentForm.value.storage_path"
[appliedCreated]="documentForm.value.created" (getSuggestions)="getSuggestions($event)"
(getSuggestions)="getSuggestions($event)" (sourceChange)="suggestionSourceOverride.set($event)"
(sourceChange)="suggestionSourceOverride.set($event)" (addTag)="createTag($event)"
(addTag)="createTag($event)" (addDocumentType)="createDocumentType($event)"
(addDocumentType)="createDocumentType($event)" (addCorrespondent)="createCorrespondent($event)">
(addCorrespondent)="createCorrespondent($event)"> </pngx-suggestions-dropdown>
</pngx-suggestions-dropdown> </div>
<div class="btn-group">
<pngx-custom-fields-dropdown
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }"
[documentId]="documentId()"
[disabled]="!userCanEdit"
[existingFields]="document()?.custom_fields"
(created)="refreshCustomFields()"
(added)="addField($event)">
</pngx-custom-fields-dropdown>
</div>
</div> </div>
<div class="ms-auto"> <div class="ps-3">
<ng-container *ngTemplateOutlet="saveButtons"></ng-container> <ng-container *ngTemplateOutlet="saveButtons"></ng-container>
</div> </div>
</ng-container> </ng-container>
@@ -256,20 +288,8 @@
} }
</div> </div>
<div class="d-flex border-top pt-3"> <div class="d-flex justify-content-end border-top pt-3">
<ng-container *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }"> <ng-container *ngTemplateOutlet="saveButtons"></ng-container>
<pngx-custom-fields-dropdown
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }"
[documentId]="documentId()"
[disabled]="!userCanEdit"
[existingFields]="document()?.custom_fields"
(created)="refreshCustomFields()"
(added)="addField($event)">
</pngx-custom-fields-dropdown>
</ng-container>
<div class="ms-auto">
<ng-container *ngTemplateOutlet="saveButtons"></ng-container>
</div>
</div> </div>
</ng-template> </ng-template>
</li> </li>
@@ -306,9 +326,9 @@
<td i18n>Original filename</td> <td i18n>Original filename</td>
<td>{{metadata()?.original_filename}}</td> <td>{{metadata()?.original_filename}}</td>
</tr> </tr>
<tr class="align-middle"> <tr>
<td i18n>Original SHA256 checksum</td> <td i18n>Original SHA256 checksum</td>
<td><span class="badge bg-light font-monospace text-wrap text-break text-start">{{metadata()?.original_checksum}}</span></td> <td>{{metadata()?.original_checksum}}</td>
</tr> </tr>
<tr> <tr>
<td i18n>Original file size</td> <td i18n>Original file size</td>
@@ -319,9 +339,9 @@
<td>{{metadata()?.original_mime_type}}</td> <td>{{metadata()?.original_mime_type}}</td>
</tr> </tr>
@if (metadata()?.has_archive_version) { @if (metadata()?.has_archive_version) {
<tr class="align-middle"> <tr>
<td i18n>Archive SHA256 checksum</td> <td i18n>Archive SHA256 checksum</td>
<td><span class="badge bg-light font-monospace text-wrap text-break text-start">{{metadata()?.archive_checksum}}</span></td> <td>{{metadata()?.archive_checksum}}</td>
</tr> </tr>
} }
@if (metadata()?.has_archive_version) { @if (metadata()?.has_archive_version) {
@@ -334,10 +354,6 @@
</table> </table>
} }
@if (metadata()?.barcodes?.length > 0) {
<h6 i18n>Barcodes</h6>
<pngx-document-barcodes [barcodes]="metadata().barcodes"></pngx-document-barcodes>
}
@if (metadata()?.original_metadata?.length > 0) { @if (metadata()?.original_metadata?.length > 0) {
<pngx-metadata-collapse i18n-title title="Original document metadata" [metadata]="metadata()?.original_metadata"></pngx-metadata-collapse> <pngx-metadata-collapse i18n-title title="Original document metadata" [metadata]="metadata()?.original_metadata"></pngx-metadata-collapse>
} }
@@ -386,7 +402,7 @@
<a ngbNavLink i18n>Permissions</a> <a ngbNavLink i18n>Permissions</a>
<ng-template ngbNavContent> <ng-template ngbNavContent>
<div class="mb-3"> <div class="mb-3">
<pngx-permissions-form title="" [users]="users()" formControlName="permissions_form"></pngx-permissions-form> <pngx-permissions-form [users]="users()" formControlName="permissions_form"></pngx-permissions-form>
</div> </div>
</ng-template> </ng-template>
</li> </li>
@@ -477,29 +493,6 @@
(loadError)="onError($event)" (loadError)="onError($event)"
(afterLoadComplete)="pdfPreviewLoaded($event)"> (afterLoadComplete)="pdfPreviewLoaded($event)">
</pngx-pdf-viewer> </pngx-pdf-viewer>
<div class="position-absolute bottom-0 start-50 translate-middle-x z-1 mb-3 d-flex gap-2 p-1 bg-body border rounded shadow">
@if (previewNumPages()) {
<div class="input-group input-group-sm w-auto flex-nowrap">
<input class="form-control flex-grow-0 w-auto" type="number" min="1" [max]="previewNumPages()" [ngModelOptions]="{standalone: true}" [ngModel]="previewCurrentPage()" (ngModelChange)="previewCurrentPage.set($event)" aria-label="Page" i18n-aria-label />
<div class="input-group-text" i18n>of {{previewNumPages()}}</div>
</div>
}
<div class="input-group input-group-sm w-auto flex-nowrap">
<button class="btn btn-outline-secondary" (click)="decreaseZoom()" aria-label="Zoom out" i18n-aria-label>
<i-bs width="1em" height="1em" name="dash"></i-bs>
</button>
<select class="form-select" (change)="setZoom($event.target.value)" [ngModelOptions]="{standalone: true}" [ngModel]="currentZoom" aria-label="Zoom" i18n-aria-label>
@for (setting of zoomSettings; track setting) {
<option [value]="setting">
{{ getZoomSettingTitle(setting) }}
</option>
}
</select>
<button class="btn btn-outline-secondary" (click)="increaseZoom()" aria-label="Zoom in" i18n-aria-label>
<i-bs width="1em" height="1em" name="plus"></i-bs>
</button>
</div>
</div>
</div> </div>
} @else { } @else {
<object [data]="previewUrl() | safeUrl" class="preview-sticky" width="100%"></object> <object [data]="previewUrl() | safeUrl" class="preview-sticky" width="100%"></object>
@@ -662,45 +662,6 @@ describe('DocumentDetailComponent', () => {
) )
}) })
it.each([
['tag', 'createTag', 'tags', 'suggested_tags'],
[
'document type',
'createDocumentType',
'document_type',
'suggested_document_types',
],
[
'correspondent',
'createCorrespondent',
'correspondent',
'suggested_correspondents',
],
])(
'should create a %s after ML-only suggestions',
(_, method, field, suggestedField) => {
initNormally()
component.suggestions.set({ tags: [1] })
let openModal: NgbModalRef
modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
component[method]('New value')
openModal.componentInstance.succeeded.next({
id: 12,
name: 'New value',
is_inbox_tag: false,
color: '#ff0000',
text_color: '#000000',
})
if (field === 'tags') {
expect(component.tagsInput.value).toContain(12)
} else {
expect(component.documentForm.get(field).value).toBe(12)
}
expect(component.suggestions()[suggestedField]).toEqual([])
}
)
it('should support creating storage path', () => { it('should support creating storage path', () => {
initNormally() initNormally()
let openModal: NgbModalRef let openModal: NgbModalRef
@@ -135,7 +135,6 @@ import { ShareLinksDialogComponent } from '../common/share-links-dialog/share-li
import { SuggestionsDropdownComponent } from '../common/suggestions-dropdown/suggestions-dropdown.component' import { SuggestionsDropdownComponent } from '../common/suggestions-dropdown/suggestions-dropdown.component'
import { DocumentNotesComponent } from '../document-notes/document-notes.component' import { DocumentNotesComponent } from '../document-notes/document-notes.component'
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component' import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
import { DocumentBarcodesComponent } from './document-barcodes/document-barcodes.component'
import { DocumentHistoryComponent } from './document-history/document-history.component' import { DocumentHistoryComponent } from './document-history/document-history.component'
import { DocumentVersionDropdownComponent } from './document-version-dropdown/document-version-dropdown.component' import { DocumentVersionDropdownComponent } from './document-version-dropdown/document-version-dropdown.component'
import { MetadataCollapseComponent } from './metadata-collapse/metadata-collapse.component' import { MetadataCollapseComponent } from './metadata-collapse/metadata-collapse.component'
@@ -178,7 +177,6 @@ interface IncomingDocumentUpdate {
DateComponent, DateComponent,
DocumentLinkComponent, DocumentLinkComponent,
MetadataCollapseComponent, MetadataCollapseComponent,
DocumentBarcodesComponent,
PermissionsFormComponent, PermissionsFormComponent,
SelectComponent, SelectComponent,
TagsComponent, TagsComponent,
@@ -1154,7 +1152,7 @@ export class DocumentDetailComponent
if (this.suggestions()) { if (this.suggestions()) {
this.suggestions.set({ this.suggestions.set({
...this.suggestions(), ...this.suggestions(),
suggested_tags: (this.suggestions().suggested_tags ?? []).filter( suggested_tags: this.suggestions().suggested_tags.filter(
(tag) => tag !== newTag.name (tag) => tag !== newTag.name
), ),
}) })
@@ -1193,12 +1191,10 @@ export class DocumentDetailComponent
this.documentForm.get('document_type').setValue(newDocumentType.id) this.documentForm.get('document_type').setValue(newDocumentType.id)
this.documentForm.get('document_type').markAsDirty() this.documentForm.get('document_type').markAsDirty()
if (this.suggestions()) { if (this.suggestions()) {
this.suggestions.set({ this.suggestions().suggested_document_types =
...this.suggestions(), this.suggestions().suggested_document_types.filter(
suggested_document_types: ( (dt) => dt !== newName
this.suggestions().suggested_document_types ?? [] )
).filter((dt) => dt !== newName),
})
} }
}) })
} }
@@ -1225,12 +1221,10 @@ export class DocumentDetailComponent
this.documentForm.get('correspondent').setValue(newCorrespondent.id) this.documentForm.get('correspondent').setValue(newCorrespondent.id)
this.documentForm.get('correspondent').markAsDirty() this.documentForm.get('correspondent').markAsDirty()
if (this.suggestions()) { if (this.suggestions()) {
this.suggestions.set({ this.suggestions().suggested_correspondents =
...this.suggestions(), this.suggestions().suggested_correspondents.filter(
suggested_correspondents: ( (c) => c !== newName
this.suggestions().suggested_correspondents ?? [] )
).filter((c) => c !== newName),
})
} }
}) })
} }
@@ -484,22 +484,6 @@ describe('FilterEditorComponent', () => {
expect(component.textFilter).toBeNull() expect(component.textFilter).toBeNull()
}) })
it('should ingest title filter rules alongside a fulltext query with date created', () => {
component.filterRules = [
{
rule_type: FILTER_SIMPLE_TITLE,
value: 'foo',
},
{
rule_type: FILTER_FULLTEXT_QUERY,
value: 'created:[-1 week to now]',
},
]
expect(component.dateCreatedRelativeDate).toEqual(1) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now']
expect(component.textFilter).toEqual('foo')
expect(component.textFilterTarget).toEqual('title') // TEXT_FILTER_TARGET_TITLE
})
it('should ingest text filter rules for fulltext query that include date added', () => { it('should ingest text filter rules for fulltext query that include date added', () => {
expect(component.dateAddedRelativeDate).toBeNull() expect(component.dateAddedRelativeDate).toBeNull()
component.filterRules = [ component.filterRules = [
@@ -1861,7 +1845,7 @@ describe('FilterEditorComponent', () => {
]) ])
}) })
it('should keep text filtering separate from date created relative date', () => { it('should carry over text filtering on date created with relative date', () => {
component.textFilter = 'foo' component.textFilter = 'foo'
const dateCreatedDropdown = fixture.debugElement.queryAll( const dateCreatedDropdown = fixture.debugElement.queryAll(
By.directive(DatesDropdownComponent) By.directive(DatesDropdownComponent)
@@ -1871,18 +1855,14 @@ describe('FilterEditorComponent', () => {
fixture.detectChanges() fixture.detectChanges()
tick(400) tick(400)
expect(component.filterRules).toEqual([ expect(component.filterRules).toEqual([
{
rule_type: FILTER_SIMPLE_TEXT,
value: 'foo',
},
{ {
rule_type: FILTER_FULLTEXT_QUERY, rule_type: FILTER_FULLTEXT_QUERY,
value: 'created:[-1 week to now]', value: 'foo,created:[-1 week to now]',
}, },
]) ])
}) })
it('should keep text filtering separate from created and added relative dates', () => { it('should carry over text filtering once with created and added relative dates', () => {
component.textFilter = 'foo' component.textFilter = 'foo'
const datesDropdown = fixture.debugElement.query( const datesDropdown = fixture.debugElement.query(
By.directive(DatesDropdownComponent) By.directive(DatesDropdownComponent)
@@ -1893,18 +1873,14 @@ describe('FilterEditorComponent', () => {
fixture.detectChanges() fixture.detectChanges()
tick(400) tick(400)
expect(component.filterRules).toEqual([ expect(component.filterRules).toEqual([
{
rule_type: FILTER_SIMPLE_TEXT,
value: 'foo',
},
{ {
rule_type: FILTER_FULLTEXT_QUERY, rule_type: FILTER_FULLTEXT_QUERY,
value: 'created:[-1 week to now],added:[-1 month to now]', value: 'foo,created:[-1 week to now],added:[-1 month to now]',
}, },
]) ])
}) })
it('should keep legacy title filters as a title search when adding a created relative date', () => { it('should convert legacy title filters into full text query when adding a created relative date', () => {
component.filterRules = [ component.filterRules = [
{ {
rule_type: FILTER_TITLE, rule_type: FILTER_TITLE,
@@ -1919,18 +1895,14 @@ describe('FilterEditorComponent', () => {
fixture.detectChanges() fixture.detectChanges()
tick(400) tick(400)
expect(component.filterRules).toEqual([ expect(component.filterRules).toEqual([
{
rule_type: FILTER_SIMPLE_TITLE,
value: 'foo',
},
{ {
rule_type: FILTER_FULLTEXT_QUERY, rule_type: FILTER_FULLTEXT_QUERY,
value: 'created:[-1 week to now]', value: 'foo,created:[-1 week to now]',
}, },
]) ])
}) })
it('should keep simple title filters as a title search when adding a created relative date', () => { it('should convert simple title filters into full text query when adding a created relative date', () => {
component.filterRules = [ component.filterRules = [
{ {
rule_type: FILTER_SIMPLE_TITLE, rule_type: FILTER_SIMPLE_TITLE,
@@ -1945,13 +1917,9 @@ describe('FilterEditorComponent', () => {
fixture.detectChanges() fixture.detectChanges()
tick(400) tick(400)
expect(component.filterRules).toEqual([ expect(component.filterRules).toEqual([
{
rule_type: FILTER_SIMPLE_TITLE,
value: 'foo',
},
{ {
rule_type: FILTER_FULLTEXT_QUERY, rule_type: FILTER_FULLTEXT_QUERY,
value: 'created:[-1 week to now]', value: 'foo,created:[-1 week to now]',
}, },
]) ])
}) })
@@ -2037,7 +2005,7 @@ describe('FilterEditorComponent', () => {
]) ])
}) })
it('should keep text filtering separate from date added relative date', () => { it('should carry over text filtering on date added with relative date', () => {
component.textFilter = 'foo' component.textFilter = 'foo'
const datesDropdown = fixture.debugElement.query( const datesDropdown = fixture.debugElement.query(
By.directive(DatesDropdownComponent) By.directive(DatesDropdownComponent)
@@ -2047,13 +2015,9 @@ describe('FilterEditorComponent', () => {
fixture.detectChanges() fixture.detectChanges()
tick(400) tick(400)
expect(component.filterRules).toEqual([ expect(component.filterRules).toEqual([
{
rule_type: FILTER_SIMPLE_TEXT,
value: 'foo',
},
{ {
rule_type: FILTER_FULLTEXT_QUERY, rule_type: FILTER_FULLTEXT_QUERY,
value: 'added:[-1 week to now]', value: 'foo,added:[-1 week to now]',
}, },
]) ])
}) })
@@ -1083,6 +1083,23 @@ export class FilterEditorComponent
(fr) => fr.rule_type == FILTER_FULLTEXT_QUERY (fr) => fr.rule_type == FILTER_FULLTEXT_QUERY
) )
// if had a title / content search and added a relative date we need to carry it over...
if (
!existingRule &&
this._textFilter?.length > 0 &&
(this.textFilterTarget == TEXT_FILTER_TARGET_TITLE_CONTENT ||
this.textFilterTarget == TEXT_FILTER_TARGET_TITLE)
) {
existingRule = filterRules.find(
(fr) =>
fr.rule_type == FILTER_TITLE_CONTENT ||
fr.rule_type == FILTER_SIMPLE_TEXT ||
fr.rule_type == FILTER_TITLE ||
fr.rule_type == FILTER_SIMPLE_TITLE
)
existingRule.rule_type = FILTER_FULLTEXT_QUERY
}
let queryArgs = existingRule?.value.split(',') ?? [] let queryArgs = existingRule?.value.split(',') ?? []
if (this.dateCreatedRelativeDate !== null) { if (this.dateCreatedRelativeDate !== null) {
const rd = RELATIVE_DATE_QUERYSTRINGS.find( const rd = RELATIVE_DATE_QUERYSTRINGS.find(
-7
View File
@@ -1,7 +0,0 @@
export interface DocumentBarcode {
page: number
value: string
format: string
}
-4
View File
@@ -1,5 +1,3 @@
import { DocumentBarcode } from './document-barcode'
export interface DocumentMetadata { export interface DocumentMetadata {
original_checksum?: string original_checksum?: string
@@ -14,6 +12,4 @@ export interface DocumentMetadata {
has_archive_version?: boolean has_archive_version?: boolean
lang?: string lang?: string
barcodes?: DocumentBarcode[]
} }
-8
View File
@@ -330,13 +330,6 @@ export const PaperlessConfigOptions: ConfigOption[] = [
config_key: 'PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT', config_key: 'PAPERLESS_CONSUMER_TAG_BARCODE_SPLIT',
category: ConfigCategory.Barcode, category: ConfigCategory.Barcode,
}, },
{
key: 'barcode_store_values',
title: $localize`Store Barcode Contents`,
type: ConfigOptionType.Boolean,
config_key: 'PAPERLESS_CONSUMER_STORE_BARCODE_VALUES',
category: ConfigCategory.Barcode,
},
{ {
key: 'ai_enabled', key: 'ai_enabled',
title: $localize`AI Enabled`, title: $localize`AI Enabled`,
@@ -465,7 +458,6 @@ export interface PaperlessConfig extends ObjectWithId {
barcode_enable_tag: boolean barcode_enable_tag: boolean
barcode_tag_mapping: object barcode_tag_mapping: object
barcode_tag_split: boolean barcode_tag_split: boolean
barcode_store_values: boolean
remote_ocr_engine: string remote_ocr_engine: string
remote_ocr_api_key: string remote_ocr_api_key: string
remote_ocr_endpoint: string remote_ocr_endpoint: string
+1 -1
View File
@@ -8,7 +8,7 @@ export const environment = {
apiVersion: '10', // match src/paperless/settings.py apiVersion: '10', // match src/paperless/settings.py
appTitle: DEFAULT_APP_TITLE, appTitle: DEFAULT_APP_TITLE,
tag: 'prod', tag: 'prod',
version: '3.3.1', version: '3.2.1',
webSocketHost: window.location.host, webSocketHost: window.location.host,
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:', webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
webSocketBaseUrl: base_url.pathname + 'ws/', webSocketBaseUrl: base_url.pathname + 'ws/',
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Loaded 100 of 274 files, more files were not shown because too many files have changed in this diff. Show more