Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SNO-177-add-view-with-specified-fields #300

Merged
merged 16 commits into from
Oct 28, 2020
5 changes: 3 additions & 2 deletions src/snovault/auditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,9 @@ def item_view_audit(context, request):
path = request.resource_path(context)
properties = request.embed(path, '@@object')
inherit = context.audit_inherit
if context.embedded and '*' in context.audit_inherit:
inherit = context.embedded
embedded_paths = context.embedded_paths()
if embedded_paths and '*' in context.audit_inherit:
inherit = embedded_paths
else:
inherit = context.audit_inherit or []
audit = inherit_audits(request, properties, inherit)
Expand Down
23 changes: 23 additions & 0 deletions src/snovault/calculated.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,26 @@ def calculate_select_properties(context, request, ns=None, category='object', se
if value is not None
}
return select_calculated_properties


def _should_render_property(include, exclude, name):
conditions = [
include is None or name in include,
exclude is None or name not in exclude,
]
return all(conditions)


def calculate_filtered_properties(context, request, ns=None, category='object', include=None, exclude=None):
namespace, props = _init_property_calculation(context, request, ns=ns, category=category)
filtered_properties = (
(name, prop(namespace))
for name, prop in props.items()
if _should_render_property(include, exclude, name)
)
filtered_calculated_properties = {
name: value
for name, value in filtered_properties
if value is not None
}
return filtered_calculated_properties
2 changes: 1 addition & 1 deletion src/snovault/elasticsearch/create_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ def type_mapping(types, item_type, embed=True):
mapping = schema_mapping(item_type, schema)
if not embed:
return mapping
for prop in type_info.embedded:
for prop in type_info.embedded_paths:
s = schema
m = mapping

Expand Down
38 changes: 38 additions & 0 deletions src/snovault/resource_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
from snovault.elasticsearch.searches.responses import FieldedResponse
from .calculated import calculate_properties
from .calculated import calculate_select_properties
from .calculated import calculate_filtered_properties
from .calculated import _should_render_property
from .etag import etag_tid
from .interfaces import CONNECTION
from .elasticsearch.interfaces import ELASTIC_SEARCH
Expand Down Expand Up @@ -237,13 +239,49 @@ def item_view_object_with_select_calculated_properties(context, request):
return properties


@view_config(
context=Item,
permission='view',
request_method='GET',
name='filtered_object'
)
def item_view_filtered_object(context, request):
properties = item_links(context, request)
qs = QueryString(request)
include = qs.param_values_to_list(
params=qs.get_key_filters(
key='include'
)
) or None
exclude = qs.param_values_to_list(
params=qs.get_key_filters(
key='exclude'
)
) or None
calculated = calculate_filtered_properties(
context,
request,
ns=properties,
include=include,
exclude=exclude,
)
properties.update(calculated)
return {
k: v
for k, v in properties.items()
if _should_render_property(include, exclude, k)
}


@view_config(context=Item, permission='view', request_method='GET',
name='embedded')
def item_view_embedded(context, request):
item_path = request.resource_path(context)
properties = request.embed(item_path, '@@object')
for path in context.embedded:
expand_path(request, properties, path)
for path in context.embedded_with_frame:
path.expand(request, properties)
return properties


