def process_request(self, req): req.perm.assert_permission('BACKLOG_VIEW') ats = AgileToolsSystem(self.env) if req.get_header('X-Requested-With') == 'XMLHttpRequest': if req.method == "POST": if not req.perm.has_permission("BACKLOG_ADMIN"): return self._json_errors(req, ["BACKLOG_ADMIN permission required"]) str_ticket= req.args.get("ticket") str_relative = req.args.get("relative", 0) direction = req.args.get("relative_direction") milestone = req.args.get("milestone") # Moving a single ticket position (and milestone) if str_ticket: try: int_ticket = int(str_ticket) int_relative = int(str_relative) except (TypeError, ValueError): return self._json_errors(req, ["Invalid arguments"]) try: ticket = Ticket(self.env, int_ticket) except ResourceNotFound: return self._json_errors(req, ["Not a valid ticket"]) response = {} # Change ticket's milestone if milestone is not None: try: self._save_ticket(req, ticket, milestone) ticket = self._get_permitted_tickets(req, constraints={'id': [str(int_ticket)]}) response['tickets'] = self._get_ticket_data(req, ticket) except ValueError as e: return self._json_errors(req, e.message) # Reposition ticket if int_relative: position = ats.position(int_relative, generate=True) if direction == "after": position += 1 ats.move(int_ticket, position, author=req.authname) response['success'] = True return self._json_send(req, response) # Dropping multiple tickets into a milestone elif all (k in req.args for k in ("tickets", "milestone", "changetimes")): changetimes = req.args["changetimes"].split(",") milestone = req.args["milestone"] try: ids = [int(tkt_id) for tkt_id in req.args["tickets"].split(",")] except (ValueError, TypeError): return self._json_errors(req, ["Invalid arguments"]) unique_errors = 0 errors_by_ticket = [] # List of [<ticket_id>, [<error>, ...]] lists if len(ids) == len(changetimes): for i, int_ticket in enumerate(ids): # Valid ticket try: ticket = Ticket(self.env, int_ticket) except ResourceNotFound: errors_by_ticket.append([int_ticket, ["Not a valid ticket"]]) # Can be saved try: self._save_ticket(req, ticket, milestone, ts=changetimes[i]) except ValueError as e: # Quirk: all errors amalgomated into single # we keep track of count at each time so that # we can split the list up to errors by # individual tickets errors_by_ticket.append([int_ticket, e.message[unique_errors:]]) unique_errors = len(e.message) if len(errors_by_ticket) > 5: errors_by_ticket.append("More than 5 tickets failed " "validation, stopping.") break if errors_by_ticket: return self._json_errors(req, errors_by_ticket) else: # Client side makes additional request for all # tickets after this return self._json_send(req, {'success': True}) else: return self._json_errors(req, ["Invalid arguments"]) else: return self._json_errors(req, ["Must provide a ticket"]) else: # TODO make client side compatible with live updates milestone = req.args.get("milestone") from_iso = req.args.get("from") to_iso = req.args.get("to") if milestone is not None: # Requesting an update constr = { 'milestone': [milestone] } if from_iso and to_iso: constr['changetime'] = [from_iso + ".." + to_iso] tickets = self._get_permitted_tickets(req, constraints=constr) formatted = self._get_ticket_data(req, tickets) self._json_send(req, {'tickets': formatted}) else: self._json_errors(req, ["Invalid arguments"]) else: add_script(req, 'agiletools/js/jquery.history.js') add_script(req, "agiletools/js/update_model.js") add_script(req, "agiletools/js/backlog.js") add_stylesheet(req, "agiletools/css/backlog.css") milestones_select2 = Milestone.select_names_select2(self.env, include_complete=False) milestones_select2['results'].insert(0, { "children": [], "text": "Product Backlog", "id": "backlog", "is_backlog": True, }) milestones_flat = [milestone.name for milestone in Milestone.select(self.env, include_completed=False, include_children=True)] script_data = { 'milestones': milestones_select2, 'milestonesFlat': milestones_flat, 'backlogAdmin': req.perm.has_permission("BACKLOG_ADMIN"), } add_script_data(req, script_data) data = {'top_level_milestones': Milestone.select(self.env)} # Just post the basic template, with a list of milestones # The JS will then make a request for tickets in no milestone # and tickets in the most imminent milestone # The client will be able to make subsequent requests to pull # tickets from other milestones and drop tickets into them return "backlog.html", data, None
def process_request(self, req): req.perm.assert_permission('TICKET_VIEW') # set the default user query if req.path_info == '/taskboard/set-default-query' and req.method == 'POST': self._set_default_query(req) # these headers are only needed when we update tickets via ajax req.send_header("Cache-Control", "no-cache, no-store, must-revalidate") req.send_header("Pragma", "no-cache") req.send_header("Expires", 0) xhr = req.get_header('X-Requested-With') == 'XMLHttpRequest' group_by = req.args.get("group", "status") user_saved_query = False milestones = Milestone.select_names_select2(self.env, include_complete=False) # Try to find a user selected milestone in request - if not found # check session_attribute for a user saved default, and if that is also # not found and fall back on the most upcoming milestone by due date milestone = req.args.get("milestone") milestone_not_found = False if milestone: try: Milestone(self.env, milestone) except ResourceNotFound: milestone_not_found = True milestone = None if not milestone: # try and find a user saved default default_milestone = req.session.get('taskboard_user_default_milestone') if default_milestone: milestone = default_milestone group_by = req.session.get('taskboard_user_default_group') user_saved_query = True # fall back to most imminent milestone by due date elif len(milestones["results"]): milestone = milestones["results"][0]["text"] # Ajax post if req.args.get("ticket") and xhr: result = self.save_change(req, milestone) req.send(to_json(result), 'text/json') else: data = {} constr = {} if milestone: constr['milestone'] = [milestone] # Ajax update: tickets changed between a period if xhr: from_iso = req.args.get("from", "") to_iso = req.args.get("to", "") if from_iso and to_iso: constr['changetime'] = [from_iso + ".." + to_iso] # Get all tickets by milestone and specify ticket fields to retrieve cols = self._get_display_fields(req, user_saved_query) tickets = self._get_permitted_tickets(req, constraints=constr, columns=cols) sorted_cols = sorted([f for f in self.valid_display_fields if f['name'] not in ('summary', 'type')], key=lambda f: f.get('label')) if tickets: s_data = self.get_ticket_data(req, milestone, group_by, tickets) s_data['total_tickets'] = len(tickets) s_data['display_fields'] = cols data['cur_group'] = s_data['groupName'] else: s_data = {} data['cur_group'] = group_by if xhr: if constr.get("changetime"): s_data['otherChanges'] = \ self.all_other_changes(req, tickets, constr['changetime']) req.send(to_json(s_data), 'text/json') else: s_data.update({ 'formToken': req.form_token, 'milestones': milestones, 'milestone': milestone, 'group': group_by, 'default_columns': self.default_display_fields }) data.update({ 'milestone_not_found': milestone_not_found, 'current_milestone': milestone, 'group_by_fields': self.valid_grouping_fields, 'fields': dict((f['name'], f) for f in self.valid_display_fields), 'all_columns': [f['name'] for f in sorted_cols], 'col': cols, 'condensed': self._show_condensed_view(req, user_saved_query) }) add_script(req, 'agiletools/js/update_model.js') add_script(req, 'agiletools/js/taskboard.js') add_script(req, 'common/js/query.js') add_script_data(req, s_data) add_stylesheet(req, 'agiletools/css/taskboard.css') add_stylesheet(req, 'common/css/ticket.css') add_ctxtnav(req, tag.a(tag.i(class_='fa fa-bookmark'), _(" Set as default"), id_='set-default-query', title=_("Make this your default taskboard"))) return "taskboard.html", data, None