Anonymous avatar Anonymous committed 59fec6a Draft

multiproduct: unique product names, update product ticket on name change and provide a Product.get_tickets class method #38

Comments (0)

Files changed (1)


 #  under the License.
 """Models to support multi-product"""
+from datetime import datetime
 from pkg_resources import resource_filename
 from trac.core import Component, TracError, implements
 from trac.resource import ResourceNotFound
 from import ITemplateProvider
 from trac.resource import Resource
 from trac.ticket.api import TicketSystem
+from trac.ticket.model import Ticket
+from trac.ticket.query import Query
+from trac.util.datefmt import utc
 DB_SYSTEM_KEY = 'bloodhound_multi_product_version'
     def insert(self):
         """Create new record in the database"""
+        sdata = None
         if self._exists or len(, where =
                                       for k in self._meta['key_fields']]))):
             sdata = {'keys':','.join(["%s='%s'" % (k, self._data[k])
                                      for k in self._meta['key_fields']])}
+        elif len(, where =
+                                dict([(k,self._data[k])
+                                      for k in self._meta['unique_fields']]))):
+            sdata = {'keys':','.join(["%s='%s'" % (k, self._data[k])
+                                     for k in self._meta['unique_fields']])}
+        if sdata:
             raise TracError('%(object_name)s %(keys)s already exists' %
+    def _update_relations(self, db):
+        """Extra actions due to update"""
+        pass
     def update(self):
         """Update the matching record in the database"""
+        if self._old_data == self._data:
+            return 
         if not self._exists:
             raise TracError('%(object_name)s does not exist' % self._meta)
-        for key in self._meta['key_fields']:
+        for key in self._meta['no_change_fields']:
             if self._data[key] != self._old_data[key]:
                 raise TracError('%s cannot be changed' % key)
+        for key in self._meta['key_fields'] + self._meta['unique_fields']:
+            if self._data[key] != self._old_data[key]:
+                if len(, where = {key:self._data[key]})):
+                    raise TracError('%s already exists' % key)
         setsql, setvalues = fields_to_kv_str(self._meta['non_key_fields'],
                                              self._data, sep=',')
                  WHERE %(where)s""" % sdata
         with self._env.db_transaction as db:
             db(sql, setvalues + values)
+            self._update_relations(db)
             'non_key_fields':['name', 'description', 'owner'],
+            'no_change_fields':['prefix',],
+            'unique_fields':['name'],
         for prm in, where=where):
             prm._data['product_id'] = resources_to
+    def _update_relations(self, db=None, author=None):
+        """Extra actions due to update"""
+        # tickets need to be updated
+        old_name = self._old_data['name']
+        new_name = self._data['name']
+        now =
+        comment = 'Product %s renamed to %s' % (old_name, new_name)
+        if old_name != new_name:
+            for t in Product.get_tickets(self._env, old_name):
+                ticket = Ticket(self._env, t['id'], db)
+                ticket['product'] = new_name
+                ticket.save_changes(author, comment, now)
+    @classmethod
+    def get_tickets(cls, env, product=''):
+        """Retrieve all tickets associated with the product."""
+        q = Query.from_string(env, 'product=%s' % product)
+        return q.execute()
 class ProductResourceMap(ModelBase):
     """Table representing the mapping of resources to their product"""
+            'no_change_fields':['id',],
+            'unique_fields':[],
     def reparent_resource(self, product=None):
     implements(IEnvironmentSetupParticipant, ITemplateProvider)
     SCHEMA = [
-        Table('bloodhound_product', key = 'prefix') [
+        Table('bloodhound_product', key = ['prefix', 'name']) [
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.