Skip to content

Commit

Permalink
Add faceting and filtering on collection label
Browse files Browse the repository at this point in the history
  • Loading branch information
blms committed Apr 4, 2022
1 parent 472414c commit 97b4709
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 9 deletions.
15 changes: 10 additions & 5 deletions apps/iiif/manifests/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from django_elasticsearch_dsl import Document, fields
from django_elasticsearch_dsl.registries import registry
from elasticsearch_dsl import analyzer

from apps.iiif.kollections.models import Collection
from .models import Manifest
from unidecode import unidecode

Expand All @@ -20,10 +22,7 @@ class ManifestDocument(Document):
# fields to map explicitly in Elasticsearch
authors = fields.KeywordField(multi=True)
collections = fields.NestedField(properties={
"summary": fields.TextField(analyzer=html_strip),
"attribution": fields.TextField(),
"pid": fields.TextField(),
"label": fields.TextField(),
"label": fields.KeywordField(),
})
# TODO: date = DateRange()
has_pdf = fields.BooleanField()
Expand All @@ -50,7 +49,7 @@ class Django:
"publisher",
"viewingdirection",
]
related_models = ["collections"]
related_models = [Collection]

def prepare_authors(self, instance):
"""convert authors string into list"""
Expand Down Expand Up @@ -78,3 +77,9 @@ def get_queryset(self):
return super().get_queryset().prefetch_related(
"collections"
)

def get_instances_from_related(self, related_instance):
"""Retrieving item to index from related collections"""
if isinstance(related_instance, Collection):
# many to many relationship
return related_instance.manifests.all()
11 changes: 11 additions & 0 deletions apps/iiif/manifests/tests/test_documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,14 @@ def test_get_queryset(self):
# should have one collection, which is the above collection
assert prefetched['collections'].count() == 1
assert prefetched['collections'].first().pk == collection.pk

def test_get_instances_from_related(self):
"""Should get manifests from related collections"""
manifest = ManifestFactory.create()
# connect a collection and manifest
collection = Collection(label="test collection")
collection.save()
manifest.collections.add(collection)
instances = self.doc.get_instances_from_related(related_instance=collection)
# should get the manifest related to this collection
self.assertQuerysetEqual(instances, [manifest])
10 changes: 10 additions & 0 deletions apps/readux/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ class ManifestSearchForm(forms.Form):
},
),
)
collection = FacetedMultipleChoiceField(
label="Collection",
required=False,
widget=forms.SelectMultiple(
attrs={
"aria-label": "Filter volumes by collection",
"class": "uk-input",
},
),
)
sort = forms.ChoiceField(
label="Sort",
required=False,
Expand Down
19 changes: 19 additions & 0 deletions apps/readux/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ def setUp(self):
)
self.volume3.save()

collection = Collection(label="test collection")
collection.save()
self.volume1.collections.add(collection)
self.volume3.collections.add(collection)

def test_get_queryset(self):
"""Should be able to query by search term"""
volume_search_view = views.VolumeSearchView()
Expand Down Expand Up @@ -166,6 +171,12 @@ def test_get_queryset_filters(self):
response = search_results.execute(ignore_cache=True)
assert response.hits.total['value'] == 2

# should filter on collections label
volume_search_view.request.GET = {"collection": ["test collection"]}
search_results = volume_search_view.get_queryset()
response = search_results.execute(ignore_cache=True)
assert response.hits.total['value'] == 2

def test_get_queryset_sorting(self):
"""Should sort according to default or chosen sort"""
volume_search_view = views.VolumeSearchView()
Expand Down Expand Up @@ -229,14 +240,22 @@ def test_get_context_data(self, mock_set_facets):
volume_search_view.facets = [
("language", Mock()),
("author", Mock()),
("collections", Mock()),
]
with patch("apps.readux.views.VolumeSearchView.get_queryset") as mock_queryset:
volume_search_view.queryset = mock_queryset
volume_search_view.object_list = mock_queryset
mock_queryset.return_value.execute.return_value = Mock()
response = mock_queryset.return_value.execute.return_value

