def userAvatarSet(handler, username): handler.title('Set avatar') requirePriv(handler, 'User') user = User.load(username = username) if not user: ErrorBox.die("Set avatar", "No user named <b>%s</b>" % stripTags(username)) if user != handler.session['user']: #TODO Allow devs redirect("/users/%s/avatar/set" % handler.session['user'].username) if not Image: ErrorBox.die("Set avatar", "This sprint install does not have the Python Imaging Library (PIL), so local avatars are not supported") print "Restrictions on a locally hosted avatar:" print "<ul>" print "<li>Type: %s</li>" % ", ".join(AVATAR_TYPES).upper() print "<li>Size: %s bytes</li>" % AVATAR_MAX_SIZE print "</ul>" print "<form method=\"post\" enctype=\"multipart/form-data\" action=\"/users/%s/avatar/set\">" % user.username print "<input type=\"file\" name=\"data\"><br>" # Using a plain button here because the file field isn't styled print "<button>Upload</button>" print "</form>" if user.hasLocalAvatar(): print "<br>" print "You can also remove your existing local avatar. Your account will switch back to using your gravatar image<br>" print "<form method=\"post\" action=\"/users/%s/avatar/remove\">" % user.username print "<button>Remove avatar</button>" print "</form>"
def adminProjects(handler): handler.title('Project Management') requirePriv(handler, 'Admin') 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>" undelay(handler) print "<h3>New Project</h3>" print "<form method=\"post\" action=\"/admin/projects\">" print "<table class=\"list\">" print "<tr><td class=\"left\">Name:</td><td class=\"right\"><input type=\"text\" name=\"name\"></td></tr>" print "<tr><td class=\"left\"> </td><td class=\"right\">" print Button('Save', id = 'save-button', type = 'submit').positive() print Button('Cancel', type = 'button', url = '/admin').negative() print "</td></tr>" print "</table>" print "</form>" print "<h3>Current Projects</h3>" for project in Project.getAllSorted(handler.session['user']): print "<a href=\"/admin/projects/%d\">%s</a><br>" % (project.id, project.safe.name)
def newNoteModify(handler, taskid, id, p_action): handler.title("New Note") requirePriv(handler, "User") if p_action != "delete": ErrorBox.die("Invalid Action", "Unrecognized action <b>%s</b>" % p_action) taskid = int(taskid) task = Task.load(taskid) if not task or task.sprint.isHidden(handler.session["user"]): ErrorBox.die("Invalid Task", "No task with ID <b>%d</b>" % taskid) id = int(id) note = Note.load(id) if not note: ErrorBox.die("Invalid Note", "No note with ID <b>%d</b>" % noteid) elif note.task != task: # Doesn't really matter, but shouldn't happen ErrorBox.die("Task mismatch", "Note/task mismatch") elif note.user != handler.session["user"]: ErrorBox.die("Permission denied", "Notes can only be deleted by their creators") note.delete() delay(handler, SuccessBox("Deleted note", close=3)) Event.deleteNote(handler, note) redirect("/tasks/%d" % task.id)
def updateSavedSearch(handler, id, p_action, p_name, p_query, p_share = False): handler.title('Update Search') requirePriv(handler, 'User') search = SavedSearch.load(int(id)) if not search: ErrorBox.die('Invalid Search', "No search with ID <b>%d</b>" % int(id)) elif search.user != handler.session['user']: ErrorBox.die('Permission Denied', "You cannot modify another user's search") if p_action == 'update': search.name = p_name search.query = p_query search.public = p_share search.save() if not search.public: search.unfollow() delay(handler, SuccessBox("Updated search <b>%s</b>" % search.safe.name)) elif p_action == 'delete': search.delete() delay(handler, SuccessBox("Deleted search <b>%s</b>" % search.safe.name)) else: ErrorBox.die('Invalid Action', "Unknown action %s" % stripTags(p_action)) redirect('/search/saved')
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 findActiveSprint(handler, project = None, search = None): handler.title('Active Sprint') requirePriv(handler, 'User') if project: projectid = int(project) project = Project.load(projectid) if not project: ErrorBox.die('Load project', "No project with ID <b>%d</b>" % projectid) url = "/sprints/%d" if search: url += "?search=%s" % search sprints = Sprint.loadAllActive(handler.session['user'], project) sprints = filter(lambda sprint: sprint.canView(handler.session['user']), sprints) for case in switch(len(sprints)): if case(0): ErrorBox.die('Active sprint', 'No active sprints found') break if case(1): redirect(url % sprints[0].id) break if case(): print "You are active in multiple sprints%s:<br><br>" % (" in the %s project" % project.safe.name if project else '') for sprint in sprints: print "<a href=\"%s\">%s</a><br>" % (url % sprint.id, sprint.safe.name) break
def searchSavedOthers(handler): handler.title('Saved Searches') requirePriv(handler, 'User') print tabs.where('others') undelay(handler) print "<style type=\"text/css\">" print ".other-search {padding-bottom: 4px; border-bottom: 1px dashed #000;}" print ".other-search h2 {margin-bottom: 4px;}" print ".other-search small {float: right; font-weight: normal; font-size: 12pt;}" print ".other-search code {font-size: 14pt;}" print "</style>" searches = filter(lambda search: search.user != handler.session['user'] and search.public, SavedSearch.loadAll(orderby = 'name')) if searches == []: print "No shared searches available" else: for search in searches: print "<div class=\"other-search\">" print "<h2>%s<small><img class=\"bumpdown\" src=\"%s\"> %s</small></h2>" % (search.safe.name, search.user.getAvatar(16), search.user.username) print "<code>%s</code><br><br>" % search.safe.query following = handler.session['user'] in search.followers print "<form method=\"post\" action=\"/search/saved/%d/%s\">" % (search.id, 'unfollow' if following else 'follow') print Button('Run', url = "/search/saved/%d/run" % search.id) btn = Button('Unfollow' if following else 'Follow', type = 'submit') if following: btn.negative() else: btn.positive() print btn print "</form>" print "</div>"
def adminUsers(handler): handler.title('User Management') requirePriv(handler, 'Admin') 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>" undelay(handler) print "<h3>New User</h3>" print "<form method=\"post\" action=\"/admin/users\">" print "<input type=\"hidden\" name=\"action\" value=\"new\">" print "<table class=\"list\">" print "<tr><td class=\"left\">Username:</td><td class=\"right\"><input type=\"text\" name=\"username\"></td></tr>" print "<tr><td class=\"left\">Privileges:</td><td class=\"right\"><div>" for name, desc in privList.iteritems(): print "<input type=\"checkbox\" name=\"privileges[]\" id=\"priv_%s\" value=\"%s\"%s><label for=\"priv_%s\">%s — %s</label><br>" % (name, name, ' checked' if name in privDefaults else '', name, name, desc) print "</div></td></tr>" if settings.smtpServer: print "<tr><td class=\"left\">Contact:</td><td class=\"right\"><div><input type=\"checkbox\" name=\"send_welcome\" id=\"send_welcome\" checked><label for=\"send_welcome\">Send welcome e-mail</label></div></td></tr>" print "<tr><td class=\"left\"> </td><td class=\"right\">" print Button('Save', id = 'save-button', type = 'submit').positive() print Button('Cancel', type = 'button', url = '/admin').negative() print "</td></tr>" print "</table>" print "</form><br>" print "<h3>Current Users</h3>" users = User.loadAll(orderby = 'username') print "<div class=\"user-list\">" for user in users: print "<div class=\"user-list-entry\"><a href=\"/users/%s\"><img src=\"%s\"></a><br>%s</div>" % (user.username, user.getAvatar(64), user.safe.username) print "</div>" print "<div class=\"clear\"></div>"
def searchSaved(handler): handler.title('Saved Searches') requirePriv(handler, 'User') print tabs.where('yours') undelay(handler) searches = SavedSearch.loadAll(userid = handler.session['user'].id) if searches != []: print "<table border=0 cellspacing=4>" print "<tr><th>Name</th><th>Query</th><th>Shared</th><th> </th></tr>" for search in searches: print "<form method=\"post\" action=\"/search/saved/%d/update\">" % search.id print "<tr>" print "<td><input type=\"text\" name=\"name\" value=\"%s\"></td>" % search.name.replace('"', '"') print "<td style=\"width: 100%%\"><input type=\"text\" name=\"query\" value=\"%s\" style=\"width: 100%%\"></td>" % search.query.replace('"', '"') print "<td style=\"text-align: center\"><input type=\"checkbox\" name=\"share\"%s></td>" % (' checked' if search.public else '') print "<td nowrap>" print "<button onClick=\"document.location = '/search/saved/%d/run'; return false;\" class=\"fancy\">run</button>" % search.id print "<button type=\"submit\" class=\"fancy\" name=\"action\" value=\"update\">update</button>" print "<button type=\"submit\" class=\"fancy danger\" name=\"action\" value=\"delete\">delete</button>" print "</td>" print "</tr>" print "</form>" print "</table>" print "<h3>New Search</h3>" newSearchForm()
def twoFactorAuthentication(handler, p_action): handler.title("Two-Factor Authentication") requirePriv(handler, "User") if p_action == "enable": handler.session["user"].hotpKey = b32encode("".join(chr(randrange(256)) for _ in range(10))) handler.session["user"].save() Event.tfa(handler, handler.session["user"]) print SuccessBox("Two-factor authentication is <b>enabled</b>") print "Your HOTP key is <b>%s</b>:<br><br>" % handler.session["user"].hotpKey print '<div style="text-align: center"><img src="https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/Sprint%%3Fsecret%%3D%s"></div><br>' % handler.session[ "user" ].hotpKey print "This key will not be displayed again — be sure to write it down, or add it to your Google Authenticator list now" elif p_action == "disable": handler.session["user"].hotpKey = "" handler.session["user"].save() Event.tfa(handler, handler.session["user"]) delay(handler, SuccessBox("Two-factor authentication is <b>disabled</b>")) redirect("/users/%s" % handler.session["user"].username) else: ErrorBox.die("Unexpected action: <b>%s</b>" % stripTags(p_action))
def adminProjectsMoveSprintsPost(handler, id, p_newproject, p_sprintid = None): handler.title('Move Sprints') requirePriv(handler, 'Admin') project = Project.load(int(id)) if not project: ErrorBox.die('Invalid Project', "No project with ID <b>%d</b>" % int(id)) p_newproject = to_int(p_newproject, 'newproject', ErrorBox.die) target = Project.load(p_newproject) if not p_sprintid: delay(handler, WarningBox("No sprints to move", close = True)) redirect("/admin/projects/%d" % project.id) sprintids = [to_int(id, 'sprintid', ErrorBox.die) for id in p_sprintid] sprints = [Sprint.load(id) for id in sprintids] if not all(sprints): ErrorBox.die("Invalid sprint ID(s)") for sprint in sprints: sprint.project = target sprint.save() delay(handler, SuccessBox("%s moved" % pluralize(len(sprints), 'sprint', 'sprints'), close = True)) redirect("/admin/projects/%d" % project.id)
def adminTime(handler): handler.title('Mock time') requirePriv(handler, 'Admin') print "<link href=\"/static/jquery.ui.timepicker.css\" rel=\"stylesheet\" type=\"text/css\" />" print "<script src=\"/static/jquery.ui.timepicker.js\" type=\"text/javascript\"></script>" print "<script src=\"/static/admin-time.js\" type=\"text/javascript\"></script>" nowDelta = getNowDelta() real, effective = datetime.now(), getNow() days = nowDelta.days if nowDelta.seconds < 0: prefix = '-' times = tsToDate(0) + timedelta(hours = 24 - datetime.fromtimestamp(0).hour) - nowDelta else: prefix = '' times = tsToDate(0) + timedelta(hours = 24 - datetime.fromtimestamp(0).hour) + nowDelta tbl = LRTable() tbl['Real time:'] = str(real) tbl['Current delta:'] = "%d %s, %s%d:%02d" % (days, 'day' if days == 1 else 'days', prefix, times.hour, times.minute) tbl['Effective time:'] = str(effective) print tbl print "<br>" print "<form method=\"post\" action=\"/admin/time\">" print "<input type=\"text\" name=\"date\" class=\"date\" value=\"%s\">" % effective.strftime('%m/%d/%Y') print "<input type=\"text\" name=\"time\" class=\"time\" value=\"%s\">" % effective.strftime('%H:%M') print Button('Set', type = 'submit').positive() print "</form>"
def exportSprints(handler, project): id = int(project) handler.title('Export Sprint') requirePriv(handler, 'User') project = Project.load(id) if not project: ErrorBox.die('Invalid project', "No project with ID <b>%d</b>" % id) print "<link href=\"/static/jquery.multiselect.css\" rel=\"stylesheet\" type=\"text/css\" />" print "<script src=\"/static/jquery.multiselect.js\" type=\"text/javascript\"></script>" print "<script src=\"/static/sprints-export.js\" type=\"text/javascript\"></script>" print "<style type=\"text/css\">" print "select {width: 50%;}" print "img.format {" print " width: 64px;" print " cursor: pointer;" print " padding: 5px;" print " border: 3px solid #fff;" print "}" print "img.format.selected, .ui-effects-transfer {border: 3px solid #f00;}" print "</style>" print "<h2>Sprints</h2>" print "<select name=\"sprints\" multiple>" for sprint in project.getSprints(): if sprint.canView(handler.session['user']): print "<option value=\"%d\"%s>%s</option>" % (sprint.id, ' selected' if sprint.isActive() else '', sprint.safe.name) print "</select>" print "<h2>Format</h2>" for export in exports.values(): print "<img class=\"format\" src=\"/static/images/%s.png\" title=\"%s\" export-name=\"%s\">" % (export.getIcon(), export.getFriendlyName(), export.getName()) print "<br><br>" print Button('Export', id = 'export-button').positive()
def adminPrivileges(handler, username = None): handler.title("Privileges") requirePriv(handler, 'Admin') undelay(handler) users = User.loadAll(orderby = 'username') counts = {name: len(filter(lambda user: name in user.privileges, users)) for name in privList} if username: print "<style type=\"text/css\">" print "table.granttable tr[username=%s] {" % username print " background-color: #faa;" print "}" print "</style>" print "<h3>List</h3>" print "<table border=\"0\" cellspacing=\"4\">" print "<tr><th>Name</th><th>Grants</th><th>Description</th></tr>" for name, desc in privList.iteritems(): print "<tr><td>%s</td><td>%d</td><td>%s</td></tr>" % (name, counts[name] if name in counts else 0, desc) print "</table>" print "<h3>Grants</h3>" print "<form method=\"post\" action=\"/admin/privileges\">" print "<table border=\"0\" cellspacing=\"0\" cellpadding=\"2\" class=\"granttable\">" print "<tr><td> </td>%s</tr>" % ''.join("<td>%s</td>" % name for name in privList) for user in users: print "<tr username=\"%s\">" % user.username print "<td>%s</td>" % user.username for name in privList: print "<td><input type=\"checkbox\" name=\"grant[%s][%s]\"%s></td>" % (user.username, name, ' checked' if user.hasPrivilege(name) else '') print "</tr>" print "<tr><td> </td><td colspan=\"3\">%s</td></tr>" % Button('Save', type = 'submit').positive() print "</table>" print "</form>"
def adminPrivilegesPost(handler, p_grant): handler.title("Privileges") requirePriv(handler, 'Admin') p_grant = {name: privs.keys() for name, privs in p_grant.iteritems()} privNames = set() for privs in p_grant.values(): privNames |= set(privs) if not privNames <= set(privList.keys()): ErrorBox.die("Update privileges", "Unrecognized privilege name") for user in User.loadAll(): for priv in privList: privs = p_grant.get(user.username, []) has = user.hasPrivilege(priv) if has and priv not in privs: print "Revoking %s from %s<br>" % (priv, user.username) user.privileges.remove(priv) Event.revokePrivilege(handler, user, priv) elif not has and priv in privs: print "Granting %s to %s<br>" % (priv, user.username) user.privileges.add(priv) Event.grantPrivilege(handler, user, priv, False) user.save() print "Done"
def adminSessionsPost(handler, p_key, p_action, p_value = None): handler.title('Sessions') requirePriv(handler, 'Admin') print "<script src=\"/static/admin-sessions.js\" type=\"text/javascript\"></script>" if not p_key in Session.getIDs(): ErrorBox.die("Retrieve session", "No session exists with key <b>%s</b>" % stripTags(p_key)) session = Session.load(p_key) for case in switch(p_action): if case('reassign'): handler.title('Reassign Session') if p_value: user = User.load(int(p_value)) if not user: ErrorBox.die("Load user", "No user exists with ID <b>%s</b>" % stripTags(p_value)) session['user'] = user redirect('/admin/sessions') else: print "<form method=\"post\" action=\"/admin/sessions\">" print "<input type=\"hidden\" name=\"action\" value=\"reassign\">" print "<input type=\"hidden\" name=\"key\" value=\"%s\">" % p_key print "<select id=\"selectUser\" name=\"value\">" for user in sorted(User.loadAll()): print "<option value=\"%d\">%s</option>" % (user.id, user.safe.username) print "</select><br>" print Button('Reassign', type = 'submit').positive() print Button('Cancel', id = 'cancel-button', type = 'button', url = '/admin/sessions').negative() print "</form>" break if case('destroy'): Session.destroy(p_key) redirect('/admin/sessions') break break
def showSprintResults(handler, id): requirePriv(handler, 'User') id = int(id) sprint = Sprint.load(id) if not sprint or sprint.isHidden(handler.session['user']): ErrorBox.die('Sprints', "No sprint with ID <b>%d</b>" % id) elif not sprint.canView(handler.session['user']): ErrorBox.die('Private', "You must be a sprint member to view this sprint") tasksNow = sprint.getTasks() tasksStart = filter(None, (task.getRevisionAt(tsToDate(sprint.start)) for task in tasksNow)) tasksNow = {task.id: task for task in tasksNow} tasksStart = {task.id: task for task in tasksStart} handler.title(sprint.safe.name) drawNavArrows(sprint, handler.session['user'], 'results') print tabs(sprint, ('wrapup', 'results')) if not sprint.isOver(): ErrorBox.die('Sprint Open', "Results aren't available until the sprint has closed") from rorn.Box import WarningBox print WarningBox("This feature is still under development") print "<ul>" completed = filter(lambda task: task.status == 'complete', tasksNow.values()) print "<li><a href=\"/sprints/%d?search=status:complete\">%s completed</a> (%d%% of the original hourly commitment)</li>" % (sprint.id, pluralize(len(completed), 'task', 'tasks'), sum(tasksStart[task.id].hours if task.id in tasksStart else 0 for task in completed) * 100 / sum(task.hours for task in tasksStart.values())) planned = filter(lambda task: task.id in tasksStart, tasksNow.values()) print "<li>%d%% of tasks were planned from the start</li>" % (len(planned) * 100 / len(tasksNow)) print "</ul>"
def adminInfo(handler): handler.title('Information') requirePriv(handler, 'Admin') print "<div class=\"info\">" print "<h3>Uptime</h3>" loadTime = getLoadtime() print "Started %s<br>" % loadTime print "Up for %s<br>" % timesince(loadTime) print "Total requests: %d<br>" % server().getTotalRequests() print "<form method=\"post\" action=\"/admin/restart\">" print Button('Restart', type = 'submit').negative() print "</form>" print "<h3>Threads</h3>" print "<table border=\"1\" cellspacing=\"0\" cellpadding=\"4\">" print "<tr><th>ID</th><th class=\"main\">Name</th><th>Alive</th><th>Daemon</th></tr>" for thread in sorted(threads(), key = lambda thread: thread.name): print "<tr><td>%s</td><td>" % ('None' if thread.ident is None else "%x" % abs(thread.ident)) print thread.name print "<br>" try: print CollapsibleBox('Traceback', formatTrace(traceback.extract_stack(sys._current_frames()[thread.ident]))) except Exception: pass print "</td><td class=\"%s\"> </td><td class=\"%s\"> </td></tr>" % ('yes' if thread.isAlive() else 'no', 'yes' if thread.daemon else 'no') print "</table>" print "<h3>Locks</h3>" print "<table border=\"1\" cellspacing=\"0\" cellpadding=\"4\">" print "<tr><th class=\"main\">Name</th><th>Available</th><th>Reentrant</th></tr>" for (name, lock) in sorted(locks.iteritems()): print "<tr><td>" print name avail = lock.avail() if not avail: print "<br>" writer = ResponseWriter() try: owner, tb = lock.owner, lock.tb name = ("%x" % abs(owner)) if owner else 'None' #TODO Is there no O(1) way to do this? for thread in threads(): if thread.ident == owner: name = "%s (%x)" % (thread.name, abs(owner)) break print "Owned by: <b>%s</b><br><br>" % name if tb: print "Acquisition traceback:<br>" print formatTrace(tb) print "<br>" print "Current traceback:<br>" print formatTrace(traceback.extract_stack(sys._current_frames()[owner])) except Exception, e: writer.clear() print "<i>(Unable to retrieve stack trace)</i>" print CollapsibleBox('Ownership', writer.done()) print "</td><td class=\"%s\">%s</td><td class=\"%s\"> </td></tr>" % ('yes' if avail else 'no', ' ' if avail else (lock.owner or '???'), 'yes' if lock.reentrant() else 'no')
def taskEditPost(handler, ids, p_hours, p_status, p_goal, p_assigned=[], p_include={}): handler.title("Edit tasks") requirePriv(handler, "Write") allIDs = map(int, uniq(ids.split(","))) ids = map(lambda i: to_int(i, "include", ErrorBox.die), p_include.keys()) if not set(ids) <= set(allIDs): ErrorBox.die("Included tasks don't match query arguments") tasks = dict((id, Task.load(id)) for id in ids) if not all(tasks.values()): ids = [str(id) for (id, task) in tasks.iteritems() if not task] ErrorBox.die( "No %s with %s %s" % ("task" if len(ids) == 1 else "tasks", "ID" if len(ids) == 1 else "IDs", ", ".join(ids)) ) tasks = [tasks[id] for id in ids] if len(set(task.sprint for task in tasks)) > 1: ErrorBox.die("All tasks must be in the same sprint") sprint = (tasks[0] if len(tasks) > 0 else Task.load(allIDs[0])).sprint if sprint.isHidden(handler.session["user"]): ErrorBox.die( "No %s with %s %s" % ("task" if len(ids) == 1 else "tasks", "ID" if len(ids) == 1 else "IDs", ", ".join(ids)) ) if not sprint.canEdit(handler.session["user"]): ErrorBox.die("You don't have permission to modify this sprint") assignedids = set(to_int(i, "assigned", ErrorBox.die) for i in p_assigned) changes = { "assigned": False if assignedids == set() else {User.load(assignedid) for assignedid in assignedids}, "hours": False if p_hours == "" else int(p_hours), "status": False if p_status == "" else p_status, "goal": False if p_goal == "" else Goal.load(int(p_goal)), } if changes["assigned"] and not all(changes["assigned"]): ErrorBox.die("Invalid assignee") if changes["assigned"] and not set(changes["assigned"]).issubset(sprint.members): ErrorBox.die("Unable to assign tasks to non-sprint members") if changes["goal"] and changes["goal"].sprint != sprint: ErrorBox.die("Unable to set goal to a goal outside the sprint") changed = set() for task in tasks: for field, value in changes.iteritems(): if value is not False and getattr(task, field) != value: setattr(task, field, value) changed.add(task) Event.taskUpdate(handler, task, field, value) if len(changed) == 0: delay(handler, WarningBox("No changes necessary", close=3, fixed=True)) else: for task in changed: task.saveRevision(handler.session["user"]) delay(handler, SuccessBox("Updated %d %s" % (len(changed), "task" if len(changed) == 1 else "tasks"))) redirect("/sprints/%d" % sprint.id)
def newTaskGeneric(handler, group, assigned=None): handler.title("New Task") requirePriv(handler, "User") page = handler.session["user"].getPrefs().defaultTasksTab url = tabs[page].getPath(to_int(group, "group", ErrorBox.die)) if assigned: url += "&assigned=%s" % assigned redirect(url)
def adminProjectsManage(handler, id): handler.title('Manage Project') requirePriv(handler, 'Admin') project = Project.load(int(id)) if not project: ErrorBox.die('Invalid Project', "No project with ID <b>%d</b>" % int(id)) undelay(handler) sprints = project.getSprints() otherProjects = sorted((p for p in Project.loadAll() if p != project), key = lambda p: p.name) for sprint in sprints: if 'deleted' in sprint.flags: print "<form method=\"post\" action=\"/admin/projects/%d/cancel-deletion/%d\">" % (project.id, sprint.id) print WarningBox("%s is flagged for deletion during nightly cleanup. %s" % (sprint.link(handler.session['user']), Button('Cancel').mini().post())) print "</form>" print "<a name=\"sprints\"></a>" print "<h3>Sprints</h3>" if len(sprints) > 0: print "<form method=\"post\" action=\"/admin/projects/%d/sprints\">" % project.id for sprint in sprints: print "<input type=\"checkbox\" name=\"sprintid[]\" value=\"%d\"> %s<br>" % (sprint.id, sprint.link(handler.session['user'])) print "<br>" print "Move to project: <select name=\"newproject\">" for p in otherProjects: print "<option value=\"%d\">%s</option>" % (p.id, p.safe.name) print "</select>" print Button('Move').post('move').positive() print "<br><br>" print "Delete sprints (irreversible once finalized):" print Button('Delete').post('delete').negative() print "</form>" else: print "No sprints" print "<a name=\"rename\"></a>" print "<h3>Rename</h3>" print "<form method=\"post\" action=\"/admin/projects/%d/edit\">" % project.id print "Name: <input type=\"text\" name=\"name\" value=\"%s\">" % project.safe.name print Button('Rename', type = 'submit').positive() print "</form>" print "<a name=\"delete\"></a>" print "<h3>Delete</h3>" print "<form method=\"post\" action=\"/admin/projects/%d/delete\">" % project.id if len(project.getSprints()) > 0: if len(otherProjects) > 0: print "Delete <b>%s</b> and move all sprints to <select name=\"newproject\">" % project.safe.name for p in otherProjects: print "<option value=\"%d\">%s</option>" % (p.id, p.safe.name) print "</select>" print Button('Delete', type = 'submit').negative() else: print "Unable to remove the only project if it has sprints" else: print Button("Delete %s" % project.safe.name, type = 'submit').negative() print "</form><br>"
def adminCronRun(handler, p_name): handler.title('Run cron job') requirePriv(handler, 'Admin') print "<script type=\"text/javascript\">" print "job_name = %s;" % toJS(p_name) print "</script>" print "<div id=\"output\"></div>"
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 distribute(handler, sprint): handler.title("Distribute Tasks") sprintid = int(sprint) sprint = Sprint.load(sprintid) if not sprint or sprint.isHidden(handler.session["user"]): ErrorBox.die("Invalid Sprint", "No sprint with ID <b>%d</b>" % id) handler.title(sprint.safe.name) print sprintTabs(sprint, "distribute") requirePriv(handler, "Write") if not (sprint.isActive() or sprint.isPlanning()): ErrorBox.die("Sprint Closed", "Unable to modify inactive sprint") if not sprint.canEdit(handler.session["user"]): ErrorBox.die("Permission Denied", "You don't have permission to modify this sprint") print '<script type="text/javascript" src="/static/highcharts/js/highcharts.js"></script>' print '<script type="text/javascript" src="/static/highcharts/js/highcharts-more.js"></script>' print '<script type="text/javascript">' print "var sprintid = %d;" % sprint.id print "</script>" print InfoBox("Loading...", id="post-status", close=True) print '<div id="distribution-range">' print "Acceptable commitment: <span></span>" print "</div>" print '<div id="distribution-range-slider"></div>' print '<div class="clear"></div>' print '<div id="distribution-chart"></div>' for col in ("left", "right"): print '<div class="distribution %s">' % col for user in sorted(sprint.members): print '<img class="user-gravatar" src="%s" userid="%d" title="%s">' % ( user.getAvatar(64), user.id, user.safe.username, ) if col == "right": print '<img class="user-gravatar" src="/static/images/revision-deferred.svg" userid="deferred" title="Deferred tasks">' print "<br><br>" print '<div class="selected">' print '<img style="visibility: hidden" class="user-gravatar" src="%s">' % User.getBlankAvatar(64) print '<div class="info">' print '<div class="username"></div>' print '<div class="hours"></div>' print '<div class="task-progress-total"><div class="progress-current" style="visibility: hidden;"></div></div>' print "</div>" print "</div>" print '<div class="clear"></div>' print '<div class="tasks"></div>' print "</div>" print '<div class="clear"></div><br><br>'
def adminProjectsEditPost(handler, id, p_name): handler.title('Edit Project') requirePriv(handler, 'Admin') project = Project.load(int(id)) if not project: ErrorBox.die('Invalid Project', "No project with ID <b>%d</b>" % int(id)) project.name = p_name project.save() delay(handler, SuccessBox("Saved project changes", close = True)) redirect("/admin/projects/%d" % project.id)
def adminRestart(handler, now = False): handler.title('Restart') requirePriv(handler, 'Admin') if now: # This is a POST, so we've already blocked other requests and it's safe to restart Event.restart(handler) brick("Restart triggered by %s" % handler.session['user'].username) # This string is checked for in main server().stop() else: print "<img src=\"/static/images/loading.gif\"> Restarting..."
def sent(handler): handler.title('Messages') requirePriv(handler, 'User') print tabs.where('sent') undelay(handler) Markdown.head('div.message .body pre code') messages = Message.loadAll(senderid = handler.session['user'].id, orderby = '-timestamp') for message in messages: printMessage(message, False)
def adminProjectsPost(handler, p_name): handler.title('Project Management') requirePriv(handler, 'Admin') if Project.load(name = p_name): ErrorBox.die('Add Project', "There is already a project named <b>%s</b>" % stripTags(p_name)) project = Project(p_name) project.save() delay(handler, SuccessBox("Added project <b>%s</b>" % stripTags(p_name), close = True)) Event.newProject(handler, project) redirect('/')
def searchRunSprint(handler, id, sprintid): handler.title('Run Search') requirePriv(handler, 'User') id = int(id) search = SavedSearch.load(id) if not search: ErrorBox.die('Invalid Search', "Search <b>%d</b> does not exist" % int(id)) elif search.user != handler.session['user'] and not search.public: ErrorBox.die('Invalid Search', "You cannot run search <b>%d</b>" % int(id)) redirect("/sprints/%s?search=%s" % (sprintid, search.query))
def userAvatarRemove(handler, username): handler.title('Remove avatar') requirePriv(handler, 'User') user = User.load(username = username) if not user: ErrorBox.die("Remove avatar", "No user named <b>%s</b>" % stripTags(username)) if user != handler.session['user']: #TODO Allow devs redirect("/users/%s/avatar/set" % handler.session['user'].username) user.avatar = None user.save() delay(handler, SuccessBox("Avatar removed")) redirect("/users/%s" % user.username)