mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-06-20 15:17:32 -05:00
Fix: more api fixes (#10204)
This commit is contained in:
parent
0a1786f39b
commit
83391af866
@ -360,9 +360,9 @@ class OwnedObjectSerializer(
|
|||||||
shared_object_pks = self.get_shared_object_pks([obj])
|
shared_object_pks = self.get_shared_object_pks([obj])
|
||||||
return obj.owner == self.user and obj.id in shared_object_pks
|
return obj.owner == self.user and obj.id in shared_object_pks
|
||||||
|
|
||||||
permissions = SerializerMethodField(read_only=True)
|
permissions = SerializerMethodField(read_only=True, required=False)
|
||||||
user_can_change = SerializerMethodField(read_only=True)
|
user_can_change = SerializerMethodField(read_only=True, required=False)
|
||||||
is_shared_by_requester = SerializerMethodField(read_only=True)
|
is_shared_by_requester = SerializerMethodField(read_only=True, required=False)
|
||||||
|
|
||||||
set_permissions = SetPermissionsSerializer(
|
set_permissions = SetPermissionsSerializer(
|
||||||
label="Set permissions",
|
label="Set permissions",
|
||||||
@ -2195,7 +2195,7 @@ class WorkflowSerializer(serializers.ModelSerializer):
|
|||||||
set_triggers = []
|
set_triggers = []
|
||||||
set_actions = []
|
set_actions = []
|
||||||
|
|
||||||
if triggers is not None:
|
if triggers is not None and triggers is not serializers.empty:
|
||||||
for trigger in triggers:
|
for trigger in triggers:
|
||||||
filter_has_tags = trigger.pop("filter_has_tags", None)
|
filter_has_tags = trigger.pop("filter_has_tags", None)
|
||||||
trigger_instance, _ = WorkflowTrigger.objects.update_or_create(
|
trigger_instance, _ = WorkflowTrigger.objects.update_or_create(
|
||||||
@ -2206,7 +2206,7 @@ class WorkflowSerializer(serializers.ModelSerializer):
|
|||||||
trigger_instance.filter_has_tags.set(filter_has_tags)
|
trigger_instance.filter_has_tags.set(filter_has_tags)
|
||||||
set_triggers.append(trigger_instance)
|
set_triggers.append(trigger_instance)
|
||||||
|
|
||||||
if actions is not None:
|
if actions is not None and actions is not serializers.empty:
|
||||||
for action in actions:
|
for action in actions:
|
||||||
assign_tags = action.pop("assign_tags", None)
|
assign_tags = action.pop("assign_tags", None)
|
||||||
assign_view_users = action.pop("assign_view_users", None)
|
assign_view_users = action.pop("assign_view_users", None)
|
||||||
@ -2288,14 +2288,16 @@ class WorkflowSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
set_actions.append(action_instance)
|
set_actions.append(action_instance)
|
||||||
|
|
||||||
instance.triggers.set(set_triggers)
|
if triggers is not serializers.empty:
|
||||||
instance.actions.set(set_actions)
|
instance.triggers.set(set_triggers)
|
||||||
|
if actions is not serializers.empty:
|
||||||
|
instance.actions.set(set_actions)
|
||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
def prune_triggers_and_actions(self):
|
def prune_triggers_and_actions(self):
|
||||||
"""
|
"""
|
||||||
ManyToMany fields dont support e.g. on_delete so we need to discard unattached
|
ManyToMany fields dont support e.g. on_delete so we need to discard unattached
|
||||||
triggers and actionas manually
|
triggers and actions manually
|
||||||
"""
|
"""
|
||||||
for trigger in WorkflowTrigger.objects.all():
|
for trigger in WorkflowTrigger.objects.all():
|
||||||
if trigger.workflows.all().count() == 0:
|
if trigger.workflows.all().count() == 0:
|
||||||
@ -2322,16 +2324,12 @@ class WorkflowSerializer(serializers.ModelSerializer):
|
|||||||
return instance
|
return instance
|
||||||
|
|
||||||
def update(self, instance: Workflow, validated_data) -> Workflow:
|
def update(self, instance: Workflow, validated_data) -> Workflow:
|
||||||
if "triggers" in validated_data:
|
triggers = validated_data.pop("triggers", serializers.empty)
|
||||||
triggers = validated_data.pop("triggers")
|
actions = validated_data.pop("actions", serializers.empty)
|
||||||
|
|
||||||
if "actions" in validated_data:
|
|
||||||
actions = validated_data.pop("actions")
|
|
||||||
|
|
||||||
instance = super().update(instance, validated_data)
|
instance = super().update(instance, validated_data)
|
||||||
|
|
||||||
self.update_triggers_and_actions(instance, triggers, actions)
|
self.update_triggers_and_actions(instance, triggers, actions)
|
||||||
|
|
||||||
self.prune_triggers_and_actions()
|
self.prune_triggers_and_actions()
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
@ -1278,3 +1278,34 @@ class TestBulkEditObjectPermissions(APITestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFullPermissionsFlag(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.admin = User.objects.create_superuser(username="admin")
|
||||||
|
|
||||||
|
def test_full_perms_flag(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- API request to list documents
|
||||||
|
WHEN:
|
||||||
|
- full_perms flag is set to true, 1, false, or a random value
|
||||||
|
THEN:
|
||||||
|
- Permissions field is included or excluded accordingly
|
||||||
|
"""
|
||||||
|
self.client.force_authenticate(self.admin)
|
||||||
|
Document.objects.create(title="Doc", checksum="xyz", owner=self.admin)
|
||||||
|
|
||||||
|
resp = self.client.get("/api/documents/?full_perms=true")
|
||||||
|
self.assertIn("permissions", resp.data["results"][0])
|
||||||
|
|
||||||
|
resp = self.client.get("/api/documents/?full_perms=1")
|
||||||
|
self.assertIn("permissions", resp.data["results"][0])
|
||||||
|
|
||||||
|
resp = self.client.get("/api/documents/?full_perms=false")
|
||||||
|
self.assertNotIn("permissions", resp.data["results"][0])
|
||||||
|
|
||||||
|
resp = self.client.get("/api/documents/?full_perms=garbage")
|
||||||
|
self.assertNotIn("permissions", resp.data["results"][0])
|
||||||
|
@ -394,6 +394,50 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
|
|||||||
self.assertEqual(workflow.triggers.first().filter_has_tags.first(), self.t1)
|
self.assertEqual(workflow.triggers.first().filter_has_tags.first(), self.t1)
|
||||||
self.assertEqual(workflow.actions.first().assign_title, "Action New Title")
|
self.assertEqual(workflow.actions.first().assign_title, "Action New Title")
|
||||||
|
|
||||||
|
def test_api_update_workflow_no_trigger_actions(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- Existing workflow
|
||||||
|
WHEN:
|
||||||
|
- API request to update an existing workflow with no triggers and actions
|
||||||
|
- API request to update an existing workflow with empty actions and no triggers
|
||||||
|
THEN:
|
||||||
|
- No changes are made to the workflow
|
||||||
|
- Actions are removed, but triggers are not
|
||||||
|
"""
|
||||||
|
response = self.client.patch(
|
||||||
|
f"{self.ENDPOINT}{self.workflow.id}/",
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"name": "Workflow Updated",
|
||||||
|
"order": 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
workflow = Workflow.objects.get(id=self.workflow.id)
|
||||||
|
self.assertEqual(workflow.name, "Workflow Updated")
|
||||||
|
self.assertEqual(workflow.triggers.count(), 1)
|
||||||
|
self.assertEqual(workflow.actions.count(), 1)
|
||||||
|
|
||||||
|
response = self.client.patch(
|
||||||
|
f"{self.ENDPOINT}{self.workflow.id}/",
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"name": "Workflow Updated 2",
|
||||||
|
"order": 1,
|
||||||
|
"actions": [],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
workflow = Workflow.objects.get(id=self.workflow.id)
|
||||||
|
self.assertEqual(workflow.name, "Workflow Updated 2")
|
||||||
|
self.assertEqual(workflow.triggers.count(), 1)
|
||||||
|
self.assertEqual(workflow.actions.count(), 0)
|
||||||
|
|
||||||
def test_api_auto_remove_orphaned_triggers_actions(self):
|
def test_api_auto_remove_orphaned_triggers_actions(self):
|
||||||
"""
|
"""
|
||||||
GIVEN:
|
GIVEN:
|
||||||
@ -630,3 +674,63 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
|
|||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, expected_resp_code)
|
self.assertEqual(response.status_code, expected_resp_code)
|
||||||
|
|
||||||
|
def test_patch_trigger_cannot_change_id(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- An existing workflow trigger
|
||||||
|
- An existing workflow action
|
||||||
|
WHEN:
|
||||||
|
- PATCHing the trigger with a different 'id' in the body
|
||||||
|
- PATCHing the action with a different 'id' in the body
|
||||||
|
THEN:
|
||||||
|
- HTTP 400 error is returned
|
||||||
|
"""
|
||||||
|
response = self.client.patch(
|
||||||
|
f"/api/workflow_triggers/{self.trigger.id}/",
|
||||||
|
{
|
||||||
|
"id": self.trigger.id + 1,
|
||||||
|
"filter_filename": "patched.pdf",
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.trigger.refresh_from_db()
|
||||||
|
self.assertNotEqual(self.trigger.filter_filename, "patched.pdf")
|
||||||
|
|
||||||
|
response = self.client.patch(
|
||||||
|
f"/api/workflow_triggers/{self.trigger.id}/",
|
||||||
|
{
|
||||||
|
"id": self.trigger.id,
|
||||||
|
"filter_filename": "patched.pdf",
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.trigger.refresh_from_db()
|
||||||
|
self.assertEqual(self.trigger.filter_filename, "patched.pdf")
|
||||||
|
|
||||||
|
response = self.client.patch(
|
||||||
|
f"/api/workflow_actions/{self.action.id}/",
|
||||||
|
{
|
||||||
|
"id": self.action.id + 1,
|
||||||
|
"assign_title": "Patched Title",
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.action.refresh_from_db()
|
||||||
|
self.assertNotEqual(self.action.assign_title, "Patched Title")
|
||||||
|
|
||||||
|
response = self.client.patch(
|
||||||
|
f"/api/workflow_actions/{self.action.id}/",
|
||||||
|
{
|
||||||
|
"id": self.action.id,
|
||||||
|
"assign_title": "Patched Title",
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.action.refresh_from_db()
|
||||||
|
self.assertEqual(self.action.assign_title, "Patched Title")
|
||||||
|
@ -6,6 +6,7 @@ import re
|
|||||||
import tempfile
|
import tempfile
|
||||||
import zipfile
|
import zipfile
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from distutils.util import strtobool
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from time import mktime
|
from time import mktime
|
||||||
from unicodedata import normalize
|
from unicodedata import normalize
|
||||||
@ -236,9 +237,15 @@ class PassUserMixin(GenericAPIView):
|
|||||||
|
|
||||||
def get_serializer(self, *args, **kwargs):
|
def get_serializer(self, *args, **kwargs):
|
||||||
kwargs.setdefault("user", self.request.user)
|
kwargs.setdefault("user", self.request.user)
|
||||||
|
try:
|
||||||
|
full_perms = bool(
|
||||||
|
strtobool(str(self.request.query_params.get("full_perms", "false"))),
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
full_perms = False
|
||||||
kwargs.setdefault(
|
kwargs.setdefault(
|
||||||
"full_perms",
|
"full_perms",
|
||||||
self.request.query_params.get("full_perms", False),
|
full_perms,
|
||||||
)
|
)
|
||||||
return super().get_serializer(*args, **kwargs)
|
return super().get_serializer(*args, **kwargs)
|
||||||
|
|
||||||
@ -592,9 +599,15 @@ class DocumentViewSet(
|
|||||||
kwargs.setdefault("context", self.get_serializer_context())
|
kwargs.setdefault("context", self.get_serializer_context())
|
||||||
kwargs.setdefault("fields", fields)
|
kwargs.setdefault("fields", fields)
|
||||||
kwargs.setdefault("truncate_content", truncate_content.lower() in ["true", "1"])
|
kwargs.setdefault("truncate_content", truncate_content.lower() in ["true", "1"])
|
||||||
|
try:
|
||||||
|
full_perms = bool(
|
||||||
|
strtobool(str(self.request.query_params.get("full_perms", "false"))),
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
full_perms = False
|
||||||
kwargs.setdefault(
|
kwargs.setdefault(
|
||||||
"full_perms",
|
"full_perms",
|
||||||
self.request.query_params.get("full_perms", False),
|
full_perms,
|
||||||
)
|
)
|
||||||
return super().get_serializer(*args, **kwargs)
|
return super().get_serializer(*args, **kwargs)
|
||||||
|
|
||||||
@ -2536,6 +2549,13 @@ class WorkflowTriggerViewSet(ModelViewSet):
|
|||||||
|
|
||||||
queryset = WorkflowTrigger.objects.all()
|
queryset = WorkflowTrigger.objects.all()
|
||||||
|
|
||||||
|
def partial_update(self, request, *args, **kwargs):
|
||||||
|
if "id" in request.data and str(request.data["id"]) != str(kwargs["pk"]):
|
||||||
|
return HttpResponseBadRequest(
|
||||||
|
"ID in body does not match URL",
|
||||||
|
)
|
||||||
|
return super().partial_update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class WorkflowActionViewSet(ModelViewSet):
|
class WorkflowActionViewSet(ModelViewSet):
|
||||||
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||||
@ -2554,6 +2574,13 @@ class WorkflowActionViewSet(ModelViewSet):
|
|||||||
"assign_custom_fields",
|
"assign_custom_fields",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def partial_update(self, request, *args, **kwargs):
|
||||||
|
if "id" in request.data and str(request.data["id"]) != str(kwargs["pk"]):
|
||||||
|
return HttpResponseBadRequest(
|
||||||
|
"ID in body does not match URL",
|
||||||
|
)
|
||||||
|
return super().partial_update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class WorkflowViewSet(ModelViewSet):
|
class WorkflowViewSet(ModelViewSet):
|
||||||
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||||
|
@ -134,6 +134,13 @@ class UserViewSet(ModelViewSet):
|
|||||||
)
|
)
|
||||||
return super().update(request, *args, **kwargs)
|
return super().update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
request=None,
|
||||||
|
responses={
|
||||||
|
200: OpenApiTypes.BOOL,
|
||||||
|
404: OpenApiTypes.STR,
|
||||||
|
},
|
||||||
|
)
|
||||||
@action(detail=True, methods=["post"])
|
@action(detail=True, methods=["post"])
|
||||||
def deactivate_totp(self, request, pk=None):
|
def deactivate_totp(self, request, pk=None):
|
||||||
request_user = request.user
|
request_user = request.user
|
||||||
|
Loading…
x
Reference in New Issue
Block a user