Commits

Alex Willmer committed 4801fa0

Implement Ticket.populate_from() for populating new ticket fields from an existing ticket, with optional linking

Comments (0)

Files changed (4)

trac/ticket/api.py

     def is_blocker(end):
         """Return True if tickets linked by end block closing.
         """
+    def get_copy_fields(end):
+        """Return an iterable of field names populated in a new ticket created
+        as a linked ticket."""
 
 class IMilestoneChangeListener(Interface):
     """Extension point interface for components that require notification
         """Default resolution for resolving (closing) tickets
         (''since 0.11'').""")
 
+    default_copy_fields = ListOption('ticket', 'default_copy_fields', [],
+        """Default fields populated for newly created linked tickets
+        (''since 0.12dev'').""")
+    
     # regular expression to match links
     NUMBERS_RE = re.compile(r'\d+', re.U)
 
 							
     def _add_link_field(self, end, controller, fields):
         label = controller.render_end(end)
+        copy_fields = controller.get_copy_fields(end)
         if end in [f['name'] for f in fields]:
             self.log.warning('Duplicate field name "%s" (ignoring)', end)
             return
         field = {'name': end, 'type': 'link', 'label': label, 
-                 'link': True, 'format': 'wiki'}
+                 'link': True, 'format': 'wiki', 'copy_fields': copy_fields}
         fields.append(field)   
 
     def get_custom_fields(self):

trac/ticket/links.py

     
     def __init__(self):
         self._links, self._labels, \
-        self._validators, self._blockers = self._get_links_config()
+        self._validators, self._blockers, \
+        self._copy_fields = self._get_links_config()
 
     def get_ends(self):
         return self._links
     def is_blocker(self, end):
         return self._blockers[end]
     
+    def get_copy_fields(self, end):
+        if end in self._copy_fields:
+            return self._copy_fields[end]
+        else:
+            return TicketSystem(self.env).default_copy_fields
+    
     def prepare_ticket(self, req, ticket, fields, actions):
         pass
         
         labels = {}
         validators = {}
         blockers = {}
+        copy_fields = {}
+        
         config = self.config['ticket-links']
         for name in [option for option, _ in config.options()
                      if '.' not in option]:
             if end2:
                 blockers[end2] = config.getbool(end2 + '.blocks', default=False)
             
-        return links, labels, validators, blockers
+            # <end>.copy_fields may be absent or intentionally set empty.
+            # config.getlist() will return [] in either case, so check that
+            # the key is present before assigning the value
+            cf_keys = ['%s.copy_fields' % end for end in [end1, end2] if end]
+            for cf_key in cf_keys:
+                if cf_key in config:
+                    copy_fields[end] = config.getlist(cf_key)
+            
+        return links, labels, validators, blockers, copy_fields
     
     def find_blockers(self, ticket, field, blockers):
         ticket_system = TicketSystem(self.env)

trac/ticket/model.py

             if name[9:] not in values:
                 self[name[9:]] = '0'
 
+    def populate_from(self, tkt_id, link_field_name=None, 
+                            copy_field_names=None):
+        """Populate the ticket with 'suitable' values from another ticket.
+        """
+        ticket_sys = TicketSystem(self.env)
+        field_names = [f['name'] for f in self.fields]
+        
+        if not link_field_name and copy_field_names is None:
+            copy_field_names = self.env.config.getlist('ticket', 
+                                                       'default_copy_fields')
+        elif (link_field_name in ticket_sys.link_ends_map 
+              and copy_field_names is None):
+            link_field = [f for f in self.fields 
+                            if f['name'] == link_field_name][0]
+            copy_field_names = link_field['copy_fields']
+            self[link_field_name] = str('#%s' % tkt_id)
+        
+        # TODO What if tkt_id isn't valid?
+        other_ticket = Ticket(self.env, tkt_id)
+        for name in [name for name in copy_field_names if name in field_names]:
+             self[name] = other_ticket[name]
+            
     def insert(self, when=None, db=None):
         """Add ticket to database.
         

trac/ticket/web_ui.py

         else:
             fields = req.args.copy()
         
-        # Populate a link field using the value of linked_end as the field 
-        # name and linked_val the actual value.
+        # If linked_end and linked_val are present in field populate ticket 
+        # using ticket with id == linked_val and configured copy fields.
+        # Also link to that to ticket with field linked_end
         if 'linked_val' in fields and 'linked_end' in fields:
-            link_val = fields.pop('linked_val')
+            link_id = int(fields.pop('linked_val'))
             link_end = fields.pop('linked_end')
-            if link_end in TicketSystem(self.env).link_ends_map:
-                fields[link_end] = link_val
+            ticket.populate_from(link_id, link_end)
+        elif 'template_ticket' in fields:
+            template_id = int(fields.pop('template_ticket'))
+            ticket.populate_from(template_id)
         
         # Prevent direct changes to protected fields (status and resolution are
         # set in the workflow, in get_ticket_changes())