Expand Down
14 changes: 14 additions & 0 deletions src/snovault/resources.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# See http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/resources.html
import logging
from collections import Mapping
from itertools import chain
from pyramid.decorator import reify
from pyramid.httpexceptions import HTTPInternalServerError
from pyramid.security import (
Expand Down Expand Up @@ -207,6 +208,7 @@ class Item(Resource):
name_key = None
rev = {}
embedded = ()
embedded_with_frame = ()
audit_inherit = []
schema = None
AbstractCollection = AbstractCollection
Expand Down Expand Up @@ -254,6 +256,18 @@ def uuid(self):
def tid(self):
return self.model.tid

@classmethod
def embedded_paths(cls):
return list(
chain(
cls.embedded,
(
p.path
for p in cls.embedded_with_frame
)
)
)

def links(self, properties):
return {
path: set(simple_path_ids(properties, path))
Expand Down
2 changes: 2 additions & 0 deletions src/snovault/tests/test_searches_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ def test_searches_queries_abstract_query_factory_get_subtypes_for_item_type(para
subtypes = aq._get_subtypes_for_item_type('Item')
assert sorted(subtypes) == sorted([
'TestingServerDefault',
'TestingCustomEmbedSource',
'TestingCustomEmbedTarget',
'TestingPostPutPatch',
'TestingDependencies',
'TestingLinkTarget',
Expand Down
130 changes: 130 additions & 0 deletions src/snovault/tests/test_skip_calculated.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,133 @@ def test_object_view_embed_object_with_select_calculated_properties_reverse(dumm
'@@object_with_select_calculated_properties?field=reverse&field=nonsense'
)
assert 'reverse' in res


def test_calculated_should_render_property():
from snovault.calculated import _should_render_property
assert _should_render_property(None, None, 'abc')
assert _should_render_property(None, [], 'abc')
assert not _should_render_property([], None, 'abc')
assert not _should_render_property([], [], 'abc')
assert _should_render_property(['abc'], None, 'abc')
assert _should_render_property(['abc', 'xyz'], None, 'abc')
assert not _should_render_property(['xyz'], None, 'abc')
assert not _should_render_property(['xyz'], None, 'abc')
assert not _should_render_property(['xyz'], [], 'abc')
assert not _should_render_property(['xyz'], ['abc'], 'abc')
assert not _should_render_property(['xyz'], ['abc'], 'abc')
assert _should_render_property(['abc', 'xyz'], [], 'abc')
assert _should_render_property(['xyz'], [], 'xyz')


def test_filtered_object_view_get_default(testapp, dummy_request, posted_targets_and_sources):
r = testapp.get('/testing-link-targets/one/')
expected_keys = [
'name',
'@id',
'@type',
'uuid',
'reverse',
'@context'
]
assert all(
[
e in r.json
for e in expected_keys
]
)


def test_filtered_object_view_get_filtered_object(testapp, dummy_request, posted_targets_and_sources):
r = testapp.get('/testing-link-targets/one/@@filtered_object')
expected_keys = [
'name',
'@id',
'@type',
'uuid',
'reverse',
]
assert all(
[
e in r.json
for e in expected_keys
]
)


def test_filtered_object_view_get_filtered_object_with_exclude(testapp, dummy_request, posted_targets_and_sources):
r = testapp.get('/testing-link-targets/one/@@filtered_object?exclude=reverse')
expected_keys = [
'name',
'@id',
'@type',
'uuid',
]
assert all(
[
e in r.json
for e in expected_keys
]
)
assert 'reverse' not in r.json


def test_filtered_object_view_get_filtered_object_with_more_exclude(testapp, dummy_request, posted_targets_and_sources):
r = testapp.get('/testing-link-targets/one/@@filtered_object?exclude=reverse&exclude=@id')
expected_keys = [
'name',
'@type',
'uuid',
]
assert all(
[
e in r.json
for e in expected_keys
]
)
assert 'reverse' not in r.json
assert '@id' not in r.json


def test_filtered_object_view_get_filtered_object_with_exclude_and_include(testapp, dummy_request, posted_targets_and_sources):
r = testapp.get('/testing-link-targets/one/@@filtered_object?exclude=reverse&exclude=@id&include=uuid')
expected_keys = [
'uuid',
]
assert all(
[
e in r.json
for e in expected_keys
]
)
assert 'reverse' not in r.json
assert '@id' not in r.json
assert 'name' not in r.json
assert '@type' not in r.json


def test_embedded_with_frame_custom_embed(testapp, dummy_request, posted_custom_embed_targets_and_sources):
r = testapp.get('/testing-custom-embed-targets/one/')
assert r.json['reverse'][0] == {
'name': 'A',
'status': 'current',
'target': '/testing-custom-embed-targets/one/',
'@id': '/testing-custom-embed-sources/16157204-8c8f-4672-a1a4-14f4b8021fcd/',
'@type': ['TestingCustomEmbedSource', 'Item'],
'uuid': '16157204-8c8f-4672-a1a4-14f4b8021fcd'
}
assert r.json['filtered_reverse'][0] == {
'status': 'current',
'uuid': '16157204-8c8f-4672-a1a4-14f4b8021fcd'
}
assert r.json['filtered_reverse1'][0] == {
'name': 'A',
'status': 'current',
'target': '/testing-custom-embed-targets/one/',
'@id': '/testing-custom-embed-sources/16157204-8c8f-4672-a1a4-14f4b8021fcd/'
}
assert r.json['reverse_uncalculated'][0] == {
'name': 'A',
'status': 'current',
'target': '/testing-custom-embed-targets/one/'
}
Loading