def getActions(self, req, id): """Returns the actions that can be performed on the ticket as a list of `[action, label, hints, [input_fields]]` elements, where `input_fields` is a list of `[name, value, [options]]` for any required action inputs.""" ts = TicketSystem(self.env) t = model.Ticket(self.env, id) actions = [] for action in ts.get_available_actions(req, t): fragment = genshi.builder.Fragment() hints = [] first_label = None for controller in ts.action_controllers: if action in [c_action for c_weight, c_action \ in controller.get_ticket_actions(req, t)]: label, widget, hint = \ controller.render_ticket_action_control(req, t, action) fragment += widget hints.append(to_unicode(hint).rstrip('.') + '.') first_label = first_label == None and label or first_label controls = [] for elem in fragment.children: if not isinstance(elem, genshi.builder.Element): continue if elem.tag == 'input': controls.append((elem.attrib.get('name'), elem.attrib.get('value'), [])) elif elem.tag == 'select': value = '' options = [] for opt in elem.children: if not (opt.tag == 'option' and opt.children): continue option = opt.children[0] options.append(option) if opt.attrib.get('selected'): value = option controls.append((elem.attrib.get('name'), value, options)) actions.append((action, first_label, " ".join(hints), controls)) return actions
def save_ticket(self, ticket_data, author): """If ticket_data contains an ID, modifies defined fields in that ticket. If not, creates new ticket. Returns the ID of new/modified ticket.""" id = None comment = '' if 'id' in ticket_data: id = ticket_data['id'] ticket = model.Ticket(self.env, id) for key, value in ticket_data.items(): if key == 'comment': comment = value elif key != 'id': ticket[key] = value if id: ticket.save_changes(author, comment) else: ticket.insert() id = ticket.id return id
def putAttachment(self, req, ticket, filename, description, data, replace=True): """ Add an attachment, optionally (and defaulting to) overwriting an existing one. Returns filename.""" if not model.Ticket(self.env, ticket).exists: raise ResourceNotFound('Ticket "%s" does not exist' % ticket) if replace: try: attachment = Attachment(self.env, 'ticket', ticket, filename) req.perm(attachment.resource).require('ATTACHMENT_DELETE') attachment.delete() except TracError: pass attachment = Attachment(self.env, 'ticket', ticket) req.perm(attachment.resource).require('ATTACHMENT_CREATE') attachment.author = req.authname attachment.description = description attachment.insert(filename, StringIO(data.data), len(data.data)) return attachment.filename
def replace_tags(self, req, name, tags): ticket = model.Ticket(self.env, name) ticket['keywords'] = ' '.join(sorted(tags)) ticket.save_changes(req.authname, None)
def add_tags(self, req, name, tags): ticket = model.Ticket(self.env, name) ticket_tags = self._ticket_tags(ticket) ticket_tags.update(tags) ticket['keywords'] = ' '.join(sorted(ticket_tags)) ticket.save_changes(req.authname, None)
def do_remove(db): ticket = model.Ticket(self.env, number, db=db) ticket.delete()
def changeLog(self, req, id, when=0): t = model.Ticket(self.env, id) return t.get_changelog(when)
def assertCommentAdded(self, ticket_id, comment): ticket = model.Ticket(self.env, int(ticket_id)) changes = ticket.get_changelog() comment_change = [c for c in changes if c[2] == 'comment'][-1] self.assertEqual(comment_change[4], comment)
def get(self, req, id): """ Fetch a ticket. Returns [id, time_created, time_changed, attributes]. """ t = model.Ticket(self.env, id) req.perm(t.resource).require('TICKET_VIEW') t['_ts'] = str(to_utimestamp(t.time_changed)) return (t.id, t.time_created, t.time_changed, t.values)
def validate_ticket(self, req, ticket): """Validate a ticket after it's been populated from user input. Must return a list of `(field, message)` tuples, one for each problem detected. `field` can be `None` to indicate an overall problem with the ticket. Therefore, a return value of `[]` means everything is OK.""" res = [] # the ticket we receive is a temporary not-yet-commited ticket # and contains fields set that weren't changed as well, # retrieve the original one so we can compare ot = model.Ticket(self.env, ticket.id) self.env.log.debug('validate_ticket: %s' % ticket.id) # refuse changes to dup_count and dups fields new = ticket.values.get('dups', None) if new is not None and new != ot.values.get('dups', None): res.append(('dups', 'Cannot manually change the dups field.')) return res new = ticket.values.get('dup_count', None) if new is not None and new != ot.values.get('dup_count', None): res.append( ('dup_count', 'Cannot manually change the dup_count field.')) return res new_id = ticket.values.get('dup_of', None) # allow unsetting if not new_id: self.env.log.debug("validate_ticket: dup_of is None, so fine") return res # care only about tickets that have dup_of changes old = ot.values.get('dup_of', None) if old == new_id: self.env.log.debug("validate_ticket: no dup_of changes") return res # refuse to change closed tickets if ticket.values['status'] == u'closed': if ticket.values['resolution'] == u'duplicate': self.env.log.debug( "validate_ticket: allowing unduplicate to get validated") # but still subject to other rules else: self.env.log.debug( "validate_ticket: refusing to dup closed ticket #%s" % ticket.id) res.append( ('dup_of', 'Ticket is already closed, and not as a duplicate.')) return res # refuse to dup_of and reopen ticket in one go if ot.values['status'] == u'closed' \ and ticket.values['status'] == u'reopened': self.env.log.debug("validate_ticket: " "refusing to dup_of and reopen ticket #%s" % ticket.id) res.append(('status', 'If you want to duplicate an already closed ticket, ' 'only change dup_of without reopening the ticket.')) return res # warn when it starts with # if len(new_id) > 0 and new_id[0] == '#': res.append(('dup_of', 'Please enter the ticket number without a leading #.')) return res # refuse to dup to non-existing tickets; this raises a TracError # if it doesn't exist # coderanger says a Ticket can have anything with a __str__ method # as id # except in the 0.10.5dev branch, a non-existing ticket id raises # a TracError with %d in the format string (fixed on TRUNK), # so make it an int here master = model.Ticket(self.env, int(new_id)) # refuse to dup to self if str(new_id) == str(ticket.id): self.env.log.debug("validate_ticket: " "cowardly refusing to dup to self #%s" % ticket.id) res.append(('dup_of', 'Cannot duplicate a ticket to itself.')) return res self.env.log.debug('validate_ticket: Validated ticket %s' % ticket.id) return res
def name_details(self, name): ticket = model.Ticket(self.env, name) href = self.env.href.ticket(name) summary = ticket['summary'] or u'' return (href, '<a href="%s">#%s</a>' % (href, name), ticket.exists and summary)
def name_details(self, name): ticket = model.Ticket(self.env, name) href = self.env.href.ticket(name) from trac.wiki.formatter import wiki_to_oneliner return (href, wiki_to_oneliner('#%s' % name, self.env), ticket.exists and ticket['summary'] or '')
def get(self, req, id): """ Fetch a ticket. Returns [id, time_created, time_changed, attributes]. """ t = model.Ticket(self.env, id) return (t.id, t.time_created, t.time_changed, t.values)
def getAvailableActions(self, req, id): """Returns the actions that can be performed on the ticket.""" ticketSystem = TicketSystem(self.env) t = model.Ticket(self.env, id) return ticketSystem.get_available_actions(t, req.perm)
def remove_tags(self, req, name, tags): ticket = model.Ticket(self.env, name) ticket_tags = self._ticket_tags(ticket) ticket_tags.symmetric_difference_update(tags) ticket['keywords'] = ' '.join(sorted(ticket_tags)) ticket.save_changes(req.authname, None)
def update(self, req, id, comment, attributes={}, notify=False, author='', when=None): """ Update a ticket, returning the new ticket in the same form as get(). 'New-style' call requires two additional items in attributes: (1) 'action' for workflow support (including any supporting fields as retrieved by getActions()), (2) '_ts' changetime token for detecting update collisions (as received from get() or update() calls). ''Calling update without 'action' and '_ts' changetime token is deprecated, and will raise errors in a future version.'' """ t = model.Ticket(self.env, id) # custom author? if author and not (req.authname == 'anonymous' \ or 'TICKET_ADMIN' in req.perm(t.resource)): # only allow custom author if anonymous is permitted or user is admin self.log.warn( "RPC ticket.update: %r not allowed to change author " "to %r for comment on #%d", req.authname, author, id) author = '' author = author or req.authname # custom change timestamp? if when and not 'TICKET_ADMIN' in req.perm(t.resource): self.log.warn( "RPC ticket.update: %r not allowed to update #%d with " "non-current timestamp (%r)", author, id, when) when = None when = when or to_datetime(None, utc) # and action... if not 'action' in attributes: # FIXME: Old, non-restricted update - remove soon! self.log.warning("Rpc ticket.update for ticket %d by user %s " \ "has no workflow 'action'." % (id, req.authname)) req.perm(t.resource).require('TICKET_MODIFY') time_changed = attributes.pop('_ts', None) if time_changed and \ str(time_changed) != str(to_utimestamp(t.time_changed)): raise TracError("Ticket has been updated since last get().") for k, v in attributes.iteritems(): t[k] = v t.save_changes(author, comment, when=when) else: ts = TicketSystem(self.env) tm = TicketModule(self.env) # TODO: Deprecate update without time_changed timestamp time_changed = attributes.pop('_ts', to_utimestamp(t.time_changed)) try: time_changed = int(time_changed) except ValueError: raise TracError("RPC ticket.update: Wrong '_ts' token " \ "in attributes (%r)." % time_changed) action = attributes.get('action') avail_actions = ts.get_available_actions(req, t) if not action in avail_actions: raise TracError("Rpc: Ticket %d by %s " \ "invalid action '%s'" % (id, req.authname, action)) controllers = list(tm._get_action_controllers(req, t, action)) all_fields = [field['name'] for field in ts.get_ticket_fields()] for k, v in attributes.iteritems(): if k in all_fields and k != 'status': t[k] = v # TicketModule reads req.args - need to move things there... req.args.update(attributes) req.args['comment'] = comment # Collision detection: 0.11+0.12 timestamp req.args['ts'] = str(from_utimestamp(time_changed)) # Collision detection: 0.13/1.0+ timestamp req.args['view_time'] = str(time_changed) changes, problems = tm.get_ticket_changes(req, t, action) for warning in problems: add_warning(req, "Rpc ticket.update: %s" % warning) valid = problems and False or tm._validate_ticket(req, t) if not valid: raise TracError(" ".join( [warning for warning in req.chrome['warnings']])) else: tm._apply_ticket_changes(t, changes) self.log.debug("Rpc ticket.update save: %s" % repr(t.values)) t.save_changes(author, comment, when=when) # Apply workflow side-effects for controller in controllers: controller.apply_action_side_effects(req, t, action) if notify: try: tn = TicketNotifyEmail(self.env) tn.notify(t, newticket=False, modtime=when) except Exception, e: self.log.exception("Failure sending notification on change of " "ticket #%s: %s" % (t.id, e))
def remove_all_tags(self, req, name): ticket = model.Ticket(self.env, name) ticket['keywords'] = '' ticket.save_changes(req.authname, None)
def delete(self, req, id): """ Delete ticket with the given id. """ t = model.Ticket(self.env, id) req.perm(t.resource).require('TICKET_ADMIN') t.delete()
dest='priority', type='string', help='ticket priority', default='normal') options, args = parser.parse_args() if None in (options.summary, options.description, options.reporter, options.owner, options.component, options.project): sys.stderr.write("Please make sure that summary, description, reporter," " owner, componnet and project are defined") sys.stderr.flush() sys.exit(1) env = open_environment(options.project) t = model.Ticket(env) t['status'] = options.status t['summary'] = options.summary t['description'] = options.description.replace("\\n", "\n") t['reporter'] = options.reporter t['owner'] = options.owner t['type'] = options.type t['component'] = options.component t['component'] = options.component if options.cc: t['cc'] = options.cc t['priority'] = options.priority t.insert() try: tn = TicketNotifyEmail(env)
def changeLog(self, req, id, when=0): t = model.Ticket(self.env, id) req.perm(t.resource).require('TICKET_VIEW') for date, author, field, old, new, permanent in t.get_changelog(when): yield (date, author, field, old, new, permanent)
def assertFieldValue(self, ticket_id, field, new_value): ticket = model.Ticket(self.env, int(ticket_id)) self.assertEqual(ticket[field], new_value)
def delete(self, req, id): """ Delete ticket with the given id. """ t = model.Ticket(self.env, id) t.delete()