incubator-bloodhound-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From and...@apache.org
Subject svn commit: r1442591 - in /incubator/bloodhound/trunk/bloodhound_search/bhsearch: ./ search_resources/ templates/ tests/
Date Tue, 05 Feb 2013 14:03:36 GMT
Author: andrej
Date: Tue Feb  5 14:03:35 2013
New Revision: 1442591

URL: http://svn.apache.org/viewvc?rev=1442591&view=rev
Log:
#361 - add basic support for grid view for Bloodhound Search

Added:
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py
      - copied, changed from r1440488, incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/utils.py
Removed:
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/utils.py
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/templates/bhsearch.html
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/api.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/index_with_whoosh.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/milestone_search.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/real_index_view.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/ticket_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/tests/wiki_search.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py
    incubator/bloodhound/trunk/bloodhound_search/bhsearch/whoosh_backend.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=1442591&r1=1442590&r2=1442591&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/__init__.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/__init__.py Tue Feb  5 14:03:35 2013
@@ -35,3 +35,4 @@ except Exception, exc:
 #    raise
     msg = "Exception %s raised: '%s'" % (exc.__class__.__name__, str(exc))
 
+BHSEARCH_CONFIG_SECTION = "bhsearch"

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/api.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/api.py?rev=1442591&r1=1442590&r2=1442591&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/api.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/api.py Tue Feb  5 14:03:35 2013
@@ -33,10 +33,6 @@ class IndexFields(object):
     AUTHOR = 'author'
     CONTENT = 'content'
     STATUS = 'status'
-    DUE = 'due'
-    COMPLETED = 'completed'
-    MILESTONE = 'milestone'
-    COMPONENT = 'component'
 
 class QueryResult(object):
     def __init__(self):
@@ -89,8 +85,14 @@ class ISearchBackend(Interface):
         Open existing index, if index does not exist, create new one
         """
 
-    def query(query, sort = None, fields = None, boost = None, filter = None,
-                  facets = None, pagenum = 1, pagelen = 20):
+    def query(
+            query,
+            sort = None,
+            fields = None,
+            filter = None,
+            facets = None,
+            pagenum = 1,
+            pagelen = 20):
         """
         Perform query implementation
 
@@ -127,10 +129,17 @@ class ISearchParticipant(Interface):
         Passes the request object to do permission checking."""
 
     def get_title():
-        """Return resource title"""
+        """Return resource title."""
 
     def get_default_facets():
-        """Return default facets for the specific resource type"""
+        """Return default facets for the specific resource type."""
+
+    def get_default_view():
+        """Return True if grid is enabled by default for specific resource."""
+
+    def get_default_view_fields(view):
+        """Return list of fields should be returned in grid by default."""
+
 
 class IQueryParser(Interface):
     """Extension point for Bloodhound Search query parser.
@@ -185,9 +194,15 @@ class BloodhoundSearchApi(Component):
 
     index_participants = ExtensionPoint(IIndexParticipant)
 
