mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Support owner API query vars
This commit is contained in:
parent
fe990b4cd2
commit
487d3a6262
@ -105,6 +105,8 @@ class DocumentFilterSet(FilterSet):
|
||||
|
||||
title_content = TitleContentFilter()
|
||||
|
||||
owner__id__none = ObjectFilter(field_name="owner", exclude=True)
|
||||
|
||||
class Meta:
|
||||
model = Document
|
||||
fields = {
|
||||
@ -125,6 +127,8 @@ class DocumentFilterSet(FilterSet):
|
||||
"storage_path": ["isnull"],
|
||||
"storage_path__id": ID_KWARGS,
|
||||
"storage_path__name": CHAR_KWARGS,
|
||||
"owner": ["isnull"],
|
||||
"owner__id": ID_KWARGS,
|
||||
}
|
||||
|
||||
|
||||
|
@ -208,11 +208,13 @@ class DelayedQuery:
|
||||
for document_type_id in v.split(","):
|
||||
criterias.append(query.Not(query.Term("type_id", document_type_id)))
|
||||
elif k == "correspondent__isnull":
|
||||
criterias.append(query.Term("has_correspondent", v == "false"))
|
||||
criterias.append(
|
||||
query.Term("has_correspondent", self.evalBoolean(v)),
|
||||
)
|
||||
elif k == "is_tagged":
|
||||
criterias.append(query.Term("has_tag", v == "true"))
|
||||
criterias.append(query.Term("has_tag", self.evalBoolean(v)))
|
||||
elif k == "document_type__isnull":
|
||||
criterias.append(query.Term("has_type", v == "false"))
|
||||
criterias.append(query.Term("has_type", self.evalBoolean(v) is False))
|
||||
elif k == "created__date__lt":
|
||||
criterias.append(
|
||||
query.DateRange("created", start=None, end=isoparse(v)),
|
||||
@ -236,7 +238,19 @@ class DelayedQuery:
|
||||
for storage_path_id in v.split(","):
|
||||
criterias.append(query.Not(query.Term("path_id", storage_path_id)))
|
||||
elif k == "storage_path__isnull":
|
||||
criterias.append(query.Term("has_path", v == "false"))
|
||||
criterias.append(query.Term("has_path", self.evalBoolean(v) is False))
|
||||
elif k == "owner__isnull":
|
||||
criterias.append(query.Term("has_owner", self.evalBoolean(v) is False))
|
||||
elif k == "owner__id":
|
||||
criterias.append(query.Term("owner_id", v))
|
||||
elif k == "owner__id__in":
|
||||
owners_in = []
|
||||
for owner_id in v.split(","):
|
||||
owners_in.append(query.Term("owner_id", owner_id))
|
||||
criterias.append(query.Or(owners_in))
|
||||
elif k == "owner__id__none":
|
||||
for owner_id in v.split(","):
|
||||
criterias.append(query.Not(query.Term("owner_id", owner_id)))
|
||||
|
||||
user_criterias = [query.Term("has_owner", False)]
|
||||
if "user" in self.query_params:
|
||||
@ -254,6 +268,12 @@ class DelayedQuery:
|
||||
else:
|
||||
return query.Or(user_criterias) if len(user_criterias) > 0 else None
|
||||
|
||||
def evalBoolean(self, val):
|
||||
if val == "false" or val == "0":
|
||||
return False
|
||||
if val == "true" or val == "1":
|
||||
return True
|
||||
|
||||
def _get_query_sortedby(self):
|
||||
if "ordering" not in self.query_params:
|
||||
return None, False
|
||||
@ -269,6 +289,7 @@ class DelayedQuery:
|
||||
"document_type__name": "type",
|
||||
"archive_serial_number": "asn",
|
||||
"num_notes": "num_notes",
|
||||
"owner": "owner",
|
||||
}
|
||||
|
||||
if field.startswith("-"):
|
||||
|
@ -0,0 +1,58 @@
|
||||
# Generated by Django 4.1.7 on 2023-05-04 04:11
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("documents", "1035_rename_comment_note"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="savedviewfilterrule",
|
||||
name="rule_type",
|
||||
field=models.PositiveIntegerField(
|
||||
choices=[
|
||||
(0, "title contains"),
|
||||
(1, "content contains"),
|
||||
(2, "ASN is"),
|
||||
(3, "correspondent is"),
|
||||
(4, "document type is"),
|
||||
(5, "is in inbox"),
|
||||
(6, "has tag"),
|
||||
(7, "has any tag"),
|
||||
(8, "created before"),
|
||||
(9, "created after"),
|
||||
(10, "created year is"),
|
||||
(11, "created month is"),
|
||||
(12, "created day is"),
|
||||
(13, "added before"),
|
||||
(14, "added after"),
|
||||
(15, "modified before"),
|
||||
(16, "modified after"),
|
||||
(17, "does not have tag"),
|
||||
(18, "does not have ASN"),
|
||||
(19, "title or content contains"),
|
||||
(20, "fulltext query"),
|
||||
(21, "more like this"),
|
||||
(22, "has tags in"),
|
||||
(23, "ASN greater than"),
|
||||
(24, "ASN less than"),
|
||||
(25, "storage path is"),
|
||||
(26, "has correspondent in"),
|
||||
(27, "does not have correspondent in"),
|
||||
(28, "has document type in"),
|
||||
(29, "does not have document type in"),
|
||||
(30, "has storage path in"),
|
||||
(31, "does not have storage path in"),
|
||||
(32, "owner is"),
|
||||
(33, "has owner in"),
|
||||
(34, "does not have owner"),
|
||||
(35, "does not have owner in"),
|
||||
],
|
||||
verbose_name="rule type",
|
||||
),
|
||||
),
|
||||
]
|
@ -448,6 +448,10 @@ class SavedViewFilterRule(models.Model):
|
||||
(29, _("does not have document type in")),
|
||||
(30, _("has storage path in")),
|
||||
(31, _("does not have storage path in")),
|
||||
(32, _("owner is")),
|
||||
(33, _("has owner in")),
|
||||
(34, _("does not have owner")),
|
||||
(35, _("does not have owner in")),
|
||||
]
|
||||
|
||||
saved_view = models.ForeignKey(
|
||||
|
@ -469,6 +469,98 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
results = response.data["results"]
|
||||
self.assertEqual(len(results), 0)
|
||||
|
||||
def test_document_owner_filters(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Documents with owners, with and without granted permissions
|
||||
WHEN:
|
||||
- User filters by owner
|
||||
THEN:
|
||||
- Owner filters work correctly but still respect permissions
|
||||
"""
|
||||
u1 = User.objects.create_user("user1")
|
||||
u2 = User.objects.create_user("user2")
|
||||
u1.user_permissions.add(*Permission.objects.filter(codename="view_document"))
|
||||
u2.user_permissions.add(*Permission.objects.filter(codename="view_document"))
|
||||
|
||||
u1_doc1 = Document.objects.create(
|
||||
title="none1",
|
||||
checksum="A",
|
||||
mime_type="application/pdf",
|
||||
owner=u1,
|
||||
)
|
||||
Document.objects.create(
|
||||
title="none2",
|
||||
checksum="B",
|
||||
mime_type="application/pdf",
|
||||
owner=u2,
|
||||
)
|
||||
u0_doc1 = Document.objects.create(
|
||||
title="none3",
|
||||
checksum="C",
|
||||
mime_type="application/pdf",
|
||||
)
|
||||
u1_doc2 = Document.objects.create(
|
||||
title="none4",
|
||||
checksum="D",
|
||||
mime_type="application/pdf",
|
||||
owner=u1,
|
||||
)
|
||||
u2_doc2 = Document.objects.create(
|
||||
title="none5",
|
||||
checksum="E",
|
||||
mime_type="application/pdf",
|
||||
owner=u2,
|
||||
)
|
||||
|
||||
self.client.force_authenticate(user=u1)
|
||||
assign_perm("view_document", u1, u2_doc2)
|
||||
|
||||
# Will not show any u1 docs or u2_doc1 which isn't shared
|
||||
response = self.client.get(f"/api/documents/?owner__id__none={u1.id}")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
results = response.data["results"]
|
||||
self.assertEqual(len(results), 2)
|
||||
self.assertCountEqual(
|
||||
[results[0]["id"], results[1]["id"]],
|
||||
[u0_doc1.id, u2_doc2.id],
|
||||
)
|
||||
|
||||
# Will not show any u1 docs, u0_doc1 which has no owner or u2_doc1 which isn't shared
|
||||
response = self.client.get(
|
||||
f"/api/documents/?owner__id__none={u1.id}&owner__isnull=false",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
results = response.data["results"]
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertCountEqual([results[0]["id"]], [u2_doc2.id])
|
||||
|
||||
# Will not show any u1 docs, u2_doc2 which is shared but has owner
|
||||
response = self.client.get(
|
||||
f"/api/documents/?owner__id__none={u1.id}&owner__isnull=true",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
results = response.data["results"]
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertCountEqual([results[0]["id"]], [u0_doc1.id])
|
||||
|
||||
# Will not show any u1 docs or u2_doc1 which is not shared
|
||||
response = self.client.get(f"/api/documents/?owner__id={u2.id}")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
results = response.data["results"]
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertCountEqual([results[0]["id"]], [u2_doc2.id])
|
||||
|
||||
# Will not show u2_doc1 which is not shared
|
||||
response = self.client.get(f"/api/documents/?owner__id__in={u1.id},{u2.id}")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
results = response.data["results"]
|
||||
self.assertEqual(len(results), 3)
|
||||
self.assertCountEqual(
|
||||
[results[0]["id"], results[1]["id"], results[2]["id"]],
|
||||
[u1_doc1.id, u1_doc2.id, u2_doc2.id],
|
||||
)
|
||||
|
||||
def test_search(self):
|
||||
d1 = Document.objects.create(
|
||||
title="invoice",
|
||||
@ -1112,18 +1204,30 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
self.assertEqual(r.data["count"], 2)
|
||||
r = self.client.get("/api/documents/?query=test&document_type__id__none=1")
|
||||
self.assertEqual(r.data["count"], 2)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__none={u1.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__in={u1.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
r = self.client.get(
|
||||
f"/api/documents/?query=test&owner__id__none={u1.id}&owner__isnull=true",
|
||||
)
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
|
||||
self.client.force_authenticate(user=u2)
|
||||
r = self.client.get("/api/documents/?query=test")
|
||||
self.assertEqual(r.data["count"], 3)
|
||||
r = self.client.get("/api/documents/?query=test&document_type__id__none=1")
|
||||
self.assertEqual(r.data["count"], 3)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__none={u2.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
|
||||
self.client.force_authenticate(user=superuser)
|
||||
r = self.client.get("/api/documents/?query=test")
|
||||
self.assertEqual(r.data["count"], 4)
|
||||
r = self.client.get("/api/documents/?query=test&document_type__id__none=1")
|
||||
self.assertEqual(r.data["count"], 4)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__none={u1.id}")
|
||||
self.assertEqual(r.data["count"], 3)
|
||||
|
||||
def test_search_filtering_with_object_perms(self):
|
||||
"""
|
||||
@ -1153,6 +1257,14 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
self.assertEqual(r.data["count"], 2)
|
||||
r = self.client.get("/api/documents/?query=test&document_type__id__none=1")
|
||||
self.assertEqual(r.data["count"], 2)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__none={u1.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id={u1.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__in={u1.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
r = self.client.get("/api/documents/?query=test&owner__isnull=true")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
|
||||
assign_perm("view_document", u1, d2)
|
||||
assign_perm("view_document", u1, d3)
|
||||
@ -1166,6 +1278,14 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
self.assertEqual(r.data["count"], 4)
|
||||
r = self.client.get("/api/documents/?query=test&document_type__id__none=1")
|
||||
self.assertEqual(r.data["count"], 4)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__none={u1.id}")
|
||||
self.assertEqual(r.data["count"], 3)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id={u1.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
r = self.client.get(f"/api/documents/?query=test&owner__id__in={u1.id}")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
r = self.client.get("/api/documents/?query=test&owner__isnull=true")
|
||||
self.assertEqual(r.data["count"], 1)
|
||||
|
||||
def test_search_sorting(self):
|
||||
u1 = User.objects.create_user("user1")
|
||||
@ -1247,6 +1367,14 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
search_query("&ordering=-num_notes"),
|
||||
[d1.id, d3.id, d2.id],
|
||||
)
|
||||
self.assertListEqual(
|
||||
search_query("&ordering=owner"),
|
||||
[d1.id, d2.id, d3.id],
|
||||
)
|
||||
self.assertListEqual(
|
||||
search_query("&ordering=-owner"),
|
||||
[d3.id, d2.id, d1.id],
|
||||
)
|
||||
|
||||
def test_statistics(self):
|
||||
doc1 = Document.objects.create(
|
||||
|
@ -264,6 +264,7 @@ class DocumentViewSet(
|
||||
"added",
|
||||
"archive_serial_number",
|
||||
"num_notes",
|
||||
"owner",
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user