Support owner API query vars

This commit is contained in:
shamoon 2023-05-02 00:38:32 -07:00
parent fe990b4cd2
commit 487d3a6262
6 changed files with 220 additions and 4 deletions

View File

@ -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,
}

View File

@ -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("-"):

View File

@ -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",
),
),
]

View File

@ -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(

View File

@ -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(

View File

@ -264,6 +264,7 @@ class DocumentViewSet(
"added",
"archive_serial_number",
"num_notes",
"owner",
)
def get_queryset(self):