-    def query(self, query, sort = None, fields = None,
-              boost = None, filter = None,
-              facets = None, pagenum = 1, pagelen = 20):
+    def query(
+            self,
+            query,
+            sort = None,
+            fields = None,
+            filter = None,
+            facets = None,
+            pagenum = 1,
+            pagelen = 20):
         """Return query result from an underlying search backend.
 
         Arguments:
@@ -221,7 +236,6 @@ class BloodhoundSearchApi(Component):
             facets = facets,
             pagenum = pagenum,
             pagelen = pagelen,
-            boost = boost,
         )
         for query_processor in self.query_processors:
             query_processor.query_pre_process(query_parameters)

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=1442591&r1=1442590&r2=1442591&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/base.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/search_resources/base.py Tue Feb  5 14:03:35 2013
@@ -29,3 +29,19 @@ class BaseIndexer(Component):
     silence_on_error = BoolOption('bhsearch', 'silence_on_error', "True",
         """If true, do not throw an exception during indexing a resource""")
 
+
+class BaseSearchParticipant(Component):
+    default_view = None
+    default_grid_fields = None
+    default_facets = None
+
+    def get_default_facets(self):
+        return self.default_facets
+
+    def get_default_view(self):
+        return self.default_view
+
+    def get_default_view_fields(self, view):
+        if view == "grid":
+            return self.default_grid_fields
+        return None

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=1442591&r1=1442590&r2=1442591&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 Tue Feb  5 14:03:35 2013
@@ -19,25 +19,29 @@
 #  under the License.
 
 r"""Milestone specifics for Bloodhound Search plugin."""
-from bhsearch.api import IIndexParticipant, BloodhoundSearchApi, IndexFields, \
-    ISearchParticipant
-from bhsearch.search_resources.base import BaseIndexer
+from bhsearch import BHSEARCH_CONFIG_SECTION
+from bhsearch.api import (IIndexParticipant, BloodhoundSearchApi, IndexFields,
+    ISearchParticipant)
+from bhsearch.search_resources.base import BaseIndexer, BaseSearchParticipant
 from trac.ticket import IMilestoneChangeListener, Milestone
-from trac.config import ListOption
-from trac.core import implements, Component
+from trac.config import ListOption, Option
+from trac.core import implements
 
 MILESTONE_TYPE = u"milestone"
 
+class MilestoneFields(IndexFields):
+    DUE = "due"
+    COMPLETED = "completed"
+
 class MilestoneIndexer(BaseIndexer):
     implements(IMilestoneChangeListener, IIndexParticipant)
 
     optional_fields = {
-        'description': IndexFields.CONTENT,
-        'due': IndexFields.DUE,
-        'completed': IndexFields.COMPLETED,
+        'description': MilestoneFields.CONTENT,
+        'due': MilestoneFields.DUE,
+        'completed': MilestoneFields.COMPLETED,
     }
 
-
     # IMilestoneChangeListener methods
     def milestone_created(self, milestone):
         self._index_milestone(milestone)
@@ -110,11 +114,31 @@ class MilestoneIndexer(BaseIndexer):
         for milestone in Milestone.select(self.env, include_completed=True):
             yield self.build_doc(milestone)
 
-class MilestoneSearchParticipant(Component):
+class MilestoneSearchParticipant(BaseSearchParticipant):
     implements(ISearchParticipant)
 
-    default_facets = ListOption('bhsearch', 'default_facets_milestone',
-        doc="""Default facets applied to search through milestones""")
+    default_facets = []
+    default_grid_fields = [
+        MilestoneFields.ID, MilestoneFields.DUE, MilestoneFields.COMPLETED]
+    prefix = MILESTONE_TYPE
+
+    default_facets = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_facets',
+        default=",".join(default_facets),
+        doc="""Default facets applied to search view of specific resource""")
+
+    default_view = Option(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_view',
+        doc = """If true, show grid as default view for specific resource in
+            Bloodhound Search results""")
+
+    default_grid_fields = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_grid_fields',
+        default=",".join(default_grid_fields),
+        doc="""Default fields for grid view for specific resource""")
 
     #ISearchParticipant members
     def get_search_filters(self, req=None):
@@ -124,9 +148,6 @@ class MilestoneSearchParticipant(Compone
     def get_title(self):
         return "Milestone"
 
-    def get_default_facets(self):
-        return self.default_facets
-
     def format_search_results(self, res):
         #TODO: add better milestone rendering
         return u'Milestone: %s' % res['id']

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=1442591&r1=1442590&r2=1442591&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 Tue Feb  5 14:03:35 2013
@@ -19,22 +19,42 @@
 #  under the License.
 
 r"""Ticket specifics for Bloodhound Search plugin."""
+from bhsearch import BHSEARCH_CONFIG_SECTION
 from bhsearch.api import ISearchParticipant, BloodhoundSearchApi, \
     IIndexParticipant, IndexFields
-from bhsearch.search_resources.base import BaseIndexer
+from bhsearch.search_resources.base import BaseIndexer, BaseSearchParticipant
 from genshi.builder import tag
 from trac.ticket.api import ITicketChangeListener
 from trac.ticket import Ticket
 from trac.ticket.query import Query
-from trac.config import ListOption
-from trac.core import implements, Component
+from trac.config import ListOption, Option
+from trac.core import implements
 
 TICKET_TYPE = u"ticket"
-TICKET_STATUS = u"status"
+
+class TicketFields(IndexFields):
+    SUMMARY = "summary"
+    MILESTONE = 'milestone'
+    COMPONENT = 'component'
+    KEYWORDS = "keywords"
+    RESOLUTION = "resolution"
+    CHANGES = 'changes'
 
 class TicketIndexer(BaseIndexer):
     implements(ITicketChangeListener, IIndexParticipant)
 
+    optional_fields = {
+        'component': TicketFields.COMPONENT,
+        'description': TicketFields.CONTENT,
+        'keywords': TicketFields.KEYWORDS,
+        'milestone': TicketFields.MILESTONE,
+        'summary': TicketFields.SUMMARY,
+        'status': TicketFields.STATUS,
+        'resolution': TicketFields.RESOLUTION,
+        'reporter': TicketFields.AUTHOR,
+    }
+
+
     #ITicketChangeListener methods
     def ticket_created(self, ticket):
         """Index a recently created ticket."""
@@ -75,28 +95,17 @@ class TicketIndexer(BaseIndexer):
     def build_doc(self, trac_doc):
         ticket = trac_doc
         doc = {
-            IndexFields.ID: unicode(ticket.id),
+            IndexFields.ID: str(ticket.id),
             IndexFields.TYPE: TICKET_TYPE,
             IndexFields.TIME: ticket.time_changed,
             }
-        fields = [
-            ('component',),
-              ('description',IndexFields.CONTENT),
-              ('keywords',),
-              ('milestone',),
-              ('summary',),
-              ('status', TICKET_STATUS),
-              ('resolution',),
-              ('reporter',IndexFields.AUTHOR),
-        ]
-        for f in fields:
-            if f[0] in ticket.values:
-                if len(f) == 1:
-                    doc[f[0]] = ticket.values[f[0]]
-                elif len(f) == 2:
-                    doc[f[1]] = ticket.values[f[0]]
-        doc['changes'] = u'\n\n'.join([x[4] for x in ticket.get_changelog()
-                                       if x[2] == u'comment'])
+
+        for field, index_field in self.optional_fields.iteritems():
+            if field in ticket.values:
+                doc[index_field] = ticket.values[field]
+
+        doc[TicketFields.CHANGES] = u'\n\n'.join(
+            [x[4] for x in ticket.get_changelog() if x[2] == u'comment'])
         return doc
 
     def get_entries_for_index(self):
@@ -111,12 +120,40 @@ class TicketIndexer(BaseIndexer):
         return query.execute()
 
 
-class TicketSearchParticipant(Component):
+class TicketSearchParticipant(BaseSearchParticipant):
     implements(ISearchParticipant)
 
-    default_facets = ListOption('bhsearch', 'default_facets_ticket',
-                            'status,milestone,component',
-        doc="""Default facets applied to search through tickets""")
+    default_facets = [
+        TicketFields.STATUS,
+        TicketFields.MILESTONE,
+        TicketFields.COMPONENT,
+        ]
+    default_grid_fields = [
+        TicketFields.ID,
+        TicketFields.SUMMARY,
+        TicketFields.STATUS,
+        TicketFields.MILESTONE,
+        TicketFields.COMPONENT,
+        ]
+    prefix = TICKET_TYPE
+
+    default_facets = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_facets',
+        default=",".join(default_facets),
+        doc="""Default facets applied to search view of specific resource""")
+
+    default_view = Option(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_view',
+        doc = """If true, show grid as default view for specific resource in
+            Bloodhound Search results""")
+
+    default_grid_fields = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_grid_fields',
+        default = ",".join(default_grid_fields),
+        doc="""Default fields for grid view for specific resource""")
 
     #ISearchParticipant members
     def get_search_filters(self, req=None):
@@ -126,22 +163,19 @@ class TicketSearchParticipant(Component)
     def get_title(self):
         return "Ticket"
 
-    def get_default_facets(self):
-        return self.default_facets
-
     def format_search_results(self, res):
-        if not TICKET_STATUS in res:
+        if not TicketFields.STATUS in res:
             stat = 'undefined_status'
             css_class = 'undefined_status'
         else:
-            css_class = res[TICKET_STATUS]
-            if res[TICKET_STATUS] == 'closed':
+            css_class = res[TicketFields.STATUS]
+            if res[TicketFields.STATUS] == 'closed':
                 resolution = ""
                 if 'resolution' in res:
                     resolution = res['resolution']
                 stat = '%s: %s' % (res['status'], resolution)
             else:
-                stat = res[TICKET_STATUS]
+                stat = res[TicketFields.STATUS]
 
         id = tag(tag.span('#'+res['id'], class_=css_class))
         return id + ': %s (%s)' % (res['summary'], stat)

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=1442591&r1=1442590&r2=1442591&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 Tue Feb  5 14:03:35 2013
@@ -19,11 +19,12 @@
 #  under the License.
 
 r"""Wiki specifics for Bloodhound Search plugin."""
-from bhsearch.api import ISearchParticipant, BloodhoundSearchApi, \
-    IIndexParticipant, IndexFields
-from bhsearch.search_resources.base import BaseIndexer
-from trac.core import implements, Component
-from trac.config import ListOption
+from bhsearch import BHSEARCH_CONFIG_SECTION
+from bhsearch.api import (ISearchParticipant, BloodhoundSearchApi,
+    IIndexParticipant, IndexFields)
+from bhsearch.search_resources.base import BaseIndexer, BaseSearchParticipant
+from trac.core import implements
+from trac.config import ListOption, Option
 from trac.wiki import IWikiChangeListener, WikiSystem, WikiPage
 
 WIKI_TYPE = u"wiki"
@@ -106,11 +107,34 @@ class WikiIndexer(BaseIndexer):
             page = WikiPage(self.env, page_name)
             yield self.build_doc(page)
 
-class WikiSearchParticipant(Component):
+class WikiSearchParticipant(BaseSearchParticipant):
     implements(ISearchParticipant)
 
-    default_facets = ListOption('bhsearch', 'default_facets_wiki',
-        doc="""Default facets applied to search through wiki pages""")
+    default_facets = []
+    default_grid_fields = [
+        IndexFields.ID,
+        IndexFields.TIME,
+        IndexFields.AUTHOR
+    ]
+    prefix = WIKI_TYPE
+
+    default_facets = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_facets',
+        default=",".join(default_facets),
+        doc="""Default facets applied to search view of specific resource""")
+
+    default_view = Option(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_view',
+        doc = """If true, show grid as default view for specific resource in
+            Bloodhound Search results""")
+
+    default_grid_fields = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_grid_fields',
+        default = ",".join(default_grid_fields),
+        doc="""Default fields for grid view for specific resource""")
 
     #ISearchParticipant members
     def get_search_filters(self, req=None):
@@ -120,9 +144,6 @@ class WikiSearchParticipant(Component):
     def get_title(self):
         return "Wiki"
 
-    def get_default_facets(self):
-        return self.default_facets
-
     def format_search_results(self, res):
         return u'%s: %s...' % (res['id'], res['content'][:50])
 

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch.html
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch.html?rev=1442591&r1=1442590&r2=1442591&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch.html (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch.html Tue Feb  5 14:03:35 2013
@@ -56,16 +56,17 @@
   <body>
     <div id="content" class="row">
 
-      <h1>This page provides prototype functionality.</h1>
+      <h1>Advanced search</h1>
       <form id="fullsearch" action="${href.bhsearch()}" method="get">
         <p>
-          <input type="text" id="q" name="q" size="40" value="${query}" />
+          <input type="text" id="q" name="q" size="60" value="${query}" />
           <!--So far, we will not support noquickjump mode for form submission-->
           <!--<input type="hidden" name="noquickjump" value="1" />-->
           <input py:if="active_type" type="hidden" name="type" value="${active_type}" />
           <py:for each="active_filter in active_filter_queries">
             <input type="hidden" name="fq" value="${active_filter.query}" />
           </py:for>
+          <input py:if="active_view" type="hidden" name="view" value="${active_view}" />
           <input type="submit" value="${_('Search')}" />
         </p>
       </form>
@@ -99,21 +100,82 @@
 
       <py:if test="results">
         <div class="span8">
-          <h2 py:if="results">
+          <h2>
             Results <small>(${results.displayed_items()})</small>
           </h2>
-          <div>
-            <dl id="results">
 
-              <py:for each="result in results">
-                <dt><a href="${result.href}" class="searchable">${result.title}</a></dt>
-                <dd class="searchable">${result.excerpt}</dd>
-                <dd>
-                  <py:if test="result.author"><span class="author" i18n:msg="author">By ${format_author(result.author)}</span> &mdash;</py:if>
-                  <span class="date">${result.date}</span>
-                </dd>
-              </py:for>
-            </dl>
+          <div class="pull-right" py:if="all_views">
+            <!--TODO: change presentation. Current implementation is very basic. -->
+            View as:
+            <py:for each="idx, view in enumerate(all_views)">
+              <b py:if="view.is_active">${view.label}</b>
+              <a href="${view.href}" py:if="not view.is_active">${view.label}</a>
+              <py:if test="idx+1!=len(all_views)"> | </py:if>
+            </py:for>
+          </div>
+
+          <div>
+            <py:if test="view=='grid'">
+              <!--todo: implement rendering in pluggable manner-->
+              <!--Rendering results in grid view-->
+              <table class="listing tickets table table-bordered table-condensed query" style="border-radius: 0px 0px 4px 4px">
+                <!--render table header-->
+                <thead>
+                  <tr class="trac-columns">
+                      <th py:for="header in headers"
+                          class="$header.name${(' desc' if header.sort=='DESC' else ' asc') if header.sort else ''}">
+                        <?python asc = _('(ascending)'); desc = _('(descending)') ?>
+                        <a title="${_('Sort by %(col)s %(direction)s', col=header.label,
+                                      direction=(desc if header.sort=='ASC' else asc))}"
+                           href="$header.href">${header.label}</a>
+                      </th>
+                  </tr>
+                </thead>
+
+                <tbody>
+                  <!--render table rows-->
+                  <py:for each="idx, result in enumerate(results)">
+                    <tr class="${'odd' if idx % 2 else 'even'} prio${result.priority_value}${
+                      ' added' if 'added' in result else ''}${
+                      ' changed' if 'changed' in result else ''}${
+                      ' removed' if 'removed' in result else ''}">
+                      <py:for each="idx, header in enumerate(headers)" py:choose="">
+                        <py:with vars="name = header.name; value = result[name];  title = _('View ')+ result['type']">
+                          <td py:when="name == 'id'" class="id"><a href="$result.href" title="${title}"
+                              class="${classes(closed=result.status == 'closed')}">#$result.id</a></td>
+                          <td py:otherwise="" class="$name" py:choose="">
+                            <a py:when="name == 'summary'" href="$result.href" title="title">$value</a>
+                            <py:when test="isinstance(value, datetime)">${pretty_dateinfo(value, dateonly=True)}</py:when>
+                            <py:when test="name == 'reporter'">${authorinfo(value)}</py:when>
+                            <py:when test="name == 'cc'">${format_emails(ticket_context, value)}</py:when>
+                            <py:when test="name == 'owner' and value">${authorinfo(value)}</py:when>
+                            <py:when test="name == 'milestone'"><a py:if="value" title="View milestone" href="${href.milestone(value)}">${value}</a></py:when>
+                            <!--<py:when test="name == 'content'">${wiki_to_oneliner(ticket_context, value)}</py:when>-->
+                            <py:when test="header.wikify">${wiki_to_oneliner(ticket_context, value)}</py:when>
+                            <py:otherwise>$value</py:otherwise>
+                          </td>
+                        </py:with>
+                      </py:for>
+                    </tr>
+                  </py:for>
+                </tbody>
+              </table>
+            </py:if>
+
+            <!--<py:if test="not headers">-->
+            <py:if test="not view">
+              <!--Rendering results in free form-->
+              <dl id="results">
+                <py:for each="result in results">
+                  <dt><a href="${result.href}" class="searchable">${result.title}</a></dt>
+                  <dd class="searchable">${result.excerpt}</dd>
+                  <dd>
+                    <py:if test="result.author"><span class="author" i18n:msg="author">By ${format_author(result.author)}</span> &mdash;</py:if>
+                    <span class="date">${result.date}</span>
+                  </dd>
+                </py:for>
+              </dl>
+            </py:if>
           </div>
 
           <xi:include py:with="paginator = results" href="bh_page_index.html" />

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/api.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/api.py?rev=1442591&r1=1442590&r2=1442591&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/api.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/api.py Tue Feb  5 14:03:35 2013
@@ -22,7 +22,7 @@ import tempfile
 import shutil
 from bhsearch.api import BloodhoundSearchApi, ASC
 from bhsearch.query_parser import DefaultQueryParser
-from bhsearch.tests.utils import BaseBloodhoundSearchTest
+from bhsearch.tests.base import BaseBloodhoundSearchTest
 from bhsearch.search_resources.ticket_search import TicketSearchParticipant
 
 from bhsearch.whoosh_backend import WhooshBackend

Copied: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py (from r1440488, incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/utils.py)
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py?p2=incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py&p1=incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/utils.py&r1=1440488&r2=1442591&rev=1442591&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/utils.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py Tue Feb  5 14:03:35 2013
@@ -21,13 +21,29 @@
 r"""
 Test utils methods
 """
-from pprint import pprint
 import unittest
+import tempfile
+import shutil
+from pprint import pprint
+
 from bhsearch.web_ui import BloodhoundSearchModule
 from trac.ticket import Ticket, Milestone
+from trac.test import EnvironmentStub
 from trac.wiki import WikiPage
 
 class BaseBloodhoundSearchTest(unittest.TestCase):
+
+    def setUp(self, enabled = None):
+        if not enabled:
+            enabled = ['bhsearch.*']
+        self.env = EnvironmentStub(enable=enabled)
+        self.env.path = tempfile.mkdtemp('bhsearch-tempenv')
+        self.env.config.set('bhsearch', 'silence_on_error', "False")
+
+    def tearDown(self):
+        shutil.rmtree(self.env.path)
+        self.env.reset_db()
+
     def print_result(self, result):
         print "Received result:"
         pprint(result.__dict__)

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/index_with_whoosh.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/index_with_whoosh.py?rev=1442591&r1=1442590&r2=1442591&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/index_with_whoosh.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/index_with_whoosh.py Tue Feb  5 14:03:35 2013
@@ -22,14 +22,12 @@ import unittest
 import tempfile
 import shutil
 from bhsearch.api import BloodhoundSearchApi
-from bhsearch.milestone_search import MilestoneIndexer
-from bhsearch.tests.utils import BaseBloodhoundSearchTest
+from bhsearch.search_resources.milestone_search import MilestoneIndexer
+from bhsearch.tests.base import BaseBloodhoundSearchTest
 from bhsearch.search_resources.ticket_search import TicketIndexer
 
 from bhsearch.whoosh_backend import WhooshBackend
-from bhsearch.search_resources.wiki_search import WikiIndexer
 from trac.test import EnvironmentStub
-from trac.ticket.api import TicketSystem
 
 
 class IndexWhooshTestCase(BaseBloodhoundSearchTest):
@@ -39,10 +37,6 @@ class IndexWhooshTestCase(BaseBloodhound
         self.whoosh_backend = WhooshBackend(self.env)
         self.whoosh_backend.recreate_index()
         self.search_api = BloodhoundSearchApi(self.env)
-        self.ticket_indexer = TicketIndexer(self.env)
-        self.wiki_indexer = WikiIndexer(self.env)
-        self.milestone_indexer = MilestoneIndexer(self.env)
-        self.ticket_system = TicketSystem(self.env)
 
     def tearDown(self):
         shutil.rmtree(self.env.path)
@@ -51,7 +45,7 @@ class IndexWhooshTestCase(BaseBloodhound
     def test_can_index_ticket(self):
         ticket = self.create_dummy_ticket()
         ticket.id = "1"
-        self.ticket_indexer.ticket_created(ticket)
+        TicketIndexer(self.env).ticket_created(ticket)
 
         results = self.search_api.query("*:*")
         self.print_result(results)
@@ -112,6 +106,7 @@ class IndexWhooshTestCase(BaseBloodhound
         self.assertEqual(2, results.hits)
 
     def test_can_reindex_milestones(self):
+        MilestoneIndexer(self.env)
         self.insert_milestone("M1")
         self.insert_milestone("M2")
         self.whoosh_backend.recreate_index()

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/milestone_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/milestone_search.py?rev=1442591&r1=1442590&r2=1442591&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/milestone_search.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/milestone_search.py Tue Feb  5 14:03:35 2013
@@ -17,17 +17,14 @@
 #  KIND, either express or implied.  See the License for the
 #  specific language governing permissions and limitations
 #  under the License.
-import shutil
 import unittest
-import tempfile
 
 from bhsearch.api import BloodhoundSearchApi
-from bhsearch.search_resources.milestone_search import MilestoneSearchParticipant
-from bhsearch.query_parser import DefaultQueryParser
-from bhsearch.tests.utils import BaseBloodhoundSearchTest
+from bhsearch.search_resources.milestone_search import (
+    MilestoneSearchParticipant)
+from bhsearch.tests.base import BaseBloodhoundSearchTest
 from bhsearch.whoosh_backend import WhooshBackend
 
-from trac.test import EnvironmentStub
 from trac.ticket import Milestone
 
 
@@ -35,18 +32,10 @@ class MilestoneIndexerEventsTestCase(Bas
     DUMMY_MILESTONE_NAME = "dummyName"
 
     def setUp(self):
-        self.env = EnvironmentStub(enable=['bhsearch.*'])
-        self.env.path = tempfile.mkdtemp('bhsearch-tempenv')
-        self.env.config.set('bhsearch', 'silence_on_error', "False")
+        super(MilestoneIndexerEventsTestCase, self).setUp()
         self.whoosh_backend = WhooshBackend(self.env)
         self.whoosh_backend.recreate_index()
         self.search_api = BloodhoundSearchApi(self.env)
-        self.milestone_participant = MilestoneSearchParticipant(self.env)
-        self.query_parser = DefaultQueryParser(self.env)
-
-    def tearDown(self):
-        shutil.rmtree(self.env.path)
-        self.env.reset_db()
 
     def test_can_index_created_milestone(self):
         #arrange
@@ -132,10 +121,32 @@ class MilestoneIndexerEventsTestCase(Bas
         self.assertEqual(self.DUMMY_MILESTONE_NAME, doc["id"])
         self.assertEqual("milestone", doc["type"])
 
+class MilestoneSearchParticipantTestCase(BaseBloodhoundSearchTest):
+    def setUp(self):
+        super(MilestoneSearchParticipantTestCase, self).setUp()
+        self.milestone_search = MilestoneSearchParticipant(self.env)
+
+    def test_can_get_default_grid_fields(self):
+        grid_fields = self.milestone_search.get_default_view_fields("grid")
+        print grid_fields
+        self.assertGreater(len(grid_fields), 0)
+
+    def test_can_get_default_facets(self):
+        default_facets = self.milestone_search.get_default_facets()
+        print default_facets
+        self.assertIsNotNone(default_facets)
+
+    def test_can_get_is_grid_view_defaults(self):
+        default_grid_fields = self.milestone_search.get_default_view_fields(
+            "grid")
+        print default_grid_fields
+        self.assertIsNotNone(default_grid_fields)
 
 def suite():
     suite = unittest.TestSuite()
     suite.addTest(unittest.makeSuite(MilestoneIndexerEventsTestCase, 'test'))
+    suite.addTest(
+        unittest.makeSuite(MilestoneSearchParticipantTestCase, 'test'))
     return suite
 
 if __name__ == '__main__':

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/real_index_view.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/real_index_view.py?rev=1442591&r1=1442590&r2=1442591&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/real_index_view.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/real_index_view.py Tue Feb  5 14:03:35 2013
@@ -20,7 +20,7 @@
 import unittest
 from bhsearch.web_ui import RequestParameters
 import os
-from bhsearch.tests.utils import BaseBloodhoundSearchTest
+from bhsearch.tests.base import BaseBloodhoundSearchTest
 
 from bhsearch.whoosh_backend import WhooshBackend
 from trac.test import EnvironmentStub, Mock, MockPerm

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/ticket_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/ticket_search.py?rev=1442591&r1=1442590&r2=1442591&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/ticket_search.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/ticket_search.py Tue Feb  5 14:03:35 2013
@@ -20,7 +20,7 @@
 import unittest
 import tempfile
 
-from bhsearch.tests.utils import BaseBloodhoundSearchTest
+from bhsearch.tests.base import BaseBloodhoundSearchTest
 from bhsearch.search_resources.ticket_search import TicketIndexer
 
 from trac.test import EnvironmentStub

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=1442591&r1=1442590&r2=1442591&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py Tue Feb  5 14:03:35 2013
@@ -23,7 +23,7 @@ import shutil
 
 from urllib import urlencode, unquote
 
-from bhsearch.tests.utils import BaseBloodhoundSearchTest
+from bhsearch.tests.base import BaseBloodhoundSearchTest
 from bhsearch.web_ui import RequestParameters
 from bhsearch.whoosh_backend import WhooshBackend
 
@@ -39,9 +39,7 @@ DEFAULT_DOCS_PER_PAGE = 10
 
 class WebUiTestCaseWithWhoosh(BaseBloodhoundSearchTest):
     def setUp(self):
-        self.env = EnvironmentStub(enable=['trac.*', 'bhsearch.*'])
-        self.env.path = tempfile.mkdtemp('bhsearch-tempenv')
-
+        super(WebUiTestCaseWithWhoosh, self).setUp(['trac.*', 'bhsearch.*'])
         whoosh_backend = WhooshBackend(self.env)
         whoosh_backend.recreate_index()
 
@@ -62,10 +60,6 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
         self.redirect_permanent = permanent
         raise RequestDone
 
-    def tearDown(self):
-        shutil.rmtree(self.env.path)
-        self.env.reset_db()
-
     def test_can_process_empty_request(self):
         data = self.process_request()
         self.assertEqual("", data["query"])
@@ -441,6 +435,86 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
         self.assertEqual('T1 (new)', quick_jump_data["description"])
         self.assertEqual('/main/ticket/1', quick_jump_data["href"])
 
+
+    def test_that_ticket_search_can_return_in_grid(self):
+        #arrange
+        self.env.config.set(
+            'bhsearch',
+            'ticket_is_grid_view_default',
+            'True')
+        self.env.config.set(
+            'bhsearch',
+            'ticket_default_grid_fields',
+            'id,status,milestone,component')
+        self.insert_ticket("T1", component="c1", status="new", milestone="A")
+        #act
+        self.req.args[RequestParameters.QUERY] = "*"
+        self.req.args[RequestParameters.TYPE] = "ticket"
+        self.req.args[RequestParameters.VIEW] = "grid"
+        data = self.process_request()
+        #assert
+        grid_data = data["headers"]
+        self.assertIsNotNone(grid_data)
+        fields = [column["name"] for column in grid_data]
+        self.assertEquals(["id", "status", "milestone", "component"], fields)
+
+    def test_that_grid_is_switched_off_by_default(self):
+        #arrange
+        self.insert_ticket("T1", component="c1", status="new", milestone="A")
+        #act
+        self.req.args[RequestParameters.QUERY] = "*"
+        data = self.process_request()
+        #assert
+        self.assertNotIn("headers", data)
+        self.assertNotIn("view", data)
+
+    def test_that_grid_is_switched_off_by_default_for_ticket(self):
+        #arrange
+        self.insert_ticket("T1", component="c1", status="new", milestone="A")
+        #act
+        self.req.args[RequestParameters.QUERY] = "*"
+        self.req.args[RequestParameters.TYPE] = "ticket"
+        data = self.process_request()
+        #assert
+        self.assertNotIn("headers", data)
+        self.assertNotIn("view", data)
+
+
+    def test_can_returns_all_views(self):
+        #arrange
+        self.insert_ticket("T1", component="c1", status="new", milestone="A")
+        #act
+        self.req.args[RequestParameters.QUERY] = "*"
+        data = self.process_request()
+        #assert
+        all_views = data["all_views"]
+        free_view = all_views[0]
+        self.assertTrue(free_view["is_active"])
+        self.assertNotIn("view=", free_view["href"])
+        grid = all_views[1]
+        self.assertFalse(grid["is_active"])
+        self.assertIn("view=grid", grid["href"])
+
+    def test_that_active_view_is_not_set_if_not_requested(self):
+        #arrange
+        self.insert_ticket("T1", component="c1", status="new", milestone="A")
+        #act
+        self.req.args[RequestParameters.QUERY] = "*"
+        data = self.process_request()
+        #assert
+        self.assertNotIn("active_view", data)
+
+    def test_that_active_view_is_set_if_requested(self):
+        #arrange
+        self.insert_ticket("T1", component="c1", status="new", milestone="A")
+        #act
+        self.req.args[RequestParameters.QUERY] = "*"
+        self.req.args[RequestParameters.VIEW] = "grid"
+        data = self.process_request()
+        #assert
+        self.assertEqual("grid", data["active_view"])
+
+
     def _count_parameter_in_url(self, url, parameter_name, value):
         parameter_to_find = (parameter_name, value)
         parsed_parameters = parse_arg_list(url)

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=1442591&r1=1442590&r2=1442591&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/whoosh_backend.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/whoosh_backend.py Tue Feb  5 14:03:35 2013
@@ -24,9 +24,9 @@ import tempfile
 import shutil
 from bhsearch.api import ASC, DESC, SCORE
 from bhsearch.query_parser import DefaultQueryParser
-from bhsearch.tests.utils import BaseBloodhoundSearchTest
-from bhsearch.whoosh_backend import WhooshBackend, \
-    WhooshEmptyFacetErrorWorkaround
+from bhsearch.tests.base import BaseBloodhoundSearchTest
+from bhsearch.whoosh_backend import (WhooshBackend,
+    WhooshEmptyFacetErrorWorkaround)
 from trac.test import EnvironmentStub
 from trac.util.datefmt import FixedOffset, utc
 from whoosh import index, sorting, query

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/wiki_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/wiki_search.py?rev=1442591&r1=1442590&r2=1442591&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/wiki_search.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/wiki_search.py Tue Feb  5 14:03:35 2013
@@ -25,7 +25,8 @@ from bhsearch.api import BloodhoundSearc
 from bhsearch.query_parser import DefaultQueryParser
 from bhsearch.tests.utils import BaseBloodhoundSearchTest
 from bhsearch.whoosh_backend import WhooshBackend
-from bhsearch.search_resources.wiki_search import WikiIndexer, WikiSearchParticipant
+from bhsearch.search_resources.wiki_search import (
+    WikiIndexer, WikiSearchParticipant)
 
 from trac.test import EnvironmentStub
 from trac.wiki import WikiSystem, WikiPage

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=1442591&r1=1442590&r2=1442591&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py Tue Feb  5 14:03:35 2013
@@ -22,28 +22,29 @@ r"""Bloodhound Search user interface."""
 import copy
 
 import pkg_resources
+from bhsearch import BHSEARCH_CONFIG_SECTION
 import re
 
 from trac.core import Component, implements, TracError
 from genshi.builder import tag
 from trac.perm import IPermissionRequestor
 from trac.search import shorten_result
-from trac.config import OrderedExtensionsOption, ListOption
+from trac.config import OrderedExtensionsOption, ListOption, Option
 from trac.util.presentation import Paginator
 from trac.util.datefmt import format_datetime, user_time
 from trac.web import IRequestHandler
 from trac.util.translation import _
 from trac.util.html import find_element
 from trac.web.chrome import (INavigationContributor, ITemplateProvider,
-    add_link, add_stylesheet, web_context)
+                             add_link, add_stylesheet, web_context)
 from bhsearch.api import (BloodhoundSearchApi, ISearchParticipant, SCORE, ASC,
-    DESC, IndexFields)
+                          DESC, IndexFields)
 from trac.wiki.formatter import extract_link
 
 SEARCH_PERMISSION = 'SEARCH_VIEW'
 DEFAULT_RESULTS_PER_PAGE = 10
 DEFAULT_SORT = [(SCORE, ASC), ("time", DESC)]
-
+#VIEW_GRID = "grid"
 
 class RequestParameters(object):
     """
