113 | | |
114 | | == Example Manipulator == |
115 | | |
116 | | The following manipulator adds ''duplicate'' ticket closing with a reference to the duplicate, basically implementing #1395. |
117 | | |
118 | | {{{ |
119 | | #!python |
120 | | class DuplicateField(Component): |
121 | | """ Allow a ticket to be closed with a reference to a ticket ID that is a |
122 | | duplicate. """ |
123 | | |
124 | | implements(ITicketManipulator, ITicketFieldProvider) |
125 | | |
126 | | # ITicketFieldProvider methods |
127 | | def get_custom_fields(self): |
128 | | # 'duplicate' is where the duplicate ticket ID is stored |
129 | | yield Text('duplicate', label='Duplicate of', hidden=True) |
130 | | |
131 | | # ITicketManipulator methods |
132 | | def filter_fields(self, req, ticket, fields): |
133 | | # If ticket is closed as a duplicate, unhide the duplicate field |
134 | | if ticket.values.get('status') == 'closed' and ticket.values.get('resolution') == 'duplicate': |
135 | | for field in fields: |
136 | | if field['name'] == 'duplicate': |
137 | | field['hidden'] = False |
138 | | field['html_value'] = wiki_to_oneliner('#' + field['value'], self.env) |
139 | | |
140 | | def filter_actions(self, req, ticket, actions): |
141 | | # Remove 'duplicate' option from resolve, and inject "duplicate of .." control |
142 | | for idx, (action, label, controls) in enumerate(actions): |
143 | | if action == 'resolve': |
144 | | controls[0]['options'].remove('duplicate') |
145 | | value = req.args.get('duplicate_of', ticket.values.get('duplicate')) |
146 | | actions.insert(idx + 1, ('resolve_duplicate', 'duplicate', |
147 | | (Text('duplicate_of', label='of:', hidden=False, |
148 | | value=value, title='Ticket ID that this is a duplicate of'),))) |
149 | | break |
150 | | |
151 | | def validate_ticket(self, req, ticket): |
152 | | # If resolving as duplicate, update duplicate field |
153 | | if req.args.get('action') == 'resolve_duplicate': |
154 | | dup_id = req.args.get('duplicate_of', '') |
155 | | if not dup_id: |
156 | | raise TracError('Duplicate ticket ID not provided') |
157 | | try: |
158 | | duplicate = Ticket(self.env, int(dup_id)) |
159 | | except TracError: |
160 | | raise TracError('Invalid duplicate ticket ID %s' % dup_id) |
161 | | except: |
162 | | raise TracError('Invalid duplicate ticket ID %s, must be an integer' % dup_id) |
163 | | duplicate.save_changes(req.authname, '#%i has been marked as a ' \ |
164 | | 'duplicate of this ticket' % ticket.id) |
165 | | for field in ticket.fields: |
166 | | if field['name'] == 'duplicate': |
167 | | req.args['duplicate'] = str(dup_id) |
168 | | req.args['action'] = 'resolve' |
169 | | req.args['resolve_resolution'] = 'duplicate' |
170 | | elif req.args.get('action') == 'reopen': |
171 | | ticket['duplicate'] = '' |
172 | | }}} |