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 projectsCalendar(handler): undelay(handler) print '<link href="/static/fullcalendar.css" rel="stylesheet" type="text/css" />' print '<script src="/static/fullcalendar.js" type="text/javascript"></script>' print '<script src="/static/projects-calendar.js" type="text/javascript"></script>' print '<div id="calendar"></div>'
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 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 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 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 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 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 inbox(handler): handler.title('Messages') requirePriv(handler, 'User') print InfoBox("Note", "You can control your automated message subscriptions from the <a href=\"/prefs\">preferences</a> page") print tabs.where('inbox') undelay(handler) Markdown.head('div.message .body pre code') messages = Message.loadAll(userid = handler.session['user'].id, orderby = '-timestamp') for message in messages: printMessage(message, True) if not message.read: message.read = True message.save()
def adminSettings(handler): handler.title('Settings') requirePriv(handler, 'Admin') undelay(handler) print "<ul id=\"autolink_icons\" class=\"contextMenu\">" for seek in AUTOLINK_ICONS: print "<li><a href=\"#%s\"><img src=\"/static/images/%s.png\"></a></li>" % (seek, seek) print "</ul>" def quot(str): return str.replace('"', '"') print "<h3>Mutable settings</h3>" print "<form method=\"post\" action=\"/admin/settings\">" print "<table class=\"list\"><tbody>" print "<tr><td class=\"left\">E-mail domain:</td><td class=\"right\"><input type=\"text\" name=\"emailDomain\" value=\"%s\"></td></tr>" % quot(settings.emailDomain) print "<tr><td class=\"left\">System message:</td><td class=\"right\"><input type=\"text\" name=\"systemMessage\" value=\"%s\"></td></tr>" % quot(settings.systemMessage or '') print "<tr><td class=\"left\">Redis host:</td><td class=\"right\"><input type=\"text\" name=\"redis\" value=\"%s\"></td></tr>" % quot(settings.redis or '') print "<tr><td class=\"left\">Kerberos realm:</td><td class=\"right\"><input type=\"text\" name=\"kerberosRealm\" value=\"%s\"></td></tr>" % quot(settings.kerberosRealm or '') print "<tr><td class=\"left\">SMTP server:</td><td class=\"right\"><input type=\"text\" name=\"smtpServer\" value=\"%s\"></td></tr>" % quot(settings.smtpServer or '') print "<tr><td class=\"left\">SMTP from address:</td><td class=\"right\"><input type=\"text\" name=\"smtpFrom\" value=\"%s\"></td></tr>" % quot(settings.smtpFrom or '') print "<tr><td class=\"left\">Autolinks:</td><td class=\"right\">" print "<table class=\"autolinks\" width=\"100%\" cellspacing=\"5\">" print "<tr><td>Icon</td><td>Pattern</td><td>URL</td></tr>" links = zip(*settings.autolink) for i in range(5): icon, pattern, url = links.pop(0) if links else ('star', '', '') print "<tr>" print "<td><img class=\"autolink_icon\" src=\"/static/images/%s.png\"><input type=\"hidden\" name=\"autolink_icons[]\" value=\"%s\"></td>" % (icon, icon) print "<td><input type=\"text\" name=\"autolink_patterns[]\" value=\"%s\"></td>" % stripTags(pattern) print "<td><input type=\"text\" name=\"autolink_urls[]\" value=\"%s\"></td>" % stripTags(url) print "</tr>\n"; print "</table>" print "</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 "</tbody></table>" print "</form>" print "<br>" print "<h3>Immutable settings</h3>" print "<table class=\"list\">" print "<tr><td>Database version:</td><td>%s</td></tr>" % settings.dbVersion print "</table>"
def adminSessions(handler, username = None): handler.title("Sessions for %s" % stripTags(username) if username else "Sessions") requirePriv(handler, 'Admin') undelay(handler) def cmpSessionTimes(s1, s2): if 'timestamp' in s1 and 'timestamp' in s2: return cmp(s1['timestamp'], s2['timestamp']) elif 'timestamp' in s1: return 1 elif 'timestamp' in s2: return -1 else: return 0 print "<table border=0 cellspacing=4>" print "<tr><th>Key</th><th>User</th><th>Last address</th><th>Last seen</th><th> </th></tr>" sessions = {sessionID: Session.load(sessionID) for sessionID in Session.getIDs()} for key, session in sorted(sessions.iteritems(), lambda (k1, s1), (k2, s2): cmpSessionTimes(s1, s2), reverse = True):
def user(handler, username): user = User.load(username = username) if not user: ErrorBox.die('User', "No user named <b>%s</b>" % stripTags(username)) Markdown.head('form#message-form .body pre code') print "<script src=\"/static/jquery.typing-0.2.0.min.js\" type=\"text/javascript\"></script>" print "<script src=\"/static/users.js\" type=\"text/javascript\"></script>" Chart.include() undelay(handler) handler.title(user.safe.username) handler.replace('$bodytitle$', '', 1) print "<img src=\"%s\" class=\"gravatar\">" % user.getAvatar(64) print "<h1>%s</h1>" % user.safe.username if isDevMode(handler): print "<div class=\"debugtext\">User ID: %d</div>" % user.id print "<div class=\"clear\"></div>" if handler.session['user'] and handler.session['user'].hasPrivilege('Admin'): print "<h3>Admin</h3>" print "<form method=\"post\" action=\"/admin/users\">" print "<input type=\"hidden\" name=\"username\" value=\"%s\">" % user.username print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"resetpw\">Reset password</button>" print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"impersonate\">Impersonate</button>" print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"sessions\">Manage sessions</button>" print "<button type=\"submit\" class=\"btn\" name=\"action\" value=\"privileges\">Manage privileges</button>" print "</form>" if user == handler.session['user']: print "<h3>Avatar</h3>" if user.hasLocalAvatar(): print "Your avatar is currently <a href=\"/users/%s/avatar/set\">locally hosted</a>" % user.username else: print "Your avatar can be changed at <a href=\"http://gravatar.com/\" target=\"_new\">http://gravatar.com/</a>. It must be associated with the e-mail <b>%s</b>, and be rated PG. You can also host an avatar <a href=\"/users/%s/avatar/set\">locally</a>, if necessary" % (user.getEmail(), user.username) print "<h3>Authentication</h3>" print "Your sprint tool password can be changed <a href=\"/resetpw\">here</a>.", if settings.kerberosRealm: print "You can also use your %s kerberos password to login" % settings.kerberosRealm, print "<br><br>" if user.hotpKey == '': print "You also have the option to use two-factor authentication via <a href=\"http://en.wikipedia.org/wiki/HOTP\">HOTP</a>. You can use <a href=\"http://support.google.com/a/bin/answer.py?hl=en&answer=1037451\">Google Authenticator</a> to generate verification codes<br><br>" print "<form method=\"post\" action=\"/security/two-factor\">" print "<button type=\"submit\" class=\"btn danger\" name=\"action\" value=\"enable\">Enable two-factor authentication</button>" print "</form>" else: print "You are currently using two-factor authentication<br><br>" print "<form method=\"post\" action=\"/security/two-factor\">" print "<button type=\"submit\" class=\"btn danger\" name=\"action\" value=\"enable\">Reset HOTP key</button>" print "<button type=\"submit\" class=\"btn danger\" name=\"action\" value=\"disable\">Disable two-factor authentication</button>" print "</form>" print "<h3>Messages</h3>" print "Your inbox and sent messages can be viewed <a href=\"/messages/inbox\">here</a><br>" print "<h3>Last seen</h3>" if not user.lastseen: print "Never" elif dateToTs(getNow()) - user.lastseen < 60: print "Just now" else: print "%s ago" % timesince(tsToDate(user.lastseen)) if handler.session['user'] and handler.session['user'] != user: print "<h3>Message</h3>" print "<small>(Messages are formatted in <a target=\"_blank\" href=\"/help/markdown\">markdown</a>)</small>" print "<form id=\"message-form\" method=\"post\" action=\"/messages/send\">" print "<input type=\"hidden\" name=\"userid\" value=\"%d\">" % user.id print "<textarea name=\"body\" class=\"large\"></textarea>" print "<div class=\"body markdown\"><div id=\"preview\"></div></div>" print Button('Send').post().positive() print "</form>" print "<h3>Project distribution</h3>" sprints = filter(lambda s: user in s.members, Sprint.loadAllActive()) sprintHours = map(lambda s: (s, Availability(s).getAllForward(getNow(), user)), sprints) projectHours = map(lambda (p, g): (p, sum(hours for sprint, hours in g)), groupby(sprintHours, lambda (s, a): s.project)) # For now at least, don't show projects with no hours projectHours = filter(lambda (p, h): h > 0, projectHours) if len(projectHours) > 0: chart = Chart('chart') chart.title.text = '' chart.tooltip.formatter = "function() {return '<b>' + this.point.name + '</b>: ' + this.point.y + '%';}" chart.plotOptions.pie.allowPointSelect = True chart.plotOptions.pie.cursor = 'pointer' chart.plotOptions.pie.dataLabels.enabled = False chart.plotOptions.pie.showInLegend = True chart.credits.enabled = False chart.series = seriesList = [] series = { 'type': 'pie', 'name': '', 'data': [] } seriesList.append(series) total = sum(hours for project, hours in projectHours) for project, hours in projectHours: series['data'].append([project.name, float("%2.2f" % (100 * hours / total))]) chart.js() chart.placeholder() else: print "Not a member of any active sprints"
def showBacklog(handler, id, search = None, devEdit = False): 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") # Redirect to search help page if searched for empty string if search == '': redirect('/help/search') handler.title(sprint.safe.name) drawNavArrows(sprint, handler.session['user'], '') tasks = sprint.getTasks() editable = sprint.canEdit(handler.session['user']) or (devEdit and isDevMode(handler)) search = Search(sprint, search) print "<script src=\"/settings/sprints.js\" type=\"text/javascript\"></script>" print "<script type=\"text/javascript\">" print "var sprintid = %d;" % id print "var currentUser = %s;" % toJS(handler.session['user'].username if handler.session['user'] else None) print "var totalTasks = %d;" % len(tasks) # True is a placeholder for the dynamic tokens (status, assigned) print "var searchTokens = %s;" % toJS(filter(None, [search.getBaseString() if search.hasBaseString() else None] + [True] + ["%s:%s" % (filt.getKey(), filt.value) for filt in search.getAll() if filt.getKey() not in ('status', 'assigned')])) print "var searchDescriptions = %s;" % toJS(filter(None, ["matching %s" % search.getBaseString() if search.hasBaseString() else None] + [True] + [filt.description() for filt in search.getAll()])) print "TaskTable.init({link_hours_status: %s});" % toJS(not sprint.isPlanning()) print "$('document').ready(function() {" if search.has('assigned'): print " $('%s').addClass('selected');" % ', '.join("#filter-assigned a[assigned=\"%s\"]" % user.username for user in search.get('assigned').users + ([handler.session['user']] if search.get('assigned').currentUser else [])) if search.has('status'): print " $('%s').addClass('selected');" % ', '.join("#filter-status a[status=\"%s\"]" % status.name for status in search.get('status').statuses) print " apply_filters();" print "});" print "</script>" print "<div id=\"selected-task-box\">" print "<span></span>" print Button('history', id = 'selected-history').positive() print Button('highlight', id = 'selected-highlight').positive() print Button('mass edit', id = 'selected-edit').positive() print Button('cancel', id = 'selected-cancel') #.negative() print "</div>" print "<div class=\"backlog-tabs\">" print tabs(sprint, 'backlog') print "<input type=\"text\" id=\"search\" value=\"%s\">" % search.getFullString().replace('"', '"') print "</div>" undelay(handler) print InfoBox('Loading...', id = 'post-status', close = True) avail = Availability(sprint) if sprint.isActive() else None dayStart = Weekday.today().date() print "<div id=\"filter-assigned\">" print "<a class=\"fancy danger\" href=\"#\"><img src=\"/static/images/cross.png\"> None</a>" for member in sorted(sprint.members): cls = ['fancy'] if not sprint.isPlanning() and avail and avail.get(member, dayStart) == 0: cls.append('away') print "<a class=\"%s\" assigned=\"%s\" href=\"/sprints/%d?search=assigned:%s\"><img src=\"%s\"> %s</a>" % (' '.join(cls), member.username, id, member.username, member.getAvatar(16), member.username) print "</div><br>" print "<div id=\"filter-status\">" print "<a class=\"fancy danger\" href=\"#\"><img src=\"/static/images/cross.png\"> None</a>" for status in sorted(statuses.values()): print "<a class=\"fancy\" status=\"%s\" href=\"/sprints/%d?search=status:%s\"><img src=\"%s\">%s</a>" % (status.name, id, status.name.replace(' ', '-'), status.getIcon(), status.text) print "</div><br>" if handler.session['user'].hasPrivilege('Admin') and 'deleted' in sprint.flags: print "<form method=\"post\" action=\"/admin/projects/%d/cancel-deletion/%d\">" % (sprint.project.id, sprint.id) print WarningBox("This sprint is flagged for deletion during nightly cleanup. %s" % Button('Cancel').mini().post()) print "</form>" if sprint.isPlanning(): if sprint.isActive(): print InfoBox("Today is <b>sprint planning</b> — tasks aren't finalized until the end of the day") else: daysTillPlanning = (tsToDate(sprint.start) - getNow()).days + 1 print InfoBox("The sprint has <b>not begun</b> — planning is %s. All changes are considered to have been made midnight of plan day" % ('tomorrow' if daysTillPlanning == 1 else "in %d days" % daysTillPlanning)) elif sprint.isReview(): print InfoBox("Today is <b>sprint review</b> — this is the last day to make changes to the backlog. All open tasks will be deferred at the end of the day") elif not sprint.isOver(): noHours = filter(lambda task: task.stillOpen() and task.hours == 0, tasks) if noHours != []: print WarningBox("There are <a href=\"/sprints/%d?search=status:not-started,in-progress,blocked hours:0\">open tasks with no hour estimate</a>" % sprint.id) tasks = search.filter(tasks) if isDevMode(handler): print Button('#all-tasks borders', "javascript:$('#all-tasks, #all-tasks tr td').css('border', '1px solid #f00').css('border-collapse', 'collapse');").negative() if not editable: print Button('make editable', "/sprints/%d?devEdit" % id).negative() elif devEdit: print Button('make uneditable', "/sprints/%d" % id).negative() print "<div class=\"debugtext\">" print "start: %d (%s)<br>" % (sprint.start, tsToDate(sprint.start)) print "end: %d (%s)<br>" % (sprint.end, tsToDate(sprint.end)) print "</div>" showing = ResponseWriter() print "<span id=\"task-count\"></span>" # save-search href set by update_task_count() print "<a class=\"save-search\"><img src=\"/static/images/save.png\" title=\"Save search\"></a>" print "<a class=\"cancel-search\" href=\"/sprints/%d\"><img src=\"/static/images/cross.png\" title=\"Clear search\"></a>" % id showing = showing.done() print TaskTable(sprint, editable = editable, tasks = tasks, tableID = 'all-tasks', dateline = showing, taskClasses = {task: ['highlight'] for task in (search.get('highlight').tasks if search.has('highlight') else [])}, debug = isDevMode(handler), groupActions = True, taskModActions = True, index = True, goal = True, status = True, name = True, assigned = True, historicalHours = True, hours = True, devEdit = devEdit)
def task(handler, ids): requirePriv(handler, "User") Chart.include() Markdown.head(".note .text .body pre code") print '<script src="/static/jquery.typing-0.2.0.min.js" type="text/javascript"></script>' undelay(handler) tasks = {} if "," not in ids: # Single ID ids = [int(ids)] tasks[ids[0]] = Task.load(ids[0]) def header(task, text, level): if level == 1: handler.title(text) else: print "<h%d>%s</h%d>" % (level, text, level) else: # Many IDs ids = map(int, uniq(ids.split(","))) tasks = {id: Task.load(id) for id in ids} handler.title("Task Information") 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)) ) if len(set(task.sprint for task in tasks.values())) == 1: # All in the same sprint print '<small>(<a href="/sprints/%d?search=highlight:%s">Show in backlog view</a>)</small><br><br>' % ( tasks.values()[0].sprint.id, ",".join(map(str, ids)), ) for id in ids: print '<a href="#task%d">%s</a><br>' % (id, tasks[id].safe.name) def header(task, text, level): if level == 1: print "<hr>" print '<a name="task%d"></a>' % task.id print '<a href="#task%d"><h2>%s</h2></a>' % (task.id, text) else: print "<h%d>%s</h%d>" % (level + 1, text, level + 1) for id in ids: task = tasks[id] if not task or task.sprint.isHidden(handler.session["user"]): ErrorBox.die("Tasks", "No task with ID <b>%d</b>" % id) elif not task.sprint.canView(handler.session["user"]): ErrorBox.die("Private", "You must be a sprint member to view this sprint's tasks") revs = task.getRevisions() startRev = task.getStartRevision() header(task, task.safe.name, 1) header(task, "Info", 2) print 'Part of <a href="/sprints/%d">%s</a>, <a href="/sprints/%d#group%d">%s</a>' % ( task.sprintid, task.sprint, task.sprintid, task.groupid, task.group, ), if task.goal: print 'to meet the goal <img class="bumpdown" src="/static/images/tag-%s.png"> <a href="/sprints/%d?search=goal:%s">%s</a>' % ( task.goal.color, task.sprintid, task.goal.color, task.goal.safe.name, ), print "<br>" print "Assigned to %s<br>" % ", ".join(map(str, task.assigned)) print "Last changed %s ago<br><br>" % timesince(tsToDate(task.timestamp)) hours, total, lbl = task.hours, startRev.hours, "<b>%s</b>" % statuses[task.status].text if task.deleted: if task.sprint.canEdit(handler.session["user"]): print '<form method="post" action="/sprints/%d">' % task.sprint.id print '<input type="hidden" name="id" value="%d">' % task.id print '<input type="hidden" name="rev_id" value="%d">' % task.revision print '<input type="hidden" name="field" value="deleted">' print '<input type="hidden" name="value" value="false">' print "Deleted (%s)" % Button("undelete", id="undelete").mini().positive() print "</form>" else: print "Deleted" print "<br>" elif task.status == "complete": print ProgressBar(lbl, total - hours, total, zeroDivZero=True, style="progress-current-green") elif task.status in ("blocked", "canceled", "deferred", "split"): hours = filter(lambda rev: rev.hours > 0, revs) hours = hours[-1].hours if len(hours) > 0 else 0 print ProgressBar(lbl, total - hours, total, zeroDivZero=True, style="progress-current-red") else: print ProgressBar(lbl, total - hours, total, zeroDivZero=True) header(task, "Notes", 2) for note in task.getNotes(): print '<div id="note%d" class="note">' % note.id print '<form method="post" action="/tasks/%d/notes/%d/modify">' % (id, note.id) print '<div class="avatar"><img src="%s"></div>' % note.user.getAvatar() print '<div class="text">' print '<div class="title"><a class="timestamp" href="#note%d">%s</a> by <span class="author">%s</span>' % ( note.id, tsToDate(note.timestamp).replace(microsecond=0), note.user.safe.username, ) if note.user == handler.session["user"]: print '<button name="action" value="delete" class="fancy mini danger">delete</button>' print "</div>" print '<div class="body markdown">%s</div>' % note.render() print "</div>" print "</form>" print "</div>" print '<div class="note new-note">' print '<form method="post" action="/tasks/%d/notes/new">' % id print '<div class="avatar"><div><img src="%s"></div></div>' % handler.session["user"].getAvatar() print '<div class="text">' print '<div class="title">' print "<b>New note</b>" print '<a target="_blank" href="/help/markdown" class="fancy mini">help</a>' print "</div>" print '<div class="body"><textarea name="body" class="large"></textarea></div>' print Button("Post").post().positive() print "<hr>" print '<div class="body markdown"><div id="preview"></div></div>' print "</div>" print "</form>" print "</div>" print '<button class="btn start-new-note">Add Note</button>' print '<div class="clear"></div>' header(task, "History", 2) chart = TaskChart("chart%d" % id, task) chart.js() chart.placeholder() showHistory(task, False) print "<br>"
def projectsList(handler): undelay(handler) print '<div class="project-list">' print '<div class="buttons">' print Button("Calendar", "/projects/calendar") print "</div>" for project in Project.getAllSorted(handler.session["user"]): sprints = filter(lambda sprint: not sprint.isHidden(handler.session["user"]), project.getSprints()) active, inactive = partition(lambda sprint: sprint.isActive() or sprint.isPlanning(), reversed(sprints)) activeMembers = set() for sprint in active: activeMembers |= sprint.members isTest = project.id < 0 classes = ["project-summary"] if len(active) > 0: classes.append("active") if isTest: classes.append("test") print '<div id="project-summary-%d" class="%s">' % (project.id, " ".join(classes)) print '<div class="project-name">%s</div>' % project.name print '<div class="buttons">' print Button("new sprint", "/sprints/new?project=%d" % project.id).mini() print Button("export", "/sprints/export?project=%d" % project.id).mini() print Button("active", "/sprints/active?project=%d" % project.id).mini() if handler.session["user"] and handler.session["user"].hasPrivilege("Admin"): print Button("manage", "/admin/projects/%d" % project.id).mini().negative() print "</div>" print '<div class="project-members %s">' % ("short" if len(inactive) <= 6 or len(active) == 0 else "long") members = set(project.getMembers()) scrummasters = set(sprint.owner for sprint in active) for member in sorted(scrummasters): print '<div class="member scrummaster">%s</div>' % member.str("scrummaster") for member in sorted(activeMembers - scrummasters): print '<div class="member active">%s</div>' % member.str("member") for member in sorted(members - activeMembers - scrummasters): print '<div class="member inactive">%s</div>' % member.str("member") print "</div>" if sprints: def printSprint(sprint): if sprint.isActive() or sprint.isPlanning(): print '<div class="sprint-active">' print '<div class="sprint-name">%s</div>' % sprint.link(handler.session["user"], "sprint-large") print '<div class="sprint-time">' if sprint.isPlanning(): print '<span class="label danger">Planning</span>' elif sprint.isReview(): print '<span class="label success">Review</span>' else: day = Weekday.today().date() sprintDays = [i.date() for i in sprint.getDays()] print '<span class="label primary">Day %d of %d</span>' % ( sprintDays.index(day) + 1, len(sprintDays), ) print "</div><br>" for tab in sprintTabs().group("").values(): print '<a href="%s">%s</a>' % (tab.getPath(sprint.id), tab.getDisplayName().lower()) if handler.session["user"] in sprint.members: print '<a href="/sprints/%d?search=assigned:me">your tasks</a>' % sprint.id print "</div>" else: print '<div class="sprint-summary">%s <span class="sprint-time">(%s - %s)</span></div>' % ( sprint.link(handler.session["user"]), sprint.getStartStr(), sprint.getEndStr(), ) print '<div class="sprint-summaries">' map(printSprint, active) if len(inactive) <= 6: map(printSprint, inactive) else: map(printSprint, inactive[:5]) print '<div class="show-old-sprints">(%d more)</div>' % (len(inactive) - 5) print '<div class="old-sprints">' map(printSprint, inactive[5:]) print "</div>" print "</div>" else: print " " print '<div class="clear"></div>' print "</div>" print "</div>"