@@ -57,21 +58,23 @@ class RequestParameters(object):
     NO_QUICK_JUMP = "noquickjump"
     PAGELEN = "pagelen"
     FILTER_QUERY = "fq"
+    VIEW = "view"
 
     def __init__(self, req):
         self.req = req
 
-        self.query = req.args.getfirst(self.QUERY)
-        if self.query is None:
+        self.original_query = req.args.getfirst(self.QUERY)
+        if self.original_query is None:
             self.query = ""
         else:
-            self.query = self.query.strip()
+            self.query = self.original_query.strip()
 
         self.no_quick_jump = int(req.args.getfirst(self.NO_QUICK_JUMP, '0'))
 
+        self.view = req.args.getfirst(self.VIEW)
         self.filter_queries = req.args.getlist(self.FILTER_QUERY)
         self.filter_queries = self._remove_possible_duplications(
-                        self.filter_queries)
+            self.filter_queries)
 
         #TODO: retrieve sort from query string
         self.sort = DEFAULT_SORT
@@ -86,11 +89,12 @@ class RequestParameters(object):
             RequestParameters.FILTER_QUERY: []
         }
 
+        if self.original_query is not None:
+            self.params[self.QUERY] = self.original_query
         if self.no_quick_jump > 0:
             self.params[self.NO_QUICK_JUMP] = self.no_quick_jump
