- Consumption templates were refactored to workflows and API endpoints
changed as such.
+
+#### Version 5
+
+- Added bulk deletion methods for documents and objects.
+
+#### Version 6
+
+- Moved acknowledge tasks endpoint to be under `/api/tasks/acknowledge/`.
result?: string
related_document?: number
+
+ owner?: number
}
it('calls acknowledge_tasks api endpoint on dismiss and reloads', () => {
tasksService.dismissTasks(new Set([1, 2, 3]))
const req = httpTestingController.expectOne(
- `${environment.apiBaseUrl}acknowledge_tasks/`
+ `${environment.apiBaseUrl}tasks/acknowledge/`
)
expect(req.request.method).toEqual('POST')
expect(req.request.body).toEqual({
public dismissTasks(task_ids: Set<number>) {
this.http
- .post(`${this.baseUrl}acknowledge_tasks/`, {
+ .post(`${this.baseUrl}tasks/acknowledge/`, {
tasks: [...task_ids],
})
.pipe(first())
export const environment = {
production: true,
apiBaseUrl: document.baseURI + 'api/',
- apiVersion: '5',
+ apiVersion: '6',
appTitle: 'Paperless-ngx',
version: '2.13.5',
webSocketHost: window.location.host,
export const environment = {
production: false,
apiBaseUrl: 'http://localhost:8000/api/',
- apiVersion: '5',
+ apiVersion: '6',
appTitle: 'Paperless-ngx',
version: 'DEVELOPMENT',
webSocketHost: 'localhost:8000',
--- /dev/null
+# Generated by Django 5.1.1 on 2024-11-04 21:56
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("documents", "1056_customfieldinstance_deleted_at_and_more"),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="paperlesstask",
+ name="owner",
+ field=models.ForeignKey(
+ blank=True,
+ default=None,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to=settings.AUTH_USER_MODEL,
+ verbose_name="owner",
+ ),
+ ),
+ ]
return self.user.username
-class PaperlessTask(models.Model):
+class PaperlessTask(ModelWithOwner):
ALL_STATES = sorted(states.ALL_STATES)
TASK_STATE_CHOICES = sorted(zip(ALL_STATES, ALL_STATES))
return ui_settings
-class TasksViewSerializer(serializers.ModelSerializer):
+class TasksViewSerializer(OwnedObjectSerializer):
class Meta:
model = PaperlessTask
depth = 1
"result",
"acknowledged",
"related_document",
+ "owner",
)
type = serializers.SerializerMethodField()
close_old_connections()
task_args = body[0]
- input_doc, _ = task_args
+ input_doc, overrides = task_args
task_file_name = input_doc.original_file.name
+ user_id = overrides.owner_id if overrides else None
PaperlessTask.objects.create(
task_id=headers["id"],
date_created=timezone.now(),
date_started=None,
date_done=None,
+ owner_id=user_id,
)
except Exception: # pragma: no cover
# Don't let an exception in the signal handlers prevent
import uuid
import celery
+from django.contrib.auth.models import Permission
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase
class TestTasks(DirectoriesMixin, APITestCase):
ENDPOINT = "/api/tasks/"
- ENDPOINT_ACKNOWLEDGE = "/api/acknowledge_tasks/"
def setUp(self):
super().setUp()
self.assertEqual(len(response.data), 1)
response = self.client.post(
- self.ENDPOINT_ACKNOWLEDGE,
+ self.ENDPOINT + "acknowledge/",
{"tasks": [task.id]},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.get(self.ENDPOINT)
self.assertEqual(len(response.data), 0)
+ def test_tasks_owner_aware(self):
+ """
+ GIVEN:
+ - Existing PaperlessTasks with owner and with no owner
+ WHEN:
+ - API call is made to get tasks
+ THEN:
+ - Only tasks with no owner or request user are returned
+ """
+
+ regular_user = User.objects.create_user(username="test")
+ regular_user.user_permissions.add(*Permission.objects.all())
+ self.client.logout()
+ self.client.force_authenticate(user=regular_user)
+
+ task1 = PaperlessTask.objects.create(
+ task_id=str(uuid.uuid4()),
+ task_file_name="task_one.pdf",
+ owner=self.user,
+ )
+
+ task2 = PaperlessTask.objects.create(
+ task_id=str(uuid.uuid4()),
+ task_file_name="task_two.pdf",
+ )
+
+ task3 = PaperlessTask.objects.create(
+ task_id=str(uuid.uuid4()),
+ task_file_name="task_three.pdf",
+ owner=regular_user,
+ )
+
+ response = self.client.get(self.ENDPOINT)
+
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(response.data), 2)
+ self.assertEqual(response.data[0]["task_id"], task3.task_id)
+ self.assertEqual(response.data[1]["task_id"], task2.task_id)
+
+ acknowledge_response = self.client.post(
+ self.ENDPOINT + "acknowledge/",
+ {"tasks": [task1.id, task2.id, task3.id]},
+ )
+ self.assertEqual(acknowledge_response.status_code, status.HTTP_200_OK)
+ self.assertEqual(acknowledge_response.data, {"result": 2})
+
def test_task_result_no_error(self):
"""
GIVEN:
from django.test import TestCase
from documents.data_models import ConsumableDocument
+from documents.data_models import DocumentMetadataOverrides
from documents.data_models import DocumentSource
from documents.models import PaperlessTask
from documents.signals.handlers import before_task_publish_handler
source=DocumentSource.ConsumeFolder,
original_file="/consume/hello-999.pdf",
),
- None,
+ DocumentMetadataOverrides(
+ title="Hello world",
+ owner_id=1,
+ ),
),
# kwargs
{},
self.assertEqual(headers["id"], task.task_id)
self.assertEqual("hello-999.pdf", task.task_file_name)
self.assertEqual("documents.tasks.consume_file", task.task_name)
+ self.assertEqual(1, task.owner_id)
self.assertEqual(celery.states.PENDING, task.status)
def test_task_prerun_handler(self):
class TasksViewSet(ReadOnlyModelViewSet):
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
serializer_class = TasksViewSerializer
+ filter_backends = (ObjectOwnedOrGrantedPermissionsFilter,)
def get_queryset(self):
queryset = (
queryset = PaperlessTask.objects.filter(task_id=task_id)
return queryset
-
-class AcknowledgeTasksView(GenericAPIView):
- permission_classes = (IsAuthenticated,)
- serializer_class = AcknowledgeTasksViewSerializer
-
- def post(self, request, *args, **kwargs):
- serializer = self.get_serializer(data=request.data)
+ @action(methods=["post"], detail=False)
+ def acknowledge(self, request):
+ serializer = AcknowledgeTasksViewSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
-
- tasks = serializer.validated_data.get("tasks")
+ task_ids = serializer.validated_data.get("tasks")
try:
- result = PaperlessTask.objects.filter(id__in=tasks).update(
+ tasks = PaperlessTask.objects.filter(id__in=task_ids)
+ if request.user is not None and not request.user.is_superuser:
+ tasks = tasks.filter(owner=request.user) | tasks.filter(owner=None)
+ result = tasks.update(
acknowledged=True,
)
return Response({"result": result})
"DEFAULT_VERSION": "1",
# Make sure these are ordered and that the most recent version appears
# last
- "ALLOWED_VERSIONS": ["1", "2", "3", "4", "5"],
+ "ALLOWED_VERSIONS": ["1", "2", "3", "4", "5", "6"],
}
if DEBUG:
from rest_framework.authtoken import views
from rest_framework.routers import DefaultRouter
-from documents.views import AcknowledgeTasksView
from documents.views import BulkDownloadView
from documents.views import BulkEditObjectsView
from documents.views import BulkEditView
name="remoteversion",
),
re_path("^ui_settings/", UiSettingsView.as_view(), name="ui_settings"),
- re_path(
- "^acknowledge_tasks/",
- AcknowledgeTasksView.as_view(),
- name="acknowledge_tasks",
- ),
re_path(
"^mail_accounts/test/",
MailAccountTestView.as_view(),