Commit Graph
11679 Commits
Author SHA1 Message Date
shamoonandGitHub 371c4f57d3 Fix: fix broken migration in 3.0.1 (#13242) 2026-07-23 17:59:03 -07:00
stumpylog 5569447ea0 Bumps the version to 3.0.1 everywhere v3.0.1 2026-07-23 15:59:53 -07:00
stumpylog 17796357ee Merge remote-tracking branch 'origin/dev' 2026-07-23 15:55:41 -07:00
b47b9800a1 New Crowdin translations by GitHub Action (#13207)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-07-23 15:54:05 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
0be884d441 Chore(deps-dev): Bump postcss (#13236)
Bumps the npm_and_yarn group with 1 update in the /src/paperless_mail/templates directory: [postcss](https://github.com/postcss/postcss).


Updates `postcss` from 8.5.6 to 8.5.22
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.6...8.5.22)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.22
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-23 20:21:15 +00:00
GitHub Actions fce2fbb8f6 Auto translate strings 2026-07-23 19:22:48 +00:00
fa01396dfd Fix: selection_data re-derives the filtered document set 5 times over (#13229)
* Fix: selection_data re-derives the filtered document set 5 times over

_get_selection_data_for_queryset() (powers ?include_selection_data=true on
the document list and search endpoints) computed document_count for
Correspondent/DocumentType/StoragePath/Tag/CustomField by embedding the
caller's full filtered queryset -- filters plus the permission check -- as
a subquery inside 5 separate Count(filter=Q(documents__in=queryset), ...)
calls. Each one re-evaluates that whole queryset from scratch.

Resolve the document ids once into a concrete list and reuse it across all
five annotations instead. For Tag/CustomField specifically (M2M via a
through-model table), also route through annotate_document_count_by_ids()
-- extracted from the tag/custom-field document_count fix (#13203) -- to
avoid the same Count(filter=Q(id__in=...), distinct=True)-on-an-M2M-relation
anti-pattern diagnosed there.

On a 400k-document/1,000-tag corpus, the correspondents portion alone
previously didn't finish within several minutes (killed twice while
investigating, including one run that left a zombie query still consuming
a CPU 30+ minutes later). All five queries together now complete in
~30-35s. Root-caused from a real report (paperless-ngx#13201) via a
different, already-fixed query (#13205) -- this one hasn't been reported
in the wild yet, found by auditing the same call path.

Depends on #13203 for annotate_document_count_by_ids().

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* Address review feedback: drop unnecessary ordering before collecting ids

queryset passed into _get_selection_data_for_queryset() carries the
default/user-specified ordering, which is irrelevant once we're only
collecting a flat id list. Clearing it removes a pointless sort.

No measurable change in benchmarking at 400k documents -- the id-list
materialization/IN-clause cost still dominates -- but it's a free,
strictly-correct cleanup, not just noise-neutral in the other direction.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 19:21:20 +00:00
GitHub Actions c9443e890f Auto translate strings 2026-07-23 18:43:46 +00:00
21e4721a82 Chore: reword "Settings > Default permissions" description for clarity (#13232)
---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-07-23 18:41:26 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1985da14f8 Chore(deps): Bump pyasn1 in the uv group across 1 directory (#13237)
Bumps the uv group with 1 update in the / directory: [pyasn1](https://github.com/pyasn1/pyasn1).


Updates `pyasn1` from 0.6.3 to 0.6.4
- [Release notes](https://github.com/pyasn1/pyasn1/releases)
- [Changelog](https://github.com/pyasn1/pyasn1/blob/main/CHANGES.rst)
- [Commits](https://github.com/pyasn1/pyasn1/compare/v0.6.3...v0.6.4)

---
updated-dependencies:
- dependency-name: pyasn1
  dependency-version: 0.6.4
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-23 18:26:56 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
59a737f669 Chore(deps): Bump the uv group across 1 directory with 8 updates (#13189)
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.14.1
  dependency-type: indirect
- dependency-name: cryptography
  dependency-version: 48.0.1
  dependency-type: indirect
- dependency-name: pi-heif
  dependency-version: 1.3.0
  dependency-type: indirect
- dependency-name: pillow
  dependency-version: 12.3.0
  dependency-type: indirect
- dependency-name: pyjwt
  dependency-version: 2.13.0
  dependency-type: indirect
- dependency-name: setuptools
  dependency-version: 83.0.0
  dependency-type: indirect
- dependency-name: torch
  dependency-version: 2.13.0
  dependency-type: direct:production
- dependency-name: tornado
  dependency-version: 6.5.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-23 17:58:06 +00:00
Trenton HandGitHub 90bee4285f Fix: exclude the source document from its own RAG similarity results (#13233)
query_similar_documents() never excluded the querying document itself, so a document could appear in its own "similar documents" context, duplicating its content into the AI-suggestions prompt and inflating prompt size/tokens  unnecessarily. Add NE filter support to the vector store and exclude the source document's id at query time.
2026-07-23 10:25:30 -07:00
Trenton HandGitHub 7dca0bfa6a Fix (#13214): split mailrule maximum_age clamp into its own migration (#13231)
Avoids a Postgres error where the clamp UPDATE and the following ALTER TABLE on paperless_mail_mailrule share a transaction, but the table's FKs are deferrable, so pending trigger events block the ALTER. 

Also fixes a verbose_name mismatch in migration 0013.
2026-07-23 17:09:28 +00:00
GitHub Actions 3a34bdc7b0 Auto translate strings 2026-07-23 16:54:02 +00:00
shamoonandGitHub c7899a9a84 Chore: mark yes in confirm button for translation (#13225) 2026-07-23 16:52:04 +00:00
shamoonandGitHub 82fc1c6cee Fix: correct URL for W001 check (#13220) 2026-07-23 16:39:00 +00:00
shamoonandGitHub cd3c162e6e Fix: ensure create dialog uses correct attribute type (#13221) 2026-07-23 16:22:27 +00:00
FabianandGitHub 50c88dab7a Fix: correct database engine from postgres to postgresql in the Porttainer Compose file (#13213) 2026-07-23 07:40:39 -07:00
GitHub Actions 5744a75e6a Auto translate strings 2026-07-23 14:24:56 +00:00
02c6b5f3e4 Fix (beta): remove unnecessary .distinct() dominating document list queries (#13205)
DocumentViewSet.get_queryset() carried a blanket .distinct() left over from
an older query shape (predating the version-file feature and the
correlated-subquery rewrite of num_notes/effective_content). Nothing in the
current base queryset can produce duplicate document rows: select_related
is all FK-to-PK, and the permission filter is a boolean id__in predicate,
not a join. id (the PK) is also always selected and inherently unique, so
.distinct() was a structural no-op for correctness.

It was not a no-op for cost. EXPLAIN showed it forcing a full sort-then-
dedupe over the entire permission-visible document set before LIMIT could
apply -- 340k rows for a 25-row page, with the effective_content/num_notes
correlated subqueries re-executed once per row as a result. This was the
dominant cost behind the original report's document overview slowness,
well beyond the get_user_can_change N+1 fixed separately.

Removing it isn't safe on its own, though -- auditing found two existing
filters that rely on an M2M join *without* deduping themselves, previously
papered over by the blanket .distinct():
- InboxFilter: a document with two tags both flagged is_inbox_tag=True
  (nothing prevents that) would be returned twice for ?is_in_inbox=true.
- CustomFieldsFilter: a document with multiple custom field instances
  matching different OR-ed branches would be returned once per match.

Fixed both locally, matching the pattern ObjectFilter already used for
tags__id__in/custom_fields__id__in. Added regression tests for both --
confirmed they fail without the local .distinct() calls.

Benchmarked against the same 400k-document/1,000-tag corpus: document list
wall-clock for a permission-restricted user drops from ~15.8s to ~2.25s
(~7x), closing most of the previous gap to a superuser's ~1.8s.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 07:22:46 -07:00
shamoon b566232542 Documentation: correct search docs for v3 2026-07-23 06:25:33 -07:00
shamoonandGitHub be40356d2e Fix: also wrap non-breaking words in chat messages, handle whitespace (#13211) 2026-07-22 22:25:19 -07:00
GitHub Actions 5ea8cf2335 Auto translate strings 2026-07-23 04:30:13 +00:00
84a04301d1 Fix: tag/custom-field document_count scales badly with tag count (#13203)
* Fix (beta): tag/custom-field document_count scales badly with tag count

TagViewSet and CustomFieldViewSet's document_count annotation used a
per-row correlated subquery (annotate_document_count_for_related_queryset),
executed once per tag/custom-field row. Fine at a few dozen rows, but at
~1,000 tags it degrades to a full per-tag GroupAggregate over the tags M2M
table -- 6s+ in production reports, confirmed via EXPLAIN (loops=1000).

Replaced with a single, independent GROUP BY over the through table
(permission filter expressed as a plain WHERE, not an aggregate FILTER),
then injected via Case/When. Also tried a more "obvious" fix -- a plain
Count(filter=Q(id__in=permitted_ids), distinct=True) directly on the M2M
relation -- but that's worse: Postgres fails to plan the id__in check as a
semi-join once a large M2M bridge table is involved, and instead re-checks
subquery membership once per joined row.

Benchmarked against a synthetic corpus (400k documents, 1,000 tags @ ~5/doc,
matching real-world reports in discussion #13161): tag list wall-clock drops
from ~180s to ~8s for a permission-restricted user, ~21s to ~8s for a
superuser. No measurable change at typical home-instance scale (tens of
tags).

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* Address review feedback: restore validation, scope aggregation to queryset

- Restore the document_count_source_field validation helper dropped during
  the refactor -- misconfiguring a viewset (document_count_through set
  without document_count_source_field) now fails fast with a clear error
  again instead of an obscure runtime failure.
- Restrict annotate_document_count_for_related_queryset()'s through-table
  aggregation to rows whose related_object_field is one of the annotated
  queryset's pks. No-op for the main tag-list call site (queryset is all
  tags), but avoids unnecessary work for narrower callers like the tag
  descendants branch in TagViewSet.list().

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 21:28:49 -07:00
GitHub Actions 3edda48324 Auto translate strings 2026-07-23 04:04:23 +00:00
f86bc57880 Fix: batch document user_can_change checks to avoid per-row N+1 (#13204)
* Fix (beta): batch document user_can_change checks to avoid per-row N+1

DocumentSerializer.get_user_can_change() built a fresh
ObjectPermissionChecker and issued a guardian permission-table query for
every document row not owned by the requesting user -- correct, but O(N)
per page load for any non-superuser viewing documents owned by others.

BulkPermissionMixin already batches this exact lookup (2 queries total,
regardless of page size) for Correspondent/Tag/DocumentType/CustomField,
but was gated behind the rarely-used `full_perms` flag and DocumentViewSet
didn't inherit it at all. Changed the gate to "any list action" (cheap:
just two extra queries per page) and added BulkPermissionMixin to
DocumentViewSet, then updated get_user_can_change to consult that batched
context before falling back to a fresh guardian check.

Preserves guardian's own superuser shortcut explicitly (has_perm() special-
cases is_superuser without a query; the batched-context path doesn't, so it
needed its own check) -- covered by a new regression test, since no
existing test exercised a superuser viewing an other-owned document.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* Fix (beta): don't batch permissions for tantivy search results

UnifiedSearchViewSet.list() returns SearchHit/dict-like objects for
text/title/query/more_like_id search requests, not Document ORM instances.
Adding BulkPermissionMixin to DocumentViewSet (previous commit) meant its
get_serializer_context() ran for search responses too, and its
_get_object_perms() -- which expects real model instances with .pk --
crashed on the dict-like hits with AttributeError, turning every search
request into a 400.

Skip the batching specifically for search requests (existing
_is_search_request() check) by calling past BulkPermissionMixin in the
MRO; non-search list() calls (which return a real Document queryset) are
unaffected and still get the batching.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 04:02:46 +00:00
GitHub Actions 97662b6c5c Auto translate strings 2026-07-23 01:55:12 +00:00
shamoonandGitHub 6e052d5507 Fix: fix app title restoration (#13208) 2026-07-22 18:53:45 -07:00
shamoonandGitHub afb19fe637 Fix: handle long wrapping titles in AI chat document list (#13206) 2026-07-22 16:15:14 -07:00
github-actions[bot]GitHubgithub-actions <41898282+github-actions[bot]@users.noreply.github.com>shamoonstumpylog
1203203e08 Documentation: Add v3.0.0 changelog (#13199)
---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
Co-authored-by: stumpylog <797416+stumpylog@users.noreply.github.com>
2026-07-22 13:05:33 -07:00
shamoon 6cf3c47814 Documentation: fix a couple broken links 2026-07-22 11:37:56 -07:00
shamoon 24aee98ad5 Documentation: fix v3 migration guide header level 2026-07-22 11:31:15 -07:00
shamoonandGitHub 2ab1ac7cb8 [Beta] Paperless-ngx v3.0.0 Beta (#12713) v3.0.0 2026-07-22 10:55:13 -07:00
shamoon c2d9c52cde Merge branch 'main' into beta 2026-07-22 10:30:57 -07:00
cdb11b94b5 New Crowdin translations by GitHub Action (#13153)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-07-22 10:30:33 -07:00
GitHub Actions cfd7a3a7f0 Auto translate strings 2026-07-22 17:18:01 +00:00
shamoon c6329f4b76 Merge branch 'beta' into dev 2026-07-22 10:16:33 -07:00
shamoonandGitHub 5e06987102 Chore (beta): mark first batch of thumbnails as priorty for LCP warning (#13196) 2026-07-22 10:15:56 -07:00
shamoonandGitHub 3f93557934 Fix (beta): prevent pdfjs offsetParent warning (#13197) 2026-07-22 10:06:41 -07:00
shamoon afe6ad2192 Chore: remove unused import 2026-07-22 09:23:18 -07:00
GitHub Actions a3f9fa0369 Auto translate strings 2026-07-22 15:45:25 +00:00
shamoonandGitHub e106618c39 Fix (beta): dont include hidden buttons in virtual scroll container height (#13194) 2026-07-22 08:42:31 -07:00
shamoonandGitHub d113826cf8 Tweakhancement: allow some bulk operations with all (#13193) 2026-07-22 08:28:49 -07:00
shamoonandGitHub c063b3799f Fix (beta): fix custom field bulk editing for 'all' (#13191) 2026-07-22 08:14:53 -07:00
81795bc93a Security (beta): enforce current permissions in autocomplete (#13188)
Co-authored-by: stumpylog <797416+stumpylog@users.noreply.github.com>
2026-07-22 08:07:43 -07:00
shamoonandGitHub 6664f05543 Security (beta): bound email linkification (#13187) 2026-07-22 07:41:12 -07:00
shamoonandGitHub 8aab34ea73 Security (beta): two small permission check fixes (#13186) 2026-07-21 16:50:28 +00:00
8404198ec8 Fix (beta): short-circuit ObjectOwnedOrGrantedPermissionsFilter for superusers (#13183)
Mirrors the existing short-circuit already present in its sibling class,
ObjectOwnedPermissionsFilter, which this one lacked.

Benchmarked as a superuser against a 100k-document corpus: no measurable
difference. django-guardian's own get_objects_for_user() (called via
super().filter_queryset()) already returns the queryset unrestricted for
superusers before this change, so the guardian permission tables were
never actually queried for superusers either way. This change only skips
building a redundant 3-way OR of conditions that are all subsets of the
already-unrestricted queryset -- worth keeping for clarity/consistency
with the sibling filter, not as a performance claim.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 07:31:58 -07:00
d8d8872414 Fix (beta): compute num_notes via a subquery instead of Count()+distinct() (#13182)
DocumentViewSet.get_queryset() annotated num_notes via a LEFT JOIN to
documents_note plus Count(), which requires the database to aggregate
every matching document's note count before it can even sort or paginate
the result -- on every list request, not just filtered ones.

Switched to the same correlated-subquery pattern already used two lines
above for effective_content (Subquery + OuterRef), which Django compiles
to portable SQL across sqlite/postgresql/mariadb rather than a join-based
aggregate.

Benchmarked against a 100k synthetic document corpus (Postgres): the
plain document list request dropped from ~2.2-2.4s to ~0.9-1.0s. Note
that a separate, larger cost remains in the same queryset's plain
.distinct() call, which still forces a full sort over every matching
row regardless of this fix -- tracked separately, not addressed here.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 13:59:44 -07:00
shamoonandGitHub ff609c2987 Change: update root modified timestamp on version changes (#13170) 2026-07-20 15:45:24 +00:00