Skip to content
This repository has been archived by the owner on May 3, 2020. It is now read-only.

Commit

Permalink
global: item revision endpoint
Browse files Browse the repository at this point in the history
* Adds the endpoint configuration for `Item.find_by_holding`.

Signed-off-by: Martin Vesper <[email protected]>
  • Loading branch information
mvesper committed Jan 16, 2017
1 parent 90e0977 commit 69c8a37
Show file tree
Hide file tree
Showing 7 changed files with 343 additions and 5 deletions.
4 changes: 2 additions & 2 deletions invenio_circulation/api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2016 CERN.
# Copyright (C) 2017 CERN.
#
# Invenio is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
Expand Down Expand Up @@ -40,7 +40,7 @@
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy_continuum import version_class

from invenio_circulation.models import ItemStatus
from .models import ItemStatus


def check_status(method=None, statuses=None):
Expand Down
23 changes: 22 additions & 1 deletion invenio_circulation/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2016 CERN.
# Copyright (C) 2017 CERN.
#
# Invenio is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
Expand Down Expand Up @@ -51,6 +51,27 @@
'item_route': '/circulation/items/<pid(crcitm):pid_value>',
'default_media_type': 'application/json',
'max_result_window': 10000,
},
'crcitmrev': {
'pid_type': 'crcitm',
'pid_minter': 'circulation_item',
'pid_fetcher': 'circulation_item',
'record_class': 'invenio_circulation.api:Item',
'record_serializers': {
'application/json': ('invenio_records_rest.serializers'
':json_v1_response'),
},
'search_class': 'invenio_circulation.search:ItemRevisionSearch',
'search_index': None,
'search_type': None,
'search_serializers': {
'application/json': ('invenio_circulation.serializers'
':revision_serializer'),
},
'list_route': '/circulation/item_revisions/',
'item_route': '/circulation/item_revisions/<pid(crcitm):pid_value>',
'default_media_type': 'application/json',
'max_result_window': 10000,
}
}
"""Basic REST circulation configuration."""
Expand Down
81 changes: 80 additions & 1 deletion invenio_circulation/search.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2016 CERN.
# Copyright (C) 2017 CERN.
#
# Invenio is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
Expand All @@ -23,9 +23,12 @@
# as an Intergovernmental Organization or submit itself to any jurisdiction.

"""Configuration for circulation search."""
from elasticsearch_dsl.query import Bool, MultiMatch, Range

from invenio_search import RecordsSearch

from .api import Item


class ItemSearch(RecordsSearch):
"""Default search class."""
Expand All @@ -35,3 +38,79 @@ class Meta:

index = 'circulation-item'
doc_types = None


class ItemRevisionSearch(object):
"""Search class utilizing `Item.find_by_holding`.
Since this function doesn't utilize elasticsearch, ItemRevisionSearch
has to mimick certain aspects of `elasticsearch_dsl.Search`.
"""

class Meta:
"""Configuration for circulation search."""

index = 'circulation-item'
doc_types = None

class Results(object):
"""Substitution of `elasticsearch_dsl.result.Result."""

class Hits(object):
"""Wrapper class for the search hits."""

def __init__(self, results):
"""Constructor to wrap the search results."""
self.hits = self.Hits()
self.hits.total = len(results)
self.results = results

def to_dict(self):
"""Convert results into a dictionary."""
return {
'hits': {
'hits': self.results,
'total': self.hits.total
}
}

def __init__(self):
"""Constructor for `elasticsearch_dsl.result.Result substituion.
Adds dummy `_index` value.
"""
self._index = ['']
self._query = {}

def query(self, q, *args, **kwargs):
"""Set the desired query."""
if type(q) is not Bool:
q = Bool(must=[q])
for must in q.must:
if type(must) == MultiMatch:
for field in must.fields:
self._query[field] = must.query
elif type(must) == Range:
for key, value in must._params.items():
self._query[key] = [value['gte'], value['lte']]
return self

def __getitem__(self, *args, **kwargs):
"""Support slicing of the search results. Currently not implemented."""
return self

def params(self, *args, **kwargs):
"""Specify query params to be used. Currently not implemented."""
return self

def execute(self):
"""Execute the search.
:returns: ItemRevisionSearch.Results
"""
res = []
for uuid, revision in Item.find_by_holding(**self._query):
item = Item.get_record(uuid)
res.append(item.revisions[revision-2])

