Rotate
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
import hashlib
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
@@ -9,6 +12,9 @@ from documents.models import StoragePath
|
||||
from documents.permissions import set_permissions_for_object
|
||||
from documents.tasks import bulk_update_documents
|
||||
from documents.tasks import update_document_archive_file
|
||||
from paperless import settings
|
||||
|
||||
logger = logging.getLogger("paperless.bulk_edit")
|
||||
|
||||
|
||||
def set_correspondent(doc_ids, correspondent):
|
||||
@@ -146,3 +152,30 @@ def set_permissions(doc_ids, set_permissions, owner=None, merge=False):
|
||||
bulk_update_documents.delay(document_ids=affected_docs)
|
||||
|
||||
return "OK"
|
||||
|
||||
|
||||
def rotate(doc_ids: list[int], degrees: int):
|
||||
qs = Document.objects.filter(id__in=doc_ids)
|
||||
affected_docs = []
|
||||
import pikepdf
|
||||
|
||||
for doc in qs:
|
||||
try:
|
||||
with pikepdf.open(doc.source_path, allow_overwriting_input=True) as pdf:
|
||||
for page in pdf.pages:
|
||||
page.rotate(degrees, relative=True)
|
||||
pdf.save()
|
||||
doc.checksum = hashlib.md5(doc.source_file.read()).hexdigest()
|
||||
doc.save()
|
||||
update_document_archive_file.delay(
|
||||
document_id=doc.id,
|
||||
)
|
||||
logger.info(f"Rotated document {doc.id} ({path}) by {degrees} degrees")
|
||||
affected_docs.append(doc.id)
|
||||
except Exception as e:
|
||||
logger.exception(f"Error rotating document {doc.id}", e)
|
||||
|
||||
if len(affected_docs) > 0:
|
||||
bulk_update_documents.delay(document_ids=affected_docs)
|
||||
|
||||
return "OK"
|
||||
|
||||
@@ -869,6 +869,7 @@ class BulkEditSerializer(DocumentListSerializer, SetPermissionsMixin):
|
||||
"delete",
|
||||
"redo_ocr",
|
||||
"set_permissions",
|
||||
"rotate",
|
||||
],
|
||||
label="Method",
|
||||
write_only=True,
|
||||
@@ -906,6 +907,8 @@ class BulkEditSerializer(DocumentListSerializer, SetPermissionsMixin):
|
||||
return bulk_edit.redo_ocr
|
||||
elif method == "set_permissions":
|
||||
return bulk_edit.set_permissions
|
||||
elif method == "rotate":
|
||||
return bulk_edit.rotate
|
||||
else:
|
||||
raise serializers.ValidationError("Unsupported method.")
|
||||
|
||||
@@ -984,6 +987,16 @@ class BulkEditSerializer(DocumentListSerializer, SetPermissionsMixin):
|
||||
if "merge" not in parameters:
|
||||
parameters["merge"] = False
|
||||
|
||||
def _validate_parameters_rotate(self, parameters):
|
||||
try:
|
||||
if (
|
||||
"degrees" not in parameters
|
||||
or not float(parameters["degrees"]).is_integer()
|
||||
):
|
||||
raise serializers.ValidationError("invalid rotation degrees")
|
||||
except ValueError:
|
||||
raise serializers.ValidationError("invalid rotation degrees")
|
||||
|
||||
def validate(self, attrs):
|
||||
method = attrs["method"]
|
||||
parameters = attrs["parameters"]
|
||||
@@ -1000,6 +1013,8 @@ class BulkEditSerializer(DocumentListSerializer, SetPermissionsMixin):
|
||||
self._validate_storage_path(parameters)
|
||||
elif method == bulk_edit.set_permissions:
|
||||
self._validate_parameters_set_permissions(parameters)
|
||||
elif method == bulk_edit.rotate:
|
||||
self._validate_parameters_rotate(parameters)
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
@@ -781,3 +781,58 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
m.assert_called_once()
|
||||
|
||||
@mock.patch("documents.serialisers.bulk_edit.rotate")
|
||||
def test_rotate(self, m):
|
||||
m.return_value = "OK"
|
||||
|
||||
response = self.client.post(
|
||||
"/api/documents/bulk_edit/",
|
||||
json.dumps(
|
||||
{
|
||||
"documents": [self.doc2.id, self.doc3.id],
|
||||
"method": "rotate",
|
||||
"parameters": {"degrees": 90},
|
||||
},
|
||||
),
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
m.assert_called_once()
|
||||
args, kwargs = m.call_args
|
||||
self.assertCountEqual(args[0], [self.doc2.id, self.doc3.id])
|
||||
self.assertEqual(kwargs["degrees"], 90)
|
||||
|
||||
@mock.patch("documents.serialisers.bulk_edit.rotate")
|
||||
def test_rotate_invalid_params(self, m):
|
||||
response = self.client.post(
|
||||
"/api/documents/bulk_edit/",
|
||||
json.dumps(
|
||||
{
|
||||
"documents": [self.doc2.id, self.doc3.id],
|
||||
"method": "rotate",
|
||||
"parameters": {"degrees": "foo"},
|
||||
},
|
||||
),
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
response = self.client.post(
|
||||
"/api/documents/bulk_edit/",
|
||||
json.dumps(
|
||||
{
|
||||
"documents": [self.doc2.id, self.doc3.id],
|
||||
"method": "rotate",
|
||||
"parameters": {"degrees": 90.5},
|
||||
},
|
||||
),
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
m.assert_not_called()
|
||||
|
||||
@@ -891,7 +891,8 @@ class BulkEditView(GenericAPIView, PassUserMixin):
|
||||
document_objs = Document.objects.filter(pk__in=documents)
|
||||
has_perms = (
|
||||
all((doc.owner == user or doc.owner is None) for doc in document_objs)
|
||||
if method == bulk_edit.set_permissions
|
||||
if method
|
||||
in [bulk_edit.set_permissions, bulk_edit.delete, bulk_edit.rotate]
|
||||
else all(
|
||||
has_perms_owner_aware(user, "change_document", doc)
|
||||
for doc in document_objs
|
||||
|
||||
Reference in New Issue
Block a user