def apply_preset(self, req, tickets, preset=None): if preset is None: return tickets presets = preset and [kw.split('=', 1) for kw in preset.split('&')] or [] fields = dict([(field, value) for field, value in presets]) warn = [] modified_tickets = [] if tickets and presets: db = self.env.get_db_cnx() ticket_module = TicketModule(self.env) action = fields.get('action') for ticket_id in tickets: if 'TICKET_CHGPROP' in req.perm('ticket', ticket_id): ticket = Ticket(self.env, ticket_id, db) ticket.populate(fields) if action: field_changes, problems = ticket_module.get_ticket_changes(req, ticket, action) if problems: for problem in problems: warn.append(problem) ticket_module._apply_ticket_changes(ticket, field_changes) # Apply changes made by the workflow ticket.save_changes(req.authname, None, db=db) modified_tickets.append(ticket_id) else: warn.append(_("You have no permission to modify ticket '%(ticket)s'", ticket=ticket_id)) db.commit() return { 'tickets' : modified_tickets, 'warnings': warn}
def _implementation(db): tkt = Ticket(self.env, ticket_id) tm = TicketModule(self.env) req.args[field] = new_value tm._populate(req, tkt, plain_fields=True) changes, problems = tm.get_ticket_changes(req, tkt, "btn_save") if problems: raise ValueError(problems) tm._apply_ticket_changes(tkt, changes) valid = tm._validate_ticket(req, tkt, force_collision_check=True) if not valid: raise ValueError(req.chrome['warnings']) else: tkt.save_changes(req.authname, "", when=datetime.now(utc))
def do_save(db): tm = TicketModule(self.env) req.args["milestone"] = milestone if ts: req.args["ts"] = ts tm._populate(req, ticket, plain_fields=True) changes, problems = tm.get_ticket_changes(req, ticket, "btn_save") if problems: raise ValueError(problems) tm._apply_ticket_changes(ticket, changes) valid = tm._validate_ticket(req, ticket, force_collision_check=True) if not valid: raise ValueError(req.chrome['warnings']) else: ticket.save_changes(req.authname, "", when=datetime.now(utc))
def _implementation(db): tkt = Ticket(self.env, ticket_id) ts = TicketSystem(self.env) tm = TicketModule(self.env) if action not in ts.get_available_actions(req, tkt): raise ValueError(["This ticket cannot be moved to this status,\ perhaps the ticket has been updated by someone else."]) field_changes, problems = \ tm.get_ticket_changes(req, tkt, action) if problems: raise ValueError(problems) tm._apply_ticket_changes(tkt, field_changes) valid = tm._validate_ticket(req, tkt, force_collision_check=True) if not valid: raise ValueError(req.chrome['warnings']) else: tkt.save_changes(req.authname, "", when=datetime.now(utc))
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 _do_save(self, req, db, milestone): if milestone.exists: req.perm(milestone.resource).require('MILESTONE_MODIFY') else: req.perm(milestone.resource).require('MILESTONE_CREATE') ticket_module = TicketModule(self.env) ticket_module._populate(req, milestone.ticket, False) if not milestone.exists: reporter_id = get_reporter_id(req, 'author') milestone.ticket.values['reporter'] = reporter_id action = req.args.get('action', 'leave') field_changes, problems = ticket_module.get_ticket_changes(req, milestone.ticket, action) if problems: for problem in problems: add_warning(req, problem) add_warning(req, tag(tag.p('Please review your configuration, ' 'probably starting with'), tag.pre('[trac]\nworkflow = ...\n'), tag.p('in your ', tag.tt('trac.ini'), '.')) ) ticket_module._apply_ticket_changes(milestone.ticket, field_changes) old_name = milestone.name new_name = milestone.ticket['summary'] milestone.name = new_name milestone.description = milestone.ticket['description'] due = req.args.get('duedate', '') milestone.due = due and parse_date(due, tzinfo=req.tz) or None milestone.ticket['duedate']=milestone.due and str(to_timestamp(milestone.due)) or None completed = req.args.get('completedate', '') retarget_to = req.args.get('target') # Instead of raising one single error, check all the constraints and # let the user fix them by going back to edit mode showing the warnings warnings = [] def warn(msg): add_warning(req, msg) warnings.append(msg) # -- check the name if new_name: if new_name != old_name: # check that the milestone doesn't already exists # FIXME: the whole .exists business needs to be clarified # (#4130) and should behave like a WikiPage does in # this respect. try: other_milestone = StructuredMilestone(self.env, new_name, db) warn(_('Milestone "%(name)s" already exists, please ' 'choose another name', name=new_name)) except ResourceNotFound: pass else: warn(_('You must provide a name for the milestone.')) # -- check completed date if action in MilestoneSystem(self.env).starting_action: milestone.ticket['started'] = str(to_timestamp(datetime.now(utc))) if action in MilestoneSystem(self.env).completing_action: milestone.completed = datetime.now(utc) if warnings: return self._render_editor(req, db, milestone) # -- actually save changes if milestone.exists: cnum = req.args.get('cnum') replyto = req.args.get('replyto') internal_cnum = cnum if cnum and replyto: # record parent.child relationship internal_cnum = '%s.%s' % (replyto, cnum) now = datetime.now(utc) milestone.save_changes(get_reporter_id(req, 'author'), req.args.get('comment'), when=now, cnum=internal_cnum) # eventually retarget opened tickets associated with the milestone if 'retarget' in req.args and completed: cursor = db.cursor() cursor.execute("UPDATE ticket SET milestone=%s WHERE " "milestone=%s and status != 'closed'", (retarget_to, old_name)) self.env.log.info('Tickets associated with milestone %s ' 'retargeted to %s' % (old_name, retarget_to)) else: milestone.insert() db.commit() add_notice(req, _('Your changes have been saved.')) jump_to = req.args.get('jump_to', 'roadmap') if jump_to=='roadmap': req.redirect(req.href.roadmap()) elif jump_to =='whiteboard': req.redirect(req.href.whiteboard('team_tasks')+'#'+milestone.name) else: req.redirect(req.href.milestone(milestone.name))
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 _do_save(self, req, db, milestone): if milestone.exists: req.perm(milestone.resource).require('MILESTONE_MODIFY') else: req.perm(milestone.resource).require('MILESTONE_CREATE') ticket_module = TicketModule(self.env) ticket_module._populate(req, milestone.ticket, False) if not milestone.exists: reporter_id = get_reporter_id(req, 'author') milestone.ticket.values['reporter'] = reporter_id action = req.args.get('action', 'leave') field_changes, problems = ticket_module.get_ticket_changes( req, milestone.ticket, action) if problems: for problem in problems: add_warning(req, problem) add_warning( req, tag( tag.p('Please review your configuration, ' 'probably starting with'), tag.pre('[trac]\nworkflow = ...\n'), tag.p('in your ', tag.tt('trac.ini'), '.'))) ticket_module._apply_ticket_changes(milestone.ticket, field_changes) old_name = milestone.name new_name = milestone.ticket['summary'] milestone.name = new_name milestone.description = milestone.ticket['description'] due = req.args.get('duedate', '') milestone.due = due and parse_date(due, tzinfo=req.tz) or None milestone.ticket['duedate'] = milestone.due and str( to_timestamp(milestone.due)) or None completed = req.args.get('completedate', '') retarget_to = req.args.get('target') # Instead of raising one single error, check all the constraints and # let the user fix them by going back to edit mode showing the warnings warnings = [] def warn(msg): add_warning(req, msg) warnings.append(msg) # -- check the name if new_name: if new_name != old_name: # check that the milestone doesn't already exists # FIXME: the whole .exists business needs to be clarified # (#4130) and should behave like a WikiPage does in # this respect. try: other_milestone = StructuredMilestone( self.env, new_name, db) warn( _( 'Milestone "%(name)s" already exists, please ' 'choose another name', name=new_name)) except ResourceNotFound: pass else: warn(_('You must provide a name for the milestone.')) # -- check completed date if action in MilestoneSystem(self.env).starting_action: milestone.ticket['started'] = str(to_timestamp(datetime.now(utc))) if action in MilestoneSystem(self.env).completing_action: milestone.completed = datetime.now(utc) if warnings: return self._render_editor(req, db, milestone) # -- actually save changes if milestone.exists: cnum = req.args.get('cnum') replyto = req.args.get('replyto') internal_cnum = cnum if cnum and replyto: # record parent.child relationship internal_cnum = '%s.%s' % (replyto, cnum) now = datetime.now(utc) milestone.save_changes(get_reporter_id(req, 'author'), req.args.get('comment'), when=now, cnum=internal_cnum) # eventually retarget opened tickets associated with the milestone if 'retarget' in req.args and completed: cursor = db.cursor() cursor.execute( "UPDATE ticket SET milestone=%s WHERE " "milestone=%s and status != 'closed'", (retarget_to, old_name)) self.env.log.info('Tickets associated with milestone %s ' 'retargeted to %s' % (old_name, retarget_to)) else: milestone.insert() db.commit() add_notice(req, _('Your changes have been saved.')) jump_to = req.args.get('jump_to', 'roadmap') if jump_to == 'roadmap': req.redirect(req.href.roadmap()) elif jump_to == 'whiteboard': req.redirect( req.href.whiteboard('team_tasks') + '#' + milestone.name) else: req.redirect(req.href.milestone(milestone.name))
def invoke(self, message, warnings): """reply to a ticket""" ticket = self.ticket reporter = self._reporter(message) # get the mailBody and attachments mailBody, attachments = get_body_and_attachments(message) if not mailBody: warnings.append("Seems to be a reply to %s but I couldn't find a comment") return message #go throught work ts = TicketSystem(self.env) tm = TicketModule(self.env) perm = PermissionSystem(self.env) # TODO: Deprecate update without time_changed timestamp mockReq = self._MockReq(perm.get_user_permissions(reporter), reporter) avail_actions = ts.get_available_actions(mockReq, ticket) mailBody, inBodyFields, actions = self._get_in_body_fields(mailBody, avail_actions, reporter) if inBodyFields or actions : # check permissions perm = PermissionSystem(self.env) #we have properties movement, cheking user permission to do so if not perm.check_permission('MAIL2TICKET_PROPERTIES', reporter) : # None -> 'anoymous' raise ("%s does not have MAIL2TICKET_PROPERTIES permissions" % (user or 'anonymous')) action = None if actions : action = actions.keys()[0] controllers = list(tm._get_action_controllers(mockReq, ticket, action)) all_fields = [field['name'] for field in ts.get_ticket_fields()] #impact changes find in inBodyFields for field in inBodyFields : ticket._old[field] = ticket[field] ticket.values[field] = inBodyFields[field] mockReq.args[field] = inBodyFields[field] if action : mockReq.args['action_%s_reassign_owner' % action] = ticket['owner'] mockReq.args['comment'] = mailBody mockReq.args['ts'] = datetime.now()#to_datetime(None, utc) mockReq.args['ts'] = str(ticket.time_changed) changes, problems = tm.get_ticket_changes(mockReq, ticket, action) valid = problems and False or tm._validate_ticket(mockReq, ticket) tm._apply_ticket_changes(ticket, changes) # add attachments to the ticket add_attachments(self.env, ticket, attachments) ticket.save_changes(reporter, mailBody) for controller in controllers: controller.apply_action_side_effects(mockReq, ticket, action) # Call ticket change listeners for listener in ts.change_listeners: listener.ticket_changed(ticket, mailBody, reporter, ticket._old) tn = TicketNotifyEmail(self.env) tn.notify(ticket, newticket=0, modtime=ticket.time_changed)