diff --git a/filingcabinet/filters.py b/filingcabinet/filters.py index 6407f82..6d1e2f6 100644 --- a/filingcabinet/filters.py +++ b/filingcabinet/filters.py @@ -131,6 +131,12 @@ class PageDocumentFilterset(filters.FilterSet): to_field_name="pk", method="filter_collection", ) + directory = filters.ModelChoiceFilter( + queryset=(CollectionDirectory.objects.all().select_related("collection")), + null_label="", + null_value=NULL_VALUE, + method="filter_directory", + ) portal = filters.ModelChoiceFilter( queryset=DocumentPortal.objects.filter(public=True), to_field_name="pk", @@ -141,8 +147,10 @@ class PageDocumentFilterset(filters.FilterSet): to_field_name="pk", method="filter_document", ) - number = filters.NumberFilter( - method="filter_number", + number = filters.NumberFilter(field_name="number", lookup_expr="exact") + + created_at = filters.DateFromToRangeFilter( + method="filter_created_at", ) class Meta: @@ -173,10 +181,21 @@ def filter_tag(self, qs, name, value): def filter_collection(self, qs, name, collection): if not collection.can_read(self.request): return qs.none() - qs = qs.filter(document__collections=collection) + qs = qs.filter(document__in=collection.documents.all()) qs = self.apply_data_filters(qs, collection.settings.get("filters", [])) return qs + def filter_directory(self, qs, name, directory): + if NULL_VALUE == directory: + return qs.filter( + document__filingcabinet_collectiondocument__directory__isnull=True + ) + if not directory.collection.can_read(self.request): + return qs.none() + return qs.filter( + document__filingcabinet_collectiondocument__directory=directory + ).order_by("document__filingcabinet_collectiondocument__order") + def filter_document(self, qs, name, value): if not value.can_read(self.request): return qs.none() @@ -187,6 +206,9 @@ def filter_portal(self, qs, name, portal): qs = self.apply_data_filters(qs, portal.settings.get("filters", [])) return qs + def filter_number(self, qs, name, value): + return qs.filter(number=value) + def apply_data_filters(self, qs, filters): for filt in filters: if not filt["key"].startswith("data."): diff --git a/tests/conftest.py b/tests/conftest.py index 922a38d..a3b40af 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,8 +4,10 @@ from pytest_factoryboy import register from .factories import ( + CollectionDirectoryFactory, DocumentCollectionFactory, DocumentFactory, + DocumentPortalFactory, PageFactory, UserFactory, ) @@ -14,6 +16,8 @@ register(DocumentFactory) register(DocumentCollectionFactory) register(PageFactory) +register(CollectionDirectoryFactory) +register(DocumentPortalFactory) os.environ.setdefault("DJANGO_ALLOW_ASYNC_UNSAFE", "true") diff --git a/tests/factories.py b/tests/factories.py index 1081d6a..cd116a8 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -4,7 +4,7 @@ import factory from filingcabinet import get_document_model, get_documentcollection_model -from filingcabinet.models import Page +from filingcabinet.models import CollectionDirectory, DocumentPortal, Page Document = get_document_model() DocumentCollection = get_documentcollection_model() @@ -38,3 +38,20 @@ class Meta: title = factory.Sequence(lambda n: "DocumentCollection {}".format(n)) slug = factory.LazyAttribute(lambda o: slugify(o.title)) user = factory.SubFactory(UserFactory) + + +class CollectionDirectoryFactory(factory.django.DjangoModelFactory): + class Meta: + model = CollectionDirectory + + collection = factory.SubFactory(DocumentCollectionFactory) + name = factory.Sequence(lambda n: "CollectionDirectory {}".format(n)) + depth = 0 + + +class DocumentPortalFactory(factory.django.DjangoModelFactory): + class Meta: + model = DocumentPortal + + title = factory.Sequence(lambda n: "DocumentPortal {}".format(n)) + slug = factory.LazyAttribute(lambda o: slugify(o.title)) diff --git a/tests/test_document_api_filters.py b/tests/test_document_api_filters.py new file mode 100644 index 0000000..3c78d55 --- /dev/null +++ b/tests/test_document_api_filters.py @@ -0,0 +1,250 @@ +from datetime import datetime, timedelta, timezone + +import pytest + +from filingcabinet.models import CollectionDocument + + +@pytest.mark.django_db +def test_document_api(client, document_factory): + document_factory.create_batch(10, public=True) + response = client.get("/api/document/") + assert response.status_code == 200 + assert len(response.json()["objects"]) == 10 + + +@pytest.mark.django_db +def test_document_api_filter_collection_directory( + client, + document_factory, + document_collection_factory, + collection_directory_factory, + dummy_user, +): + collection = document_collection_factory.create(user=dummy_user) + collection_dir = collection_directory_factory.create( + collection=collection, user=dummy_user + ) + + document_factory.create_batch(5, public=True, user=dummy_user) + + # Put some documents in collection, first in directory + documents = document_factory.create_batch(10, public=True, user=dummy_user) + CollectionDocument.objects.create( + collection=collection, + document=documents[0], + directory=collection_dir, + ) + collection.documents.add(*documents[1:]) + + response = client.get("/api/document/?collection={}".format(collection.pk)) + assert response.status_code == 200 + data = response.json() + assert len(data["objects"]) == 10 + + response = client.get( + "/api/document/?collection={}&directory=-".format(collection.pk) + ) + assert response.status_code == 200 + data = response.json() + assert len(data["objects"]) == 9 + assert documents[0].id not in {d["id"] for d in data["objects"]} + + response = client.get( + "/api/document/?collection={}&directory={}".format( + collection.pk, collection_dir.pk + ) + ) + assert response.status_code == 200 + data = response.json() + assert len(data["objects"]) == 1 + assert data["objects"][0]["id"] == documents[0].pk + + collection.public = False + collection.save() + + response = client.get("/api/document/?collection={}".format(collection.pk)) + data = response.json() + assert len(data["objects"]) == 0 + + response = client.get("/api/document/?directory={}".format(collection_dir.pk)) + data = response.json() + assert len(data["objects"]) == 0 + + # Login as user + client.force_login(dummy_user) + + response = client.get("/api/document/?collection={}".format(collection.pk)) + data = response.json() + assert len(data["objects"]) == 10 + + response = client.get("/api/document/?directory={}".format(collection_dir.pk)) + data = response.json() + assert len(data["objects"]) == 1 + + +@pytest.mark.django_db +def test_document_api_filter_id( + client, + document_factory, +): + + documents = document_factory.create_batch(5, public=True) + + response = client.get( + "/api/document/?ids={},{}".format(documents[0].pk, documents[1].pk) + ) + data = response.json() + assert len(data["objects"]) == 2 + assert documents[0].id in {d["id"] for d in data["objects"]} + assert documents[1].id in {d["id"] for d in data["objects"]} + + # Filter is ignored on bad input + response = client.get( + "/api/document/?ids={},{},a".format(documents[0].pk, documents[1].pk) + ) + data = response.json() + assert len(data["objects"]) == 5 + + +@pytest.mark.django_db +def test_document_api_filter_portal(client, document_factory, document_portal_factory): + portal = document_portal_factory.create(public=False) + document_factory.create_batch(5, public=True, portal=portal) + + response = client.get("/api/document/?portal={}".format(portal.pk)) + assert response.status_code == 400 + + portal.public = True + portal.save() + + response = client.get("/api/document/?portal={}".format(portal.pk)) + data = response.json() + assert response.status_code == 200 + assert len(data["objects"]) == 5 + + +@pytest.mark.django_db +def test_document_api_filter_data_filters( + client, document_factory, document_portal_factory +): + publisher_value = "wd1" + portal = document_portal_factory.create( + public=True, + settings={ + "filters": [ + { + "id": "publisher", + "key": "data.publisher", + "type": "choice", + "facet": False, + "label": {"en": "publisher"}, + "choices": [ + {"label": {"en": "Some Label"}, "value": publisher_value}, + ], + }, + { + "id": "index", + "key": "data.index", + "type": "choice", + "datatype": "int", + "label": {"en": "index"}, + "choices": [], + }, + { + "id": "date", + "key": "created_at", + "type": "daterange", + "label": {"en": "date"}, + }, + ] + }, + ) + documents = document_factory.create_batch(5, public=True, portal=portal) + documents[0].data["publisher"] = "wd1" + documents[0].data["index"] = 42 + documents[0].save() + + documents[1].data["publisher"] = "wd1" + documents[1].data["index"] = 23 + documents[1].save() + + response = client.get("/api/document/?portal={}&data.publisher=".format(portal.pk)) + data = response.json() + assert response.status_code == 200 + assert len(data["objects"]) == 5 + + response = client.get( + "/api/document/?portal={}&data.publisher={}".format(portal.pk, publisher_value) + ) + data = response.json() + assert response.status_code == 200 + assert len(data["objects"]) == 2 + assert data["objects"][0]["id"] == documents[0].pk + + response = client.get( + "/api/document/?portal={}&data.publisher={}&data.index={}".format( + portal.pk, publisher_value, "a" + ) + ) + data = response.json() + assert response.status_code == 200 + assert len(data["objects"]) == 2 + assert data["objects"][0]["id"] == documents[0].pk + + response = client.get( + "/api/document/?portal={}&data.publisher={}&data.index={}".format( + portal.pk, publisher_value, 42 + ) + ) + data = response.json() + assert response.status_code == 200 + assert len(data["objects"]) == 1 + assert data["objects"][0]["id"] == documents[0].pk + + +@pytest.mark.django_db +def test_document_api_filter_tags( + client, + document_factory, +): + + documents = document_factory.create_batch(5, public=True) + documents[0].tags.add("tag1") + + response = client.get("/api/document/?tag=tag1") + data = response.json() + assert len(data["objects"]) == 1 + assert documents[0].id in {d["id"] for d in data["objects"]} + + +@pytest.mark.django_db +def test_document_api_filter_created_at( + client, + document_factory, +): + + week = timedelta(days=7) + date = datetime(2019, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + documents = document_factory.create_batch(5, public=True, created_at=date) + documents[0].created_at = date + week + documents[0].save() + documents[1].published_at = date + week + documents[1].save() + + query_date_after = (date + timedelta(days=3)).date().isoformat() + + response = client.get("/api/document/?created_at_after={}".format(query_date_after)) + data = response.json() + assert len(data["objects"]) == 2 + ids = {d["id"] for d in data["objects"]} + assert documents[0].id in ids + assert documents[1].id in ids + + response = client.get( + "/api/document/?created_at_before={}".format(query_date_after) + ) + data = response.json() + assert len(data["objects"]) == 3 + ids = {d["id"] for d in data["objects"]} + assert {documents[2].id, documents[3].id, documents[4].id} == ids diff --git a/tests/test_page_api_filters.py b/tests/test_page_api_filters.py new file mode 100644 index 0000000..c1ff5cc --- /dev/null +++ b/tests/test_page_api_filters.py @@ -0,0 +1,361 @@ +from datetime import datetime, timedelta, timezone + +import pytest + +from filingcabinet.models import CollectionDocument + + +@pytest.mark.django_db +def test_page_api(client, processed_document): + # Require document/collection query parameter + response = client.get("/api/page/") + assert response.status_code == 200 + data = response.json() + assert len(data["objects"]) == 0 + + response = client.get("/api/page/?document={}".format(processed_document.pk)) + assert response.status_code == 200 + data = response.json() + assert len(data["objects"]) == 4 + + +@pytest.mark.django_db +def test_page_api_filter_q(client, processed_document): + page = processed_document.pages.all()[0] + page.content = "test search test" + page.save() + response = client.get( + "/api/page/?document={}&q=search".format(processed_document.pk) + ) + assert response.status_code == 200 + data = response.json() + assert len(data["objects"]) == 1 + assert data["objects"][0]["number"] == page.number + + +@pytest.mark.django_db +def test_page_api_filter_number(client, processed_document): + response = client.get( + "/api/page/?document={}&number=3".format(processed_document.pk) + ) + assert response.status_code == 200 + data = response.json() + assert len(data["objects"]) == 1 + assert data["objects"][0]["number"] == 3 + + +@pytest.mark.django_db +def test_page_api_filter_collection_directory( + client, + processed_document, + document_collection_factory, + collection_directory_factory, + dummy_user, +): + collection = document_collection_factory.create(user=dummy_user) + collection_dir = collection_directory_factory.create( + collection=collection, user=dummy_user + ) + + # Put document in collection and directory + col_doc = CollectionDocument.objects.create( + collection=collection, + document=processed_document, + # directory=collection_dir, + ) + + response = client.get("/api/page/?collection={}".format(collection.pk)) + assert response.status_code == 200 + data = response.json() + assert len(data["objects"]) == processed_document.num_pages + + response = client.get("/api/page/?collection={}&directory=-".format(collection.pk)) + assert response.status_code == 200 + data = response.json() + assert len(data["objects"]) == processed_document.num_pages + + response = client.get( + "/api/page/?collection={}&directory={}".format(collection.pk, collection_dir.pk) + ) + assert response.status_code == 200 + data = response.json() + assert len(data["objects"]) == 0 + + col_doc.directory = collection_dir + col_doc.save() + + response = client.get("/api/page/?collection={}&directory=-".format(collection.pk)) + assert response.status_code == 200 + data = response.json() + assert len(data["objects"]) == 0 + + response = client.get( + "/api/page/?collection={}&directory={}".format(collection.pk, collection_dir.pk) + ) + assert response.status_code == 200 + data = response.json() + assert len(data["objects"]) == processed_document.num_pages + + collection.public = False + collection.save() + + response = client.get("/api/page/?collection={}".format(collection.pk)) + data = response.json() + assert len(data["objects"]) == 0 + + response = client.get( + "/api/page/?collection={}&directory={}".format(collection.pk, collection_dir.pk) + ) + data = response.json() + assert len(data["objects"]) == 0 + + # Login as user + client.force_login(dummy_user) + + response = client.get("/api/page/?collection={}".format(collection.pk)) + data = response.json() + assert len(data["objects"]) == processed_document.num_pages + + response = client.get( + "/api/page/?collection={}&directory={}".format(collection.pk, collection_dir.pk) + ) + + data = response.json() + assert len(data["objects"]) == processed_document.num_pages + + +@pytest.mark.django_db +def test_document_api_filter_document(client, processed_document, dummy_user): + + processed_document.public = False + processed_document.user = dummy_user + processed_document.save() + + response = client.get("/api/page/?document={}".format(processed_document.pk)) + data = response.json() + assert len(data["objects"]) == 0 + + client.force_login(dummy_user) + + # Filter is ignored on bad input + response = client.get("/api/page/?document={}".format(processed_document.pk)) + data = response.json() + assert len(data["objects"]) == processed_document.num_pages + + +@pytest.mark.django_db +def test_document_api_filter_portal( + client, + processed_document, + document_factory, + document_portal_factory, + document_collection_factory, + page_factory, + dummy_user, +): + + portal = document_portal_factory.create(public=False) + processed_document.portal = portal + processed_document.save() + document = document_factory(public=True, num_pages=1) + page_factory(document=document) + processed_document.tags.add("tag1") + collection = document_collection_factory.create(user=dummy_user) + collection.documents.add(processed_document, document) + + response = client.get( + "/api/page/?collection={}&portal={}".format(collection.pk, portal.pk) + ) + assert response.status_code == 400 + + portal.public = True + portal.save() + + response = client.get( + "/api/page/?collection={}&portal={}".format(collection.pk, portal.pk) + ) + data = response.json() + assert response.status_code == 200 + assert len(data["objects"]) == processed_document.num_pages + + +@pytest.mark.django_db +def test_document_api_filter_data_filters( + client, + processed_document, + document_factory, + page_factory, + document_collection_factory, + dummy_user, +): + + publisher_value = "wd1" + document = document_factory( + public=True, num_pages=1, data={"publisher": publisher_value, "index": 23} + ) + page_factory(document=document) + other_doc = document_factory(public=True) + page_factory(document=other_doc) + collection = document_collection_factory.create( + user=dummy_user, + settings={ + "filters": [ + { + "id": "publisher", + "key": "data.publisher", + "type": "choice", + "facet": False, + "label": {"en": "publisher"}, + "choices": [ + {"label": {"en": "Some Label"}, "value": publisher_value}, + ], + }, + { + "id": "index", + "key": "data.index", + "type": "choice", + "datatype": "int", + "label": {"en": "index"}, + "choices": [], + }, + { + "id": "date", + "key": "created_at", + "type": "daterange", + "label": {"en": "date"}, + }, + ] + }, + ) + collection.documents.add(processed_document, document) + + processed_document.data["publisher"] = "wd1" + processed_document.data["index"] = 42 + processed_document.save() + + response = client.get( + "/api/page/?collection={}&data.publisher=".format(collection.pk) + ) + data = response.json() + assert response.status_code == 200 + assert len(data["objects"]) == 5 + + response = client.get( + "/api/page/?collection={}&data.publisher={}".format( + collection.pk, publisher_value + ) + ) + data = response.json() + assert response.status_code == 200 + assert len(data["objects"]) == processed_document.num_pages + document.num_pages + + response = client.get( + "/api/page/?collection={}&data.publisher={}&data.index={}".format( + collection.pk, publisher_value, "a" + ) + ) + data = response.json() + assert response.status_code == 200 + assert len(data["objects"]) == processed_document.num_pages + document.num_pages + + response = client.get( + "/api/page/?collection={}&data.publisher={}&data.index={}".format( + collection.pk, publisher_value, 42 + ) + ) + data = response.json() + assert response.status_code == 200 + assert len(data["objects"]) == processed_document.num_pages + + +@pytest.mark.django_db +def test_page_api_filter_tags( + client, + processed_document, + document_factory, + document_collection_factory, + page_factory, + dummy_user, +): + + document = document_factory(public=True) + page_factory(document=document) + processed_document.tags.add("tag1") + collection = document_collection_factory.create(user=dummy_user) + collection.documents.add(processed_document, document) + + response = client.get("/api/page/?collection={}&tag=tag1".format(collection.pk)) + data = response.json() + assert len(data["objects"]) == processed_document.num_pages + + +@pytest.mark.django_db +def test_page_api_filter_created_at( + client, + processed_document, + document_factory, + document_collection_factory, + page_factory, + dummy_user, +): + + week = timedelta(days=7) + date = datetime(2019, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + + document = document_factory(public=True, created_at=date, num_pages=1) + page_factory(document=document) + processed_document.tags.add("tag1") + collection = document_collection_factory.create(user=dummy_user) + collection.documents.add(processed_document, document) + + processed_document.published_at = None + processed_document.created_at = date + week + processed_document.save() + query_date_after = (date + timedelta(days=3)).date().isoformat() + + response = client.get( + "/api/page/?collection={}&created_at_after={}".format( + collection.pk, query_date_after + ) + ) + data = response.json() + assert len(data["objects"]) == processed_document.num_pages + + response = client.get( + "/api/page/?collection={}&created_at_before={}".format( + collection.pk, query_date_after + ) + ) + data = response.json() + assert len(data["objects"]) == document.num_pages + + processed_document.created_at = date + processed_document.published_at = date + week + processed_document.save() + + response = client.get( + "/api/page/?collection={}&created_at_after={}".format( + collection.pk, query_date_after + ) + ) + data = response.json() + assert len(data["objects"]) == processed_document.num_pages + + response = client.get( + "/api/page/?collection={}&created_at_before={}".format( + collection.pk, query_date_after + ) + ) + data = response.json() + assert len(data["objects"]) == document.num_pages + + processed_document.published_at = date + processed_document.save() + + response = client.get( + "/api/page/?collection={}&created_at_before={}".format( + collection.pk, query_date_after + ) + ) + data = response.json() + assert len(data["objects"]) == processed_document.num_pages + document.num_pages