You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bloodhound.apache.org by an...@apache.org on 2013/02/10 18:56:36 UTC

svn commit: r1444577 - in /incubator/bloodhound/trunk/bloodhound_search/bhsearch: ./ search_resources/ tests/ tests/search_resources/

Author: andrej
Date: Sun Feb 10 17:56:35 2013
New Revision: 1444577

URL: http://svn.apache.org/r1444577
Log:
#260 Bloodhound Search, make code refactoring, improve pylint results

Modified:
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/__init__.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/api.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/base.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/milestone_search.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/ticket_search.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/wiki_search.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/__init__.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/search_resources/milestone_search.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/search_resources/wiki_search.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/whoosh_backend.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/__init__.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/__init__.py?rev=1444577&r1=1444576&r2=1444577&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/__init__.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/__init__.py Sun Feb 10 17:56:35 2013
@@ -29,6 +29,7 @@ from trac.core import TracError
 TracError.__str__ = lambda self: unicode(self).encode('ascii', 'ignore')
 
 try:
+    # pylint: disable=wildcard-import
     from bhsearch import *
     msg = 'Ok'
 except Exception, exc:

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/api.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/api.py?rev=1444577&r1=1444576&r2=1444577&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/api.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/api.py Sun Feb 10 17:56:35 2013
@@ -124,10 +124,13 @@ class ISearchParticipant(Interface):
     def format_search_results(contents):
         """Called to see if the module wants to format the search results."""
 
-    def get_search_filters(req):
+    def is_allowed(req):
         """Called when we want to build the list of components with search.
         Passes the request object to do permission checking."""
 
+    def get_type():
+        """Return type of search participant"""
+
     def get_title():
         """Return resource title."""
 

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/base.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/base.py?rev=1444577&r1=1444576&r2=1444577&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/base.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/base.py Sun Feb 10 17:56:35 2013
@@ -34,6 +34,8 @@ class BaseSearchParticipant(Component):
     default_view = None
     default_grid_fields = None
     default_facets = None
+    participant_type = None
+    required_permission = None
 
     def get_default_facets(self):
         return self.default_facets
@@ -45,3 +47,9 @@ class BaseSearchParticipant(Component):
         if view == "grid":
             return self.default_grid_fields
         return None
+
+    def is_allowed(self, req=None):
+        return (not req or self.required_permission in req.perm)
+
+    def get_participant_type(self):
+        return self.participant_type
\ No newline at end of file

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/milestone_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/milestone_search.py?rev=1444577&r1=1444576&r2=1444577&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/milestone_search.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/milestone_search.py Sun Feb 10 17:56:35 2013
@@ -117,6 +117,9 @@ class MilestoneIndexer(BaseIndexer):
 class MilestoneSearchParticipant(BaseSearchParticipant):
     implements(ISearchParticipant)
 
+    participant_type = MILESTONE_TYPE
+    required_permission = 'MILESTONE_VIEW'
+
     default_facets = []
     default_grid_fields = [
         MilestoneFields.ID, MilestoneFields.DUE, MilestoneFields.COMPLETED]
@@ -141,10 +144,6 @@ class MilestoneSearchParticipant(BaseSea
         doc="""Default fields for grid view for specific resource""")
 
     #ISearchParticipant members
-    def get_search_filters(self, req=None):
-        if not req or 'MILESTONE_VIEW' in req.perm:
-            return MILESTONE_TYPE
-
     def get_title(self):
         return "Milestone"
 

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/ticket_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/ticket_search.py?rev=1444577&r1=1444576&r2=1444577&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/ticket_search.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/ticket_search.py Sun Feb 10 17:56:35 2013
@@ -62,6 +62,7 @@ class TicketIndexer(BaseIndexer):
 
     def ticket_changed(self, ticket, comment, author, old_values):
         """Reindex a recently modified ticket."""
+        # pylint: disable=unused-argument
         self._index_ticket(ticket)
 
     def ticket_deleted(self, ticket):
@@ -123,6 +124,9 @@ class TicketIndexer(BaseIndexer):
 class TicketSearchParticipant(BaseSearchParticipant):
     implements(ISearchParticipant)
 
