| 137 | |
| 138 | == Example Manipulator == |
| 139 | |
| 140 | The following manipulator adds ''duplicate'' ticket closing with a reference to the duplicate, basically implementing #1395. |
| 141 | |
| 142 | {{{ |
| 143 | #!python |
| 144 | class DuplicateField(Component): |
| 145 | """ Allow a ticket to be closed with a reference to a ticket ID that is a |
| 146 | duplicate. """ |
| 147 | |
| 148 | implements(ITicketManipulator, ITicketFieldProvider) |
| 149 | |
| 150 | # ITicketFieldProvider methods |
| 151 | def get_custom_fields(self): |
| 152 | # 'duplicate' is where the duplicate ticket ID is stored |
| 153 | yield Text('duplicate', label='Duplicate of', hidden=True) |
| 154 | |
| 155 | # ITicketManipulator methods |
| 156 | def filter_fields(self, req, ticket, fields): |
| 157 | # If ticket is closed as a duplicate, unhide the duplicate field |
| 158 | if ticket.values.get('status') == 'closed' and ticket.values.get('resolution') == 'duplicate': |
| 159 | for field in fields: |
| 160 | if field['name'] == 'duplicate': |
| 161 | field['hidden'] = False |
| 162 | field['html_value'] = wiki_to_oneliner('#' + field['value'], self.env) |
| 163 | |
| 164 | def filter_actions(self, req, ticket, actions): |
| 165 | # Remove 'duplicate' option from resolve, and inject "duplicate of .." control |
| 166 | for idx, (action, label, controls) in enumerate(actions): |
| 167 | if action == 'resolve': |
| 168 | controls[0]['options'].remove('duplicate') |
| 169 | value = req.args.get('duplicate_of', ticket.values.get('duplicate')) |
| 170 | actions.insert(idx + 1, ('resolve_duplicate', 'duplicate', |
| 171 | (Text('duplicate_of', label='of:', hidden=False, |
| 172 | value=value, title='Ticket ID that this is a duplicate of'),))) |
| 173 | break |
| 174 | |
| 175 | def validate_ticket(self, req, ticket): |
| 176 | # If resolving as duplicate, update duplicate field |
| 177 | if req.args.get('action') == 'resolve_duplicate': |
| 178 | dup_id = req.args.get('duplicate_of', '') |
| 179 | if not dup_id: |
| 180 | raise TracError('Duplicate ticket ID not provided') |
| 181 | try: |
| 182 | duplicate = Ticket(self.env, int(dup_id)) |
| 183 | except TracError: |
| 184 | raise TracError('Invalid duplicate ticket ID %s' % dup_id) |
| 185 | except: |
| 186 | raise TracError('Invalid duplicate ticket ID %s, must be an integer' % dup_id) |
| 187 | duplicate.save_changes(req.authname, '#%i has been marked as a ' \ |
| 188 | 'duplicate of this ticket' % ticket.id) |
| 189 | for field in ticket.fields: |
| 190 | if field['name'] == 'duplicate': |
| 191 | req.args['duplicate'] = str(dup_id) |
| 192 | req.args['action'] = 'resolve' |
| 193 | req.args['resolve_resolution'] = 'duplicate' |
| 194 | elif req.args.get('action') == 'reopen': |
| 195 | ticket['duplicate'] = '' |
| 196 | }}} |