return self.Results(res)
54 changes: 54 additions & 0 deletions invenio_circulation/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2017 CERN.
#
# Invenio is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Invenio is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Invenio; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
#
# In applying this license, CERN does not
# waive the privileges and immunities granted to it by virtue of its status
# as an Intergovernmental Organization or submit itself to any jurisdiction.

"""Serializers for circulation search."""
import json

from invenio_records_rest.serializers import JSONSerializer
from invenio_records_rest.serializers.response import search_responsify
from invenio_records_rest.serializers.schemas.json import RecordSchemaJSONV1


class RevisionSerializer(JSONSerializer):
"""JSON serializer for items found by `Item.find_by_holding`."""

def serialize_search(self, pid_fetcher, search_result, links=None,
item_links_factory=None):
"""Serialize a search result.
:param search_result: Elasticsearch search result.
:param links: Dictionary of links to add to response.
"""
return json.dumps({
'hits': {
'hits': [hit for hit in search_result['hits']['hits']],
'total': search_result['hits']['total'],
},
'links': links or {},
'aggregations': search_result.get('aggregations', {}),
}, **self._format_args())


revision_serializer = search_responsify(RevisionSerializer(RecordSchemaJSONV1),
'application/json')
9 changes: 8 additions & 1 deletion tests/test_examples_app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2016 CERN.
# Copyright (C) 2017 CERN.
#
# Invenio is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
Expand Down Expand Up @@ -83,3 +83,10 @@ def test_example_app(example_app):
subprocess.check_output(cmd, shell=True).decode('utf-8')
)
assert len(output['hits']['hits']) > 0

# item revision search API
cmd = 'curl http://localhost:5000/api/circulation/item_revisions/'
output = json.loads(
subprocess.check_output(cmd, shell=True).decode('utf-8')
)
assert output and 'hits' in output and 'hits' in output['hits']
120 changes: 120 additions & 0 deletions tests/test_search_revision.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2017 CERN.
#
# Invenio is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Invenio is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Invenio; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
#
# In applying this license, CERN does not
# waive the privileges and immunities granted to it by virtue of its status
# as an Intergovernmental Organization or submit itself to any jurisdiction.


"""Revision search tests."""

import datetime
import json

import pytest
from elasticsearch_dsl.query import Bool, MultiMatch, Range

from invenio_circulation.api import Item, ItemStatus, Location
from invenio_circulation.search import ItemRevisionSearch
from invenio_circulation.validators import LoanItemSchema


def test_item_revision_search_result():
search_result = ['result1', 'result2']
result = ItemRevisionSearch.Results(search_result)

assert result.hits.total == len(search_result)
assert result.results == search_result
assert result.to_dict() == {
'hits': {
'hits': search_result,
'total': len(search_result)
}
}


def test_item_revision_search_query():
# Testing the initial setup
item_revision_search = ItemRevisionSearch()

assert item_revision_search._index == ['']
assert item_revision_search._query == {}

# Testing a MultiMatch query
item_revision_search = ItemRevisionSearch()
mm = MultiMatch(fields=['foo'], query='bar')

item_revision_search.query(mm)

assert item_revision_search._query == {'foo': 'bar'}

# Testing a range query
item_revision_search = ItemRevisionSearch()
r = Range(foo={'gte': 'bar', 'lte': u'baz'})

item_revision_search.query(r)

assert item_revision_search._query == {'foo': ['bar', 'baz']}


def test_item_revision_search_execute(app, db):
# Prepare the item
item = Item.create({'foo': 'bar'})
db.session.commit()

# Create loan data
la = LoanItemSchema()
la.context['item'] = item

# Prepare the loan data
tmp = la.load({'user_id': 1}).data
data = la.dump(tmp).data

# Loan item
item.loan_item(**data)
item.commit()
db.session.commit()

# Return item
item.return_item()
item.commit()
db.session.commit()

# Prepare ItemRevisionSearch
item_revision_search = ItemRevisionSearch()
mm = MultiMatch(fields=['user_id'], query=1)

result = item_revision_search.query(mm).execute()

assert result.hits.total == 1
assert result.to_dict() == {
'hits': {
'hits': [item],
'total': 1
}
}


def test_item_revision_search_dummies():
"""Tests necessary dummy implementations."""
item_revision_search = ItemRevisionSearch()

assert item_revision_search == item_revision_search[0]
assert item_revision_search == item_revision_search.params()
Loading

0 comments on commit 69c8a37

Please sign in to comment.