-
-        if self.query:
-            self.params[self.QUERY] = self.query
+        if self.view is not None:
+            self.params[self.VIEW] = self.view
         if self.pagelen != DEFAULT_RESULTS_PER_PAGE:
             self.params[self.PAGELEN] = self.pagelen
         if self.page > 1:
@@ -107,31 +111,36 @@ class RequestParameters(object):
 
     def create_href(
             self,
-            page = None,
+            page=None,
             type=None,
-            skip_type = False,
-            skip_page = False,
-            additional_filter = None,
-            force_filters = None,
-            ):
+            skip_type=False,
+            skip_page=False,
+            additional_filter=None,
+            force_filters=None,
+            view=None,
+            skip_view=False,
+    ):
         params = copy.deepcopy(self.params)
 
         #noquickjump parameter should be always set to 1 for urls
         params[self.NO_QUICK_JUMP] = 1
 
-        if page:
+        if skip_view:
+            self._delete_if_exists(params, self.VIEW)
+        elif view:
+            params[self.VIEW] = view
+
+        if skip_page:
+            self._delete_if_exists(params, self.PAGE)
+        elif page:
             params[self.PAGE] = page
 
-        if skip_page and self.PAGE in params:
-            del(params[self.PAGE])
-
-        if type:
+        if skip_type:
+            self._delete_if_exists(params, self.TYPE)
+        elif type:
             params[self.TYPE] = type
 
