You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bloodhound.apache.org by ah...@apache.org on 2013/08/12 20:05:02 UTC

svn commit: r1513212 - in /bloodhound/branches/bep_0007_embeddable_objects: ./ bloodhound_dashboard/bhdashboard/layouts/templates/ bloodhound_dashboard/bhdashboard/widgets/ bloodhound_multiproduct/ bloodhound_multiproduct/multiproduct/ bloodhound_multi...

Author: ahorincar
Date: Mon Aug 12 18:05:01 2013
New Revision: 1513212

URL: http://svn.apache.org/r1513212
Log:
Merged trunk to bep_0007_embeddable_objects branch

Added:
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/batch.py
      - copied unchanged from r1513209, bloodhound/trunk/bloodhound_multiproduct/multiproduct/ticket/batch.py
Modified:
    bloodhound/branches/bep_0007_embeddable_objects/   (props changed)
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/layouts/templates/bs_btnbar_full.html
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/layouts/templates/bs_grid_full.html
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/widgets/query.py
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/widgets/ticket.py
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/env.py
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/hooks.py
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/query.py
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/setup.py
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/tests/env.py
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/tests/product-query-link-tests.txt
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/tests/wikisyntax.py
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_relations/bhrelations/templates/manage.html
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_relations/bhrelations/tests/base.py
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_relations/bhrelations/web_ui.py
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/htdocs/bloodhound.css
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_admin_plugins.html
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_milestone_view.html
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_ticket.html
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_ticket_box.html
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_ticket_change.html
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_wiki_view.html
    bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/theme.py
    bloodhound/branches/bep_0007_embeddable_objects/installer/bloodhound_setup.py
    bloodhound/branches/bep_0007_embeddable_objects/installer/requirements-dev.txt
    bloodhound/branches/bep_0007_embeddable_objects/installer/requirements.txt
    bloodhound/branches/bep_0007_embeddable_objects/trac/   (props changed)
    bloodhound/branches/bep_0007_embeddable_objects/trac/trac/ticket/tests/query.py

