You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bloodhound.apache.org by ju...@apache.org on 2013/02/11 14:05:19 UTC
svn commit: r1444754 [1/2] - in
/incubator/bloodhound/branches/bep_0003_multiproduct: ./
bloodhound_dashboard/ bloodhound_multiproduct/ bloodhound_search/
bloodhound_search/bhsearch/ bloodhound_search/bhsearch/search_resources/
bloodhound_search/bhsear...
Author: jure
Date: Mon Feb 11 13:05:18 2013
New Revision: 1444754
URL: http://svn.apache.org/r1444754
Log:
sync merge from trunk
Added:
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/search_resources/
- copied from r1444753, incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/search_resources/
Removed:
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/milestone_search.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/ticket_search.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/wiki_search.py
Modified:
incubator/bloodhound/branches/bep_0003_multiproduct/ (props changed)
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_dashboard/setup.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/setup.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/__init__.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/api.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/base.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/milestone_search.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/ticket_search.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/wiki_search.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/templates/bhsearch.html
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/__init__.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/base.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/index_with_whoosh.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/web_ui.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/whoosh_backend.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/web_ui.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/setup.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/htdocs/bloodhound.css
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin.html
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin_milestones.html
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin_plugins.html
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin_repositories.html
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_search.html
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_ticket.html
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_ticket_box.html
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bloodhound_theme.html
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/theme.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/setup.py
Propchange: incubator/bloodhound/branches/bep_0003_multiproduct/
------------------------------------------------------------------------------
Merged /incubator/bloodhound/trunk:r1442601-1444753
Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_dashboard/setup.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_dashboard/setup.py?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_dashboard/setup.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_dashboard/setup.py Mon Feb 11 13:05:18 2013
@@ -23,7 +23,7 @@ try:
except ImportError:
from distutils.core import setup
-DESC = """Project dashboard for Apache(TM) Bloodhound
+DESC = """Project dashboard for Apache(TM) Bloodhound.
Add custom dashboards in multiple pages of Bloodhound sites.
"""
@@ -131,6 +131,9 @@ setup(
name=DIST_NM,
version=latest,
description=DESC.split('\n', 1)[0],
+ author = "Apache Bloodhound",
+ license = "Apache License v2",
+ url = "http://incubator.apache.org/bloodhound/",
requires = ['trac'],
tests_require = ['dutest>=0.2.4', 'TracXMLRPC'],
install_requires = [
Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/setup.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/setup.py?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/setup.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/setup.py Mon Feb 11 13:05:18 2013
@@ -22,6 +22,10 @@ from setuptools import setup
setup(
name = 'BloodhoundMultiProduct',
version = '0.4.0',
+ description = "Multiproduct support for Apache(TM) Bloodhound.",
+ author = "Apache Bloodhound",
+ license = "Apache License v2",
+ url = "http://incubator.apache.org/bloodhound/",
packages = ['multiproduct', 'multiproduct.ticket', 'tests',],
package_data = {'multiproduct' : ['templates/*.html',]},
entry_points = {'trac.plugins': [
Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/__init__.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/__init__.py?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/__init__.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/__init__.py Mon Feb 11 13:05:18 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/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/api.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/api.py?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/api.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/api.py Mon Feb 11 13:05:18 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."""
@@ -245,6 +248,7 @@ class BloodhoundSearchApi(Component):
for post_processor in self.result_post_processors:
post_processor.post_process(query_result)
+ query_result.debug["api_parameters"] = query_parameters
return query_result
Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/base.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/base.py?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/base.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/base.py Mon Feb 11 13:05:18 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/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/milestone_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/milestone_search.py?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/milestone_search.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/milestone_search.py Mon Feb 11 13:05:18 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/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/ticket_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/ticket_search.py?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/ticket_search.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/ticket_search.py Mon Feb 11 13:05:18 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/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/wiki_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/wiki_search.py?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/wiki_search.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/wiki_search.py Mon Feb 11 13:05:18 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,11 +112,15 @@ class WikiIndexer(BaseIndexer):
class WikiSearchParticipant(BaseSearchParticipant):
implements(ISearchParticipant)
+ participant_type = WIKI_TYPE
+ required_permission = 'WIKI_VIEW'
+
default_facets = []
default_grid_fields = [
IndexFields.ID,
IndexFields.TIME,
- IndexFields.AUTHOR
+ IndexFields.AUTHOR,
+ IndexFields.CONTENT,
]
prefix = WIKI_TYPE
@@ -137,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/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/templates/bhsearch.html
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/templates/bhsearch.html?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/templates/bhsearch.html (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/templates/bhsearch.html Mon Feb 11 13:05:18 2013
@@ -55,152 +55,159 @@
</head>
<body>
<div id="content" class="row">
+ <div class="span12">
+ <h1>Advanced search</h1>
+ <div class="btn-group">
+ <form id="fullsearch" action="${href.bhsearch()}" method="get">
+ <!--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}" />
+ <input py:if="active_view" type="hidden" name="view" value="${active_view}" />
+ <py:for each="active_filter in active_filter_queries">
+ <input type="hidden" name="fq" value="${active_filter.query}" />
+ </py:for>
- <h1>Advanced search</h1>
- <form id="fullsearch" action="${href.bhsearch()}" method="get">
- <p>
- <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>
-
- <py:if test="quickjump">
- <dt id="quickjump">
- <a href="${quickjump.href}" i18n:msg="name">Quickjump to ${quickjump.name}</a>
- </dt>
- <dd>${quickjump.description}</dd>
- </py:if>
-
- <!--This just a prototype implementation. Should be replaced by proper UI mocks-->
- <div>
- <!--Render type tabs: All, Ticket, Wiki, etc.-->
- <ul class="nav nav-tabs" id="search_types">
- <li py:for="idx, item in enumerate(i for i in types)"
- class="${classes(first_last(idx, types), active=item.active)}">
- <a href="${item.href}">${item.label}</a>
- </li>
-
- </ul>
+ <div class="input-append">
+ <input type="text" id="q" name="q" class="span4" value="${query}" />
+ <button type="submit" class="btn btn-warning">
+ <span class="hidden-phone">${_('Search')}</span>
+ <i class="icon-search icon-white"></i>
+ </button>
+ </div>
+ </form>
+ </div>
</div>
- <py:if test="active_filter_queries">
- <div id="active_filter_queries">
- <py:for each="active_filter in active_filter_queries">
- > <a href="${active_filter.href}">${active_filter.label}</a>
- </py:for>
+ <div class="span12">
+ <py:if test="quickjump">
+ <dt id="quickjump">
+ <a href="${quickjump.href}" i18n:msg="name">Quickjump to ${quickjump.name}</a>
+ </dt>
+ <dd>${quickjump.description}</dd>
+ </py:if>
+
+ <!--This just a prototype implementation. Should be replaced by proper UI mocks-->
+ <div>
+ <!--Render type tabs: All, Ticket, Wiki, etc.-->
+ <ul class="nav nav-tabs" id="search_types">
+ <li py:for="idx, item in enumerate(i for i in types)"
+ class="${classes(first_last(idx, types), active=item.active)}">
+ <a href="${item.href}">${item.label}</a>
+ </li>
+
+ </ul>
</div>
- </py:if>
- <py:if test="results">
- <div class="span8">
- <h2>
- Results <small>(${results.displayed_items()})</small>
- </h2>
-
- <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:if test="active_filter_queries">
+ <div id="active_filter_queries">
+ <py:for each="active_filter in active_filter_queries">
+ > <a href="${active_filter.href}">${active_filter.label}</a>
</py:for>
</div>
+ </py:if>
- <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>
+ <div py:if="results" class="row">
+ <div class="span3 facets">
+ <py:if test="facet_counts">
+ <!--Render facet counts-->
+ <h3>Facets</h3>
+ <ul id="facet_counts">
+ <li py:for="field, per_value_counts in facet_counts.iteritems()">
+ <h4 style="display: inline;">${field}</h4>
+ <ul id="facet_counts_value">
+ <li py:for="value, item in per_value_counts.iteritems()">
+ <a href="${item.href}"><strong>${display_value(value)}</strong></a>
+ <span class="badge badge-info">${item.count}</span>
+ </li>
+ </ul>
+ </li>
- <!--<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> —</py:if>
- <span class="date">${result.date}</span>
- </dd>
- </py:for>
- </dl>
+ </ul>
</py:if>
</div>
- <xi:include py:with="paginator = results" href="bh_page_index.html" />
- </div>
+ <div class="${'span12' if not facet_counts else 'span9 search_results'}">
+ <h2>
+ Results <small>(${results.displayed_items()})</small>
+ </h2>
+
+ <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>
- <div class="span4">
- <py:if test="facet_counts">
- <!--Render facet counts-->
- <h3>Facets</h3>
- <ul id="facet_counts">
- <li py:for="field, per_value_counts in facet_counts.iteritems()">
- <h4 style="display: inline;">${field}</h4>
- <ul id="facet_counts_value">
- <li py:for="value, item in per_value_counts.iteritems()">
- <a href="${item.href}"><strong>${display_value(value)}</strong></a>
- <span class="badge badge-info">${item.count}</span>
- </li>
- </ul>
- </li>
+ <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="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.content}</dd>
+ <dd>
+ <py:if test="result.author"><span class="author" i18n:msg="author">By ${format_author(result.author)}</span> —</py:if>
+ <span class="date">${result.date}</span>
+ </dd>
+ </py:for>
+ </dl>
+ </py:if>
+ </div>
- </ul>
- </py:if>
+ <xi:include py:with="paginator = results" href="bh_page_index.html" />
+ </div>
</div>
-
- </py:if>
+ </div>
<div class="span12"
Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/__init__.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/__init__.py?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/__init__.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/__init__.py Mon Feb 11 13:05:18 2013
@@ -18,18 +18,21 @@
# specific language governing permissions and limitations
# under the License.
import unittest
-from bhsearch.tests import whoosh_backend, index_with_whoosh, web_ui, \
- ticket_search, api, wiki_search
+from bhsearch.tests import (whoosh_backend, index_with_whoosh, web_ui,
+ api)
+from bhsearch.tests.search_resources import (ticket_search, wiki_search,
+ 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())
- 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/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/base.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/base.py?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/base.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/base.py Mon Feb 11 13:05:18 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/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/index_with_whoosh.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/index_with_whoosh.py?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/index_with_whoosh.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/index_with_whoosh.py Mon Feb 11 13:05:18 2013
@@ -19,7 +19,6 @@
# under the License.
import unittest
-import tempfile
import shutil
from bhsearch.api import BloodhoundSearchApi
from bhsearch.search_resources.milestone_search import MilestoneIndexer
@@ -27,13 +26,11 @@ from bhsearch.tests.base import BaseBloo
from bhsearch.search_resources.ticket_search import TicketIndexer
from bhsearch.whoosh_backend import WhooshBackend
-from trac.test import EnvironmentStub
class IndexWhooshTestCase(BaseBloodhoundSearchTest):
def setUp(self):
- self.env = EnvironmentStub(enable=['bhsearch.*'])
- self.env.path = tempfile.mkdtemp('bhsearch-tempenv')
+ super(IndexWhooshTestCase, self).setUp()
self.whoosh_backend = WhooshBackend(self.env)
self.whoosh_backend.recreate_index()
self.search_api = BloodhoundSearchApi(self.env)
Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/web_ui.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/web_ui.py?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/web_ui.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/web_ui.py Mon Feb 11 13:05:18 2013
@@ -18,17 +18,17 @@
# specific language governing permissions and limitations
# under the License.
import unittest
-import tempfile
-import shutil
from urllib import urlencode, unquote
+from bhsearch.api import ASC, DESC
from bhsearch.tests.base import BaseBloodhoundSearchTest
from bhsearch.web_ui import RequestParameters
from bhsearch.whoosh_backend import WhooshBackend
-from trac.test import EnvironmentStub, Mock, MockPerm
+from trac.test import Mock, MockPerm
from trac.ticket import Ticket
+from trac.core import TracError
from trac.util.datefmt import FixedOffset
from trac.util import format_datetime
from trac.web import Href, arg_list_to_args, parse_arg_list, RequestDone
@@ -514,6 +514,31 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
#assert
self.assertEqual("grid", data["active_view"])
+ def test_can_apply_sorting(self):
+ #arrange
+ self.insert_ticket("T1", component="c1", status="new", milestone="A")
+ self.insert_ticket("T2", component="c1", status="new", milestone="B")
+ self.insert_ticket("T3", component="c3", status="new", milestone="C")
+ #act
+ self.req.args[RequestParameters.QUERY] = "*"
+ self.req.args[RequestParameters.SORT] = "component, milestone desc"
+ data = self.process_request()
+ #assert
+ api_sort = data["debug"]["api_parameters"]["sort"]
+ self.assertEqual([("component", ASC), ("milestone", DESC)], api_sort)
+ ids = [item["summary"] for item in data["results"].items]
+ 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)
@@ -539,8 +564,53 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
for i in range(1, n+1):
self.insert_wiki("test %s" % i)
+class RequestParametersTest(unittest.TestCase):
+ def setUp(self):
+ self.req = Mock(
+ perm=MockPerm(),
+ chrome={'logo': {}},
+ href=Href("/main"),
+ base_path=BASE_PATH,
+ args=arg_list_to_args([]),
+ )
+
+ def test_can_parse_multiple_sort_terms(self):
+ self.assertEqual(
+ None,
+ self._evaluate_sort(" "))
+ self.assertEqual(
+ None,
+ self._evaluate_sort(" , , "))
+ self.assertEqual(
+ [("f1", ASC),],
+ self._evaluate_sort(" f1 "))
+ self.assertEqual(
+ [("f1", ASC),],
+ self._evaluate_sort(" f1 asc"))
+ self.assertEqual(
+ [("f1", DESC),],
+ self._evaluate_sort("f1 desc"))
+ self.assertEqual(
+ [("f1", ASC), ("f2", DESC)],
+ self._evaluate_sort("f1, f2 desc"))
+
+ def test_can_raise_error_on_invalid_sort_term(self):
+ self.assertRaises(
+ TracError,
+ self._evaluate_sort,
+ "f1 desc bb")
+
+ def _evaluate_sort(self, sort_condition):
+ self.req.args[RequestParameters.SORT] = sort_condition
+ parameters = RequestParameters(self.req)
+ return parameters.sort
+
+
def suite():
- return unittest.makeSuite(WebUiTestCaseWithWhoosh, 'test')
+ 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/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/whoosh_backend.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/whoosh_backend.py?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/whoosh_backend.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/whoosh_backend.py Mon Feb 11 13:05:18 2013
@@ -27,7 +27,6 @@ from bhsearch.query_parser import Defaul
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
from whoosh.fields import Schema, ID, TEXT, KEYWORD
@@ -37,8 +36,7 @@ from whoosh.qparser import MultifieldPlu
class WhooshBackendTestCase(BaseBloodhoundSearchTest):
def setUp(self):
- self.env = EnvironmentStub(enable=['bhsearch.*'])
- self.env.path = tempfile.mkdtemp('bhsearch-tempenv')
+ super(WhooshBackendTestCase, self).setUp()
self.whoosh_backend = WhooshBackend(self.env)
self.whoosh_backend.recreate_index()
self.parser = DefaultQueryParser(self.env)
@@ -453,8 +451,7 @@ class WhooshFunctionalityTestCase(unitte
class WhooshEmptyFacetErrorWorkaroundTestCase(BaseBloodhoundSearchTest):
def setUp(self):
- self.env = EnvironmentStub(enable=['bhsearch.*'])
- self.env.path = tempfile.mkdtemp('bhsearch-tempenv')
+ super(WhooshEmptyFacetErrorWorkaroundTestCase, self).setUp()
self.whoosh_backend = WhooshBackend(self.env)
self.whoosh_backend.recreate_index()
self.parser = DefaultQueryParser(self.env)
@@ -498,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/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/web_ui.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/web_ui.py?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/web_ui.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/web_ui.py Mon Feb 11 13:05:18 2013
@@ -43,8 +43,6 @@ from trac.wiki.formatter import extract_
SEARCH_PERMISSION = 'SEARCH_VIEW'
DEFAULT_RESULTS_PER_PAGE = 10
-DEFAULT_SORT = [(SCORE, ASC), ("time", DESC)]
-#VIEW_GRID = "grid"
class RequestParameters(object):
"""
@@ -59,6 +57,7 @@ class RequestParameters(object):
PAGELEN = "pagelen"
FILTER_QUERY = "fq"
VIEW = "view"
+ SORT = "sort"
def __init__(self, req):
self.req = req
@@ -76,14 +75,14 @@ class RequestParameters(object):
self.filter_queries = self._remove_possible_duplications(
self.filter_queries)
- #TODO: retrieve sort from query string
- self.sort = DEFAULT_SORT
+ sort_string = req.args.getfirst(self.SORT)
+ self.sort = self._parse_sort(sort_string)
self.pagelen = int(req.args.getfirst(
RequestParameters.PAGELEN,
DEFAULT_RESULTS_PER_PAGE))
self.page = int(req.args.getfirst(self.PAGE, '1'))
- self.type = req.args.getfirst(self.TYPE, None)
+ self.type = req.args.getfirst(self.TYPE)
self.params = {
RequestParameters.FILTER_QUERY: []
@@ -103,6 +102,42 @@ class RequestParameters(object):
self.params[self.TYPE] = self.type
if self.filter_queries:
self.params[RequestParameters.FILTER_QUERY] = self.filter_queries
+ if sort_string:
+ self.params[RequestParameters.SORT] = sort_string
+
+ def _parse_sort(self, sort_string):
+ if not sort_string:
+ return None
+ sort_terms = sort_string.split(",")
+ sort = []
+
+ def parse_sort_order(sort_order):
+ sort_order = sort_order.lower()
+ if sort_order == ASC:
+ return ASC
+ elif sort_order == DESC:
+ return DESC
+ else:
+ raise TracError(
+ "Invalid sort order %s in sort parameter %s" %
+ (sort_order, sort_string))
+
+ for term in sort_terms:
+ term = term.strip()
+ if not term:
+ continue
+ term_parts = term.split()
+ parts_count = len(term_parts)
+ if parts_count == 1:
+ sort.append((term_parts[0], ASC))
+ elif parts_count == 2:
+ sort.append((term_parts[0], parse_sort_order(term_parts[1])))
+ else:
+ raise TracError("Invalid sort term %s " % term)
+
+ return sort if sort else None
+
+
def _remove_possible_duplications(self, parameters_list):
seen = set()
@@ -152,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]
-
implements(INavigationContributor, IPermissionRequestor, IRequestHandler,
ITemplateProvider,
# IWikiSyntaxProvider #todo: implement later
@@ -196,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(
@@ -204,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),
@@ -213,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):
@@ -231,175 +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)
-
- query_system = BloodhoundSearchApi(self.env)
- query_result = query_system.query(
- parameters.query,
- pagenum=parameters.page,
- pagelen=parameters.pagelen,
- sort=parameters.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
- 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)]
+
+ 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,
- parameters,
- data):
- if not parameters.query:
+ 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
@@ -425,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},
@@ -500,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,
@@ -524,81 +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'])
- 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
Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/setup.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/setup.py?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/setup.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/setup.py Mon Feb 11 13:05:18 2013
@@ -23,7 +23,7 @@ try:
except ImportError:
from distutils.core import setup
-DESC = """Search plugin for Apache(TM) Bloodhound
+DESC = """Search plugin for Apache(TM) Bloodhound.
Add free text search and query functionality to Bloodhound sites.
"""
@@ -128,6 +128,9 @@ setup(
name=DIST_NM,
version=latest,
description=DESC.split('\n', 1)[0],
+ author = "Apache Bloodhound",
+ license = "Apache License v2",
+ url = "http://incubator.apache.org/bloodhound/",
requires = ['trac'],
install_requires = [
'setuptools>=0.6b1',
Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/htdocs/bloodhound.css
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/htdocs/bloodhound.css?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/htdocs/bloodhound.css (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/htdocs/bloodhound.css Mon Feb 11 13:05:18 2013
@@ -67,6 +67,10 @@ body {
display: none
}
+.nav-header-selected {
+ color: #b94a48;
+}
+
/* @end */
/* @group Heading anchors */
@@ -538,6 +542,18 @@ input[type="submit"].btn.btn-micro {
}
}
+@media (min-width: 1200px) {
+ .facets {
+ width: 220px;
+ }
+}
+
+@media (min-width: 1200px) {
+ .search_results {
+ width: 920px;
+ }
+}
+
@media (min-width: 979px) and (max-width: 1199px) {
.main-nav ul {
background-color: red;
@@ -553,13 +569,13 @@ input[type="submit"].btn.btn-micro {
#logo, #usermenu, #breadcrumbbar {
float: left;
}
-
+
#searchbox {
float: left;
margin-right: 10px;
}
- .breadcrumb {
+ #mainnav {
margin-top: 7px;
}
@@ -583,12 +599,6 @@ input[type="submit"].btn.btn-micro {
margin-top: 5px;
}
- .ticket .properties div[class*="span"] {
- float: left;
- margin-left: 10px;
- margin-right: 10px;
- }
-
#changelog .change .span2 {
float: left;
}
@@ -609,7 +619,7 @@ input[type="submit"].btn.btn-micro {
.container:first-child {
border-left: 0px solid white;
border-right: 0px solid white;
- padding: 0px;
+ padding: 0px 4px;
}
header, #belowStatus, #mobile-activity,
Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin.html
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin.html?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin.html (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin.html Mon Feb 11 13:05:18 2013
@@ -25,18 +25,17 @@
xmlns:py="http://genshi.edgewall.org/"
py:strip="">
- <py:def function="admin_nav_list()">
- <div class="well">
- <ul class="nav nav-list">
+ <py:def function="admin_nav_list(phone)">
+ <div class="${'btn-group' if phone else 'well'}">
+ <py:if test="phone">
+ <a class="btn btn-large dropdown-toggle" data-toggle="dropdown" href="#">Select Module <span class="caret"></span></a>
+ </py:if>
+
+ <ul class="nav nav-list${' dropdown-menu' if phone else None}">
<py:for each="category, panels in groupby(panels, lambda p: p.category)"
py:with="cat_is_active = category.id == active_cat">
- <li class="nav-header">
- <py:choose test="">
- <span py:when="cat_is_active" class="label label-important">
- ${category.label}
- </span>
- <py:otherwise>${category.label}</py:otherwise>
- </py:choose>
+ <li class="nav-header${' nav-header-selected' if cat_is_active else None}">
+ ${category.label}
</li>
<li py:for="panel in panels" py:with="panel = panel.panel;
pan_is_active = cat_is_active and panel.id == active_panel"
@@ -49,10 +48,16 @@
</py:def>
<py:def function="admin_function(fix_legacy)">
<div id="content" class="admin row">
- <h1>Administration</h1>
- <div id="tabs" class="span3">
- ${admin_nav_list()}
+ <h1 class="span12">Administration</h1>
+
+ <div id="tabs" class="span3 hidden-phone">
+ ${admin_nav_list(False)}
</div>
+
+ <div class="span9 visible-phone">
+ ${admin_nav_list(True)}
+ </div>
+
<div id="tabcontent" class="span9">
<py:choose>
<py:when test="fix_legacy">
Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin_milestones.html
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin_milestones.html?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin_milestones.html (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin_milestones.html Mon Feb 11 13:05:18 2013
@@ -36,7 +36,7 @@
</head>
<body>
- <h2>Manage Milestones</h2>
+ <h2>Milestone Management</h2>
<py:choose test="view">
<form py:when="'detail'" class="well form-horizontal" method="post"
@@ -107,11 +107,51 @@
</form>
<py:otherwise>
+ <div py:if="'MILESTONE_CREATE' in req.perm" class="row">
+ <div class="span9">
+ <form class="well form-horizontal" id="addmilestone" method="post" action="">
+ <fieldset>
+ <legend>Add Milestone</legend>
+ <div class="control-group">
+ <label class="control-label" for="name">Milestone Name</label>
+ <div class="controls">
+ <input type="text" name="name" id="name" class="input-xlarge" />
+ </div>
+ </div>
+
+ <div class="control-group">
+ <label class="control-label" for="duedate">Due Date</label>
+ <div class="controls">
+ <input type="text" id="duedate"
+ name="duedate" size="${len(datetime_hint)}"
+ class="input-xlarge"
+ placeholder="${_('Format: %(datehint)s', datehint=datetime_hint)}" />
+ </div>
+ </div>
+
+ <div class="control-group">
+ <div class="controls">
+ <input class="btn" type="submit" name="add" value="${_('Add')}" />
+ </div>
+
+ <p class="help-block">
+ <span class="label label-info">Hint</span>
+ <i18n:msg params="datehint">Format: $datetime_hint</i18n:msg>
+ </p>
+ </div>
+ </fieldset>
+ </form>
+ </div>
+ </div>
+
<div class="row">
- <div class="${'MILESTONE_CREATE' in req.perm and 'span6' or 'span9'}">
+ <div class="span9">
<py:choose>
- <form id="milestone_table" method="post" action=""
+ <form id="milestone_table" method="post" action="" class="well"
py:when="milestones" py:with="can_remove = 'MILESTONE_DELETE' in req.perm">
+
+ <legend>Modify Milestones</legend>
+
<table id="millist"
class="table table-bordered table-striped table-condensed">
@@ -161,28 +201,6 @@
</p>
</py:choose>
</div>
- <div class="span3" py:if="'MILESTONE_CREATE' in req.perm">
- <form class="well" id="addmilestone" method="post" action="">
- <fieldset>
- <legend>Add Milestone:</legend>
- <label for="name">Name:</label>
- <input class="input-medium" type="text" name="name"
- id="name" size="22" />
- <label for="duedate">Due:</label>
- <input class="input-medium" type="text" id="duedate"
- name="duedate" size="${len(datetime_hint)}"
- placeholder="${_('Format: %(datehint)s', datehint=datetime_hint)}" />
- <p class="help-block">
- <span class="label label-info">Hint</span>
- <i18n:msg params="datehint">Format: $datetime_hint</i18n:msg>
- </p>
- <div class="control-group">
- <input class="btn" type="submit" name="add"
- value="${_('Add')}" />
- </div>
- </fieldset>
- </form>
- </div>
</div>
</py:otherwise>
</py:choose>
Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin_plugins.html
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin_plugins.html?rev=1444754&r1=1444753&r2=1444754&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin_plugins.html (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_admin_plugins.html Mon Feb 11 13:05:18 2013
@@ -29,6 +29,33 @@
<title>Plugins</title>
<script type="text/javascript"
src="${href.chrome('dashboard/js/bootstrap-collapse.js')}"></script>
+ <script type="text/javascript">/*<![CDATA[*/
+ jQuery(document).ready(function($) {
+ // Sets state of group toggler when component checkboxes are clicked
+ function setGroupTogglerState() {
+ var table = $(this).closest("table.table");
+ var checkboxes = $("td.sel input:checkbox:enabled", table);
+ var numSelected = checkboxes.filter(":checked").length;
+ var noneSelected = numSelected === 0;
+ var allSelected = numSelected === checkboxes.length;
+ $("th.sel input:checkbox", table)
+ .prop({"checked": allSelected,
+ "indeterminate": !(noneSelected || allSelected)});
+ }
+ // Add group event behavior for controlling state of toggler
+ $("table.table td.sel input:checkbox:enabled")
+ .click(setGroupTogglerState);
+ // Add click behavior for the group toggler and initialize its state
+ $("table.table th.sel").each(function() {
+ $(this).attr("title", $(this).text())
+ }).html('<input type="checkbox" />')
+ .children()
+ .click(function() {
+ $("td.sel input:checkbox:enabled",
+ $(this).closest("table.table")).prop("checked", this.checked);
+ }).each(setGroupTogglerState);
+ });
+ /*]]>*/</script>
</head>
<body>