-        if skip_type and self.TYPE in params:
-            del(params[self.TYPE])
-
-        if additional_filter and \
+        if additional_filter and\
            additional_filter not in params[self.FILTER_QUERY]:
             params[self.FILTER_QUERY].append(additional_filter)
         elif force_filters is not None:
@@ -139,15 +148,33 @@ class RequestParameters(object):
 
         return self.req.href.bhsearch(**params)
 
+    def _delete_if_exists(self, params, name):
+        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]
 
     implements(INavigationContributor, IPermissionRequestor, IRequestHandler,
-               ITemplateProvider,
-    #           IWikiSyntaxProvider #todo: implement later
+        ITemplateProvider,
+        #           IWikiSyntaxProvider #todo: implement later
     )
 
     search_participants = OrderedExtensionsOption(
@@ -157,9 +184,31 @@ class BloodhoundSearchModule(Component):
         "TicketSearchParticipant, WikiSearchParticipant"
     )
 
-
-    default_facets_all = ListOption('bhsearch', 'default_facets_all',
-        doc="""Default facets applied to search through all resources""")
+    prefix = "all"
+    default_grid_fields = [
+        IndexFields.ID,
+        IndexFields.TYPE,
+        IndexFields.TIME,
+        IndexFields.AUTHOR,
+        IndexFields.CONTENT,
+    ]
+
+    default_facets = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_facets',
+        doc="""Default facets applied to search view of all resources""")
+
+    default_view = Option(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_view',
+        doc="""If true, show grid as default view for specific resource in
+            Bloodhound Search results""")
+
+    default_grid_fields = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_grid_fields',
+        default=",".join(default_grid_fields),
+        doc="""Default fields for grid view for specific resource""")
 
 
     # INavigationContributor methods
