Skip to content

Commit

Permalink
SNO-177-add-view-with-specified-fields (#300)
Browse files Browse the repository at this point in the history
  • Loading branch information
keenangraham committed Oct 28, 2020
1 parent 914e01d commit b2d14bc
Show file tree
Hide file tree
Showing 12 changed files with 600 additions and 3 deletions.
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

0 comments on commit b2d14bc

Please sign in to comment.