# these are not nested facets, so delete "inner" attributes
del response.aggregations.language.inner
del response.aggregations.author.inner

volume_search_view.get_context_data()
mock_set_facets.assert_called_with({
"language": response.aggregations.language.buckets,
"author": response.aggregations.author.buckets,
# collections IS nested, so it should have "inner" attribute
"collections": response.aggregations.collections.inner.buckets,
})
16 changes: 14 additions & 2 deletions apps/readux/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from django.contrib.sitemaps import Sitemap
from django.db.models import Max, Count
from django.urls import reverse
from elasticsearch_dsl import TermsFacet
from elasticsearch_dsl import Q, NestedFacet, TermsFacet
from elasticsearch_dsl.query import MultiMatch
from apps.iiif.manifests.documents import ManifestDocument
from apps.readux.forms import ManifestSearchForm
Expand Down Expand Up @@ -334,6 +334,7 @@ class VolumeSearchView(ListView, FormMixin):
("language", TermsFacet(field="languages", size=1000, min_doc_count=0)),
# TODO: Determine a good size for authors or consider alternate approach (i.e. not faceted)
("author", TermsFacet(field="authors", size=2000, min_doc_count=0)),
("collection", NestedFacet("collections", TermsFacet(field="collections.label", min_doc_count=0)))
]
defaults = {
"sort": "label_alphabetical"
Expand Down Expand Up @@ -369,9 +370,13 @@ def get_context_data(self, **kwargs):
facets = {}
for (facet, _) in self.facets:
if hasattr(volumes_response.aggregations, facet):
aggs = getattr(volumes_response.aggregations, facet)
# use "inner" to handle NestedFacet
if hasattr(aggs, "inner"):
aggs = getattr(aggs,"inner")
facets.update({
# get buckets array from each facet in the aggregations dict
facet: getattr(getattr(volumes_response.aggregations, facet), "buckets"),
facet: getattr(aggs, "buckets"),
})
context_data["form"].set_facets(facets)

Expand Down Expand Up @@ -402,6 +407,13 @@ def get_queryset(self):
if language_filter:
volumes = volumes.filter("terms", languages=language_filter)

# filter on collections
collection_filter = form_data.get("collection", "")
if collection_filter:
volumes = volumes.filter("nested", path="collections", query=Q(
"terms", **{ "collections.label": collection_filter}
))

# create aggregation buckets for facet fields
for (facet_name, facet) in self.facets:
volumes.aggs.bucket(facet_name, facet.get_aggregation())
Expand Down
2 changes: 1 addition & 1 deletion apps/static/css/project.css
Original file line number Diff line number Diff line change
Expand Up @@ -1659,7 +1659,7 @@ form#search-form fieldset.two-column div:first-child {
padding-right: 1rem;
}

form#search-form fieldset.two-column select[multiple] {
form#search-form select[multiple] {
height: 150px;
width: 100%;
overflow-y: scroll;
Expand Down
6 changes: 5 additions & 1 deletion apps/templates/search_results.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,15 @@ <h1 class="uk-heading-medium uk-text-center">Search</h1>
<div class="uk-form-label">{{ form.language.label }}</div>
{{ form.language }}
</div>
</fieldset>
<fieldset class="uk-margin uk-width-1-1">
<div class="uk-form-label">{{ form.collection.label }}</div>
{{ form.collection }}
<span class="uk-text-small">
Hold down “Control”, or “Command” on a Mac, to select more than one, or to deselect a selected item. Selecting multiple options will include all results matching any of the options.
</span>
</fieldset>
<fieldset clas="uk-margin uk-width-1-1">
<fieldset class="uk-margin uk-width-1-1">
<div class="uk-form-label">{{ form.sort.label }}</div>
{{ form.sort }}
</fieldset>
Expand Down

0 comments on commit 97b4709

Please sign in to comment.