+    participant_type = TICKET_TYPE
+    required_permission = 'TICKET_VIEW'
+
     default_facets = [
         TicketFields.STATUS,
         TicketFields.MILESTONE,
@@ -156,10 +160,6 @@ class TicketSearchParticipant(BaseSearch
         doc="""Default fields for grid view for specific resource""")
 
     #ISearchParticipant members
-    def get_search_filters(self, req=None):
-        if not req or 'TICKET_VIEW' in req.perm:
-            return TICKET_TYPE
-
     def get_title(self):
         return "Ticket"
 

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/wiki_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/wiki_search.py?rev=1444577&r1=1444576&r2=1444577&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/wiki_search.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/wiki_search.py Sun Feb 10 17:56:35 2013
@@ -37,8 +37,10 @@ class WikiIndexer(BaseIndexer):
         """Index a recently created ticket."""
         self._index_wiki(page)
 
+
     def wiki_page_changed(self, page, version, t, comment, author, ipnr):
         """Reindex a recently modified ticket."""
+        # pylint: disable=too-many-arguments, unused-argument
         self._index_wiki(page)
 
     def wiki_page_deleted(self, page):
@@ -110,6 +112,9 @@ class WikiIndexer(BaseIndexer):
 class WikiSearchParticipant(BaseSearchParticipant):
     implements(ISearchParticipant)
 
+    participant_type = WIKI_TYPE
+    required_permission = 'WIKI_VIEW'
+
     default_facets = []
     default_grid_fields = [
         IndexFields.ID,
@@ -138,10 +143,6 @@ class WikiSearchParticipant(BaseSearchPa
         doc="""Default fields for grid view for specific resource""")
 
     #ISearchParticipant members
-    def get_search_filters(self, req=None):
-        if not req or 'WIKI_VIEW' in req.perm:
-            return WIKI_TYPE
-
     def get_title(self):
         return "Wiki"
 

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/__init__.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/__init__.py?rev=1444577&r1=1444576&r2=1444577&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/__init__.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/__init__.py Sun Feb 10 17:56:35 2013
@@ -24,15 +24,15 @@ from bhsearch.tests.search_resources imp
                                              milestone_search)
 
 def suite():
-    suite = unittest.TestSuite()
-    suite.addTest(index_with_whoosh.suite())
-    suite.addTest(whoosh_backend.suite())
-    suite.addTest(web_ui.suite())
-    suite.addTest(api.suite())
-    suite.addTest(ticket_search.suite())
-    suite.addTest(wiki_search.suite())
-    suite.addTest(milestone_search.suite())
-    return suite
+    test_suite = unittest.TestSuite()
+    test_suite.addTest(index_with_whoosh.suite())
+    test_suite.addTest(whoosh_backend.suite())
+    test_suite.addTest(web_ui.suite())
+    test_suite.addTest(api.suite())
+    test_suite.addTest(ticket_search.suite())
+    test_suite.addTest(wiki_search.suite())
+    test_suite.addTest(milestone_search.suite())
+    return test_suite
 
 if __name__ == '__main__':
     unittest.main(defaultTest='suite')
\ No newline at end of file

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py?rev=1444577&r1=1444576&r2=1444577&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py Sun Feb 10 17:56:35 2013
@@ -101,8 +101,9 @@ class BaseBloodhoundSearchTest(unittest.
         return milestone
 
     def process_request(self):
-        response = BloodhoundSearchModule(self.env).process_request(self.req)
-        url, data, x = response
+        # pylint: disable=unused-variable
+        url, data, x = BloodhoundSearchModule(self.env).process_request(
+            self.req)
         print "Received url: %s data:" % url
         pprint(data)
         if data.has_key("results"):

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/search_resources/milestone_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/search_resources/milestone_search.py?rev=1444577&r1=1444576&r2=1444577&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/search_resources/milestone_search.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/search_resources/milestone_search.py Sun Feb 10 17:56:35 2013
@@ -143,11 +143,12 @@ class MilestoneSearchParticipantTestCase
         self.assertIsNotNone(default_grid_fields)
 
 def suite():
-    suite = unittest.TestSuite()
-    suite.addTest(unittest.makeSuite(MilestoneIndexerEventsTestCase, 'test'))
-    suite.addTest(
+    test_suite = unittest.TestSuite()
+    test_suite.addTest(
+        unittest.makeSuite(MilestoneIndexerEventsTestCase, 'test'))
+    test_suite.addTest(
         unittest.makeSuite(MilestoneSearchParticipantTestCase, 'test'))
-    return suite
+    return test_suite
 
 if __name__ == '__main__':
     unittest.main()

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/search_resources/wiki_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/search_resources/wiki_search.py?rev=1444577&r1=1444576&r2=1444577&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/search_resources/wiki_search.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/search_resources/wiki_search.py Sun Feb 10 17:56:35 2013
@@ -129,11 +129,11 @@ class WikiIndexerEventsTestCase(BaseBloo
         self.assertEqual("version1", results.docs[0]["content"])
 
 def suite():
-    suite = unittest.TestSuite()
-    suite.addTest(unittest.makeSuite(
+    test_suite = unittest.TestSuite()
+    test_suite.addTest(unittest.makeSuite(
         WikiIndexerSilenceOnExceptionTestCase, 'test'))
-    suite.addTest(unittest.makeSuite(WikiIndexerEventsTestCase, 'test'))
-    return suite
+    test_suite.addTest(unittest.makeSuite(WikiIndexerEventsTestCase, 'test'))
+    return test_suite
 
 if __name__ == '__main__':
     unittest.main()

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py?rev=1444577&r1=1444576&r2=1444577&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py Sun Feb 10 17:56:35 2013
@@ -530,6 +530,16 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
         self.assertEqual(["T2", "T1", "T3"], ids)
 
 
+    def test_that_title_is_set_for_free_text_view(self):
+        #arrange
+        self.insert_ticket("T1", component="c1", status="new", milestone="A")
+        #act
+        self.req.args[RequestParameters.QUERY] = "*"
+        data = self.process_request()
+        #assert
+        self.assertIn("title", data["results"].items[0])
+
+
     def _count_parameter_in_url(self, url, parameter_name, value):
         parameter_to_find = (parameter_name, value)
         parsed_parameters = parse_arg_list(url)
@@ -597,10 +607,10 @@ class RequestParametersTest(unittest.Tes
 
 
 def suite():
-    suite = unittest.TestSuite()
-    suite.addTest(unittest.makeSuite(WebUiTestCaseWithWhoosh, 'test'))
-    suite.addTest(unittest.makeSuite(RequestParametersTest, 'test'))
-    return suite
+    test_suite = unittest.TestSuite()
+    test_suite.addTest(unittest.makeSuite(WebUiTestCaseWithWhoosh, 'test'))
+    test_suite.addTest(unittest.makeSuite(RequestParametersTest, 'test'))
+    return test_suite
 
 if __name__ == '__main__':
     unittest.main()

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/whoosh_backend.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/whoosh_backend.py?rev=1444577&r1=1444576&r2=1444577&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/whoosh_backend.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/whoosh_backend.py Sun Feb 10 17:56:35 2013
@@ -495,12 +495,12 @@ class WhooshEmptyFacetErrorWorkaroundTes
         self.assertEquals('(type:ticket AND milestone:aaa)', str(result_filter))
 
 def suite():
-    suite = unittest.TestSuite()
-    suite.addTest(unittest.makeSuite(WhooshBackendTestCase, 'test'))
-    suite.addTest(unittest.makeSuite(WhooshFunctionalityTestCase, 'test'))
-    suite.addTest(unittest.makeSuite(WhooshEmptyFacetErrorWorkaroundTestCase,
-        'test'))
-    return suite
+    test_suite = unittest.TestSuite()
+    test_suite.addTest(unittest.makeSuite(WhooshBackendTestCase, 'test'))
+    test_suite.addTest(unittest.makeSuite(WhooshFunctionalityTestCase, 'test'))
+    test_suite.addTest(
+        unittest.makeSuite(WhooshEmptyFacetErrorWorkaroundTestCase, 'test'))
+    return test_suite
 
 if __name__ == '__main__':
     unittest.main()

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py?rev=1444577&r1=1444576&r2=1444577&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py Sun Feb 10 17:56:35 2013
@@ -187,26 +187,8 @@ class RequestParameters(object):
         if name in params:
             del params[name]
 
-    def is_show_all_mode(self):
-        return self.type is None
-
-
 class BloodhoundSearchModule(Component):
     """Main search page"""
-    DATA_HEADERS = "headers"
-    DATA_ALL_VIEWS = "all_views"
-    DATA_ACTIVE_VIEW = "active_view"
-    DATA_VIEW = "view"
-    DATA_VIEW_GRID = "grid"
-    #bhsearch may support more pluggable views later
-    VIEWS_SUPPORTED = {
-        None: "Free text",
-        DATA_VIEW_GRID: "Grid"
-    }
-    VIEWS_WITH_KNOWN_FIELDS = [DATA_VIEW_GRID]
-    OBLIGATORY_FIELDS_TO_SELECT = [IndexFields.ID, IndexFields.TYPE]
-    DEFAULT_SORT = [(SCORE, ASC), ("time", DESC)]
-
     implements(INavigationContributor, IPermissionRequestor, IRequestHandler,
         ITemplateProvider,
         #           IWikiSyntaxProvider #todo: implement later
@@ -231,6 +213,7 @@ class BloodhoundSearchModule(Component):
     default_facets = ListOption(
         BHSEARCH_CONFIG_SECTION,
         prefix + '_default_facets',
+        default=",".join([IndexFields.TYPE]),
         doc="""Default facets applied to search view of all resources""")
 
     default_view = Option(
@@ -239,7 +222,7 @@ class BloodhoundSearchModule(Component):
         doc="""If true, show grid as default view for specific resource in
             Bloodhound Search results""")
 
-    default_grid_fields = ListOption(
+    all_grid_fields = ListOption(
         BHSEARCH_CONFIG_SECTION,
         prefix + '_default_grid_fields',
         default=",".join(default_grid_fields),
@@ -248,6 +231,7 @@ class BloodhoundSearchModule(Component):
 
     # INavigationContributor methods
     def get_active_navigation_item(self, req):
+        # pylint: disable=unused-argument
         return 'bhsearch'
 
     def get_navigation_items(self, req):
@@ -266,178 +250,181 @@ class BloodhoundSearchModule(Component):
 
     def process_request(self, req):
         req.perm.assert_permission(SEARCH_PERMISSION)
-        parameters = RequestParameters(req)
-        allowed_participants = self._get_allowed_participants(req)
-        data = {
-            'query': parameters.query,
-        }
-        self._prepare_allowed_types(allowed_participants, parameters, data)
-        self._prepare_active_filter_queries(
-            parameters,
-            data,
+        request_context = RequestContext(
+            self.env,
+            req,
+            self.search_participants,
+            self.default_view,
+            self.all_grid_fields,
+            self.default_facets
         )
 
-        #TBD: should search return results on empty query?
-        #        if not any((
-        #            query,
-        #            parameters.type,
-        #            parameters.filter_queries,
-        #            )):
-        #            return self._return_data(req, data)
-
-        self._prepare_quick_jump(
-            parameters,
-            data)
-        fields = self._prepare_fields_and_view(
-            allowed_participants, parameters, data)
-
-        query_filter = self._prepare_query_filter(
-            parameters, allowed_participants)
-
-        facets = self._prepare_facets(parameters, allowed_participants)
-
-        sort = parameters.sort if parameters.sort else self.DEFAULT_SORT
-
-        query_system = BloodhoundSearchApi(self.env)
-        query_result = query_system.query(
-            parameters.query,
-            pagenum=parameters.page,
-            pagelen=parameters.pagelen,
-            sort=sort,
-            fields=fields,
-            facets=facets,
-            filter=query_filter,
+        query_result =  BloodhoundSearchApi(self.env).query(
+            request_context.parameters.query,
+            pagenum=request_context.page,
+            pagelen=request_context.pagelen,
+            sort=request_context.sort,
+            fields=request_context.fields,
+            facets=request_context.facets,
+            filter=request_context.query_filter,
         )
 
-        is_free_text_view = (self.DATA_VIEW not in data)
-        ui_docs = [self._process_doc(
-                        doc, req, allowed_participants, is_free_text_view)
-                   for doc in query_result.docs]
+        request_context.process_results(query_result)
+        return self._return_data(req, request_context.data)
 
-        results = Paginator(
-            ui_docs,
-            parameters.page - 1,
-            parameters.pagelen,
-            query_result.hits)
-
-        results.shown_pages = self._prepare_shown_pages(
-            parameters,
-            shown_pages=results.get_shown_pages(parameters.pagelen))
+    def _return_data(self, req, data):
+        add_stylesheet(req, 'common/css/search.css')
+        return 'bhsearch.html', data, None
 
-        results.current_page = {'href': None,
-                                'class': 'current',
-                                'string': str(results.page + 1),
-                                'title': None}
+    # ITemplateProvider methods
+    def get_htdocs_dirs(self):
+    #        return [('bhsearch',
+    #                 pkg_resources.resource_filename(__name__, 'htdocs'))]
+        return []
 
-        if results.has_next_page:
-            next_href = parameters.create_href(page=parameters.page + 1)
-            add_link(req, 'next', next_href, _('Next Page'))
+    def get_templates_dirs(self):
+        return [pkg_resources.resource_filename(__name__, 'templates')]
 
-        if results.has_previous_page:
-            prev_href = parameters.create_href(page=parameters.page - 1)
-            add_link(req, 'prev', prev_href, _('Previous Page'))
+class RequestContext(object):
+    DATA_ACTIVE_FILTER_QUERIES = 'active_filter_queries'
+    DATA_ACTIVE_TYPE = "active_type"
+    DATA_TYPES = "types"
+    DATA_HEADERS = "headers"
+    DATA_ALL_VIEWS = "all_views"
+    DATA_ACTIVE_VIEW = "active_view"
+    DATA_VIEW = "view"
+    DATA_VIEW_GRID = "grid"
+    DATA_FACET_COUNTS = 'facet_counts'
+    DATA_DEBUG = 'debug'
+    DATA_PAGE_HREF = 'page_href'
+    DATA_RESULTS = 'results'
+    DATA_QUICK_JUMP = "quickjump"
 
-        data['results'] = results
-        data['debug'] = query_result.debug
 
-        self._prepare_result_facet_counts(
-            parameters,
-            query_result,
-            data,
-        )
+    #bhsearch may support more pluggable views later
+    VIEWS_SUPPORTED = {
+        None: "Free text",
+        DATA_VIEW_GRID: "Grid"
+    }
 
-        data['page_href'] = parameters.create_href()
-        return self._return_data(req, data)
+    VIEWS_WITH_KNOWN_FIELDS = [DATA_VIEW_GRID]
+    OBLIGATORY_FIELDS_TO_SELECT = [IndexFields.ID, IndexFields.TYPE]
+    DEFAULT_SORT = [(SCORE, ASC), ("time", DESC)]
 
+    def __init__(
+            self,
+            env,
+            req,
+            search_participants,
+            default_view,
+            all_grid_fields,
+            default_facets,
+            ):
+        self.env = env
+        self.req = req
+        self.parameters = RequestParameters(req)
+        self.search_participants = search_participants
+        self.default_view = default_view
+        self.all_grid_fields = all_grid_fields
+        self.default_facets = default_facets
+        self.view = None
+        self.page = self.parameters.page
+        self.pagelen = self.parameters.pagelen
+        self.allowed_participants, self.sorted_participants = \
+            self._get_allowed_participants(req)
+
+        if self.parameters.type in self.allowed_participants:
+            self.active_type = self.parameters.type
+            self.active_participant = self.allowed_participants[
+                                      self.active_type]
+        else:
+            self.active_type = None
+            self.active_participant = None
 
-    def _prepare_fields_and_view(self, allowed_participants, parameters, data):
-        self._add_views_selector(parameters, data)
-        active_participant = self._get_active_participant(
-            parameters, allowed_participants)
-        view = self._get_view(parameters, active_participant)
-        if view:
-            data[self.DATA_VIEW] = view
-        fields_to_select = None
-        if view in self.VIEWS_WITH_KNOWN_FIELDS:
-            if active_participant:
-                fields_in_view = active_participant.get_default_view_fields(
-                    view)
-            elif view == self.DATA_VIEW_GRID:
-                fields_in_view = self.default_grid_fields
-            else:
-                raise TracError("Unsupported view: %s" % view)
-            data[self.DATA_HEADERS] = [self._create_headers_item(field)
-                                        for field in fields_in_view]
-            fields_to_select = self._optionally_add_obligatory_fields(
-                fields_in_view)
-        return fields_to_select
+        self.data = {'query': self.parameters.query}
+        self._prepare_allowed_types()
+        self._prepare_active_filter_queries()
+        self._prepare_quick_jump()
+        self.fields = self._prepare_fields_and_view()
+        self.query_filter = self._prepare_query_filter()
+        self.facets = self._prepare_facets()
 
-    def _add_views_selector(self, parameters, data):
-        active_view = parameters.view
-        all_views = []
-        for view, label in self.VIEWS_SUPPORTED.iteritems():
-            all_views.append(dict(
-                label=_(label),
-                href=parameters.create_href(
-                    view=view, skip_view=(view is None)),
-                is_active = (view == active_view)
-            ))
-        data[self.DATA_ALL_VIEWS] = all_views
-        if parameters.view:
-            data[self.DATA_ACTIVE_VIEW] = parameters.view
+        self.sort = self.parameters.sort if self.parameters.sort \
+            else self.DEFAULT_SORT
 
-    def _optionally_add_obligatory_fields(self, fields_in_view):
-        fields_to_select = list(fields_in_view)
-        for obligatory_field in self.OBLIGATORY_FIELDS_TO_SELECT:
-            if obligatory_field is not fields_to_select:
-                fields_to_select.append(obligatory_field)
-        return fields_to_select
 
-    def _create_headers_item(self, field):
-        return dict(
-            name=field,
-            href="",
-            #TODO:add translated column label. Now it is really temporary
-            #workaround
-            label=field,
-            sort=None,
-        )
 
-    def _get_view(self, parameters, active_participant):
-        view = parameters.view
-        if view is None:
-            if active_participant is not None:
-                view = active_participant.get_default_view()
-            else:
-                view = self.default_view
-        if view is not None:
-            view =  view.strip().lower()
-        return view
+    def _get_allowed_participants(self, req):
+        allowed_participants = {}
+        ordered_participants = []
+        for participant in self.search_participants:
+            if participant.is_allowed(req):
+                allowed_participants[
+                    participant.get_participant_type()] = participant
+                ordered_participants.append(participant)
+        return allowed_participants, ordered_participants
 
 
-    def _get_active_participant(self, parameters, allowed_participants):
-        active_type = parameters.type
-        if active_type is not None and \
-           active_type in allowed_participants:
-            return allowed_participants[active_type]
-        else:
-            return None
+    def _prepare_allowed_types(self):
+        active_type = self.parameters.type
+        if active_type and active_type not in self.allowed_participants:
+            raise TracError(_("Unsupported resource type: '%(name)s'",
+                name=active_type))
+        allowed_types = [
+            dict(
+                label=_("All"),
+                active=(active_type is None),
+                href=self.parameters.create_href(
+                    skip_type=True,
+                    skip_page=True,
+                    force_filters=[],
+                ),
+            )
+        ]
+        #we want obtain the same order as in search participants options
+        for participant in self.sorted_participants:
+            allowed_types.append(dict(
+                label=_(participant.get_title()),
+                active=(participant.get_participant_type() == active_type),
+                href=self.parameters.create_href(
+                    type=participant.get_participant_type(),
+                    skip_page=True,
+                    force_filters=[],
+                ),
+            ))
+        self.data[self.DATA_TYPES] = allowed_types
+        self.data[self.DATA_ACTIVE_TYPE] = active_type
+
+    def _prepare_active_filter_queries(self):
+        current_filters = self.parameters.filter_queries
 
+        def remove_filters_from_list(filer_to_cut_from):
+            return current_filters[:current_filters.index(filer_to_cut_from)]
 
-    def _prepare_quick_jump(self,
-                            parameters,
-                            data):
-        if not parameters.query:
+        active_filter_queries = [
+            dict(
+                href=self.parameters.create_href(
+                    force_filters=remove_filters_from_list(filter_query)
+                ),
+                label=filter_query,
+                query=filter_query,
+            ) for filter_query in self.parameters.filter_queries
+        ]
+        self.data[self.DATA_ACTIVE_FILTER_QUERIES] = active_filter_queries
+
+    def _prepare_quick_jump(self):
+        if not self.parameters.query:
             return
         check_result = self._check_quickjump(
-            parameters.req,
-            parameters.query)
+            self.req,
+            self.parameters.query)
         if check_result:
-            data["quickjump"] = check_result
+            self.data[self.DATA_QUICK_JUMP] = check_result
 
     #the method below is "copy/paste" from trac search/web_ui.py
     def _check_quickjump(self, req, kwd):
         """Look for search shortcuts"""
+        # pylint: disable=maybe-no-member
         noquickjump = int(req.args.get('noquickjump', '0'))
         # Source quickjump   FIXME: delegate to ISearchSource.search_quickjump
         quickjump_href = None
@@ -463,65 +450,155 @@ class BloodhoundSearchModule(Component):
                 req.redirect(quickjump_href)
 
 
-    def _prepare_allowed_types(self, allowed_participants, parameters, data):
-        active_type = parameters.type
-        if active_type and active_type not in allowed_participants:
-            raise TracError(_("Unsupported resource type: '%(name)s'",
-                name=active_type))
-        allowed_types = [
-            dict(
-                label=_("All"),
-                active=(active_type is None),
-                href=parameters.create_href(
-                    skip_type=True,
-                    skip_page=True,
-                    force_filters=[],
-                ),
-            )
-        ]
-
-        #we want obtain the same order as in search participants options
-        participant_with_type = dict((participant, type)
-            for type, participant in allowed_participants.iteritems())
-        for participant in self.search_participants:
-            if participant in participant_with_type:
-                type = participant_with_type[participant]
-                allowed_types.append(dict(
-                    label=_(participant.get_title()),
-                    active=(type == active_type),
-                    href=parameters.create_href(
-                        type=type,
-                        skip_page=True,
-                        force_filters=[],
-                    ),
-                ))
-        data["types"] = allowed_types
-        data["active_type"] = active_type
+    def _prepare_fields_and_view(self):
+        self._add_views_selector()
+        self.view = self._get_view()
+        if self.view:
+            self.data[self.DATA_VIEW] = self.view
+        fields_to_select = None
+        if self.view in self.VIEWS_WITH_KNOWN_FIELDS:
+            if self.active_participant:
+                fields_in_view = self.active_participant.\
+                    get_default_view_fields(self.view)
+            elif self.view == self.DATA_VIEW_GRID:
+                fields_in_view = self.all_grid_fields
+            else:
+                raise TracError("Unsupported view: %s" % self.view)
+            self.data[self.DATA_HEADERS] = [self._create_headers_item(field)
+                                        for field in fields_in_view]
+            fields_to_select = self._add_obligatory_fields(
+                fields_in_view)
+        return fields_to_select
 
+    def _add_views_selector(self):
+        active_view = self.parameters.view
+        if active_view:
+            self.data[self.DATA_ACTIVE_VIEW] = active_view
 
-    def _prepare_active_filter_queries(
-            self,
-            parameters,
-            data):
-        active_filter_queries = []
-        for filter_query in parameters.filter_queries:
-            active_filter_queries.append(dict(
-                href=parameters.create_href(
-                    force_filters=self._cut_filters(
-                        parameters.filter_queries,
-                        filter_query)),
-                label=filter_query,
-                query=filter_query,
+        all_views = []
+        for view, label in self.VIEWS_SUPPORTED.iteritems():
+            all_views.append(dict(
+                label=_(label),
+                href=self.parameters.create_href(
+                    view=view, skip_view=(view is None)),
+                is_active = (view == active_view)
             ))
-        data['active_filter_queries'] = active_filter_queries
+        self.data[self.DATA_ALL_VIEWS] = all_views
 
-    def _cut_filters(self, filter_queries, filer_to_cut_from):
-        return filter_queries[:filter_queries.index(filer_to_cut_from)]
+    def _get_view(self):
+        view = self.parameters.view
+        if view is None:
+            if self.active_participant:
+                view = self.active_participant.get_default_view()
+            else:
+                view = self.default_view
+        if view is not None:
+            view =  view.strip().lower()
+        if view == "":
+            view = None
+        return view
 
+    def _add_obligatory_fields(self, fields_in_view):
+        fields_to_select = list(fields_in_view)
+        for obligatory_field in self.OBLIGATORY_FIELDS_TO_SELECT:
+            if obligatory_field is not fields_to_select:
+                fields_to_select.append(obligatory_field)
+        return fields_to_select
 
-    def _prepare_result_facet_counts(self, parameters, query_result, data):
-        """
+    def _create_headers_item(self, field):
+        return dict(
+            name=field,
+            href="",
+            #TODO:add translated column label. Now it is really temporary
+            #workaround
+            label=field,
+            sort=None,
+        )
+
+    def _prepare_query_filter(self):
+        query_filters = list(self.parameters.filter_queries)
+        if self.active_type:
+            query_filters.append(
+                self._create_term_expression(
+                    IndexFields.TYPE, self.active_type))
+        return query_filters
+
+    def _create_term_expression(self, field, field_value):
+        if field_value is None:
+            query = "NOT (%s:*)" % field
+        elif isinstance(field_value, basestring):
+            query = '%s:"%s"' % (field, field_value)
+        else:
+            query = '%s:%s' % (field, field_value)
+        return query
+
+    def _prepare_facets(self):
+        #TODO: add possibility of specifying facets in query parameters
+        if self.active_participant:
+            facets =  self.active_participant.get_default_facets()
+        else:
+            facets =  self.default_facets
+        return facets
+
+    def _process_doc(self, doc):
+        ui_doc = dict(doc)
+        ui_doc["href"] = self.req.href(doc['type'], doc['id'])
+        #todo: perform content adaptation here
+        if doc.has_key('content'):
+            ui_doc['content'] = shorten_result(doc['content'])
+        if doc.has_key('time'):
+            ui_doc['date'] = user_time(self.req, format_datetime, doc['time'])
+
+        is_free_text_view = self.view is None
+        if is_free_text_view:
+            ui_doc['title'] = self.allowed_participants[
+                              doc['type']].format_search_results(doc)
+        return ui_doc
+
+    def _prepare_results(self, result_docs, hits):
+        ui_docs = [self._process_doc(doc) for doc in result_docs]
+
+        results = Paginator(
+            ui_docs,
+            self.page - 1,
+            self.pagelen,
+            hits)
+
+        self._prepare_shown_pages(results)
+        results.current_page = {'href': None,
+                                'class': 'current',
+                                'string': str(results.page + 1),
+                                'title': None}
+
+        parameters = self.parameters
+        if results.has_next_page:
+            next_href = parameters.create_href(page=parameters.page + 1)
+            add_link(self.req, 'next', next_href, _('Next Page'))
+
+        if results.has_previous_page:
+            prev_href = parameters.create_href(page=parameters.page - 1)
+            add_link(self.req, 'prev', prev_href, _('Previous Page'))
 
+        self.data[self.DATA_RESULTS] = results
+
+    def _prepare_shown_pages(self, results):
+        shown_pages = results.get_shown_pages(self.pagelen)
+        page_data = []
+        for shown_page in shown_pages:
+            page_href = self.parameters.create_href(page=shown_page)
+            page_data.append([page_href, None, str(shown_page),
+                              'page ' + str(shown_page)])
+        fields = ['href', 'class', 'string', 'title']
+        results.shown_pages = [dict(zip(fields, p)) for p in page_data]
+
+    def process_results(self, query_result):
+        self._prepare_results(query_result.docs, query_result.hits)
+        self._prepare_result_facet_counts(query_result.facets)
+        self.data[self.DATA_DEBUG] = query_result.debug
+        self.data[self.DATA_PAGE_HREF] = self.parameters.create_href()
+
+    def _prepare_result_facet_counts(self, result_facets):
+        """
         Sample query_result.facets content returned by query
         {
            'component': {None:2},
@@ -538,19 +615,18 @@ class BloodhoundSearchModule(Component):
         }
 
         """
-        result_facets = query_result.facets
         facet_counts = dict()
         if result_facets:
             for field, facets_dict in result_facets.iteritems():
                 per_field_dict = dict()
                 for field_value, count in facets_dict.iteritems():
                     if field == IndexFields.TYPE:
-                        href = parameters.create_href(
+                        href = self.parameters.create_href(
                             skip_page=True,
                             force_filters=[],
                             type=field_value)
                     else:
-                        href = parameters.create_href(
+                        href = self.parameters.create_href(
                             skip_page=True,
                             additional_filter=self._create_term_expression(
                                 field,
@@ -562,82 +638,5 @@ class BloodhoundSearchModule(Component):
                     )
                 facet_counts[_(field)] = per_field_dict
 
-        data['facet_counts'] = facet_counts
-
-    def _create_term_expression(self, field, field_value):
-        if field_value is None:
-            query = "NOT (%s:*)" % field
-        elif isinstance(field_value, basestring):
-            query = '%s:"%s"' % (field, field_value)
-        else:
-            query = '%s:%s' % (field, field_value)
-        return query
-
-    def _prepare_query_filter(self, parameters, allowed_participants):
-        query_filters = list(parameters.filter_queries)
-        type = parameters.type
-        if type in allowed_participants:
-            query_filters.append(
-                self._create_term_expression(IndexFields.TYPE, type))
-        else:
-            self.log.debug("Unsupported type in web request: %s", type)
-        return query_filters
-
-    def _prepare_facets(self, parameters, allowed_participants):
-        #TODO: add possibility of specifying facets in query parameters
-        if parameters.is_show_all_mode():
-            facets = [IndexFields.TYPE]
-            facets.extend(self.default_facets)
-        else:
-            type_participant = allowed_participants[parameters.type]
-            facets = type_participant.get_default_facets()
-        return facets
-
-    def _get_allowed_participants(self, req):
-        allowed_participants = {}
-        for participant in self.search_participants:
-            type = participant.get_search_filters(req)
-            if type is not None:
-                allowed_participants[type] = participant
-        return allowed_participants
-
-    def _return_data(self, req, data):
-        add_stylesheet(req, 'common/css/search.css')
-        return 'bhsearch.html', data, None
-
-    def _process_doc(self, doc, req, allowed_participants, is_free_text_view):
-        ui_doc = dict(doc)
-        ui_doc["href"] = req.href(doc['type'], doc['id'])
-        #todo: perform content adaptation here
-        if doc.has_key('content'):
-#            ui_doc['excerpt'] = shorten_result(doc['content'])
-            ui_doc['content'] = shorten_result(doc['content'])
-        if doc.has_key('time'):
-            ui_doc['date'] = user_time(req, format_datetime, doc['time'])
-
-        if is_free_text_view:
-            ui_doc['title'] = allowed_participants[
-                              doc['type']].format_search_results(doc)
-        return ui_doc
-
-    def _prepare_shown_pages(self, parameters, shown_pages):
-        page_data = []
-        for shown_page in shown_pages:
-            page_href = parameters.create_href(page=shown_page)
-            page_data.append([page_href, None, str(shown_page),
-                              'page ' + str(shown_page)])
-        fields = ['href', 'class', 'string', 'title']
-        result_shown_pages = [dict(zip(fields, p)) for p in page_data]
-        return result_shown_pages
-
-
-    # ITemplateProvider methods
-    def get_htdocs_dirs(self):
-    #        return [('bhsearch',
-    #                 pkg_resources.resource_filename(__name__, 'htdocs'))]
-        return []
-
-    def get_templates_dirs(self):
-        return [pkg_resources.resource_filename(__name__, 'templates')]
-
+        self.data[self.DATA_FACET_COUNTS] = facet_counts