mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-09-30 01:32:43 -05:00
Merge branch 'dev' into feature-ai
This commit is contained in:
@@ -181,6 +181,7 @@ def modify_custom_fields(
|
||||
defaults[value_field] = value
|
||||
if (
|
||||
custom_field.data_type == CustomField.FieldDataType.DOCUMENTLINK
|
||||
and value
|
||||
and doc_id in value
|
||||
):
|
||||
# Prevent self-linking
|
||||
|
@@ -230,6 +230,7 @@ class CustomFieldsFilter(Filter):
|
||||
| qs.filter(custom_fields__value_monetary__icontains=value)
|
||||
| qs.filter(custom_fields__value_document_ids__icontains=value)
|
||||
| qs.filter(custom_fields__value_select__in=option_ids)
|
||||
| qs.filter(custom_fields__value_long_text__icontains=value)
|
||||
)
|
||||
else:
|
||||
return qs
|
||||
@@ -314,6 +315,7 @@ class CustomFieldQueryParser:
|
||||
CustomField.FieldDataType.MONETARY: ("basic", "string", "arithmetic"),
|
||||
CustomField.FieldDataType.DOCUMENTLINK: ("basic", "containment"),
|
||||
CustomField.FieldDataType.SELECT: ("basic",),
|
||||
CustomField.FieldDataType.LONG_TEXT: ("basic", "string"),
|
||||
}
|
||||
|
||||
DATE_COMPONENTS = [
|
||||
@@ -845,7 +847,10 @@ class DocumentsOrderingFilter(OrderingFilter):
|
||||
|
||||
annotation = None
|
||||
match field.data_type:
|
||||
case CustomField.FieldDataType.STRING:
|
||||
case (
|
||||
CustomField.FieldDataType.STRING
|
||||
| CustomField.FieldDataType.LONG_TEXT
|
||||
):
|
||||
annotation = Subquery(
|
||||
CustomFieldInstance.objects.filter(
|
||||
document_id=OuterRef("id"),
|
||||
|
@@ -0,0 +1,39 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-13 17:11
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("documents", "1069_workflowtrigger_filter_has_storage_path_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="customfieldinstance",
|
||||
name="value_long_text",
|
||||
field=models.TextField(null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="customfield",
|
||||
name="data_type",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("string", "String"),
|
||||
("url", "URL"),
|
||||
("date", "Date"),
|
||||
("boolean", "Boolean"),
|
||||
("integer", "Integer"),
|
||||
("float", "Float"),
|
||||
("monetary", "Monetary"),
|
||||
("documentlink", "Document Link"),
|
||||
("select", "Select"),
|
||||
("longtext", "Long Text"),
|
||||
],
|
||||
editable=False,
|
||||
max_length=50,
|
||||
verbose_name="data type",
|
||||
),
|
||||
),
|
||||
]
|
@@ -760,6 +760,7 @@ class CustomField(models.Model):
|
||||
MONETARY = ("monetary", _("Monetary"))
|
||||
DOCUMENTLINK = ("documentlink", _("Document Link"))
|
||||
SELECT = ("select", _("Select"))
|
||||
LONG_TEXT = ("longtext", _("Long Text"))
|
||||
|
||||
created = models.DateTimeField(
|
||||
_("created"),
|
||||
@@ -817,6 +818,7 @@ class CustomFieldInstance(SoftDeleteModel):
|
||||
CustomField.FieldDataType.MONETARY: "value_monetary",
|
||||
CustomField.FieldDataType.DOCUMENTLINK: "value_document_ids",
|
||||
CustomField.FieldDataType.SELECT: "value_select",
|
||||
CustomField.FieldDataType.LONG_TEXT: "value_long_text",
|
||||
}
|
||||
|
||||
created = models.DateTimeField(
|
||||
@@ -884,6 +886,8 @@ class CustomFieldInstance(SoftDeleteModel):
|
||||
|
||||
value_select = models.CharField(null=True, max_length=16)
|
||||
|
||||
value_long_text = models.TextField(null=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ("created",)
|
||||
verbose_name = _("custom field instance")
|
||||
|
@@ -202,6 +202,7 @@ def get_custom_fields_context(
|
||||
CustomField.FieldDataType.MONETARY,
|
||||
CustomField.FieldDataType.STRING,
|
||||
CustomField.FieldDataType.URL,
|
||||
CustomField.FieldDataType.LONG_TEXT,
|
||||
}:
|
||||
value = pathvalidate.sanitize_filename(
|
||||
field_instance.value,
|
||||
|
@@ -18,14 +18,17 @@ class TestDocument(TestCase):
|
||||
self.originals_dir = tempfile.mkdtemp()
|
||||
self.thumb_dir = tempfile.mkdtemp()
|
||||
|
||||
override_settings(
|
||||
self.overrides = override_settings(
|
||||
ORIGINALS_DIR=self.originals_dir,
|
||||
THUMBNAIL_DIR=self.thumb_dir,
|
||||
).enable()
|
||||
)
|
||||
|
||||
self.overrides.enable()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
shutil.rmtree(self.originals_dir)
|
||||
shutil.rmtree(self.thumb_dir)
|
||||
self.overrides.disable()
|
||||
|
||||
def test_file_deletion(self):
|
||||
document = Document.objects.create(
|
||||
|
@@ -97,12 +97,6 @@ class TestArchiver(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
|
||||
|
||||
class TestDecryptDocuments(FileSystemAssertsMixin, TestCase):
|
||||
@override_settings(
|
||||
ORIGINALS_DIR=(Path(__file__).parent / "samples" / "originals"),
|
||||
THUMBNAIL_DIR=(Path(__file__).parent / "samples" / "thumb"),
|
||||
PASSPHRASE="test",
|
||||
FILENAME_FORMAT=None,
|
||||
)
|
||||
@mock.patch("documents.management.commands.decrypt_documents.input")
|
||||
def test_decrypt(self, m):
|
||||
media_dir = tempfile.mkdtemp()
|
||||
@@ -111,55 +105,55 @@ class TestDecryptDocuments(FileSystemAssertsMixin, TestCase):
|
||||
originals_dir.mkdir(parents=True, exist_ok=True)
|
||||
thumb_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
override_settings(
|
||||
with override_settings(
|
||||
ORIGINALS_DIR=originals_dir,
|
||||
THUMBNAIL_DIR=thumb_dir,
|
||||
PASSPHRASE="test",
|
||||
).enable()
|
||||
FILENAME_FORMAT=None,
|
||||
):
|
||||
doc = Document.objects.create(
|
||||
checksum="82186aaa94f0b98697d704b90fd1c072",
|
||||
title="wow",
|
||||
filename="0000004.pdf.gpg",
|
||||
mime_type="application/pdf",
|
||||
storage_type=Document.STORAGE_TYPE_GPG,
|
||||
)
|
||||
|
||||
doc = Document.objects.create(
|
||||
checksum="82186aaa94f0b98697d704b90fd1c072",
|
||||
title="wow",
|
||||
filename="0000004.pdf.gpg",
|
||||
mime_type="application/pdf",
|
||||
storage_type=Document.STORAGE_TYPE_GPG,
|
||||
)
|
||||
shutil.copy(
|
||||
(
|
||||
Path(__file__).parent
|
||||
/ "samples"
|
||||
/ "documents"
|
||||
/ "originals"
|
||||
/ "0000004.pdf.gpg"
|
||||
),
|
||||
originals_dir / "0000004.pdf.gpg",
|
||||
)
|
||||
shutil.copy(
|
||||
(
|
||||
Path(__file__).parent
|
||||
/ "samples"
|
||||
/ "documents"
|
||||
/ "thumbnails"
|
||||
/ "0000004.webp.gpg"
|
||||
),
|
||||
thumb_dir / f"{doc.id:07}.webp.gpg",
|
||||
)
|
||||
|
||||
shutil.copy(
|
||||
(
|
||||
Path(__file__).parent
|
||||
/ "samples"
|
||||
/ "documents"
|
||||
/ "originals"
|
||||
/ "0000004.pdf.gpg"
|
||||
),
|
||||
originals_dir / "0000004.pdf.gpg",
|
||||
)
|
||||
shutil.copy(
|
||||
(
|
||||
Path(__file__).parent
|
||||
/ "samples"
|
||||
/ "documents"
|
||||
/ "thumbnails"
|
||||
/ "0000004.webp.gpg"
|
||||
),
|
||||
thumb_dir / f"{doc.id:07}.webp.gpg",
|
||||
)
|
||||
call_command("decrypt_documents")
|
||||
|
||||
call_command("decrypt_documents")
|
||||
doc.refresh_from_db()
|
||||
|
||||
doc.refresh_from_db()
|
||||
self.assertEqual(doc.storage_type, Document.STORAGE_TYPE_UNENCRYPTED)
|
||||
self.assertEqual(doc.filename, "0000004.pdf")
|
||||
self.assertIsFile(Path(originals_dir) / "0000004.pdf")
|
||||
self.assertIsFile(doc.source_path)
|
||||
self.assertIsFile(Path(thumb_dir) / f"{doc.id:07}.webp")
|
||||
self.assertIsFile(doc.thumbnail_path)
|
||||
|
||||
self.assertEqual(doc.storage_type, Document.STORAGE_TYPE_UNENCRYPTED)
|
||||
self.assertEqual(doc.filename, "0000004.pdf")
|
||||
self.assertIsFile(Path(originals_dir) / "0000004.pdf")
|
||||
self.assertIsFile(doc.source_path)
|
||||
self.assertIsFile(Path(thumb_dir) / f"{doc.id:07}.webp")
|
||||
self.assertIsFile(doc.thumbnail_path)
|
||||
|
||||
with doc.source_file as f:
|
||||
checksum: str = hashlib.md5(f.read()).hexdigest()
|
||||
self.assertEqual(checksum, doc.checksum)
|
||||
with doc.source_file as f:
|
||||
checksum: str = hashlib.md5(f.read()).hexdigest()
|
||||
self.assertEqual(checksum, doc.checksum)
|
||||
|
||||
|
||||
class TestMakeIndex(TestCase):
|
||||
|
@@ -3410,8 +3410,8 @@ class TestDateWorkflowLocalization(
|
||||
w.actions.add(action)
|
||||
w.save()
|
||||
|
||||
settings.SCRATCH_DIR = tmp_path / "scratch"
|
||||
(tmp_path / "scratch").mkdir(parents=True, exist_ok=True)
|
||||
(tmp_path / "thumbnails").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Temporarily override "now" for the environment so templates using
|
||||
# added/created placeholders behave as if it's a different system date.
|
||||
@@ -3424,6 +3424,10 @@ class TestDateWorkflowLocalization(
|
||||
"django.utils.timezone.now",
|
||||
return_value=self.TEST_DATETIME,
|
||||
),
|
||||
override_settings(
|
||||
SCRATCH_DIR=tmp_path / "scratch",
|
||||
THUMBNAIL_DIR=tmp_path / "thumbnails",
|
||||
),
|
||||
):
|
||||
tasks.consume_file(
|
||||
ConsumableDocument(
|
||||
|
Reference in New Issue
Block a user