From 705fb82d3e2424db88dce47a22ceefb18a3e8658 Mon Sep 17 00:00:00 2001 From: Hillel Arnold Date: Tue, 26 Nov 2024 16:02:32 -0500 Subject: [PATCH 1/3] adding citation settings --- argo/config.py.deploy | 3 +++ argo/config.py.example | 3 +++ argo/settings.py | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/argo/config.py.deploy b/argo/config.py.deploy index 1e76b89..9077e24 100644 --- a/argo/config.py.deploy +++ b/argo/config.py.deploy @@ -13,3 +13,6 @@ SQL_HOST = "${SQL_HOST}" SQL_PORT = ${SQL_PORT} USE_X_FORWARDED_HOST = ${USE_X_FORWARDED_HOST} SECURE_PROXY_SSL_HEADER = ${SECURE_PROXY_SSL_HEADER} +CITATION_REPOSITORY_NAME = "${REPOSITORY_NAME}" +CITATION_REPOSITORY_BASEURL = "${REPOSITORY_BASEURL}" +CITATION_SEPARATOR = "${CITATION_SEPARATOR}" \ No newline at end of file diff --git a/argo/config.py.example b/argo/config.py.example index 2fdeed3..4701333 100644 --- a/argo/config.py.example +++ b/argo/config.py.example @@ -13,3 +13,6 @@ SQL_HOST = "argo-db" # host for the application database (string) SQL_PORT = 5432 # port on which the application database can be reached (integer) USE_X_FORWARDED_HOST = False # Set to True if the application is behind a reverse proxy (boolean) SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") # If the application is being a reverse proxy, and if a header value indicates an HTTPS connection proxied over HTTP, name that header and value (tuple) +CITATION_REPOSITORY_NAME = "Rockefeller Archive Center" # name of repository used in citations +CITATION_REPOSITORY_BASEURL = "https://dimes.rockarch.org/" # base url for citation links +CITATION_SEPARATOR = "; " # string to separate different parts of the citation \ No newline at end of file diff --git a/argo/settings.py b/argo/settings.py index 9b4e70c..c9b3a73 100644 --- a/argo/settings.py +++ b/argo/settings.py @@ -169,3 +169,7 @@ CSP_FONT_SRC = ("'self'", "https://assets.rockarch.org") CSP_FRAME_SRC = ("'none'") CSP_FRAME_ANCESTORS = ("'none'") + +CITATION_REPOSITORY_NAME = config.CITATION_REPOSITORY_NAME +CITATION_REPOSITORY_BASEURL = config.CITATION_REPOSITORY_BASEURL +CITATION_SEPARATOR = config.CITATION_SEPARATOR From c1457638e81e82cfadd1ba59a239e32da62504ba Mon Sep 17 00:00:00 2001 From: Hillel Arnold Date: Tue, 26 Nov 2024 16:02:58 -0500 Subject: [PATCH 2/3] add citation action view --- api_formatter/view_helpers.py | 20 ++++++++++++++++++++ api_formatter/views.py | 27 +++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/api_formatter/view_helpers.py b/api_formatter/view_helpers.py index fd880d9..4a7e68b 100644 --- a/api_formatter/view_helpers.py +++ b/api_formatter/view_helpers.py @@ -188,3 +188,23 @@ def date_string(dates): def description_from_notes(notes): return text_from_notes(notes, "abstract") if text_from_notes(notes, "abstract") else text_from_notes(notes, "scopecontent") + + +def flatten_ancestors(ancestors): + ancestors_list = [] + for key, value in ancestors.items(): + if isinstance(value, dict): + ancestors_list += flatten_ancestors(value) + elif key == 'title': + ancestors_list.append(value) + return ancestors_list + + +def citation_title(title, date_string): + if all([title, date_string]): + if title != date_string: + return f"{title}, {date_string}" + else: + return date_string + else: + return [t for t in [title, date_string] if t][0] diff --git a/api_formatter/views.py b/api_formatter/views.py index 7f57d3a..4b03f37 100644 --- a/api_formatter/views.py +++ b/api_formatter/views.py @@ -1,4 +1,5 @@ from django.http import Http404 +from django.urls import reverse from django_elasticsearch_dsl_drf.constants import SUGGESTER_TERM from django_elasticsearch_dsl_drf.pagination import LimitOffsetPagination from elasticsearch_dsl import A, Q @@ -23,8 +24,9 @@ NESTED_FILTER_FIELDS, NUMBER_LOOKUPS, ORDERING_FIELDS, SEARCH_BACKENDS, SEARCH_FIELDS, SEARCH_NESTED_FIELDS, STRING_LOOKUPS, - ChildrenPaginator, SearchMixin, date_string, - description_from_notes) + ChildrenPaginator, SearchMixin, citation_title, + date_string, description_from_notes, + flatten_ancestors) class AncestorMixin(object): @@ -199,6 +201,27 @@ def get_structured_query(self): def list_fields(self): return list(set(list(self.filter_fields) + list(self.ordering_fields) + list(self.search_fields) + ["type", "dates"])) + @action(detail=True) + def citation(self, request, pk): + """Returns a citation for a document""" + resolved = self.resolve_object(self.document, pk, source_fields=["dates", "title"]) + dates = date_string(resolved.to_dict().get("dates", [])) + title = resolved.title + object_path = reverse('object-detail', kwargs={'pk': pk}).lstrip('/') + url = f"{settings.CITATION_REPOSITORY_BASEURL.rstrip('/')}/{object_path}" + ancestors = [] + if getattr(self, 'ancestors', False): + ancestors = settings.CITATION_SEPARATOR.join( + flatten_ancestors(self.ancestors(request, pk).data) + ) + + citation = [ + citation_title(title, dates), + ancestors, + settings.CITATION_REPOSITORY_NAME, + url] + return Response(settings.CITATION_SEPARATOR.join([c for c in citation if c])) + class AgentViewSet(DocumentViewSet): """ From e10d9355e8d476e79806cac78591a235be753815 Mon Sep 17 00:00:00 2001 From: Hillel Arnold Date: Tue, 26 Nov 2024 16:03:15 -0500 Subject: [PATCH 3/3] add tests for citation view and helpers --- api_formatter/tests.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/api_formatter/tests.py b/api_formatter/tests.py index 1fbe9a2..9d96e9f 100644 --- a/api_formatter/tests.py +++ b/api_formatter/tests.py @@ -14,7 +14,7 @@ from argo import settings -from .view_helpers import date_string +from .view_helpers import citation_title, date_string, flatten_ancestors from .views import (AgentViewSet, CollectionViewSet, MyListView, ObjectViewSet, SearchView, TermViewSet) @@ -229,6 +229,14 @@ def minimap_view(self, pk): for key in ["index", "uri", "title", "online"]: self.assertIsNot(result.get(key), None) + def citation_view(self, basename, pk): + """Asserts citation is generated.""" + response = self.client.get(reverse(f"{basename}-citation", args=[pk])).json() + self.assertIsInstance(response, str) + self.assertIn(settings.CITATION_REPOSITORY_NAME, response) + self.assertIn(settings.CITATION_REPOSITORY_BASEURL, response) + self.assertIn(settings.CITATION_SEPARATOR, response) + def mylist_view(self, added_ids): """Asserts the MyList view returns the expected response status and results.""" list = random.sample(added_ids, 5) @@ -253,6 +261,7 @@ def test_documents(self): added_ids = self.index_fixture_data('fixtures/{}'.format(doc_type), doc_cls) self.list_view(doc_cls, doc_type, viewset, len(added_ids)) for ident in added_ids: + self.citation_view(doc_type, ident) self.detail_view(doc_type, viewset, ident) if doc_type in ["collection", "object"]: self.ancestors_view(doc_type, viewset, ident) @@ -286,3 +295,18 @@ def test_date_string(self): ([{"begin": "1945"}, {"expression": "1950"}], "1945, 1950"), ([{"begin": "1945", "end": "1946"}, {"expression": "1950"}], "1945-1946, 1950")]: self.assertEqual(date_string(input), expected) + + def test_flatten_ancestors(self): + input = {"title": "top level", "child": {"title": "second level", "child": {"title": "third level"}}} + output = flatten_ancestors(input) + self.assertEqual(output, ["top level", "second level", "third level"]) + + def test_citation_title(self): + for input, expected in [ + (["foo", "bar"], "foo, bar"), + (["foo", None], "foo"), + ([None, "bar"], "bar"), + (["foo", "foo"], "foo"), + ]: + output = citation_title(*input) + self.assertEqual(output, expected)