Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
...
Checking mergeability… Don't worry, you can still create the pull request.
  • 6 commits
  • 7 files changed
  • 0 commit comments
  • 2 contributors
View
5 substanced/sdi/static/css/sdi_bootstrap.css
@@ -7665,3 +7665,8 @@ a.site-title {
vertical-align: top;
display: inline-block;
}
+#sdi_header #sdi_searchbox {
+ vertical-align: bottom;
+ width: 8em;
+ margin: 0 0.2em 0 0;
+}
View
8 substanced/sdi/static/css/sdi_bootstrap.less
@@ -59,3 +59,11 @@ a.site-title {
vertical-align: top;
display: inline-block;
}
+
+#sdi_header {
+ #sdi_searchbox {
+ vertical-align: bottom;
+ width: 8em;
+ margin: 0 0.2em 0 0;
+ }
+}
View
86 substanced/sdi/static/js/sdi.searchbox.js
@@ -0,0 +1,86 @@
+/* Searchbox in the SDI header, based on Twitter Bootstrap
+ * Typeahead */
+
+
+/*
+
+TODO
+- keyUp buffering
+- Hide when not logged in
+- Change this JS to not be just searchbox?
+- Search button at end of field, handle "enter"
+- Option to disable LiveSearch if there are too many items
+- CQE?
+
+ */
+
+$(document).ready(function () {
+
+ var sb = $('#sdi_searchbox');
+ var sb_url = sb.data('sourceurl');
+ var server_data = {};
+
+ sb.typeahead(
+ {
+ minLength:2,
+ source:function (query, process) {
+ /*
+
+ The server return a sequence of dicts like:
+ [{'url': 'http://1/', 'label':'Some Title'},]
+
+ The URL is, of course, the URL and is expected to be
+ unique for each resource. The label is what is shown
+ in the typeahead dropdown. When a user chooses an item,
+ we navigate to the resource. If they press enter in the
+ searchbox while typing, and don't select a result,
+ we go to search results.
+
+ */
+ return $.ajax(
+ {
+ url:sb_url,
+ type:'get',
+ data:{query:query},
+ dataType:'json'
+ })
+ .done(function (json) {
+ $.each(json, function (index, value) {
+ server_data[value.url] = value.label;
+ })
+
+ return process(jQuery.map(json, function (val) {
+ return val.url;
+ }));
+ });
+ },
+ matcher:function (item) {
+ /* Don't do any client-side matching */
+ return true;
+ },
+ updater:function (item) {
+ /* Navigate away if the user chooses an item */
+
+ window.location.href = item;
+ },
+ highlighter:function (item) {
+ /* We have the URL as the data-value, get the label */
+ var label = server_data[item];
+ return $('<span>' + label + '</span>');
+ },
+ sorter:function (items) {
+ /*
+
+ First item is auto selected, which forces the search
+ to be exact. Adding the query at the top of the
+ results gets around this problem, so we can return a
+ result page when there's no exact match
+
+ */
+ item_url = sb_url + '?results=1&query=' + this.query;
+ server_data[item_url] = this.query;
+ items.unshift(sb_url + '?results=1&query=' + this.query);
+ return items;
+ }
+ });
+});
View
55 substanced/sdi/views/search.py
@@ -0,0 +1,55 @@
+from ..import mgmt_view
+from ...util import find_catalog
+
+class SearchViews(object):
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def _query_results(self, query):
+ request = self.request
+ context = self.context
+ query = query + '*'
+
+ catalog = find_catalog(context, 'system')
+ allowed = catalog['allowed']
+ name = catalog['name']
+ text = catalog['text']
+
+ q = (allowed.allows(request, 'sdi.view') & text.eq(query))
+ resultset = q.execute()
+ resultset = resultset.sort(name)
+
+ results = []
+ for res_id in resultset.ids:
+ res = resultset.resolver(res_id)
+ url = request.sdiapi.mgmt_path(res, '@@manage_main')
+ label = getattr(res, 'title', res.__name__)
+ result = dict(label=label, url=url)
+ results.append(result)
+
+ return results
+
+ @mgmt_view(
+ name='search',
+ permission='sdi.sdi.manage-contents',
+ tab_condition=False,
+ renderer='json'
+ )
+ def search(self):
+ request = self.request
+ query = request.params['query']
+ return self._query_results(query)
+
+ @mgmt_view(
+ name='search',
+ permission='sdi.sdi.manage-contents',
+ tab_condition=False,
+ request_param='results=1',
+ renderer='templates/search_results.pt'
+ )
+ def search_results(self):
+ request = self.request
+ query = request.params['query']
+ return {'results': self._query_results(query),
+ 'query': query}
View
14 substanced/sdi/views/templates/master.pt
@@ -39,7 +39,7 @@
<body tal:define="home_path request.sdiapi.mgmt_path(request.root)">
-<div class="navbar navbar-fixed-top">
+<div id="sdi_header" class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container-fluid">
<a class="btn btn-navbar" data-toggle="collapse"
@@ -52,6 +52,16 @@
<div class="nav-collapse">
<div class="nav pull-right">
+
+ <input id="sdi_searchbox" type="text"
+ data-sourceurl='/manage/@@search'
+ placeholder="Search..."
+ data-provide="typeahead"
+ data-source='["Alabama", "Arkansas",
+ "California", "Kentucky"]'
+ title="Provide at least two letters"
+ />
+
<div class="btn-group" tal:condition="request.user">
<a class="btn btn-primary"
href="${request.sdiapi.mgmt_path(request.user)}"><i
@@ -138,6 +148,8 @@
</div>
+<script type="text/javascript"
+ src="${request.static_url('substanced.sdi:static/js/sdi.searchbox.js')}"></script>
<more tal:omit-tag="" metal:define-slot="tail-more"></more>
</body>
View
23 substanced/sdi/views/templates/search_results.pt
@@ -0,0 +1,23 @@
+<div metal:use-macro="request.sdiapi.main_template">
+
+ <div metal:fill-slot="main">
+
+ <h2>Search Results</h2>
+
+ <h4>Search term: ${query}</h4>
+
+ <ol tal:condition="results|None" class="table table-striped">
+
+ <li tal:repeat="result results">
+ <a href="${result['url']}">${result['label']}</a>
+ </li>
+
+ </ol>
+
+ <p tal:condition="not:results">
+ There were no results for this search term.
+ </p>
+
+ </div>
+
+</div>
View
136 substanced/sdi/views/tests/test_search.py
@@ -0,0 +1,136 @@
+import unittest
+from pyramid import testing
+
+class TestSearchViews(unittest.TestCase):
+ def _makeOne(self, context, request):
+ from ..search import SearchViews
+ return SearchViews(context, request)
+
+ def _makeRequest(self, **kw):
+ request = testing.DummyRequest()
+ request.sdiapi = DummySDIAPI()
+ return request
+
+ def _makeCatalogs(self, oids=(), resources=()):
+ catalogs = DummyCatalogs()
+ catalog = DummyCatalog(oids, resources)
+ catalogs['system'] = catalog
+ return catalogs
+
+ def test_search_no_results(self):
+ from substanced.interfaces import IFolder
+ context = testing.DummyResource(__provides__=IFolder)
+ request = self._makeRequest()
+ request.params['query'] = 'abc'
+ context['catalogs'] = self._makeCatalogs()
+ result = testing.DummyResource()
+ result.__name__ = 'abcde'
+ context.__objectmap__ = DummyObjectMap(result)
+ inst = self._makeOne(context, request)
+ results = inst.search()
+ self.assertEqual(results, [])
+
+ def test_search_resource_with_title(self):
+ from substanced.interfaces import IFolder
+ context = testing.DummyResource(__provides__=IFolder)
+ request = self._makeRequest()
+ request.params['query'] = 'abc'
+ result = testing.DummyResource()
+ result.title = 'Some Resource'
+ context.__objectmap__ = DummyObjectMap(result)
+ context['catalogs'] = self._makeCatalogs(oids=[1],
+ resources=(result,))
+ inst = self._makeOne(context, request)
+ results = inst.search()
+ self.assertEqual(len(results), 1)
+ item = results[0]
+ self.assertEqual(item['label'], 'Some Resource')
+ self.assertEqual(item['url'], '/mgmt_path')
+
+ def test_search_resource_no_title(self):
+ from substanced.interfaces import IFolder
+ context = testing.DummyResource(__provides__=IFolder)
+ request = self._makeRequest()
+ request.params['query'] = 'abc'
+ result = testing.DummyResource()
+ result.__name__ = 'abcde'
+ context.__objectmap__ = DummyObjectMap(result)
+ context['catalogs'] = self._makeCatalogs(oids=[1],
+ resources=(result,))
+ inst = self._makeOne(context, request)
+ results = inst.search()
+ self.assertEqual(len(results), 1)
+ item = results[0]
+ self.assertEqual(item['label'], 'abcde')
+ self.assertEqual(item['url'], '/mgmt_path')
+
+ def test_search_results(self):
+ from substanced.interfaces import IFolder
+ context = testing.DummyResource(__provides__=IFolder)
+ request = self._makeRequest()
+ request.params['query'] = 'abc'
+ request.params['results'] = '1'
+ result = testing.DummyResource()
+ result.__name__ = 'abcde'
+ context.__objectmap__ = DummyObjectMap(result)
+ context['catalogs'] = self._makeCatalogs(oids=[1],
+ resources=(result,))
+ inst = self._makeOne(context, request)
+ response = inst.search_results()
+ results = response['results']
+ self.assertEqual(len(results), 1)
+ item = results[0]
+ self.assertEqual(item['label'], 'abcde')
+ self.assertEqual(item['url'], '/mgmt_path')
+ term = response['query']
+ self.assertEqual(term, 'abc')
+
+class DummyCatalogs(testing.DummyResource):
+ __is_service__ = True
+
+class DummyCatalog(object):
+ def __init__(self, result=(), resources=()):
+ self.result = DummyResultSet(result, resources)
+
+ def __getitem__(self, name):
+ return DummyIndex(self.result)
+
+class DummyResultSet(object):
+ def __init__(self, result, resources):
+ self.ids = result
+ self.resources = resources
+
+ def sort(self, *arg, **kw):
+ return self
+
+ def __len__(self):
+ return len(self.ids)
+
+ def resolver(self, oid):
+ return self.resources[0]
+
+class DummyIndex(object):
+ def __init__(self, result):
+ self.result = result
+
+ def execute(self):
+ return self.result
+
+ def eq(self, *arg, **kw):
+ return self
+
+ def allows(self, *arg, **kw):
+ return self
+
+ def __and__(self, other):
+ return self
+
+class DummyObjectMap(object):
+ def __init__(self, result):
+ self.result = result
+ def object_for(self, oid):
+ return self.result
+
+class DummySDIAPI(object):
+ def mgmt_path(self, *arg, **kw):
+ return '/mgmt_path'

No commit comments for this range

Something went wrong with that request. Please try again.