mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-07-26 22:04:56 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1efdd2c1a |
@@ -25,7 +25,7 @@
|
||||
<input #textFilterInput class="form-control form-control-sm" type="text"
|
||||
[disabled]="textFilterModifierIsNull"
|
||||
[(ngModel)]="textFilter"
|
||||
(keydown)="textFilterKeydown($event)"
|
||||
(keyup)="textFilterKeyup($event)"
|
||||
[ngbTypeahead]="searchAutoComplete"
|
||||
(selectItem)="itemSelected($event)"
|
||||
[readonly]="textFilterTarget === 'fulltext-morelike'">
|
||||
|
||||
+3
-3
@@ -2180,17 +2180,17 @@ describe('FilterEditorComponent', () => {
|
||||
it('should support Enter / Esc key on text field', () => {
|
||||
component.textFilterInput.nativeElement.value = 'foo'
|
||||
component.textFilterInput.nativeElement.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'Enter' })
|
||||
new KeyboardEvent('keyup', { key: 'Enter' })
|
||||
)
|
||||
expect(component.textFilter).toEqual('foo')
|
||||
component.textFilterInput.nativeElement.value = 'foo bar'
|
||||
component.textFilterInput.nativeElement.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'Escape' })
|
||||
new KeyboardEvent('keyup', { key: 'Escape' })
|
||||
)
|
||||
expect(component.textFilter).toEqual('')
|
||||
const blurSpy = jest.spyOn(component.textFilterInput.nativeElement, 'blur')
|
||||
component.textFilterInput.nativeElement.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'Escape' })
|
||||
new KeyboardEvent('keyup', { key: 'Escape' })
|
||||
)
|
||||
expect(blurSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -1312,7 +1312,7 @@ export class FilterEditorComponent
|
||||
}
|
||||
}
|
||||
|
||||
textFilterKeydown(event: KeyboardEvent) {
|
||||
textFilterKeyup(event: KeyboardEvent) {
|
||||
if (event.key == 'Enter') {
|
||||
const filterString = (
|
||||
this.textFilterInput.nativeElement as HTMLInputElement
|
||||
|
||||
@@ -2520,7 +2520,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/tasks/tasks.component.ts</context>
|
||||
<context context-type="linenumber">77</context>
|
||||
</context-group>
|
||||
<target state="translated">LLM-Index</target>
|
||||
<target state="translated">LLM-Indizierung</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6402092370576716734" datatype="html" approved="yes">
|
||||
<source>Empty Trash</source>
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-25 07:12+0000\n"
|
||||
"PO-Revision-Date: 2026-07-25 07:14\n"
|
||||
"PO-Revision-Date: 2026-07-26 12:32\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Language: de_DE\n"
|
||||
|
||||
@@ -5,30 +5,12 @@ from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
def clamp_applicationconfiguration_integer_fields(apps, schema_editor):
|
||||
# Clamp barcode_dpi, barcode_max_pages, image_dpi and pages because of
|
||||
# PositiveIntegerField --> PositiveSmallIntegerField
|
||||
ApplicationConfiguration = apps.get_model("paperless", "ApplicationConfiguration")
|
||||
ApplicationConfiguration.objects.filter(barcode_dpi__gt=32767).update(
|
||||
barcode_dpi=32767,
|
||||
)
|
||||
ApplicationConfiguration.objects.filter(barcode_max_pages__gt=32767).update(
|
||||
barcode_max_pages=32767,
|
||||
)
|
||||
ApplicationConfiguration.objects.filter(image_dpi__gt=32767).update(image_dpi=32767)
|
||||
ApplicationConfiguration.objects.filter(pages__gt=32767).update(pages=32767)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("paperless", "0006_applicationconfiguration_barcode_tag_split"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
clamp_applicationconfiguration_integer_fields,
|
||||
migrations.RunPython.noop,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="applicationconfiguration",
|
||||
name="barcode_dpi",
|
||||
|
||||
@@ -2,8 +2,6 @@ from pydantic import BaseModel
|
||||
|
||||
|
||||
class DocumentClassifierSchema(BaseModel):
|
||||
"""Schema for document classification suggestions."""
|
||||
|
||||
title: str
|
||||
tags: list[str]
|
||||
correspondents: list[str]
|
||||
|
||||
@@ -5,7 +5,6 @@ from datetime import timedelta
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils import timezone
|
||||
from filelock import FileLock
|
||||
from filelock import ReadWriteLock
|
||||
@@ -168,19 +167,6 @@ def write_store(embed_model_name: str | None = None):
|
||||
yield store
|
||||
|
||||
|
||||
def _safe_related_name(document: Document, field: str) -> str | None:
|
||||
"""
|
||||
Returns the ``name`` of a related object (correspondent, document_type,
|
||||
storage_path), or None if the FK is unset or points at a row that has
|
||||
since been deleted (e.g. concurrently with this call).
|
||||
"""
|
||||
try:
|
||||
related = getattr(document, field)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
return related.name if related else None
|
||||
|
||||
|
||||
def build_document_node(
|
||||
document: Document,
|
||||
*,
|
||||
@@ -194,10 +180,14 @@ def build_document_node(
|
||||
"document_id": str(document.id),
|
||||
"title": document.title,
|
||||
"tags": [t.name for t in document.tags.all()],
|
||||
"correspondent": _safe_related_name(document, "correspondent"),
|
||||
"document_type": _safe_related_name(document, "document_type"),
|
||||
"correspondent": document.correspondent.name
|
||||
if document.correspondent
|
||||
else None,
|
||||
"document_type": document.document_type.name
|
||||
if document.document_type
|
||||
else None,
|
||||
"filename": document.filename,
|
||||
"storage_path": _safe_related_name(document, "storage_path"),
|
||||
"storage_path": document.storage_path.name if document.storage_path else None,
|
||||
"archive_serial_number": document.archive_serial_number,
|
||||
"created": document.created.isoformat() if document.created else None,
|
||||
"added": document.added.isoformat() if document.added else None,
|
||||
|
||||
@@ -8,9 +8,7 @@ from django.test import override_settings
|
||||
from django.utils import timezone
|
||||
from llama_index.core.schema import MetadataMode
|
||||
|
||||
from documents.models import Correspondent
|
||||
from documents.models import Document
|
||||
from documents.models import DocumentType
|
||||
from documents.models import PaperlessTask
|
||||
from documents.signals import document_consumption_finished
|
||||
from documents.signals import document_updated
|
||||
@@ -97,38 +95,6 @@ def test_build_document_node_structured_fields_in_metadata(
|
||||
assert "modified" in node.metadata
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_build_document_node_survives_concurrently_deleted_correspondent(
|
||||
real_document: Document,
|
||||
) -> None:
|
||||
"""Regression test for #13314.
|
||||
|
||||
If a document's correspondent (or document type) is deleted after the
|
||||
in-memory Document instance was loaded but before build_document_node
|
||||
resolves the relation, accessing the FK must not raise -- it should
|
||||
behave like an unset FK and produce None in the metadata instead of
|
||||
aborting the whole indexing pass.
|
||||
"""
|
||||
correspondent = Correspondent.objects.create(name="Stale Correspondent")
|
||||
document_type = DocumentType.objects.create(name="Stale Type")
|
||||
real_document.correspondent = correspondent
|
||||
real_document.document_type = document_type
|
||||
real_document.save()
|
||||
|
||||
# Re-fetch to get an instance whose correspondent/document_type relations
|
||||
# are unresolved (not yet cached), mirroring a task that loaded the
|
||||
# document before the concurrent deletion below.
|
||||
stale_document = Document.objects.get(pk=real_document.pk)
|
||||
|
||||
correspondent.delete()
|
||||
document_type.delete()
|
||||
|
||||
nodes = indexing.build_document_node(stale_document)
|
||||
assert len(nodes) > 0
|
||||
assert nodes[0].metadata["correspondent"] is None
|
||||
assert nodes[0].metadata["document_type"] is None
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_build_document_node_excludes_document_id_from_llm_context(
|
||||
real_document: Document,
|
||||
|
||||
@@ -10,24 +10,6 @@ def clamp_mailrule_maximum_age(apps, schema_editor):
|
||||
MailRule.objects.filter(maximum_age__gt=32767).update(maximum_age=32767)
|
||||
|
||||
|
||||
def clamp_mailrule_order(apps, schema_editor):
|
||||
# The order field of MailRule is only used for relative sorting
|
||||
# (account.rules.order_by("order")), so out-of-range values must be
|
||||
# renumbered by rank rather than clamped to a single value, which
|
||||
# would collapse distinct rules to the same order.
|
||||
MailRule = apps.get_model("paperless_mail", "MailRule")
|
||||
if (
|
||||
MailRule.objects.filter(order__gt=32767).exists()
|
||||
or MailRule.objects.filter(
|
||||
order__lt=-32768,
|
||||
).exists()
|
||||
): # pragma: no cover
|
||||
for index, rule_id in enumerate(
|
||||
MailRule.objects.order_by("order", "pk").values_list("pk", flat=True),
|
||||
):
|
||||
MailRule.objects.filter(pk=rule_id).update(order=index)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
# The data update must commit before PostgreSQL rewrites the table, otherwise
|
||||
# pending foreign-key trigger events can block the subsequent ALTER TABLE.
|
||||
@@ -153,10 +135,6 @@ class Migration(migrations.Migration):
|
||||
verbose_name="maximum age",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
clamp_mailrule_order,
|
||||
migrations.RunPython.noop,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="mailrule",
|
||||
name="order",
|
||||
|
||||
Reference in New Issue
Block a user