@@ -186,44 +235,46 @@ class BloodhoundSearchModule(Component):
         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,
         )
-        working_query_string = parameters.query.strip()
-
 
         #TBD: should search return results on empty query?
-#        if not any((
-#            working_query_string,
-#            parameters.type,
-#            parameters.filter_queries,
-#            )):
-#            return self._return_data(req, data)
+        #        if not any((
+        #            query,
+        #            parameters.type,
+        #            parameters.filter_queries,
+        #            )):
+        #            return self._return_data(req, data)
 
         self._prepare_quick_jump(
             parameters,
-            working_query_string,
             data)
+        fields = self._prepare_fields_and_view(
+            allowed_participants, parameters, data)
 
         query_filter = self._prepare_query_filter(
-            parameters,
-            allowed_participants)
+            parameters, allowed_participants)
 
         facets = self._prepare_facets(parameters, allowed_participants)
 
         query_system = BloodhoundSearchApi(self.env)
         query_result = query_system.query(
-            working_query_string,
+            parameters.query,
             pagenum=parameters.page,
             pagelen=parameters.pagelen,
             sort=parameters.sort,
+            fields=fields,
             facets=facets,
-            filter=query_filter)
+            filter=query_filter,
+        )
 
-        ui_docs = [self._process_doc(doc, req, allowed_participants)
+        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]
 
         results = Paginator(
@@ -234,22 +285,21 @@ class BloodhoundSearchModule(Component):
 
         results.shown_pages = self._prepare_shown_pages(
             parameters,
-            shown_pages = results.get_shown_pages(parameters.pagelen))
+            shown_pages=results.get_shown_pages(parameters.pagelen))
 
         results.current_page = {'href': None,
                                 'class': 'current',
                                 'string': str(results.page + 1),
-                                'title':None}
+                                'title': None}
 
         if results.has_next_page:
