mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-28 18:24:38 -05:00
Feature: email document button (#8950)
This commit is contained in:
@@ -15,6 +15,7 @@ from dateutil import parser
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.auth.models import User
|
||||
from django.core import mail
|
||||
from django.core.cache import cache
|
||||
from django.db import DataError
|
||||
from django.test import override_settings
|
||||
@@ -2651,6 +2652,153 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(doc1.tags.count(), 2)
|
||||
|
||||
@override_settings(
|
||||
EMAIL_ENABLED=True,
|
||||
EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend",
|
||||
)
|
||||
def test_email_document(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Existing document
|
||||
WHEN:
|
||||
- API request is made to email document action
|
||||
THEN:
|
||||
- Email is sent, with document (original or archive) attached
|
||||
"""
|
||||
doc = Document.objects.create(
|
||||
title="test",
|
||||
mime_type="application/pdf",
|
||||
content="this is a document 1",
|
||||
checksum="1",
|
||||
filename="test.pdf",
|
||||
archive_checksum="A",
|
||||
archive_filename="archive.pdf",
|
||||
)
|
||||
doc2 = Document.objects.create(
|
||||
title="test2",
|
||||
mime_type="application/pdf",
|
||||
content="this is a document 2",
|
||||
checksum="2",
|
||||
filename="test2.pdf",
|
||||
)
|
||||
|
||||
archive_file = Path(__file__).parent / "samples" / "simple.pdf"
|
||||
source_file = Path(__file__).parent / "samples" / "simple.pdf"
|
||||
|
||||
shutil.copy(archive_file, doc.archive_path)
|
||||
shutil.copy(source_file, doc2.source_path)
|
||||
|
||||
self.client.post(
|
||||
f"/api/documents/{doc.pk}/email/",
|
||||
{
|
||||
"addresses": "hello@paperless-ngx.com",
|
||||
"subject": "test",
|
||||
"message": "hello",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertEqual(mail.outbox[0].attachments[0][0], "archive.pdf")
|
||||
|
||||
self.client.post(
|
||||
f"/api/documents/{doc2.pk}/email/",
|
||||
{
|
||||
"addresses": "hello@paperless-ngx.com",
|
||||
"subject": "test",
|
||||
"message": "hello",
|
||||
"use_archive_version": False,
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(len(mail.outbox), 2)
|
||||
self.assertEqual(mail.outbox[1].attachments[0][0], "test2.pdf")
|
||||
|
||||
@mock.patch("django.core.mail.message.EmailMessage.send", side_effect=Exception)
|
||||
def test_email_document_errors(self, mocked_send):
|
||||
"""
|
||||
GIVEN:
|
||||
- Existing document
|
||||
WHEN:
|
||||
- API request is made to email document action with insufficient permissions
|
||||
- API request is made to email document action with invalid document id
|
||||
- API request is made to email document action with missing data
|
||||
- API request is made to email document action with invalid email address
|
||||
- API request is made to email document action and error occurs during email send
|
||||
THEN:
|
||||
- Error response is returned
|
||||
"""
|
||||
user1 = User.objects.create_user(username="test1")
|
||||
user1.user_permissions.add(*Permission.objects.all())
|
||||
user1.save()
|
||||
|
||||
doc = Document.objects.create(
|
||||
title="test",
|
||||
mime_type="application/pdf",
|
||||
content="this is a document 1",
|
||||
checksum="1",
|
||||
filename="test.pdf",
|
||||
archive_checksum="A",
|
||||
archive_filename="archive.pdf",
|
||||
)
|
||||
|
||||
doc2 = Document.objects.create(
|
||||
title="test2",
|
||||
mime_type="application/pdf",
|
||||
content="this is a document 2",
|
||||
checksum="2",
|
||||
owner=self.user,
|
||||
)
|
||||
|
||||
self.client.force_authenticate(user1)
|
||||
|
||||
resp = self.client.post(
|
||||
f"/api/documents/{doc2.pk}/email/",
|
||||
{
|
||||
"addresses": "hello@paperless-ngx.com",
|
||||
"subject": "test",
|
||||
"message": "hello",
|
||||
},
|
||||
)
|
||||
self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
resp = self.client.post(
|
||||
"/api/documents/999/email/",
|
||||
{
|
||||
"addresses": "hello@paperless-ngx.com",
|
||||
"subject": "test",
|
||||
"message": "hello",
|
||||
},
|
||||
)
|
||||
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
resp = self.client.post(
|
||||
f"/api/documents/{doc.pk}/email/",
|
||||
{
|
||||
"addresses": "hello@paperless-ngx.com",
|
||||
},
|
||||
)
|
||||
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
resp = self.client.post(
|
||||
f"/api/documents/{doc.pk}/email/",
|
||||
{
|
||||
"addresses": "hello@paperless-ngx.com,hello",
|
||||
"subject": "test",
|
||||
"message": "hello",
|
||||
},
|
||||
)
|
||||
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
resp = self.client.post(
|
||||
f"/api/documents/{doc.pk}/email/",
|
||||
{
|
||||
"addresses": "hello@paperless-ngx.com",
|
||||
"subject": "test",
|
||||
"message": "hello",
|
||||
},
|
||||
)
|
||||
self.assertEqual(resp.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
@mock.patch("django_softdelete.models.SoftDeleteModel.delete")
|
||||
def test_warn_on_delete_with_old_uuid_field(self, mocked_delete):
|
||||
"""
|
||||
|
@@ -37,6 +37,7 @@ from django.http import HttpResponse
|
||||
from django.http import HttpResponseBadRequest
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.http import HttpResponseServerError
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
@@ -106,6 +107,7 @@ from documents.filters import ObjectOwnedPermissionsFilter
|
||||
from documents.filters import ShareLinkFilterSet
|
||||
from documents.filters import StoragePathFilterSet
|
||||
from documents.filters import TagFilterSet
|
||||
from documents.mail import send_email
|
||||
from documents.matching import match_correspondents
|
||||
from documents.matching import match_document_types
|
||||
from documents.matching import match_storage_paths
|
||||
@@ -1023,6 +1025,57 @@ class DocumentViewSet(
|
||||
|
||||
return Response(sorted(entries, key=lambda x: x["timestamp"], reverse=True))
|
||||
|
||||
@action(methods=["post"], detail=True)
|
||||
def email(self, request, pk=None):
|
||||
try:
|
||||
doc = Document.objects.select_related("owner").get(pk=pk)
|
||||
if request.user is not None and not has_perms_owner_aware(
|
||||
request.user,
|
||||
"view_document",
|
||||
doc,
|
||||
):
|
||||
return HttpResponseForbidden("Insufficient permissions")
|
||||
except Document.DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
try:
|
||||
if (
|
||||
"addresses" not in request.data
|
||||
or "subject" not in request.data
|
||||
or "message" not in request.data
|
||||
):
|
||||
return HttpResponseBadRequest("Missing required fields")
|
||||
|
||||
use_archive_version = request.data.get("use_archive_version", True)
|
||||
|
||||
addresses = request.data.get("addresses").split(",")
|
||||
if not all(
|
||||
re.match(r"[^@]+@[^@]+\.[^@]+", address.strip())
|
||||
for address in addresses
|
||||
):
|
||||
return HttpResponseBadRequest("Invalid email address found")
|
||||
|
||||
send_email(
|
||||
subject=request.data.get("subject"),
|
||||
body=request.data.get("message"),
|
||||
to=addresses,
|
||||
attachment=(
|
||||
doc.archive_path
|
||||
if use_archive_version and doc.has_archive_version
|
||||
else doc.source_path
|
||||
),
|
||||
attachment_mime_type=doc.mime_type,
|
||||
)
|
||||
logger.debug(
|
||||
f"Sent document {doc.id} via email to {addresses}",
|
||||
)
|
||||
return Response({"message": "Email sent"})
|
||||
except Exception as e:
|
||||
logger.warning(f"An error occurred emailing document: {e!s}")
|
||||
return HttpResponseServerError(
|
||||
"Error emailing document, check logs for more detail.",
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
|
Reference in New Issue
Block a user