Propchange: bloodhound/branches/bep_0007_embeddable_objects/
------------------------------------------------------------------------------
  Merged /bloodhound/trunk:r1507815-1513209

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/layouts/templates/bs_btnbar_full.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/layouts/templates/bs_btnbar_full.html?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/layouts/templates/bs_btnbar_full.html (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/layouts/templates/bs_btnbar_full.html Mon Aug 12 18:05:01 2013
@@ -32,7 +32,7 @@
     <xi:include href="bs_btnbar.html" />
     <div id="ft" class="row">
       <div id="help" class="span8">
-        <strong>Note:</strong> See 
+        <span class="label label-info">Note:</span> See
         <a href="${href.wiki('BloodhoundDashboard')}">BloodhoundDashboard</a>
         for help on using the dashboard.
       </div>

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/layouts/templates/bs_grid_full.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/layouts/templates/bs_grid_full.html?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/layouts/templates/bs_grid_full.html (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/layouts/templates/bs_grid_full.html Mon Aug 12 18:05:01 2013
@@ -83,7 +83,7 @@
     <xi:include href="bs_grid.html" />
     <div id="ft" class="row">
       <div id="help" class="span8">
-        <strong>Note:</strong> See
+        <span class="label label-info">Note:</span> See
         <a href="${href.wiki('BloodhoundDashboard')}">BloodhoundDashboard</a>
         for help on using the dashboard.
       </div>

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/widgets/query.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/widgets/query.py?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/widgets/query.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/widgets/query.py Mon Aug 12 18:05:01 2013
@@ -42,7 +42,6 @@ from bhdashboard.util import WidgetBase,
                               trac_tags
 
 from multiproduct.env import ProductEnvironment
-from multiproduct.ticket.query import ProductQueryModule
 
 class TicketQueryWidget(WidgetBase):
     """Display tickets matching a TracQuery using a grid
@@ -94,12 +93,11 @@ class TicketQueryWidget(WidgetBase):
             more_link_href = req.href('query', args)
             args.update({'page' : page, 'max': maxrows})
 
-            qrymdl = self.env[QueryModule
-                if isinstance(self.env, ProductEnvironment)
-                else ProductQueryModule]
+            qrymdl = self.env[QueryModule]
             if qrymdl is None :
                 raise TracError('Query module not available (disabled?)')
-            data = qrymdl.process_request(fakereq)[1]
+
+            data = qrymdl.process_request(fakereq, self.env)[1]
         except TracError, exc:
             if data is not None:
                 exc.title = data.get('title', 'TracQuery')

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/widgets/ticket.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/widgets/ticket.py?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/widgets/ticket.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_dashboard/bhdashboard/widgets/ticket.py Mon Aug 12 18:05:01 2013
@@ -34,6 +34,7 @@ from trac.ticket.api import TicketSystem
 from trac.ticket.query import Query
 from trac.ticket.roadmap import apply_ticket_permissions, get_ticket_stats, \
                             ITicketGroupStatsProvider, RoadmapModule
+from trac.util.text import unicode_urlencode
 from trac.util.translation import _
 from trac.web.chrome import add_stylesheet
 
@@ -217,7 +218,7 @@ class TicketFieldValuesWidget(WidgetBase
 
             query_href = query.get_href(req.href)
             item_link= lambda item: query_href + \
-                    '&' + urlencode([(fieldnm, item[0])])
+                    '&' + unicode_urlencode([(fieldnm, item[0])])
 
         if fieldnm in self.DASH_ITEM_HREF_MAP:
             def dash_item_link(item):

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/env.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/env.py?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/env.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/env.py Mon Aug 12 18:05:01 2013
@@ -1,3 +1,5 @@
+# -*- coding: UTF-8 -*-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -47,10 +49,12 @@ class ComponentEnvironmentContext(object
     def __init__(self, env, component):
         self._env = env
         self._component = component
+
     def __enter__(self):
         self._old_env = self._component.env
         self._env.component_activated(self._component)
         return self
+
     def __exit__(self, type, value, traceback):
         self._old_env.component_activated(self._component)
 
@@ -262,6 +266,7 @@ class Environment(trac.env.Environment):
 # replace trac.env.Environment with Environment
 trac.env.Environment = Environment
 
+
 # this must follow the monkey patch (trac.env.Environment) above, otherwise
 # trac.test.EnvironmentStub will not be correct as the class will derive from
 # not replaced trac.env.Environment
@@ -333,6 +338,7 @@ class EnvironmentStub(trac.test.Environm
 # replace trac.test.EnvironmentStub
 trac.test.EnvironmentStub = EnvironmentStub
 
+
 class ProductEnvironment(Component, ComponentManager):
     """Bloodhound product-aware environment manager.
 
@@ -342,7 +348,7 @@ class ProductEnvironment(Component, Comp
 
     Product environments contain among other things:
 
-    * configuration key-value pairs stored in the database, 
+    * configuration key-value pairs stored in the database,
     * product-aware clones of the wiki and ticket attachments files,
 
     Product environments do not have:
@@ -536,7 +542,6 @@ class ProductEnvironment(Component, Comp
         self.parent = env
         self.product = product
         self.systeminfo = []
-        self._href = self._abs_href = None
 
         self.setup_config()
 
@@ -852,44 +857,45 @@ class ProductEnvironment(Component, Comp
     @lazy
     def href(self):
         """The application root path"""
-        if not self._href:
-            self._href = Href(urlsplit(self.abs_href.base)[2])
-        return self._href
+        return Href(urlsplit(self.abs_href.base).path)
 
     @lazy
     def abs_href(self):
         """The application URL"""
-        if not self._abs_href:
-            if not self.base_url:
-                urlpattern = MultiProductSystem(self.parent).product_base_url
-                if not urlpattern:
-                    self.log.warn("product_base_url option not set in "
-                                  "configuration, generated links may be "
-                                  "incorrect")
-                    urlpattern = 'products/$(prefix)s'
-                envname = os.path.basename(self.parent.path)
-                prefix = unicode_quote(self.product.prefix, safe="")
-                name = unicode_quote(self.product.name, safe="")
-                url = urlpattern.replace('$(', '%(') \
-                                .replace('%(envname)s', envname) \
-                                .replace('%(prefix)s', prefix) \
-                                .replace('%(name)s', name)
-                if urlsplit(url).netloc:
-                    #  Absolute URLs
-                    self._abs_href = Href(url)
-                else:
-                    # Relative URLs
-                    parent_href = Href(self.parent.abs_href(),
-                                       path_safe="/!~*'()%",
-                                       query_safe="!~*'()%")
-                    self._abs_href = Href(parent_href(url))
+        if not self.base_url:
+            urlpattern = MultiProductSystem(self.parent).product_base_url
+            if not urlpattern:
+                self.log.warn("product_base_url option not set in "
+                              "configuration, generated links may be "
+                              "incorrect")
+                urlpattern = 'products/$(prefix)s'
+            envname = os.path.basename(self.parent.path)
+            prefix = unicode_quote(self.product.prefix, safe="")
+            name = unicode_quote(self.product.name, safe="")
+            url = urlpattern.replace('$(', '%(') \
+                            .replace('%(envname)s', envname) \
+                            .replace('%(prefix)s', prefix) \
+                            .replace('%(name)s', name)
+            if urlsplit(url).netloc:
+                #  Absolute URLs
+                _abs_href = Href(url)
             else:
-                self._abs_href = Href(self.base_url)
-        return self._abs_href
+                # Relative URLs
+                parent_href = Href(self.parent.abs_href(),
+                                   path_safe="/!~*'()%",
+                                   query_safe="!~*'()%")
+                _abs_href = Href(parent_href(url))
+        else:
+            _abs_href = Href(self.base_url)
+        return _abs_href
 
     # Multi-product API extensions
 
     @classmethod
+    def lookup_global_env(cls, env):
+        return env.parent if isinstance(env, ProductEnvironment) else env
+
+    @classmethod
     def lookup_env(cls, env, prefix=None, name=None):
         """Instantiate environment according to product prefix or name
 

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/hooks.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/hooks.py?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/hooks.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/hooks.py Mon Aug 12 18:05:01 2013
@@ -20,6 +20,8 @@
 import multiproduct.env
 import multiproduct.dbcursor
 import multiproduct.versioncontrol
+import multiproduct.ticket.query
+import multiproduct.ticket.batch
 
 import re
 
@@ -31,13 +33,15 @@ from trac.web.main import RequestWithSes
 
 PRODUCT_RE = re.compile(r'^/products(?:/(?P<pid>[^/]*)(?P<pathinfo>.*))?')
 REDIRECT_DEFAULT_RE = \
-    re.compile(r'^/(?P<section>milestone|roadmap|query|report|newticket|'
+    re.compile(r'^/(?P<section>milestone|roadmap|report|newticket|'
                r'ticket|qct|timeline|diff|batchmodify|search|'
                r'(raw-|zip-)?attachment/(ticket|milestone))(?P<pathinfo>.*)')
 
 
 class MultiProductEnvironmentFactory(EnvironmentFactoryBase):
     def open_environment(self, environ, env_path, global_env, use_cache=False):
+        environ.setdefault('SCRIPT_NAME', '')  # bh:ticket:594
+
         env = pid = None
         path_info = environ.get('PATH_INFO')
         if not path_info:

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/query.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/query.py?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/query.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/query.py Mon Aug 12 18:05:01 2013
@@ -20,6 +20,8 @@
 
 from __future__ import with_statement
 
+import re
+
 from itertools import groupby
 from math import ceil
 from datetime import datetime, timedelta
@@ -52,6 +54,9 @@ class ProductQuery(Query):
     """
     
     def _count(self, sql, args):
+        if isinstance(self.env, ProductEnvironment):
+            return super(ProductQuery, self)._count(sql, args)
+
         cnt = self.env.db_direct_query("SELECT COUNT(*) FROM (%s) AS x"
                                 % sql, args)[0][0]
         # "AS x" is needed for MySQL ("Subqueries in the FROM Clause")
@@ -79,6 +84,10 @@ class ProductQuery(Query):
         :since 1.0: the `db` parameter is no longer needed and will be removed
         in version 1.1.1
         """
+        if isinstance(self.env, ProductEnvironment):
+            return super(ProductQuery, self).execute(req, db, cached_ids, authname,
+                tzinfo, href, locale)
+
         if req is not None:
             href = req.href
         with self.env.db_direct_query as db:
@@ -142,184 +151,68 @@ class ProductQuery(Query):
             cursor.close()
             return results
 
+import trac.ticket.query
+trac.ticket.query.Query = ProductQuery
+trac.ticket.Query = ProductQuery
 
-class ProductQueryModule(QueryModule):
-    def process_request(self, req):
-        req.perm.assert_permission('TICKET_VIEW')
-
-        constraints = self._get_constraints(req)
-        args = req.args
-        if not constraints and not 'order' in req.args:
-            # If no constraints are given in the URL, use the default ones.
-            if req.authname and req.authname != 'anonymous':
-                qstring = self.default_query
-                user = req.authname
-            else:
-                email = req.session.get('email')
-                name = req.session.get('name')
-                qstring = self.default_anonymous_query
-                user = email or name or None
-
-            self.log.debug('QueryModule: Using default query: %s', str(qstring))
-            if qstring.startswith('?'):
-                arg_list = parse_arg_list(qstring[1:])
-                args = arg_list_to_args(arg_list)
-                constraints = self._get_constraints(arg_list=arg_list)
-            else:
-                query = ProductQuery.from_string(self.env, qstring)
-                args = {'order': query.order, 'group': query.group,
-                        'col': query.cols, 'max': query.max}
-                if query.desc:
-                    args['desc'] = '1'
-                if query.groupdesc:
-                    args['groupdesc'] = '1'
-                constraints = query.constraints
-
-            # Substitute $USER, or ensure no field constraints that depend
-            # on $USER are used if we have no username.
-            for clause in constraints:
-                for field, vals in clause.items():
-                    for (i, val) in enumerate(vals):
-                        if user:
-                            vals[i] = val.replace('$USER', user)
-                        elif val.endswith('$USER'):
-                            del clause[field]
-                            break
-
-        cols = args.get('col')
-        if isinstance(cols, basestring):
-            cols = [cols]
-        # Since we don't show 'id' as an option to the user,
-        # we need to re-insert it here.
-        if cols and 'id' not in cols:
-            cols.insert(0, 'id')
-        rows = args.get('row', [])
-        if isinstance(rows, basestring):
-            rows = [rows]
-        format = req.args.get('format')
-        max = args.get('max')
-        if max is None and format in ('csv', 'tab'):
-            max = 0 # unlimited unless specified explicitly
-        query = ProductQuery(self.env, req.args.get('report'),
-                      constraints, cols, args.get('order'),
-                      'desc' in args, args.get('group'),
-                      'groupdesc' in args, 'verbose' in args,
-                      rows,
-                      args.get('page'),
-                      max)
-
-        if 'update' in req.args:
-            # Reset session vars
-            for var in ('query_constraints', 'query_time', 'query_tickets'):
-                if var in req.session:
-                    del req.session[var]
-            req.redirect(query.get_href(req.href))
-
-        # Add registered converters
-        for conversion in Mimeview(self.env).get_supported_conversions(
-                                             'trac.ticket.Query'):
-            add_link(req, 'alternate',
-                     query.get_href(req.href, format=conversion[0]),
-                     conversion[1], conversion[4], conversion[0])
-
-        if format:
-            filename = 'query' if format != 'rss' else None
-            Mimeview(self.env).send_converted(req, 'trac.ticket.Query', query,
-                                              format, filename=filename)
-
-        return self.display_html(req, query)
-
-    def display_html(self, req, query):
-        # The most recent query is stored in the user session;
-        orig_list = None
-        orig_time = datetime.now(utc)
-        query_time = int(req.session.get('query_time', 0))
-        query_time = datetime.fromtimestamp(query_time, utc)
-        query_constraints = unicode(query.constraints)
-        try:
-            if query_constraints != req.session.get('query_constraints') \
-                    or query_time < orig_time - timedelta(hours=1):
-                tickets = query.execute(req)
-                # New or outdated query, (re-)initialize session vars
-                req.session['query_constraints'] = query_constraints
-                req.session['query_tickets'] = ' '.join([str(t['id'])
-                                                         for t in tickets])
-            else:
-                orig_list = [int(id) for id
-                             in req.session.get('query_tickets', '').split()]
-                tickets = query.execute(req, cached_ids=orig_list)
-                orig_time = query_time
-        except QueryValueError, e:
-            tickets = []
-            for error in e.errors:
-                add_warning(req, error)
-
-        context = web_context(req, 'query')
-        owner_field = [f for f in query.fields if f['name'] == 'owner']
-        if owner_field:
-            TicketSystem(self.env).eventually_restrict_owner(owner_field[0])
-        data = query.template_data(context, tickets, orig_list, orig_time, req)
-
-        req.session['query_href'] = query.get_href(context.href)
-        req.session['query_time'] = to_timestamp(orig_time)
-        req.session['query_tickets'] = ' '.join([str(t['id'])
-                                                 for t in tickets])
-        title = _('Custom Query')
-
-        # Only interact with the report module if it is actually enabled.
-        #
-        # Note that with saved custom queries, there will be some convergence
-        # between the report module and the query module.
-        from trac.ticket.report import ReportModule
-        if 'REPORT_VIEW' in req.perm and \
-               self.env.is_component_enabled(ReportModule):
-            data['report_href'] = req.href.report()
-            add_ctxtnav(req, _('Available Reports'), req.href.report())
-            add_ctxtnav(req, _('Custom Query'), req.href.query())
-            if query.id:
-                for title, description in self.env.db_query("""
-                        SELECT title, description FROM report WHERE id=%s
-                        """, (query.id,)):
-                    data['report_resource'] = Resource('report', query.id)
-                    data['description'] = description
-        else:
-            data['report_href'] = None
 
-        # Only interact with the batch modify module it it is enabled
-        # TODO: fix this for multiproduct
-        """
-        from trac.ticket.batch import BatchModifyModule
-        if 'TICKET_BATCH_MODIFY' in req.perm and \
-                self.env.is_component_enabled(BatchModifyModule):
-            self.env[BatchModifyModule].add_template_data(req, data, tickets)
-        """
-
-        data.setdefault('report', None)
-        data.setdefault('description', None)
-        data['title'] = title
-
-        data['all_columns'] = query.get_all_columns()
-        # Don't allow the user to remove the id column
-        data['all_columns'].remove('id')
-        data['all_textareas'] = query.get_all_textareas()
-
-        properties = dict((name, dict((key, field[key])
-                                      for key in ('type', 'label', 'options',
-                                                  'optgroups')
-                                      if key in field))
-                          for name, field in data['fields'].iteritems())
-        add_script_data(req, properties=properties, modes=data['modes'])
-
-        add_stylesheet(req, 'common/css/report.css')
-        Chrome(self.env).add_jquery_ui(req)
-        add_script(req, 'common/js/query.js')
+class ProductQueryModule(QueryModule):
+    def process_request(self, req, env=None):
+        tmpenv = self.env
+        if isinstance(self.env, ProductEnvironment) and env is not None:
+            self.env = env
+        result = super(ProductQueryModule, self).process_request(req)
+        self.env = tmpenv
+        return result
 
-        return 'query.html', data, None
+trac.ticket.query.QueryModule = ProductQueryModule
+trac.ticket.QueryModule = ProductQueryModule
 
 
 class ProductTicketQueryMacro(TicketQueryMacro):
     """TracQuery macro retrieving results across product boundaries. 
     """
+    @staticmethod
+    def parse_args(content):
+        """Parse macro arguments and translate them to a query string."""
+        clauses = [{}]
+        argv = []
+        kwargs = {}
+        for arg in TicketQueryMacro._comma_splitter.split(content):
+            arg = arg.replace(r'\,', ',')
+            m = re.match(r'\s*[^=]+=', arg)
+            if m:
+                kw = arg[:m.end() - 1].strip()
+                value = arg[m.end():]
+                if kw in ('order', 'max', 'format', 'col', 'product'):
+                    kwargs[kw] = value
+                else:
+                    clauses[-1][kw] = value
+            elif arg.strip() == 'or':
+                clauses.append({})
+            else:
+                argv.append(arg)
+        clauses = filter(None, clauses)
+
+        if len(argv) > 0 and not 'format' in kwargs: # 0.10 compatibility hack
+            kwargs['format'] = argv[0]
+        if 'order' not in kwargs:
+            kwargs['order'] = 'id'
+        if 'max' not in kwargs:
+            kwargs['max'] = '0' # unlimited by default
+
+        format = kwargs.pop('format', 'list').strip().lower()
+        if format in ('list', 'compact'): # we need 'status' and 'summary'
+            if 'col' in kwargs:
+                kwargs['col'] = 'status|summary|' + kwargs['col']
+            else:
+                kwargs['col'] = 'status|summary'
+
+        query_string = '&or&'.join('&'.join('%s=%s' % item
+                                            for item in clause.iteritems())
+                                   for clause in clauses)
+        return query_string, kwargs, format
+
     def expand_macro(self, formatter, name, content):
         req = formatter.req
         query_string, kwargs, format = self.parse_args(content)
@@ -327,7 +220,9 @@ class ProductTicketQueryMacro(TicketQuer
             query_string += '&'
         query_string += '&'.join('%s=%s' % item
                                  for item in kwargs.iteritems())
-        query = ProductQuery.from_string(self.env, query_string)
+
+        env = ProductEnvironment.lookup_global_env(self.env)
+        query = ProductQuery.from_string(env, query_string)
 
         if format == 'count':
             cnt = query.count(req)
@@ -342,7 +237,7 @@ class ProductTicketQueryMacro(TicketQuer
 
             add_stylesheet(req, 'common/css/report.css')
 
-            return Chrome(self.env).render_template(
+            return Chrome(env).render_template(
                 req, 'query_results.html', data, None, fragment=True)
 
         if format == 'progress':
@@ -354,7 +249,7 @@ class ProductTicketQueryMacro(TicketQuer
             add_stylesheet(req, 'common/css/roadmap.css')
 
             def query_href(extra_args, group_value = None):
-                q = Query.from_string(self.env, query_string)
+                q = ProductQuery.from_string(env, query_string)
                 if q.group:
                     extra_args[q.group] = group_value
                     q.group = None
@@ -363,9 +258,9 @@ class ProductTicketQueryMacro(TicketQuer
                 if not q.constraints:
                     q.constraints.append(extra_args)
                 return q.get_href(formatter.context)
-            chrome = Chrome(self.env)
-            tickets = apply_ticket_permissions(self.env, req, tickets)
-            stats_provider = RoadmapModule(self.env).stats_provider
+            chrome = Chrome(env)
+            tickets = apply_ticket_permissions(env, req, tickets)
+            stats_provider = RoadmapModule(env).stats_provider
             by = query.group
             if not by:
                 stat = get_ticket_stats(stats_provider, tickets)
@@ -393,7 +288,7 @@ class ProductTicketQueryMacro(TicketQuer
                     'legend': False,
                 }
 
-            groups = grouped_stats_data(self.env, stats_provider, tickets, by,
+            groups = grouped_stats_data(env, stats_provider, tickets, by,
                                         per_group_stats_data)
             data = {
                 'groups': groups, 'grouped_by': by,
@@ -438,7 +333,7 @@ class ProductTicketQueryMacro(TicketQuer
         def ticket_groups():
             groups = []
             for v, g in groupby(tickets, lambda t: t[query.group]):
-                q = Query.from_string(self.env, query_string)
+                q = ProductQuery.from_string(env, query_string)
                 # produce the hint for the group
                 q.group = q.groupdesc = None
                 order = q.order

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/setup.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/setup.py?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/setup.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/setup.py Mon Aug 12 18:05:01 2013
@@ -17,10 +17,22 @@
 #  under the License.
 
 """setup for multi product plugin"""
-import sys
+import sys, codecs
 from pkg_resources import parse_version
 from setuptools import setup
 
+
+# Force UTF-8 for stdout/err if --utf8 option is specified.
+# For some reason python doesn't obey LANG/LC_CTYPE settings
+# if output is not a terminal (e.g. pipes don't work).
+if __name__ == '__main__':
+    for ac,av in enumerate(sys.argv):
+        if av == '--utf8':
+            sys.stdout = codecs.getwriter('utf8')(sys.stdout)
+            sys.stderr = codecs.getwriter('utf8')(sys.stderr)
+            del sys.argv[ac]
+            break
+
 setup(
     name = 'BloodhoundMultiProduct',
     version = '0.7.0',

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/tests/env.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/tests/env.py?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/tests/env.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/tests/env.py Mon Aug 12 18:05:01 2013
@@ -486,6 +486,18 @@ class ProductEnvApiTestCase(Multiproduct
         # Unregister class C as its fake module might break something else.
         ComponentMeta._components.remove(C)
 
+    def test_href_is_lazy(self):
+        href = self.product_env.href
+        self.assertIs(href, self.product_env.href)
+
+    def test_abs_href_is_lazy(self):
+        abs_href = self.product_env.abs_href
+        self.assertIs(abs_href, self.product_env.abs_href)
+
+    def test_path_is_lazy(self):
+        path = self.product_env.path
+        self.assertIs(path, self.product_env.path)
+
     def test_path(self):
         """Testing env.path"""
         self.assertEqual(self.product_env.path,
@@ -582,12 +594,15 @@ class ProductEnvHrefTestCase(Multiproduc
     def test_href_subdomain(self):
         """Test product sub domain base URL
         """
+        self.assertEqual('/', self.product_env.href())
         self.assertEqual('http://tp1.domain.tld', self.product_env.abs_href())
 
     @product_base_url('/path/to/bloodhound/$(prefix)s')
     def test_href_sibling_paths(self):
         """Test product base URL at sibling paths
         """
+        self.assertEqual('/trac.cgi/path/to/bloodhound/tp1',
+                         self.product_env.href())
         self.assertEqual('http://globalenv.com/trac.cgi/path/to/bloodhound/tp1',
                          self.product_env.abs_href())
 
@@ -595,6 +610,7 @@ class ProductEnvHrefTestCase(Multiproduc
     def test_href_inherit_sibling_paths(self):
         """Test product base URL at sibling paths inheriting configuration.
         """
+        self.assertEqual('/trac.cgi/env/tp1', self.product_env.href())
         self.assertEqual('http://globalenv.com/trac.cgi/env/tp1',
                          self.product_env.abs_href())
 
@@ -602,6 +618,7 @@ class ProductEnvHrefTestCase(Multiproduc
     def test_href_default(self):
         """Test product base URL is to a default
         """
+        self.assertEqual('/trac.cgi/products/tp1', self.product_env.href())
         self.assertEqual('http://globalenv.com/trac.cgi/products/tp1',
                          self.product_env.abs_href())
 
@@ -609,6 +626,7 @@ class ProductEnvHrefTestCase(Multiproduc
     def test_href_embed(self):
         """Test default product base URL /products/prefix
         """
+        self.assertEqual('/trac.cgi/products/tp1', self.product_env.href())
         self.assertEqual('http://globalenv.com/trac.cgi/products/tp1',
                          self.product_env.abs_href())
 
@@ -616,6 +634,7 @@ class ProductEnvHrefTestCase(Multiproduc
     def test_href_complex(self):
         """Test complex product base URL
         """
+        self.assertEqual('/bh/tp1', self.product_env.href())
         self.assertEqual('http://env.tld/bh/tp1', self.product_env.abs_href())
 
     @product_base_url('http://$(prefix)s.$(envname)s.tld/')
@@ -626,10 +645,12 @@ class ProductEnvHrefTestCase(Multiproduc
         """
         # Global URLs
         self.assertEqual('http://globalenv.com/trac.cgi', self.env.base_url)
+        self.assertEqual('/trac.cgi', self.env.href())
         self.assertEqual('http://globalenv.com/trac.cgi', self.env.abs_href())
 
         # Product URLs
         self.assertEqual('', self.product_env.base_url)
+        self.assertEqual('/', self.product_env.href())
         self.assertEqual('http://tp1.env.tld', self.product_env.abs_href())
 
     @product_base_url('http://$(prefix)s.$(envname)s.tld/')
@@ -643,6 +664,7 @@ class ProductEnvHrefTestCase(Multiproduc
         self.product_env.config.save()
 
         self.assertEqual('http://productenv.com', self.product_env.base_url)
+        self.assertEqual('/', self.product_env.href())
         self.assertEqual('http://productenv.com', self.product_env.abs_href())
 
     @product_base_url('http://$(prefix)s.$(envname)s.tld/')
@@ -656,6 +678,7 @@ class ProductEnvHrefTestCase(Multiproduc
         self.product_env.config.save()
 
         self.assertEqual('', self.product_env.base_url)
+        self.assertEqual('/', self.product_env.href())
         self.assertEqual('http://tp1.env.tld', self.product_env.abs_href())
 
     product_base_url = staticmethod(product_base_url)

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/tests/product-query-link-tests.txt
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/tests/product-query-link-tests.txt?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/tests/product-query-link-tests.txt (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/tests/product-query-link-tests.txt Mon Aug 12 18:05:01 2013
@@ -52,7 +52,7 @@
 ============================== %(tc_title_prefix)s - ProductTicketQuery macro: no results, list form
 Reopened tickets: [[ProductTicketQuery(status=reopened)]]
 
-Reopened tickets: [[ProductTicketQuery(status=reopened, product=%(setup_product_name)s)]]
+Reopened tickets: [[ProductTicketQuery(status=reopened, product=%(setup_product)s)]]
 ------------------------------
 <p>
 Reopened tickets: <span class="query_no_results">No results</span>
@@ -64,19 +64,19 @@ Reopened tickets: <span class="query_no_
 ============================== %(tc_title_prefix)s - ProductTicketQuery macro: no results, count 0
 Reopened tickets: [[ProductTicketQuery(status=reopened, format=count)]]
 
-Reopened tickets: [[ProductTicketQuery(status=reopened, format=count, product=%(setup_product_name)s)]]
+Reopened tickets: [[ProductTicketQuery(status=reopened, format=count, product=%(setup_product)s)]]
 ------------------------------
 <p>
 Reopened tickets: <span class="query_count" title="0 tickets for which status=reopened&amp;max=0&amp;order=id">0</span>
 </p>
 <p>
-Reopened tickets: <span class="query_count" title="0 tickets for which status=reopened&amp;product=%(setup_product_name)s&amp;max=0&amp;order=id">0</span>
+Reopened tickets: <span class="query_count" title="0 tickets for which status=reopened&amp;max=0&amp;product=%(setup_product)s&amp;order=id">0</span>
 </p>
 ------------------------------
 ============================== %(tc_title_prefix)s - ProductTicketQuery macro: no results, compact form
 Reopened tickets: [[ProductTicketQuery(status=reopened, format=compact)]]
 
-Reopened tickets: [[ProductTicketQuery(status=reopened, format=compact, product=%(setup_product_name)s)]]
+Reopened tickets: [[ProductTicketQuery(status=reopened, format=compact, product=%(setup_product)s)]]
 ------------------------------
 <p>
 Reopened tickets: <span class="query_no_results">No results</span>
@@ -88,7 +88,7 @@ Reopened tickets: <span class="query_no_
 ============================== %(tc_title_prefix)s - ProductTicketQuery macro: one result, list form
 New tickets: [[ProductTicketQuery(status=new)]]
 
-New tickets: [[ProductTicketQuery(status=new, product=%(setup_product_name)s)]]
+New tickets: [[ProductTicketQuery(status=new, product=%(setup_product)s)]]
 ------------------------------
 <p>
 New tickets: </p><div><dl class="wiki compact"><dt><a class="new" href="%(path_prefix)s/ticket/1" title="This is the summary">#1</a></dt><dd>This is the summary</dd></dl></div><p>
@@ -100,19 +100,19 @@ New tickets: </p><div><dl class="wiki co
 ============================== %(tc_title_prefix)s - ProductTicketQuery macro: one result, count 1
 New tickets: [[ProductTicketQuery(status=new, format=count)]]
 
-New tickets: [[ProductTicketQuery(status=new, format=count, product=%(setup_product_name)s)]]
+New tickets: [[ProductTicketQuery(status=new, format=count, product=%(setup_product)s)]]
 ------------------------------
 <p>
 New tickets: <span class="query_count" title="1 tickets for which status=new&amp;max=0&amp;order=id">1</span>
 </p>
 <p>
-New tickets: <span class="query_count" title="1 tickets for which status=new&amp;product=%(setup_product_name)s&amp;max=0&amp;order=id">1</span>
+New tickets: <span class="query_count" title="1 tickets for which status=new&amp;max=0&amp;product=%(setup_product)s&amp;order=id">1</span>
 </p>
 ------------------------------
 ============================== %(tc_title_prefix)s - ProductTicketQuery macro: one result, compact form
 New tickets: [[ProductTicketQuery(status=new, format=compact)]]
 
-New tickets: [[ProductTicketQuery(status=new, format=compact, product=%(setup_product_name)s)]]
+New tickets: [[ProductTicketQuery(status=new, format=compact, product=%(setup_product)s)]]
 ------------------------------
 <p>
 New tickets: <span><a class="new" href="%(path_prefix)s/ticket/1" title="This is the summary">#1</a></span>

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/tests/wikisyntax.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/tests/wikisyntax.py?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/tests/wikisyntax.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/tests/wikisyntax.py Mon Aug 12 18:05:01 2013
@@ -65,7 +65,7 @@ def ticket_setup(tc):
                           'status': 'new'})
 
     # FIXME : UGLY ! Should not be explicit for product environments
-    ticket['product'] = (tc.env.product.name
+    ticket['product'] = (tc.env.product.prefix
                          if isinstance(tc.env, ProductEnvironment)
                          else '')
 

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_relations/bhrelations/templates/manage.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_relations/bhrelations/templates/manage.html?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_relations/bhrelations/templates/manage.html (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_relations/bhrelations/templates/manage.html Mon Aug 12 18:05:01 2013
@@ -47,27 +47,27 @@
 
         <form id="addrelation" class="well form-horizontal" method="post" action="">
           <fieldset>
-            <legend>Add relation:</legend>
+            <legend>Add relation</legend>
           </fieldset>
 
           <div class="control-group">
-            <label class="control-label" for="dest_tid">Related ticket:</label>
+            <label class="control-label" for="reltype">This ticket</label>
             <div class="controls">
-              <input type="text" id="dest_tid" class="span4" name="dest_tid" value="$relation.destination" />
+              <select class="span4" id="reltype" name="reltype">
+                <option py:for="reltype,label in reltypes" value="$reltype" selected="${True if reltype == relation.type else None}">$label</option>
+              </select>
             </div>
           </div>
 
           <div class="control-group">
-            <label class="control-label" for="reltype">Relation type:</label>
+            <label class="control-label" for="dest_tid">Related ticket</label>
             <div class="controls">
-              <select class="span4" id="reltype" name="reltype">
-                <option py:for="reltype,label in reltypes.iteritems()" value="$reltype" selected="${True if reltype == relation.type else None}">$label</option>
-              </select>
+              <input type="text" id="dest_tid" class="span4" name="dest_tid" value="$relation.destination" />
             </div>
           </div>
 
           <div class="control-group">
-            <label class="control-label" for="comment">Comment:</label>
+            <label class="control-label" for="comment">Comment</label>
             <div class="controls">
               <textarea name="comment" rows="3" class="span4">${relation.comment}</textarea>
             </div>

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_relations/bhrelations/tests/base.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_relations/bhrelations/tests/base.py?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_relations/bhrelations/tests/base.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_relations/bhrelations/tests/base.py Mon Aug 12 18:05:01 2013
@@ -59,8 +59,8 @@ class BaseRelationsTestCase(Multiproduct
         env.config.set(config_name, 'oneway', 'refersto')
         env.config.set(config_name, 'duplicate', 'duplicateof,duplicatedby')
         env.config.set(config_name, 'duplicate.validators', 'ReferencesOlder')
-        env.config.set(config_name, 'duplicateof.label', 'Duplicate of')
-        env.config.set(config_name, 'duplicatedby.label', 'Duplicated by')
+        env.config.set(config_name, 'duplicateof.label', 'is a duplicate of')
+        env.config.set(config_name, 'duplicatedby.label', 'duplicates')
         env.config.set(config_name, 'blocker', 'blockedby,blocks')
         env.config.set(config_name, 'blockedby.blocks', 'true')
 

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_relations/bhrelations/web_ui.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_relations/bhrelations/web_ui.py?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_relations/bhrelations/web_ui.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_relations/bhrelations/web_ui.py Mon Aug 12 18:05:01 2013
@@ -111,7 +111,8 @@ class RelationManagementModule(Component
 
         data.update({
             'ticket': ticket,
-            'reltypes': relsys.get_relation_types(),
+            'reltypes': sorted(relsys.get_relation_types().iteritems(),
+                key=lambda x: x[0]),
             'relations': self.get_ticket_relations(ticket),
         })
         return 'manage.html', data, None

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/htdocs/bloodhound.css
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/htdocs/bloodhound.css?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/htdocs/bloodhound.css (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/htdocs/bloodhound.css Mon Aug 12 18:05:01 2013
@@ -221,6 +221,10 @@ div.reports h2 {
 
 /* @group Wiki */
 
+div.trac-content {
+ padding: 19px;
+}
+
 .wiki-toc {
  float: right;
  margin-left: 25px;
@@ -388,6 +392,10 @@ h1, h2, h3, h4 {
  margin: 0.5em;
 }
 
+#content textarea, .popover-content textarea {
+ font-family: monospace;
+}
+
 /* copied from bootstrap for appropriate focus/unfocused state for contenteditable*/
 [contenteditable="true"] {
  border-radius: 4px 4px 4px 4px;

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_admin_plugins.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_admin_plugins.html?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_admin_plugins.html (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_admin_plugins.html Mon Aug 12 18:05:01 2013
@@ -136,7 +136,7 @@
                           <i class="icon-tag"></i>
                           <span class="label">${module_name}.*</span>
                           <span py:if="module.summary" class="help-inline">
-                            &mdash; ${module.summary}
+                            &mdash; ${wiki_to_oneliner(context, module.summary)}
                           </span>
                         </td>
                         <td class="sel"></td>
@@ -149,7 +149,7 @@
                           </span>
                           <span py:if="component.summary" class="help-inline"
                               style="display: inline;">
-                            &mdash; ${component.summary}
+                            &mdash; ${wiki_to_oneliner(context, component.summary)}
                           </span>
                         </td>
                         <td class="sel">

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_milestone_view.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_milestone_view.html?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_milestone_view.html (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_milestone_view.html Mon Aug 12 18:05:01 2013
@@ -51,20 +51,9 @@
     </py:choose>
   </py:def>
 
-  <py:def function="milestone_owner()">
-    <py:choose>
-      <py:when test="milestone.owner">
-        Assigned to
-        <a href="${href.query(owner=milestone.owner, status='!closed',
-            milestone=milestone.name, order='priority')}">${milestone.name}</a>
-      </py:when>
-      <py:otherwise>&nbsp;</py:otherwise>
-    </py:choose>
-  </py:def>
-
   <py:def function="milestone_heading()">
     <h1>Milestone ${milestone.name}</h1>
-    <h6>${milestone_date()} &nbsp;&nbsp;&nbsp; ${milestone_owner()}</h6>
+    <h6>${milestone_date()}</h6>
   </py:def>
 
   <py:def function="milestone_buttons()">

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_ticket.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_ticket.html?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_ticket.html (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_ticket.html Mon Aug 12 18:05:01 2013
@@ -329,7 +329,17 @@
       <h5 id="trac-ticket-title" py:choose="">
         <py:when test="t.exists">
           <a href="${href.ticket(t.id)}"
-              i18n:msg="id">Ticket #${t.id}</a> -
+              i18n:msg="id">Ticket #${t.id}</a>
+          <small py:if="version is not None">
+            <py:choose test="">
+              <py:when test="version == 0">
+                <i18n:msg>at <a href="#comment:description" class="muted">Initial Version</a></i18n:msg>
+              </py:when>
+              <py:otherwise>
+                <i18n:msg params="version">at <a href="#comment:$version" class="muted">Version $version</a></i18n:msg>
+              </py:otherwise>
+            </py:choose>
+          </small> &mdash;
         </py:when>
         <py:otherwise>
           New Ticket <small><span py:if="preview_mode and t.type" class="status">(${t.type})</span></small>
@@ -345,7 +355,8 @@
           <span py:if="not t.exists" class="label label-warning">(ticket not yet created)</span>
         </span>
       </h5>
-      <span id="inplace-edit-button" py:if="can_modify or can_edit or can_create" class="btn-toolbar" style="${'display: None;' if not t.exists else ''}">
+      <span id="inplace-edit-button" py:if="has_property_editor"
+            class="btn-toolbar" style="${'display: None;' if not t.exists else ''}">
         <button id="inplace-edit" class="btn">
           <i class="icon-edit"></i>
           <span>${_('Modify Ticket')}</span>
@@ -406,7 +417,7 @@
       </span>
     </py:def>
 
-    <div class="ticket">
+    <div id="content" class="ticket">
       <div class="trac-topnav span2" py:if="has_property_editor">
         <a href="#propertyform" title="Go to the ticket editor">Modify</a> &darr;
       </div>
@@ -433,14 +444,14 @@
               ${render_ticket_summary_details(ticket)}
             </div>
 
-            <div id="content">
+            <div>
               <div id="belowStatus">
                 <!--! ticket details -->
                 <!--! main ticket info -->
                 <div class="properties">
                   <xi:include href="bh_ticket_box.html"
                       py:with="preview_mode = change_preview.fields if change_preview else False;
-                          colcount = 4 if bhdb else 6"/>
+                               colcount = 4 if bhdb else 6"/>
                 </div>
 
                 <div id="dummy-vc-summary" style="display: none"></div>
@@ -481,7 +492,7 @@
             </div>
           </form>
 
-          <div py:if="bhrelations and ticket.exists" class="relations">
+          <div py:if="bhrelations and ticket.exists and has_property_editor" class="relations">
             <bh:widget urn="TicketRelations">
               <bh:args>
                 <bh:arg name="tid">$ticket.id</bh:arg>
@@ -525,7 +536,7 @@
               <!--! Add comment -->
               <div id="trac-add-comment" class="field">
                 <div class="trac-nav">
-                  <a href="#content" title="View ticket fields and description">View</a> &uarr;
+                  <a href="#belowStatus" title="View ticket fields and description">View</a> &uarr;
                 </div>
                 <div id="trac-edit-warning" class="warning system-message alert"
                       style="${'display: none' if start_time == ticket['changetime'] else None}"

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_ticket_box.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_ticket_box.html?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_ticket_box.html (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_ticket_box.html Mon Aug 12 18:05:01 2013
@@ -123,11 +123,10 @@ Arguments:
 
     <!--! Quote the description (only for existing tickets) -->
     <div class="pull-right">
-      <form py:if="ticket.exists and ticket.description and can_append"
-          id="addreply" method="get" action="#comment">
-        <a href="?replyto=description#trac-add-comment" class="btn btn-mini"
-            title="Reply, quoting this description" type="submit">Reply</a>
-      </form>
+      <a py:if="ticket.exists and ticket.description and can_append"
+          id="addreply" class="btn btn-mini" type="submit"
+          href="?replyto=description#trac-add-comment"
+          title="Reply, quoting this description">Reply</a>
     </div>
     <div class="searchable" xml:space="preserve"
         data-edit="${'inplace' if can_edit or can_create else None}"

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_ticket_change.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_ticket_change.html?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_ticket_change.html (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_ticket_change.html Mon Aug 12 18:05:01 2013
@@ -84,7 +84,7 @@ Arguments:
               action="${href.ticket(ticket.id) + '#comment:%d' % cnum}">
           <div>
             <textarea name="edited_comment" class="wikitext trac-resizable" rows="10" cols="78">
-  ${edited_comment if edited_comment is not None else change.comment}</textarea>
+${edited_comment if edited_comment is not None else change.comment}</textarea>
             <input type="hidden" name="cnum_edit" value="${cnum}"/>
           </div>
           <div class="btn-toolbar buttons">

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_wiki_view.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_wiki_view.html?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_wiki_view.html (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/templates/bh_wiki_view.html Mon Aug 12 18:05:01 2013
@@ -83,7 +83,7 @@
             </span>
             <span class="trac-print" i18n:msg="date">Last modified on ${format_datetime(page.time)}</span>
           </div>
-          <div id="wikipage" py:content="wiki_to_html(context, text)" />
+          <div id="wikipage" class="trac-content" py:content="wiki_to_html(context, text)" />
         </py:when>
         <py:otherwise>
           <p i18n:msg="name">The page ${name_of(page.resource)} does not exist. You can create it here.</p>

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/theme.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/theme.py?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/theme.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_theme/bhtheme/theme.py Mon Aug 12 18:05:01 2013
@@ -430,7 +430,8 @@ class BloodhoundTheme(ThemeBase):
 
     def _add_products_general_breadcrumb(self, req, template, data,
                                          content_type, is_active):
-        data['resourcepath_template'] = 'bh_path_general.html'
+        if isinstance(req.perm.env, ProductEnvironment):
+            data['resourcepath_template'] = 'bh_path_general.html'
 
     # INavigationContributor methods
 

Modified: bloodhound/branches/bep_0007_embeddable_objects/installer/bloodhound_setup.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/installer/bloodhound_setup.py?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/installer/bloodhound_setup.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/installer/bloodhound_setup.py Mon Aug 12 18:05:01 2013
@@ -97,22 +97,23 @@ BASE_CONFIG = {'components': {'bhtheme.*
                         'duplicateof',
                },
                'bhrelations_links': {
-                    'children.label': 'Child',
+                    'children.label': 'is a child of',
                     'dependency': 'dependson,dependent',
                     'dependency.validators': 'NoCycles,SingleProduct',
                     'dependson.blocks': 'true',
-                    'dependson.label': 'Depends on',
-                    'dependent.label': 'Dependent',
+                    'dependson.label': 'depends on',
+                    'dependent.label': 'is a dependency of',
                     'oneway': 'refersto',
                     'parent_children': 'parent,children',
                     'parent.exclusive': 'true',
+                    'parent.label': 'is a parent of',
                     'parent_children.validators':
                         'OneToMany,SingleProduct,NoCycles',
-                    'refersto.label': 'Refers to',
+                    'refersto.label': 'refers to',
                     'duplicate': 'duplicateof,duplicatedby',
                     'duplicate.validators': 'ReferencesOlder',
-                    'duplicateof.label': 'Duplicate of',
-                    'duplicatedby.label': 'Duplicated by',
+                    'duplicateof.label': 'is a duplicate of',
+                    'duplicatedby.label': 'is duplicated by',
                },
 
                }

Modified: bloodhound/branches/bep_0007_embeddable_objects/installer/requirements-dev.txt
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/installer/requirements-dev.txt?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/installer/requirements-dev.txt (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/installer/requirements-dev.txt Mon Aug 12 18:05:01 2013
@@ -18,8 +18,8 @@
 Pygments
 pytz
 sqlparse==0.1.7
-Babel
-Genshi
+Babel==0.9.6
+Genshi==0.6.1
 -e ../trac
 TracAccountManager
 -e ../bloodhound_multiproduct

Modified: bloodhound/branches/bep_0007_embeddable_objects/installer/requirements.txt
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/installer/requirements.txt?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/installer/requirements.txt (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/installer/requirements.txt Mon Aug 12 18:05:01 2013
@@ -18,8 +18,8 @@
 Pygments
 pytz
 sqlparse==0.1.7
-Babel
-Genshi
+Babel==0.9.6
+Genshi==0.6.1
 ../trac
 TracAccountManager
 ../bloodhound_multiproduct

Propchange: bloodhound/branches/bep_0007_embeddable_objects/trac/
------------------------------------------------------------------------------
  Merged /bloodhound/trunk/trac:r1507815-1513209

Modified: bloodhound/branches/bep_0007_embeddable_objects/trac/trac/ticket/tests/query.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/trac/trac/ticket/tests/query.py?rev=1513212&r1=1513211&r2=1513212&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/trac/trac/ticket/tests/query.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/trac/trac/ticket/tests/query.py Mon Aug 12 18:05:01 2013
@@ -110,7 +110,7 @@ ORDER BY COALESCE(priority.value,'')='' 
         query = Query(self.env, order='version')
         sql, args = query.get_sql()
         self.assertEqualSQL(sql,
-"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.version AS version,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
+"""SELECT t.product AS product,t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.version AS version,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
 FROM ticket AS t
   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
   LEFT OUTER JOIN version ON (version.name=version)
@@ -122,7 +122,7 @@ ORDER BY COALESCE(t.version,'')='',COALE
         query = Query(self.env, order='version', desc=1)
         sql, args = query.get_sql()
         self.assertEqualSQL(sql,
-"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.version AS version,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
+"""SELECT t.product AS product,t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.version AS version,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
 FROM ticket AS t
   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
   LEFT OUTER JOIN version ON (version.name=version)
@@ -134,7 +134,7 @@ ORDER BY COALESCE(t.version,'')='' DESC,
         query = Query.from_string(self.env, 'milestone=milestone1', order='id')
         sql, args = query.get_sql()
         self.assertEqualSQL(sql,
-"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.component AS component,t.time AS time,t.changetime AS changetime,t.milestone AS milestone,priority.value AS priority_value
+"""SELECT t.product AS product,t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.component AS component,t.time AS time,t.changetime AS changetime,t.milestone AS milestone,priority.value AS priority_value
 FROM ticket AS t
   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
 WHERE ((COALESCE(t.milestone,'')=%s))
@@ -146,7 +146,7 @@ ORDER BY COALESCE(t.id,0)=0,t.id""")
         query = Query(self.env, order='id', group='milestone')
         sql, args = query.get_sql()
         self.assertEqualSQL(sql,
-"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.component AS component,t.milestone AS milestone,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
+"""SELECT t.product AS product,t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.component AS component,t.milestone AS milestone,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
 FROM ticket AS t
   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
   LEFT OUTER JOIN milestone ON (milestone.name=milestone)
@@ -158,7 +158,7 @@ ORDER BY COALESCE(t.milestone,'')='',COA
         query = Query(self.env, order='id', group='milestone', groupdesc=1)
         sql, args = query.get_sql()
         self.assertEqualSQL(sql,
-"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.component AS component,t.milestone AS milestone,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
+"""SELECT t.product AS product,t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.component AS component,t.milestone AS milestone,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
 FROM ticket AS t
   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
   LEFT OUTER JOIN milestone ON (milestone.name=milestone)
@@ -170,7 +170,7 @@ ORDER BY COALESCE(t.milestone,'')='' DES
         query = Query(self.env, group='priority')
         sql, args = query.get_sql()
         self.assertEqualSQL(sql,
-"""SELECT t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.milestone AS milestone,t.component AS component,t.priority AS priority,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
+"""SELECT t.product AS product,t.id AS id,t.summary AS summary,t.owner AS owner,t.type AS type,t.status AS status,t.milestone AS milestone,t.component AS component,t.priority AS priority,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
 FROM ticket AS t
   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
 ORDER BY COALESCE(priority.value,'')='',%(cast_priority)s,t.id""" % {
@@ -182,7 +182,7 @@ ORDER BY COALESCE(priority.value,'')='',
         query = Query.from_string(self.env, 'milestone!=milestone1', order='id')
         sql, args = query.get_sql()
         self.assertEqualSQL(sql,
-"""SELECT t.id AS id,t.summary AS summary,t.milestone AS milestone,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
+"""SELECT t.product AS product,t.id AS id,t.summary AS summary,t.milestone AS milestone,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
 FROM ticket AS t
   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
 WHERE ((COALESCE(t.milestone,'')!=%s))
@@ -357,7 +357,7 @@ ORDER BY COALESCE(t.id,0)=0,t.id""")
         query = Query.from_string(self.env, 'created=2008-08-01..2008-09-01', order='id')
         sql, args = query.get_sql(self.req)
         self.assertEqualSQL(sql,
-"""SELECT t.id AS id,t.summary AS summary,t.time AS time,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.changetime AS changetime,priority.value AS priority_value
+"""SELECT t.product AS product,t.id AS id,t.summary AS summary,t.time AS time,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.changetime AS changetime,priority.value AS priority_value
 FROM ticket AS t
   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
 WHERE (((%(cast_time)s>=%%s AND %(cast_time)s<%%s)))
@@ -370,7 +370,7 @@ ORDER BY COALESCE(t.id,0)=0,t.id""" % {
         query = Query.from_string(self.env, 'created!=2008-08-01..2008-09-01', order='id')
         sql, args = query.get_sql(self.req)
         self.assertEqualSQL(sql,
-"""SELECT t.id AS id,t.summary AS summary,t.time AS time,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.changetime AS changetime,priority.value AS priority_value
+"""SELECT t.product AS product,t.id AS id,t.summary AS summary,t.time AS time,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.changetime AS changetime,priority.value AS priority_value
 FROM ticket AS t
   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
 WHERE ((NOT (%(cast_time)s>=%%s AND %(cast_time)s<%%s)))
@@ -383,7 +383,7 @@ ORDER BY COALESCE(t.id,0)=0,t.id""" % {
         query = Query.from_string(self.env, 'created=2008-08-01..', order='id')
         sql, args = query.get_sql(self.req)
         self.assertEqualSQL(sql,
-"""SELECT t.id AS id,t.summary AS summary,t.time AS time,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.changetime AS changetime,priority.value AS priority_value
+"""SELECT t.product AS product,t.id AS id,t.summary AS summary,t.time AS time,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.changetime AS changetime,priority.value AS priority_value
 FROM ticket AS t
   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
 WHERE ((%(cast_time)s>=%%s))
@@ -396,7 +396,7 @@ ORDER BY COALESCE(t.id,0)=0,t.id""" % {
         query = Query.from_string(self.env, 'created=..2008-09-01', order='id')
         sql, args = query.get_sql(self.req)
         self.assertEqualSQL(sql,
-"""SELECT t.id AS id,t.summary AS summary,t.time AS time,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.changetime AS changetime,priority.value AS priority_value
+"""SELECT t.product AS product,t.id AS id,t.summary AS summary,t.time AS time,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.changetime AS changetime,priority.value AS priority_value
 FROM ticket AS t
   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
 WHERE ((%(cast_time)s<%%s))
@@ -409,7 +409,7 @@ ORDER BY COALESCE(t.id,0)=0,t.id""" % {
         query = Query.from_string(self.env, 'modified=2008-08-01..2008-09-01', order='id')
         sql, args = query.get_sql(self.req)
         self.assertEqualSQL(sql,
-"""SELECT t.id AS id,t.summary AS summary,t.changetime AS changetime,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.time AS time,priority.value AS priority_value
+"""SELECT t.product AS product,t.id AS id,t.summary AS summary,t.changetime AS changetime,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.time AS time,priority.value AS priority_value
 FROM ticket AS t
   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
 WHERE (((%(cast_changetime)s>=%%s AND %(cast_changetime)s<%%s)))
@@ -423,7 +423,7 @@ ORDER BY COALESCE(t.id,0)=0,t.id""" % {
                                   order='id')
         sql, args = query.get_sql()
         self.assertEqualSQL(sql,
-"""SELECT t.id AS id,t.summary AS summary,t.keywords AS keywords,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
+"""SELECT t.product AS product,t.id AS id,t.summary AS summary,t.keywords AS keywords,t.owner AS owner,t.type AS type,t.status AS status,t.priority AS priority,t.time AS time,t.changetime AS changetime,priority.value AS priority_value
 FROM ticket AS t
   LEFT OUTER JOIN enum AS priority ON (priority.type='priority' AND priority.name=priority)
 WHERE (((COALESCE(t.keywords,'') %(like)s AND COALESCE(t.keywords,'') NOT %(like)s AND COALESCE(t.keywords,'') %(like)s)))