-            next_href = parameters.create_href(page = parameters.page + 1)
+            next_href = parameters.create_href(page=parameters.page + 1)
             add_link(req, 'next', next_href, _('Next Page'))
 
         if results.has_previous_page:
-            prev_href = parameters.create_href(page = parameters.page - 1)
+            prev_href = parameters.create_href(page=parameters.page - 1)
             add_link(req, 'prev', prev_href, _('Previous Page'))
 
-
         data['results'] = results
 
         self._prepare_result_facet_counts(
@@ -261,15 +311,89 @@ class BloodhoundSearchModule(Component):
         data['page_href'] = parameters.create_href()
         return self._return_data(req, data)
 
+
+    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
+
+    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
+
+    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_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_quick_jump(self,
                             parameters,
-                            working_query_string,
                             data):
-        if not working_query_string:
+        if not parameters.query:
             return
         check_result = self._check_quickjump(
             parameters.req,
-            working_query_string)
+            parameters.query)
         if check_result:
             data["quickjump"] = check_result
 
@@ -301,7 +425,6 @@ 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:
@@ -327,16 +450,15 @@ class BloodhoundSearchModule(Component):
                 type = participant_with_type[participant]
                 allowed_types.append(dict(
                     label=_(participant.get_title()),
-                    active=(type ==active_type),
+                    active=(type == active_type),
                     href=parameters.create_href(
                         type=type,
                         skip_page=True,
                         force_filters=[],
                     ),
                 ))
