mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-08-12 00:19:48 +00:00
Merge branch 'dev' into feature-improve-paperless-task
This commit is contained in:
@@ -144,6 +144,7 @@ class DocumentSource(IntEnum):
|
||||
ConsumeFolder = 1
|
||||
ApiUpload = 2
|
||||
MailFetch = 3
|
||||
WebUI = 4
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
|
@@ -0,0 +1,52 @@
|
||||
# Generated by Django 5.1.6 on 2025-02-20 04:55
|
||||
|
||||
import multiselectfield.db.fields
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
# WebUI source was added, so all existing APIUpload sources should be updated to include WebUI
|
||||
def update_workflow_sources(apps, schema_editor):
|
||||
WorkflowTrigger = apps.get_model("documents", "WorkflowTrigger")
|
||||
for trigger in WorkflowTrigger.objects.all():
|
||||
sources = list(trigger.sources)
|
||||
if 2 in sources:
|
||||
sources.append(4)
|
||||
trigger.sources = sources
|
||||
trigger.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("documents", "1062_alter_savedviewfilterrule_rule_type"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="workflowactionwebhook",
|
||||
name="url",
|
||||
field=models.CharField(
|
||||
help_text="The destination URL for the notification.",
|
||||
max_length=256,
|
||||
verbose_name="webhook url",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="workflowtrigger",
|
||||
name="sources",
|
||||
field=multiselectfield.db.fields.MultiSelectField(
|
||||
choices=[
|
||||
(1, "Consume Folder"),
|
||||
(2, "Api Upload"),
|
||||
(3, "Mail Fetch"),
|
||||
(4, "Web UI"),
|
||||
],
|
||||
default="1,2,3,4",
|
||||
max_length=7,
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=update_workflow_sources,
|
||||
reverse_code=migrations.RunPython.noop,
|
||||
),
|
||||
]
|
@@ -1054,6 +1054,7 @@ class WorkflowTrigger(models.Model):
|
||||
CONSUME_FOLDER = DocumentSource.ConsumeFolder.value, _("Consume Folder")
|
||||
API_UPLOAD = DocumentSource.ApiUpload.value, _("Api Upload")
|
||||
MAIL_FETCH = DocumentSource.MailFetch.value, _("Mail Fetch")
|
||||
WEB_UI = DocumentSource.WebUI.value, _("Web UI")
|
||||
|
||||
class ScheduleDateField(models.TextChoices):
|
||||
ADDED = "added", _("Added")
|
||||
@@ -1068,9 +1069,9 @@ class WorkflowTrigger(models.Model):
|
||||
)
|
||||
|
||||
sources = MultiSelectField(
|
||||
max_length=5,
|
||||
max_length=7,
|
||||
choices=DocumentSourceChoices.choices,
|
||||
default=f"{DocumentSource.ConsumeFolder},{DocumentSource.ApiUpload},{DocumentSource.MailFetch}",
|
||||
default=f"{DocumentSource.ConsumeFolder},{DocumentSource.ApiUpload},{DocumentSource.MailFetch},{DocumentSource.WebUI}",
|
||||
)
|
||||
|
||||
filter_path = models.CharField(
|
||||
|
@@ -1147,6 +1147,15 @@ class SavedViewSerializer(OwnedObjectSerializer):
|
||||
if "user" in validated_data:
|
||||
# backwards compatibility
|
||||
validated_data["owner"] = validated_data.pop("user")
|
||||
if (
|
||||
"display_fields" in validated_data
|
||||
and isinstance(
|
||||
validated_data["display_fields"],
|
||||
list,
|
||||
)
|
||||
and len(validated_data["display_fields"]) == 0
|
||||
):
|
||||
validated_data["display_fields"] = None
|
||||
super().update(instance, validated_data)
|
||||
if rules_data is not None:
|
||||
SavedViewFilterRule.objects.filter(saved_view=instance).delete()
|
||||
@@ -1537,6 +1546,12 @@ class PostDocumentSerializer(serializers.Serializer):
|
||||
required=False,
|
||||
)
|
||||
|
||||
from_webui = serializers.BooleanField(
|
||||
label="Documents are from Paperless-ngx WebUI",
|
||||
write_only=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
def validate_document(self, document):
|
||||
document_data = document.file.read()
|
||||
mime_type = magic.from_buffer(document_data, mime=True)
|
||||
|
@@ -360,7 +360,7 @@ def empty_trash(doc_ids=None):
|
||||
)
|
||||
|
||||
try:
|
||||
deleted_document_ids = documents.values_list("id", flat=True)
|
||||
deleted_document_ids = list(documents.values_list("id", flat=True))
|
||||
# Temporarily connect the cleanup handler
|
||||
models.signals.post_delete.connect(cleanup_document_deletion, sender=Document)
|
||||
documents.delete() # this is effectively a hard delete
|
||||
|
@@ -38,6 +38,7 @@ from documents.models import SavedView
|
||||
from documents.models import ShareLink
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.models import WorkflowTrigger
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
from documents.tests.utils import DocumentConsumeDelayMixin
|
||||
|
||||
@@ -1362,6 +1363,30 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
self.assertEqual(overrides.filename, "simple.pdf")
|
||||
self.assertEqual(overrides.custom_field_ids, [custom_field.id])
|
||||
|
||||
def test_upload_with_webui_source(self):
|
||||
"""
|
||||
GIVEN: A document with a source file
|
||||
WHEN: Upload the document with 'from_webui' flag
|
||||
THEN: Consume is called with the source set as WebUI
|
||||
"""
|
||||
self.consume_file_mock.return_value = celery.result.AsyncResult(
|
||||
id=str(uuid.uuid4()),
|
||||
)
|
||||
|
||||
with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
|
||||
response = self.client.post(
|
||||
"/api/documents/post_document/",
|
||||
{"document": f, "from_webui": True},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
self.consume_file_mock.assert_called_once()
|
||||
|
||||
input_doc, overrides = self.get_last_consume_delay_call_args()
|
||||
|
||||
self.assertEqual(input_doc.source, WorkflowTrigger.DocumentSourceChoices.WEB_UI)
|
||||
|
||||
def test_upload_invalid_pdf(self):
|
||||
"""
|
||||
GIVEN: Invalid PDF named "*.pdf" that mime_type is in settings.CONSUMER_PDF_RECOVERABLE_MIME_TYPES
|
||||
@@ -1815,6 +1840,19 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# empty display fields treated as none
|
||||
response = self.client.patch(
|
||||
f"/api/saved_views/{v1.id}/",
|
||||
{
|
||||
"display_fields": [],
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
v1.refresh_from_db()
|
||||
self.assertEqual(v1.display_fields, None)
|
||||
|
||||
def test_saved_view_display_customfields(self):
|
||||
"""
|
||||
GIVEN:
|
||||
|
@@ -1387,6 +1387,7 @@ class PostDocumentView(GenericAPIView):
|
||||
created = serializer.validated_data.get("created")
|
||||
archive_serial_number = serializer.validated_data.get("archive_serial_number")
|
||||
custom_field_ids = serializer.validated_data.get("custom_fields")
|
||||
from_webui = serializer.validated_data.get("from_webui")
|
||||
|
||||
t = int(mktime(datetime.now().timetuple()))
|
||||
|
||||
@@ -1401,7 +1402,7 @@ class PostDocumentView(GenericAPIView):
|
||||
os.utime(temp_file_path, times=(t, t))
|
||||
|
||||
input_doc = ConsumableDocument(
|
||||
source=DocumentSource.ApiUpload,
|
||||
source=DocumentSource.WebUI if from_webui else DocumentSource.ApiUpload,
|
||||
original_file=temp_file_path,
|
||||
)
|
||||
input_doc_overrides = DocumentMetadataOverrides(
|
||||
|
@@ -305,6 +305,11 @@ urlpatterns = [
|
||||
],
|
||||
),
|
||||
),
|
||||
re_path(
|
||||
r"^confirm-email/(?P<key>[-:\w]+)/$",
|
||||
allauth_account_views.ConfirmEmailView.as_view(),
|
||||
name="account_confirm_email",
|
||||
),
|
||||
re_path(
|
||||
r"^password/reset/key/(?P<uidb36>[0-9A-Za-z]+)-(?P<key>.+)/$",
|
||||
allauth_account_views.password_reset_from_key,
|
||||
|
Reference in New Issue
Block a user