def newGroupPost(handler, p_group, p_name): def die(msg): print msg done() handler.title('New Group') requirePriv(handler, 'User') handler.wrappers = False predid = to_int(p_group, 'group', die) pred = Group.load(predid) if not pred: die("No group with ID <b>%d</b>" % predid) elif p_name.strip() == '': die("Group must have a non-empty name") group = Group(pred.sprint.id, p_name, pred.seq + 1) group.save() handler.responseCode = 299 delay(handler, """ <script type=\"text/javascript\"> $(document).ready(function() { $('#group%d').effect('highlight', {}, 3000); }); </script>""" % group.id) print "/sprints/%d#group%d" % (group.sprint.id, group.id) Event.newGroup(handler, group)
def deleteGroupPost(handler, id, p_newGroup = None): def die(msg): print msg done() handler.title('Manage Group') requirePriv(handler, 'User') handler.wrappers = False id = to_int(id, 'id', die) if p_newGroup is not None: p_newGroup = to_int(p_newGroup, 'newGroup', die) group = Group.load(id) if not group: ErrorBox.die('Invalid Group', "No group with ID <b>%d</b>" % id) if not group.deletable: ErrorBox.die('Invalid Group', "Group is not deletable") if len(group.sprint.getGroups()) == 1: ErrorBox.die('Invalid Group', "Cannot delete the last group in a sprint") tasks = group.getTasks() newGroup = None if p_newGroup == 0: # Delete tasks for task in tasks: task.deleted = True task.revise() elif p_newGroup is not None: # Move tasks newGroup = Group.load(p_newGroup) if not newGroup: ErrorBox.die('Invalid Group', "No group with ID <b>%d</b>" % p_newGroup) # Move all the tasks to the end of the new group seq = len(newGroup.getTasks()) for task in tasks: seq += 1 task.move(seq, newGroup) elif len(tasks): ErrorBox.die('Invalid Request', "Missing new group request argument") sprintid = group.sprintid group.delete() Event.deleteGroup(handler, group) redirect("/sprints/%d%s" % (sprintid, "#group%d" % newGroup.id if newGroup else ''))
def newTaskPost(handler, p_group, p_name, p_goal, p_status, p_hours, p_assigned=[]): def die(msg): print msg done() requirePriv(handler, "User") handler.wrappers = False groupid = to_int(p_group, "group", die) group = Group.load(groupid) if not group or group.sprint.isHidden(handler.session["user"]): die("No group with ID <b>%d</b>" % groupid) sprint = group.sprint if not (sprint.isActive() or sprint.isPlanning()): die("Unable to modify inactive sprint") elif not sprint.canEdit(handler.session["user"]): die("You don't have permission to modify this sprint") if p_name.strip() == "": die("Task must have a non-empty name") assignedids = set(to_int(i, "assigned", die) for i in p_assigned) assigned = set(User.load(assignedid) for assignedid in assignedids) if assigned == set(): assigned.add(handler.session["user"] if handler.session["user"] in sprint.members else sprint.owner) if not all(assigned): die("Invalid assignee") goalid = to_int(p_goal, "goal", die) if goalid != 0: goal = Goal.load(goalid) if not goal: die("No goal with ID <b>%d</b>" % goalid) if goal.sprint != group.sprint: die("Goal does not belong to the correct sprint") hours = to_int(p_hours, "hours", die) task = Task(groupid, group.sprintid, handler.session["user"].id, goalid, p_name, p_status, hours) task.assigned |= assigned task.save() handler.responseCode = 299 delay( handler, """ <script type=\"text/javascript\"> $(document).ready(function() { $('#task%d').effect('highlight', {}, 3000); }); </script>""" % task.id, ) delay(handler, SuccessBox("Added task <b>%s</b>" % task.safe.name, close=3, fixed=True)) Event.newTask(handler, task)
def renameGroupPost(handler, id, p_name): def die(msg): print msg done() handler.title('Manage Group') requirePriv(handler, 'User') id = int(id) group = Group.load(id) if not group: ErrorBox.die('Invalid Group', "No group with ID <b>%d</b>" % id) elif p_name.strip() == '': ErrorBox.die('Invalid Name', "Group must have a non-empty name") oldName = group.name group.name = p_name group.save() Event.renameGroup(handler, group, oldName) redirect("/sprints/%d#group%d" % (group.sprintid, group.id))
def assignGroupGoalPost(handler, id, p_goal): def die(msg): print msg done() handler.title('Manage Group') requirePriv(handler, 'User') handler.wrappers = False id = int(id) group = Group.load(id) if not group: ErrorBox.die('Invalid Group', "No group with ID <b>%d</b>" % id) if p_goal == '0': goal = None else: goal = Goal.load(int(p_goal)) if not goal: ErrorBox.die('Invalid Goal', "No goal with ID <b>%d</b>" % int(p_goal)) elif not goal.sprint == group.sprint: ErrorBox.die('Invalid Goal', "Selected goal is not part of the correct sprint") for task in group.getTasks(): if task.goal != goal: task.goal = goal if task.creator == handler.session['user'] and (dateToTs(getNow()) - task.timestamp) < 5*60: task.save() else: task.saveRevision(handler.session['user']) #TODO Event # NO redirect("/sprints/%d#group%d" % (group.sprintid, group.id))
def newGroup(handler, after): handler.title('New Group') requirePriv(handler, 'User') afterID = int(after) afterGroup = Group.load(afterID) if not afterGroup: ErrorBox.die('Invalid Group', "No group with ID <b>%d</b>" % afterID) print "<style type=\"text/css\">" print "table.list td.right > * {width: 400px;}" print "table.list td.right button {width: 200px;}" # Half of the above value print "</style>" print "<script type=\"text/javascript\">" print "next_url = '/sprints/%d';" % afterGroup.sprint.id print "</script>" print "<script src=\"/static/groups.js\" type=\"text/javascript\"></script>" print InfoBox('', id = 'post-status') print "<form method=\"post\" action=\"/groups/new\">" print "<table class=\"list\">" print "<tr><td class=\"left\">Sprint:</td><td class=\"right\"><select id=\"selectSprint\" disabled><option>%s</option></select></td></tr>" % afterGroup.sprint print "<tr><td class=\"left\">Predecessor:</td><td class=\"right\">" print "<select id=\"select-group\" name=\"group\" size=\"5\">" for sGroup in afterGroup.sprint.getGroups('name'): print "<option value=\"%d\"%s>%s</option>" % (sGroup.id, ' selected' if sGroup == afterGroup else '', sGroup.safe.name) print "</select>" print "</td></tr>" print "<tr><td class=\"left\">Name:</td><td class=\"right\"><input type=\"text\" name=\"name\" class=\"defaultfocus\"></td></tr>" print "<tr><td class=\"left\"> </td><td class=\"right\">" print Button('Save', id = 'save-button', type = 'button').positive() print Button('Cancel', id = 'cancel-button', type = 'button').negative() print "</td></tr>" print "</table>" print "</form>"
def sprintPost(handler, sprintid, p_id, p_rev_id, p_field, p_value): def die(msg): print msg done() handler.wrappers = False sprintid = to_int(sprintid, 'sprintid', die) p_id = to_int(p_id, 'id', die) p_rev_id = to_int(p_rev_id, 'rev_id', die) if not handler.session['user']: die("You must be logged in to modify tasks") sprint = Sprint.load(sprintid) if not sprint or sprint.isHidden(handler.session['user']): die("There is no sprint with ID %d" % sprintid) elif not (sprint.isActive() or sprint.isPlanning()): die("Unable to modify inactive sprint") elif not sprint.canEdit(handler.session['user']): die("You don't have permission to modify this sprint") # Special case group moves; p_id is the group ID, not task task = None if p_field != 'groupmove': task = Task.load(p_id) if not task: die("Task %d does not exist" % p_id) if task.sprint != sprint: die("Attempting to modify task outside the specified sprint") if task.revision != p_rev_id: #TODO Implement collision support die("Collision with %s detected. Changes not saved" % task.creator) if p_value.strip() == '': die("Value cannot be empty") if p_field in ['status', 'name', 'goal', 'assigned', 'hours', 'deleted']: for case in switch(p_field): if case('status') or case('name'): parsedValue = p_value break elif case('goal'): parsedValue = None if p_value != '0': parsedValue = Goal.load(to_int(p_value, 'goal', die)) if not parsedValue: die("Unknown goal: <b>%s</b>" % stripTags(p_value)) if parsedValue.sprint != sprint: die("Attempting to use goal outside the specified sprint") break elif case('assigned'): parsedValue = set(User.load(username = username) for username in p_value.split(' ')) if not all(parsedValue): die("Unknown user(s): <b>%s</b>" % stripTags(p_value)) break elif case('hours'): parsedValue = int(p_value) break elif case('deleted'): parsedValue = True if p_value == 'true' else False if p_value == 'false' else die("Bad value for field 'deleted'") break if task.__getattribute__(p_field) != parsedValue: # Only save if the field has changed task.__setattr__(p_field, parsedValue) # Is this within the 5-minute window, by the same user? # If we're in pre-planning, the task's timestamp will be in the future, so (ts - task.timestamp) will be negative, which satisfies the check if task.creator == handler.session['user'] and (dateToTs(getNow()) - task.timestamp) < 5*60: task.save() else: task.saveRevision(handler.session['user']) Event.taskUpdate(handler, task, p_field, parsedValue) elif p_field == 'taskmove': if ':' not in p_value: die("Malformed value") newGroupID, newSeq = map(lambda i: to_int(i, 'value', die), p_value.split(':', 1)) newGroup = Group.load(newGroupID) if not newGroup: die("No group with ID %d" % newGroupID) maxSeq = len(Task.loadAll(groupid = newGroup.id)) if task.group != newGroup: maxSeq += 1 if not 1 <= newSeq <= maxSeq: die("Bad sequence number") task.move(newSeq, newGroup) elif p_field == 'groupmove': group = Group.load(p_id) if not group: die("Group %d does not exist" % p_id) if group.sprint != sprint: die("Attempting to modify group outside the specified sprint") newSeq = to_int(p_value, 'value', die) if not 1 <= newSeq <= len(sprint.getGroups()): die("Bad sequence number") group.move(newSeq) else: die("Unexpected field name: %s" % stripTags(p_field)) handler.responseCode = 299 if task is not None: print task.revision
def newTaskImportPost(handler, group, source, p_data): def die(msg): print msg done() handler.title("Import Tasks") requirePriv(handler, "User") handler.wrappers = False id = int(group) group = Group.load(id) if not group or group.sprint.isHidden(handler.session["user"]): die("No group with ID <b>%d</b>" % id) sprint = group.sprint if not (sprint.isActive() or sprint.isPlanning()): die("Unable to modify inactive sprint") elif not sprint.canEdit(handler.session["user"]): die("You don't have permission to modify this sprint") id = int(source) source = Sprint.load(id) if not source: die("No sprint with ID <b>%d</b>" % id) try: data = fromJS(p_data) except ValueError: die("Improperly encoded data") if not isinstance(data, list) or not all( set(task.keys()) == {"name", "assigned", "status", "groupid", "hours"} for task in data ): die("Improperly encoded data") usernames = {user.username for user in sprint.members} if not all( set(task["assigned"].split(" ")) <= usernames and task["status"] in statuses and isinstance(task["groupid"], int) and Group.load(task["groupid"]) is not None and isinstance(task["hours"], int) and task["hours"] >= 0 for task in data ): die("Invalid data") dataByGroup = {} for task in data: if task["groupid"] not in dataByGroup: dataByGroup[task["groupid"]] = [] dataByGroup[task["groupid"]].append(task) newGroups = {} # old sprint's group ID -> new sprint's new Group object for groupid in dataByGroup: oldGroup = Group.load(groupid) group = Group.load(sprintid=sprint.id, name=oldGroup.name) if not group: # No group in this sprint with the right name if groupid in newGroups: # Already made a new group group = newGroups[groupid] else: # Need a new group group = newGroups[groupid] = Group(sprint.id, oldGroup.name) group.save() for taskData in dataByGroup[groupid]: task = Task( group.id, sprint.id, handler.session["user"].id, 0, taskData["name"], taskData["status"], taskData["hours"], {User.load(username=username).id for username in taskData["assigned"].split(" ")}, ) task.save() Event.newTask(handler, task) numGroups, numTasks = len(newGroups), len(data) if numGroups > 0 and numGroups > 0: delay( handler, SuccessBox( "Added %d %s, %d %s" % (numGroups, "group" if numGroups == 1 else "groups", numTasks, "task" if numTasks == 1 else "tasks"), close=3, fixed=True, ), ) elif numGroups > 0: delay( handler, SuccessBox("Added %d %s" % (numGroups, "group" if numGroups == 1 else "groups"), close=3, fixed=True), ) elif numTasks > 0: delay( handler, SuccessBox("Added %d %s" % (numTasks, "task" if numTasks == 1 else "tasks"), close=3, fixed=True) ) else: delay(handler, WarningBox("No changes", close=3, fixed=True)) handler.responseCode = 299
def newTaskImport(handler, group, source=None, assigned=None): # 'assigned' is ignored, it's just in case the user gets here from a filtered backlog handler.title("New Tasks") requirePriv(handler, "User") id = int(group) print tabs.format(id).where("import") group = Group.load(id) if not group or group.sprint.isHidden(handler.session["user"]): ErrorBox.die("Invalid Group", "No group with ID <b>%d</b>" % id) sprint = group.sprint if not (sprint.isActive() or sprint.isPlanning()): ErrorBox.die("Sprint Closed", "Unable to modify inactive sprint") elif not sprint.canEdit(handler.session["user"]): ErrorBox.die("Permission Denied", "You don't have permission to modify this sprint") sprints = sprint.project.getSprints() sprintIdx = sprints.index(sprint) prevSprint = sprints[sprintIdx - 1] if sprintIdx > 0 else None if not source: print "Select a sprint to import from:<br><br>" print '<form method="get" action="/tasks/new/import">' print '<input type="hidden" name="group" value="%d">' % group.id print '<select name="source" id="import-source">' for projectIter in Project.getAllSorted(handler.session["user"], sprint.project): print '<optgroup label="%s">' % projectIter.safe.name for sprintIter in projectIter.getSprints(): print '<option value="%d"%s>%s</option>' % ( sprintIter.id, " selected" if sprintIter == prevSprint else "", sprintIter.safe.name, ) print "</optgroup>" print "</select>" print "<br><br>" print Button("Next").positive().post() print "</form>" else: id = int(source) source = Sprint.load(id) if not source: ErrorBox.die("Invalid Sprint", "No sprint with ID <b>%d</b>" % id) print '<script type="text/javascript">' nextURL = "/sprints/%d" % sprint.id if assigned: nextURL += "?search=assigned:%s" % stripTags(assigned.replace(" ", ",")) print "next_url = %s;" % toJS(nextURL) print 'post_url = "/tasks/new/import?group=%d&source=%d";' % (group.id, source.id) print "scrummaster = %s;" % toJS(sprint.owner.username) print "TaskTable.init();" print "</script>" print '<b>Source sprint</b>: <a href="/sprints/%d">%s</a><br>' % (source.id, source.name) print '<b>Target sprint</b>: <a href="/sprints/%d">%s</a><br><br>' % (sprint.id, sprint.name) print "All incomplete tasks are listed here, with their current values from the source sprint. You can change any of the fields before importing. Only checked tasks will be imported<br><br>" assignedList = [sprint.owner] + list(sprint.members - {sprint.owner}) print TaskTable( source, editable=True, assignedList=assignedList, checkbox=True, status=True, name=True, assigned=True, hours=True, debug=isDevMode(handler), ) print InfoBox("Loading...", id="post-status", close=True) print Button("Import", id="save-button", type="button").positive() print Button("Cancel", id="cancel-button", type="button").negative() print "</form><br><br>"
def newTaskMany(handler, group, p_body, dryrun=False): def die(msg): print msg done() handler.wrappers = False requirePriv(handler, "User") id = int(group) defaultGroup = Group.load(id) if not defaultGroup or defaultGroup.sprint.isHidden(handler.session["user"]): die("No group with ID <b>%d</b>" % id) sprint = defaultGroup.sprint if not (sprint.isActive() or sprint.isPlanning()): die("Unable to modify inactive sprint") elif not sprint.canEdit(handler.session["user"]): die("You don't have permission to modify this sprint") group = defaultGroup groups = [group] newGroups = [] tasks = {group: []} sep = "|" lines = map(lambda x: x.strip(" \r\n"), p_body.split("\n")) errors = [] defaultAssigned = {handler.session["user"] if handler.session["user"] in sprint.members else sprint.owner} for line in lines: if line == "": group = defaultGroup elif line[0] == "#": # Comment continue elif len(line) == 1: # Separator sep = line[0] elif ( line[0] == "[" and line[-1] == "]" and set(line[1:-1].split(" ")) <= set(u.username for u in sprint.members) ): # Default assigned defaultAssigned = {User.load(username=username) for username in line[1:-1].split(" ")} elif line[-1] == ":": # Group line = line[:-1] group = Group.load(sprintid=sprint.id, name=line) if not group: # Check if new group already defined for newGroup in newGroups: if newGroup.name == line: group = newGroup break if not group: # Make new group group = Group(sprint.id, line) newGroups.append(group) group.id = -len(newGroups) if not group in groups: # First time this group has been used in the script groups.append(group) tasks[group] = [] else: parts = line.split(sep) name, assigned, status, hours = None, None, None, None if not 2 <= len(parts) <= 4: errors.append("Unable to parse (field count mismatch): %s" % stripTags(line)) continue for part in parts: part = part.strip() if part == "": errors.append("Unable to parse (empty field): %s" % stripTags(line)) continue # Hours if hours is None: try: hours = int(part) continue except ValueError: pass # Status if status is None and part.lower() in statuses: status = part.lower() continue # Assigned if assigned is None and set(part.split(" ")) <= set(u.username for u in sprint.members): assigned = set(User.load(username=username) for username in part.split(" ")) continue # Name if name is None: name = part continue errors.append("Unable to parse (no field match on '%s'): %s" % (stripTags(part), stripTags(line))) if assigned is None: assigned = defaultAssigned if status is None: status = "not started" if name is None or hours is None: errors.append("Unable to parse (missing required fields): %s" % stripTags(line)) if not any(v is None for v in (name, assigned, status, hours)): tasks[group].append((name, assigned, status, hours)) if dryrun: handler.log = False numTasks = sum(len(taskSet) for taskSet in tasks.values()) taskHours = sum( hours for taskSet in tasks.values() for name, assigned, status, hours in taskSet if status != "deferred" ) ownTaskHours = sum( hours for taskSet in tasks.values() for name, assigned, status, hours in taskSet if status != "deferred" and handler.session["user"] in assigned ) avail = Availability(sprint) availHours = avail.getAllForward(getNow().date(), handler.session["user"]) usedHours = sum(task.effectiveHours() for task in sprint.getTasks() if handler.session["user"] in task.assigned) availHours -= usedHours if errors: print ErrorBox("<br>".join(errors)) if numTasks: box = InfoBox stats = "Adding %s " % pluralize(numTasks, "task", "tasks") if newGroups: stats += "and %s " % pluralize(len(newGroups), "group", "groups") stats += "for a total of %s" % pluralize(taskHours, "hour", "hours") if ownTaskHours != taskHours: stats += ", %s yours" % pluralize(ownTaskHours, "hour", "hours") if ownTaskHours: if availHours == 0: stats += ". You have no future availability for these tasks" box = WarningBox elif availHours < 0: stats += ". You are already overcommitted by %s" % pluralize(-availHours, "hour", "hours") box = WarningBox else: stats += ", %d%% of your future availability" % (100 * ownTaskHours / availHours) box = WarningBox if ownTaskHours > availHours else InfoBox print box(stats) elif not errors: print InfoBox('Waiting for tasks. Click "Help" above if needed') groupedTasks = OrderedDict( ( group, [ Task( group.id, sprint.id, handler.session["user"].id, 0, name, status, hours, {user.id for user in assigned}, 1, id=0, ) for name, assigned, status, hours in tasks[group] ], ) for group in groups ) print TaskTable(sprint, False, tasks=groupedTasks, status=True, name=True, assigned=True, hours=True) elif errors: die("There are unparseable lines in the task script. See the preview for more information") else: # There's some weirdness in the way groups auto-sequence that breaks when multiple groups are made without saving seq = maxOr(group.seq for group in sprint.getGroups()) + 1 for group in newGroups: group.seq = seq seq += 1 for group in groups: # Changing a group's ID will change its hash, so this pulls from tasks before saving the group in case it's new groupTasks = tasks[group] if group in newGroups: group.id = 0 group.save() for name, assigned, status, hours in groupTasks: task = Task(group.id, group.sprint.id, handler.session["user"].id, 0, name, status, hours) task.assigned |= assigned task.save() Event.newTask(handler, task) numGroups = len(newGroups) numTasks = sum(map(lambda g: len(g), tasks.values())) if numGroups > 0 and numGroups > 0: delay( handler, SuccessBox( "Added %d %s, %d %s" % ( numGroups, "group" if numGroups == 1 else "groups", numTasks, "task" if numTasks == 1 else "tasks", ), close=3, fixed=True, ), ) elif numGroups > 0: delay( handler, SuccessBox("Added %d %s" % (numGroups, "group" if numGroups == 1 else "groups"), close=3, fixed=True), ) elif numTasks > 0: delay( handler, SuccessBox("Added %d %s" % (numTasks, "task" if numTasks == 1 else "tasks"), close=3, fixed=True), ) else: delay(handler, WarningBox("No changes", close=3, fixed=True)) handler.responseCode = 299
def newTaskMany(handler, group, assigned=None): handler.title("New Tasks") requirePriv(handler, "User") id = int(group) body = "" if "many-upload" in handler.session: body = handler.session["many-upload"] del handler.session["many-upload"] elif assigned: body = "[%s]\n" % stripTags(assigned) defaultGroup = Group.load(id) if not defaultGroup or defaultGroup.sprint.isHidden(handler.session["user"]): ErrorBox.die("Invalid Group", "No group with ID <b>%d</b>" % id) sprint = defaultGroup.sprint print '<script src="/static/jquery.typing-0.2.0.min.js" type="text/javascript"></script>' print '<script type="text/javascript">' nextURL = "/sprints/%d" % sprint.id if assigned: nextURL += "?search=assigned:%s" % stripTags(assigned.replace(" ", ",")) print "next_url = %s;" % toJS(nextURL) print "TaskTable.init();" print "</script>" print tabs.format(id).where("many") if not (sprint.isActive() or sprint.isPlanning()): ErrorBox.die("Sprint Closed", "Unable to modify inactive sprint") elif not sprint.canEdit(handler.session["user"]): ErrorBox.die("Permission Denied", "You don't have permission to modify this sprint") help = ResponseWriter() print "Each line needs to match the following syntax. Unparseable lines generate an error message in the preview and must be resolved before saving" print "<ul>" print "<li><b>X</b> — A single character changes the field separator to that character. The exception is #, which starts a comment. The default field separator is |, so that's used in the examples here</li>" print '<li><b>X...X:</b> — A line ending in a colon is a group name. All tasks after that line will be added to that group. If no group of that name exists, it will be created (the preview will label that group as "(NEW)"). A blank line switches back to the default group, which is the group you clicked the new task button on, %s' % defaultGroup.safe.name print "<li><b>X...X|X...X[|X...X[|X...X]]</b> — 2-4 fields are a new task. The fields can appear in any order:<ul>" print "<li><b>name</b> — The name of the task</li>" print "<li><b>hours</b> — The number of hours this task will take</li>" print "<li><b>assignee</b> — The person assigned to this task. If multiple people, separate usernames with spaces. This field is optional as long as <b>status</b> is also omitted; it defaults to the current user if a sprint member, or the scrummaster otherwise, unless overridden (see below)</li>" print '<li><b>status</b> — The initial status of the task. This field is optional; it defaults to "not started"</li>' print "</ul></li>" print "<li><b>[X...X]</b> — A username (or space-separated list of usernames) wrapped in brackets makes that user or group the default assignee for all tasks that don't specify an assignee</li>" print "<li><b>#...</b> — A line starting with a hash character is a comment, and is ignored. You can only comment out entire lines; a hash within a line does not start a comment at that point</li>" print "</ul>" print "You can also use the form above the textarea to upload a text file. The file will be used to fill the textarea, so it should match the syntax described above" print CollapsibleBox("Help", help.done()) print CollapsibleBox( "Groups", "<ul>%s</ul>" % "".join( "<li>%s</li>" % ("<b>%s</b> (default)" if group == defaultGroup else "%s") % group.safe.name for group in sprint.getGroups() ), ) print '<form id="upload-tasks" method="post" enctype="multipart/form-data" action="/tasks/new/many/upload?group=%d">' % defaultGroup.id print '<input type="file" name="data"><br><br>' print "</form>" print '<form id="write-tasks" method="post" action="/tasks/new/many?group=%d">' % defaultGroup.id print '<textarea id="many-body" name="body" class="defaultfocus">%s</textarea>' % body print '<div id="preview"></div>' print InfoBox("Loading...", id="post-status", close=True) print '<div id="new-task-many-buttons">' print Button("Save All", id="save-button", type="button").positive() print Button("Cancel", id="cancel-button", type="button").negative() print "</div>" print "</form>"
def newTaskSingle(handler, group, assigned=""): handler.title("New Task") requirePriv(handler, "User") id = int(group) assigned = assigned.split(" ") print tabs.format(id).where("single") group = Group.load(id) if not group or group.sprint.isHidden(handler.session["user"]): ErrorBox.die("Invalid Group", "No group with ID <b>%d</b>" % id) sprint = group.sprint if not (sprint.isActive() or sprint.isPlanning()): ErrorBox.die("Sprint closed", "Unable to modify inactive sprint") elif not sprint.canEdit(handler.session["user"]): ErrorBox.die("Permission denied", "You don't have permission to modify this sprint") print '<script type="text/javascript">' nextURL = "/sprints/%d" % group.sprint.id if assigned: nextURL += "?search=assigned:%s" % ",".join(assigned) nextURL += "#group%d" % group.id print "next_url = %s;" % toJS(nextURL) print "</script>" print InfoBox("", id="post-status", close=True) print '<form method="post" action="/tasks/new/single">' print '<table class="list">' print '<tr><td class="left">Sprint:</td><td class="right"><select id="selectSprint" disabled><option>%s</option></select></td></tr>' % group.sprint print '<tr><td class="left">Group:</td><td class="right">' print '<select id="select-group" name="group" size="5">' for sGroup in group.sprint.getGroups("name"): print '<option value="%d"%s>%s</option>' % (sGroup.id, " selected" if sGroup == group else "", sGroup.safe.name) print "</select>" print "</td></tr>" print '<tr><td class="left">Name:</td><td class="right"><input type="text" name="name" class="defaultfocus"></td></tr>' print '<tr><td class="left">Sprint Goal:</td><td class="right">' print '<select id="select-goal" name="goal" size="5">' print '<option value="0" selected>None</option>' for goal in group.sprint.getGoals(): print '<option value="%d">%s</option>' % (goal.id, goal.safe.name) print "</select>" print "</td></tr>" print '<tr><td class="left">Status:</td><td class="right">' print '<select id="select-status" name="status" size="10">' first = True for statusSet in statusMenu: for name in statusSet: print '<option value="%s"%s>%s</option>' % (name, " selected" if first else "", statuses[name].text) first = False print "</status>" print "</td></tr>" print '<tr><td class="left">Assigned:</td><td class="right">' print '<select id="select-assigned" name="assigned[]" data-placeholder="Choose assignees (or leave blank to self-assign)" size="10" multiple>' for user in sorted(group.sprint.members): print '<option value="%d"%s>%s</option>' % ( user.id, " selected" if user.username in assigned else "", user.safe.username, ) print "</select>" print "</td></tr>" print '<tr><td class="left">Hours:</td><td class="right"><input type="text" name="hours" value="8"></td></tr>' print '<tr><td class="left"> </td><td class="right">' print Button("Save", id="save-button", type="button").positive() print Button("Cancel", id="cancel-button", type="button").negative() print "</td></tr>" print "</table>" print "</form>"
def editGroup(handler, id): requirePriv(handler, 'User') handler.title('Manage Group') id = int(id) group = Group.load(id) if not group: ErrorBox.die('Invalid Group', "No group with ID <b>%d</b>" % id) tasks = group.getTasks() chart = GroupGoalsChart(group) Chart.include() chart.js() print "<style type=\"text/css\">" print "table.list td.left {position: relative; top: 4px;}" print "table.list td.right *, button {width: 400px;}" print "table.list td.right button {width: 200px;}" # Half of the above value print "</style>" print "<h2>Edit Group</h2>" print "<form method=\"post\" action=\"/groups/%d/rename\">" % id print "<table class=\"list\">" print "<tr><td class=\"left\">Name:</td><td class=\"right\"><input type=\"text\" name=\"name\" value=\"%s\">" % (group.safe.name) print "<tr><td class=\"left\">Predecessor:</td><td class=\"right\">" #TODO Waiting on group moving print "<select name=\"predecessor\" disabled>" print "<option>%s</option>" % ('None' if group.seq == 1 else Group.load(sprintid = group.sprintid, seq = group.seq-1).safe.name) print "</select>" print "</td></tr>" print "<tr><td class=\"left\"> </td><td class=\"right\">" print Button('Save', type = 'submit').positive() print Button('Cancel', "/sprints/%d#group%d" % (group.sprintid, id), type = 'button').negative() print "</td></tr>" print "</table>" print "</form>" print "<h2>Assign Goal</h2>" print "This is a quick way to set all the tasks in the group to the same sprint goal. The current breakdown is:" chart.placeholder() # The default is whichever goal currently has the most occurrences defaultGoal = max((sum(task.goal == goal or False for task in tasks), goal) for goal in group.sprint.getGoals() + [None])[1] print "<form method=\"post\" action=\"/groups/%d/goal\">" % id for goal in group.sprint.getGoals(): if goal.name: print "<input type=\"radio\" id=\"goal%d\" name=\"goal\" value=\"%d\"%s> <label for=\"goal%d\"><img class=\"bumpdown\" src=\"/static/images/tag-%s.png\"> %s</label><br>" % (goal.id, goal.id, ' checked' if goal == defaultGoal else '', goal.id, goal.color, goal.name) print "<input type=\"radio\" id=\"goal0\" name=\"goal\" value=\"0\"%s> <label for=\"goal0\"><img class=\"bumpdown\" src=\"/static/images/tag-none.png\"> Other</label><br><br>" % ('' if defaultGoal else ' checked') print Button('Assign', type = 'submit').positive() print "</form>" print "<h2>Delete Group</h2>" if len(group.sprint.getGroups()) == 1: print "You can't delete the last group in a sprint" elif not group.deletable: print "This group is undeletable" else: print "<form method=\"post\" action=\"/groups/%d/delete\">" % id if len(tasks): print "This group has %d %s. Move %s to the <select name=\"newGroup\">" % (len(tasks), 'task' if len(tasks) == 1 else 'tasks', 'it' if len(tasks) == 1 else 'them') for thisGroup in group.sprint.getGroups('name'): if group == thisGroup: continue print "<option value=\"%d\">%s</option>" % (thisGroup.id, thisGroup.safe.name) print "<option value=\"0\">None (delete)</option>" print "</select> group<br><br>" print Button('Delete', type = 'submit') print "</form>"