-        data["types"] =  allowed_types
-        data["active_type"] =  active_type
-
+        data["types"] = allowed_types
+        data["active_type"] = active_type
 
 
     def _prepare_active_filter_queries(
@@ -393,8 +515,8 @@ class BloodhoundSearchModule(Component):
                         href = parameters.create_href(
                             skip_page=True,
                             additional_filter=self._create_term_expression(
-                                    field,
-                                    field_value)
+                                field,
+                                field_value)
                         )
                     per_field_dict[field_value] = dict(
                         count=count,
@@ -427,7 +549,7 @@ class BloodhoundSearchModule(Component):
         #TODO: add possibility of specifying facets in query parameters
         if parameters.is_show_all_mode():
             facets = [IndexFields.TYPE]
-            facets.extend(self.default_facets_all)
+            facets.extend(self.default_facets)
         else:
             type_participant = allowed_participants[parameters.type]
             facets = type_participant.get_default_facets()
@@ -445,10 +567,8 @@ class BloodhoundSearchModule(Component):
         add_stylesheet(req, 'common/css/search.css')
         return 'bhsearch.html', data, None
 
-    def _process_doc(self, doc, req, allowed_participants):
-        #todo: introduce copy by predefined value
+    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'):
@@ -456,8 +576,9 @@ class BloodhoundSearchModule(Component):
         if doc.has_key('time'):
             ui_doc['date'] = user_time(req, format_datetime, doc['time'])
 
-        ui_doc['title'] = allowed_participants[doc['type']]\
-                .format_search_results(doc)
+        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):
@@ -465,7 +586,7 @@ class BloodhoundSearchModule(Component):
         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)])
+                              '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
@@ -473,8 +594,8 @@ class BloodhoundSearchModule(Component):
 
     # ITemplateProvider methods
     def get_htdocs_dirs(self):
-#        return [('bhsearch',
-#                 pkg_resources.resource_filename(__name__, 'htdocs'))]
+    #        return [('bhsearch',
+    #                 pkg_resources.resource_filename(__name__, 'htdocs'))]
         return []
 
     def get_templates_dirs(self):

Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/whoosh_backend.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/whoosh_backend.py?rev=1442591&r1=1442590&r2=1442591&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_search/bhsearch/whoosh_backend.py (original)
+++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/whoosh_backend.py Tue Feb  5 14:03:35 2013
@@ -23,6 +23,7 @@ from bhsearch.api import ISearchBackend,
     IDocIndexPreprocessor, IResultPostprocessor, IndexFields, \
     IQueryPreprocessor
 import os
+from bhsearch.search_resources.ticket_search import TicketFields
 from trac.core import Component, implements, TracError
 from trac.config import Option
 from trac.util.text import empty
@@ -162,7 +163,6 @@ class WhooshBackend(Component):
               query,
               sort = None,
               fields = None,
-              boost = None,
               filter = None,
               facets = None,
               pagenum = 1,
@@ -359,8 +359,8 @@ class WhooshEmptyFacetErrorWorkaround(Co
 
     should_not_be_empty_fields = [
         IndexFields.STATUS,
-        IndexFields.MILESTONE,
-        IndexFields.COMPONENT,
+        TicketFields.MILESTONE,
+        TicketFields.COMPONENT,
     ]
 
     #IDocIndexPreprocessor methods



Mime
View raw message