Repository: paperless-ngx/paperless-ngx

Issue:
listing of documents in views takes longer in 1.14
### Description

After updating from 1.13 to 1.14 the listing of documents in the different views (doesn't matter what view) takes much longer

![image](https://user-images.githubusercontent.com/4005090/234216956-2ea06ff0-41d9-4474-a82f-193b30856dca.png)
Loading of the view takes approx. 2-3 secs. Views with more/all documents takes much longer but even a view with a tag filter with 22 documents takes a second.


![image](https://user-images.githubusercontent.com/4005090/234220447-4161fc0a-966a-48ac-be92-843643583b8c.png)



### Steps to reproduce

1. Use a "saved view" view
2. Wait until loading spinner is disabled and count of documents is showed up.

### Webserver logs

```bash
no logs are written during this action
```


### Browser logs

_No response_

### Paperless-ngx version

1.14

### Host OS

RPI4 armv7, Docker 20.10.22

### Installation method

Docker - official image

### Browser

Chrome 112.0.5615.138

### Configuration changes

_No response_

### Other

_No response_


Discussion:
"It takes longer" isn't something that's going to be very actionable.  It's going to be dependent on hardware (a Pi for example) and configuration (using a proxy? compression? caching?).  To me, loading the documents view or a saved view appears to be roughly the same as before.  It's highly subjective.

If you can gather browser logs and show actual timing, that would be more useful.

Resolving patch:
diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py
--- a/src/documents/serialisers.py
+++ b/src/documents/serialisers.py
@@ -14,6 +14,7 @@
 from django.contrib.auth.models import User
 from django.utils.text import slugify
 from django.utils.translation import gettext as _
+from guardian.core import ObjectPermissionChecker
 from guardian.shortcuts import get_users_with_perms
 from rest_framework import serializers
 from rest_framework.fields import SerializerMethodField
@@ -149,11 +150,21 @@ def _set_permissions(self, permissions, object):
 class OwnedObjectSerializer(serializers.ModelSerializer, SetPermissionsMixin):
     def __init__(self, *args, **kwargs):
         self.user = kwargs.pop("user", None)
+        full_perms = kwargs.pop("full_perms", False)
         super().__init__(*args, **kwargs)
 
+        try:
+            if full_perms:
+                self.fields.pop("user_can_change")
+            else:
+                self.fields.pop("permissions")
+        except KeyError:
+            pass
+
     def get_permissions(self, obj):
         view_codename = f"view_{obj.__class__.__name__.lower()}"
         change_codename = f"change_{obj.__class__.__name__.lower()}"
+
         return {
             "view": {
                 "users": get_users_with_perms(
@@ -179,7 +190,19 @@ def get_permissions(self, obj):
             },
         }
 
+    def get_user_can_change(self, obj):
+        checker = ObjectPermissionChecker(self.user) if self.user is not None else None
+        return (
+            obj.owner is None
+            or obj.owner == self.user
+            or (
+                self.user is not None
+                and checker.has_perm(f"change_{obj.__class__.__name__.lower()}", obj)
+            )
+        )
+
     permissions = SerializerMethodField(read_only=True)
+    user_can_change = SerializerMethodField(read_only=True)
 
     set_permissions = serializers.DictField(
         label="Set permissions",
@@ -235,6 +258,7 @@ class Meta:
             "last_correspondence",
             "owner",
             "permissions",
+            "user_can_change",
             "set_permissions",
         )
 
@@ -252,6 +276,7 @@ class Meta:
             "document_count",
             "owner",
             "permissions",
+            "user_can_change",
             "set_permissions",
         )
 
@@ -303,6 +328,7 @@ class Meta:
             "document_count",
             "owner",
             "permissions",
+            "user_can_change",
             "set_permissions",
         )
 
@@ -338,6 +364,7 @@ class Meta:
             "document_count",
             "owner",
             "permissions",
+            "user_can_change",
             "set_permissions",
         )
 
@@ -437,6 +464,7 @@ class Meta:
             "archived_file_name",
             "owner",
             "permissions",
+            "user_can_change",
             "set_permissions",
             "notes",
         )
@@ -464,6 +492,7 @@ class Meta:
             "filter_rules",
             "owner",
             "permissions",
+            "user_can_change",
             "set_permissions",
         ]
 
@@ -783,6 +812,7 @@ class Meta:
             "document_count",
             "owner",
             "permissions",
+            "user_can_change",
             "set_permissions",
         )
 
diff --git a/src/documents/views.py b/src/documents/views.py
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -156,6 +156,10 @@ class PassUserMixin(CreateModelMixin):
 
     def get_serializer(self, *args, **kwargs):
         kwargs.setdefault("user", self.request.user)
+        kwargs.setdefault(
+            "full_perms",
+            self.request.query_params.get("full_perms", False),
+        )
         return super().get_serializer(*args, **kwargs)
 
 
@@ -274,6 +278,10 @@ def get_serializer(self, *args, **kwargs):
         kwargs.setdefault("context", self.get_serializer_context())
         kwargs.setdefault("fields", fields)
         kwargs.setdefault("truncate_content", truncate_content.lower() in ["true", "1"])
+        kwargs.setdefault(
+            "full_perms",
+            self.request.query_params.get("full_perms", False),
+        )
         return serializer_class(*args, **kwargs)
 
     def update(self, request, *args, **kwargs):


Test patch:
diff --git a/src/documents/tests/test_api.py b/src/documents/tests/test_api.py
--- a/src/documents/tests/test_api.py
+++ b/src/documents/tests/test_api.py
@@ -3396,6 +3396,36 @@ def test_object_permissions(self):
             status.HTTP_404_NOT_FOUND,
         )
 
+    def test_dynamic_permissions_fields(self):
+        Document.objects.create(title="Test", content="content 1", checksum="1")
+
+        user1 = User.objects.create_superuser(username="test1")
+        self.client.force_authenticate(user1)
+
+        response = self.client.get(
+            "/api/documents/",
+            format="json",
+        )
+
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+        resp_data = response.json()
+
+        self.assertNotIn("permissions", resp_data["results"][0])
+        self.assertIn("user_can_change", resp_data["results"][0])
+
+        response = self.client.get(
+            "/api/documents/?full_perms=true",
+            format="json",
+        )
+
+        self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+        resp_data = response.json()
+
+        self.assertIn("permissions", resp_data["results"][0])
+        self.assertNotIn("user_can_change", resp_data["results"][0])
+
 
 class TestApiRemoteVersion(DirectoriesMixin, APITestCase):
     ENDPOINT = "/api/remote_version/"