예제 #1
0
파일: tasks.py 프로젝트: nolandda/Sprint
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)
예제 #2
0
파일: sprints.py 프로젝트: mrozekma/Sprint
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()
예제 #3
0
파일: security.py 프로젝트: nolandda/Sprint
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 &mdash; 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))
예제 #4
0
파일: admin.py 프로젝트: mrozekma/Sprint
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"
예제 #5
0
파일: sprints.py 프로젝트: mrozekma/Sprint
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
예제 #6
0
파일: users.py 프로젝트: mrozekma/Sprint
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>"
예제 #7
0
파일: admin.py 프로젝트: mrozekma/Sprint
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
예제 #8
0
파일: admin.py 프로젝트: mrozekma/Sprint
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)
예제 #9
0
파일: admin.py 프로젝트: mrozekma/Sprint
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\">&nbsp;%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>"
예제 #10
0
파일: admin.py 프로젝트: mrozekma/Sprint
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)
예제 #11
0
파일: users.py 프로젝트: mrozekma/Sprint
def userTasks(handler, username):
	handler.title('User tasks')
	user = User.load(username = username)
	if not user:
		ErrorBox.die("User tasks", "No user named <b>%s</b>" % stripTags(username))

	tasks = [task.id for task in Task.loadAll() if user in task.assigned and task.stillOpen() and task.sprint.isActive()]
	if len(tasks) == 0:
		ErrorBox.die("User tasks", "%s has no open tasks in active sprints" % user)

	redirect("/tasks/%s" % ','.join(map(str, tasks)))
예제 #12
0
파일: search.py 프로젝트: mrozekma/Sprint
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))
예제 #13
0
파일: admin.py 프로젝트: mrozekma/Sprint
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('/')
예제 #14
0
파일: users.py 프로젝트: mrozekma/Sprint
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)
예제 #15
0
파일: tasks.py 프로젝트: nolandda/Sprint
def newTaskManyUpload(handler, group, p_data):
    requirePriv(handler, "User")

    # Vague sanity check that this is actually text, from http://stackoverflow.com/a/7392391/309308
    textchars = "".join(map(chr, [7, 8, 9, 10, 12, 13, 27] + range(0x20, 0x100)))
    is_binary_string = lambda bytes: bool(bytes.translate(None, textchars))
    if is_binary_string(p_data):
        ErrorBox.die(
            "The uploaded file appears to be binary -- it should be a text file matching the normal add tasks format"
        )

    handler.session["many-upload"] = p_data
    redirect("/tasks/new/many?group=%s" % group)
예제 #16
0
파일: sprints.py 프로젝트: mrozekma/Sprint
def newSprint(handler, project):
	id = int(project)
	handler.title('New Sprint')
	requirePriv(handler, 'User')
	project = Project.load(id)
	if not project:
		ErrorBox.die('Invalid project', "No project with ID <b>%d</b>" % id)
	sprints = project.getSprints()

	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 "#select-members {padding-right: 5px;}"
	print "</style>"
	print "<script src=\"/static/sprints-new.js\" type=\"text/javascript\"></script>"

	print InfoBox('', id = 'post-status')

	print "<form method=\"post\" action=\"/sprints/new\">"
	print "<table class=\"list\">"
	print "<tr><td class=\"left\">Project:</td><td class=\"right\">"
	print "<select id=\"select-project\" name=\"project\">"
	for thisProject in Project.loadAll():
		print "<option value=\"%d\"%s>%s</option>" % (thisProject.id, ' selected' if thisProject == project else '', thisProject.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\">Planning:</td><td class=\"right\"><input type=\"text\" name=\"start\" class=\"date\" value=\"%s\"></td></tr>" % Weekday.today().strftime('%m/%d/%Y')
	print "<tr><td class=\"left\">Wrapup:</td><td class=\"right\"><input type=\"text\" name=\"end\" class=\"date\"></td></tr>"
	print "<tr><td class=\"left no-bump\">Members:</td><td class=\"right\">"
	print "<select name=\"members[]\" id=\"select-members\" multiple>"
	# Default to last sprint's members
	members = {handler.session['user']}
	if sprints:
		members |= sprints[-1].members
	for user in sorted(User.loadAll()):
		if user.hasPrivilege('User'):
			print "<option value=\"%d\"%s>%s</option>" % (user.id, ' selected' if user in members else '', user.safe.username)
	print "</select>"
	print "</td></tr>"
	print "<tr><td class=\"left no-bump\">Options:</td><td class=\"right\">"
	print "<div><input type=\"checkbox\" name=\"private\" id=\"flag-private\"><label for=\"flag-private\">Private &ndash; Only sprint members can view tasks</label></div>"
	print "<div><input type=\"checkbox\" name=\"hidden\" id=\"flag-hidden\"><label for=\"flag-hidden\">Hidden &ndash; Only sprint members can see the sprint</label></div>"
	print "</td></tr>"
	print "<tr><td class=\"left\">&nbsp;</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>"
예제 #17
0
파일: resetpw.py 프로젝트: mrozekma/Sprint
def sendResetEmailPost(handler, p_username):
	handler.title('Reset password')
	user = User.load(username = p_username)
	if not user:
		ErrorBox.die("Invalid User", "No user named <b>%s</b>" % stripTags(p_username))

	user.resetkey = "%x" % random.randint(0x10000000, 0xffffffff)
	try:
		sendmail(user.getEmail(), "Sprint - Password Reset", "Someone (hopefully you) requested a Sprint password reset. You can follow this link to reset your password: http://%s:%d/resetpw/%s?key=%s. If you didn't request this or no longer need it, it will expire in a day or two." % (gethostname(), PORT, user.safe.username, user.resetkey))
	except Exception:
		Event.error(handler, "<div style=\"white-space: normal\">%s</div>" % formatException())
		ErrorBox.die("Reset failed", "Unable to send password reset e-mail")

	user.save()
	print SuccessBox("A password reset e-mail has been sent")
예제 #18
0
파일: sprints.py 프로젝트: mrozekma/Sprint
def showMetrics(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")

	context = {}
	context['sprint'] = sprint
	context['allTasks'] = sprint.getTasks(includeDeleted = True)
	context['tasks'] = tasks = filter(lambda task: not task.deleted, context['allTasks'])
	context['revisions'] = {(task.id, day): task.getRevisionAt(day) for task, day in product(context['allTasks'], sprint.getDays())}

	handler.title(sprint.safe.name)
	drawNavArrows(sprint, handler.session['user'], 'metrics')

	print "<style type=\"text/css\">"
	print "h2 a {color: #000;}"
	print "</style>"

	charts = [
		('general', 'Hours (general)', HoursChart('chart-general', **context)),
		('commitment-by-user', 'Commitment (by user)', CommitmentByUserChart(**context)),
		('earned-value', 'Earned Value', EarnedValueChart('chart-earned-value', **context)),
		('by-user', 'Hours (by user)', HoursByUserChart('chart-by-user', **context)),
		('status', 'Task status', StatusChart('chart-status', **context)),
		('commitment', 'Total commitment', CommitmentChart('chart-commitment', **context)),
		('goals', 'Sprint goals', GoalsChart(**context)),
	]

	Chart.include()
	map(lambda (anchor, title, chart): chart.js(), charts)
	print tabs(sprint, 'metrics')

	if 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>. These unestimated tasks can artifically lower the tasking lines in the following charts" % sprint.id)

	for anchor, title, chart in charts:
		print "<a name=\"%s\">" % anchor
		print "<h2><a href=\"#%s\">%s</a></h2>" % (anchor, title)
		chart.placeholder()

	print "<br><br>"
예제 #19
0
파일: sprints.py 프로젝트: mrozekma/Sprint
def exportRender(handler, sprints, format):
	if not handler.session['user']:
		ErrorBox.die('Forbidden', "You must be logged in to create a new sprint")
	ids = map(int, sprints.split(','))
	sprints = map(Sprint.load, ids)

	try:
		export = exports[format]
	except KeyError:
		ErrorBox.die('Export format', "No format named <b>%s</b>" % stripTags(format))

	for sprint in sprints:
		if sprint and sprint.canView(handler.session['user']):
			export.process(sprint)

	handler.wrappers = False
	handler.forceDownload = "%s.%s" % (sprints[0].name if len(sprints) == 1 else 'sprints', export.getExtension())
예제 #20
0
파일: resetpw.py 프로젝트: mrozekma/Sprint
def sendResetEmail(handler):
	handler.title('Reset password')
	if handler.session['user']:
		redirect('/resetpw')
	if not settings.smtpServer:
		ErrorBox.die("Sprint is not configured for sending e-mail. You will need to contact an administrator to reset your password")

	print "A reset link will be send to your e-mail address. You can also contact an administrator to reset your password.<br><br>"
	print "<form method=\"post\" action=\"/resetpw/:mail\">"
	print "<table class=\"list\">"
	print "<tr><td class=\"left\">Username:</td><td><select name=\"username\">"
	for user in User.loadAll(orderby = 'username'):
		print "<option value=\"%s\">%s</option>" % (user.safe.username, user.safe.username)
	print "</select></td></tr>"
	print "<tr><td>&nbsp;</td><td>"
	print Button('Send e-mail', type = 'submit').positive()
	print "</td></tr>"
	print "</table>"
예제 #21
0
파일: resetpw.py 프로젝트: mrozekma/Sprint
def resetUserPassword(handler, username, key = None):
	handler.title('Reset password')

	user = User.load(username = username)
	if not user:
		ErrorBox.die('User', "No user named <b>%s</b>" % stripTags(username))

	print "<style type=\"text/css\">"
	print "input {"
	print "    position: relative;"
	print "    top: -2px;"
	print "}"
	print "</style>"

	if user == handler.session['user'] or (user.resetkey and user.resetkey == key):
		printResetForm(handler, user, user.resetkey)
	else:
		ErrorBox.die('Reset Password', 'Invalid reset key')
예제 #22
0
파일: groups.py 프로젝트: mrozekma/Sprint
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 ''))
예제 #23
0
파일: sprints.py 프로젝트: mrozekma/Sprint
def showSprintHistory(handler, id, assigned = None):
	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")
	tasks = sprint.getTasks(includeDeleted = True)

	handler.title(sprint.safe.name)
	drawNavArrows(sprint, handler.session['user'], 'history')

	Chart.include()
	chart = TaskChart('chart', sprint.getTasks())
	chart.js()
	print "<script type=\"text/javascript\">"
	tasksByAssigned = {member.username: [task.id for task in tasks if member in task.assigned] for member in sprint.members}
	print "var tasks_by_assigned = %s;" % toJS(tasksByAssigned)
	print "$(document).ready(function() {"
	if assigned:
		print "    $('%s').addClass('selected');" % ', '.join("#filter-assigned a[assigned=\"%s\"]" % username for username in assigned.split(','))
	print "    setup_filter_buttons();"
	print "    apply_filters();"
	print "});"
	print "</script>"

	print tabs(sprint, 'history')
	if len(tasks) == 0:
		print ErrorBox("This sprint has no tasks")
		print "<br>"
		return

	print "<div id=\"filter-assigned\">"
	print "<a class=\"fancy danger\" href=\"#\"><img src=\"/static/images/cross.png\">&nbsp;None</a>"
	for member in sorted(sprint.members):
		print "<a class=\"fancy\" assigned=\"%s\" href=\"/sprints/%d/history?assigned=%s\"><img src=\"%s\">&nbsp;%s</a>" % (member.username, id, member.username, member.getAvatar(16), member.username)
	print "</div><br>"

	chart.placeholder()
	showHistory(tasks, True)
	print "<br>"
예제 #24
0
파일: messages.py 프로젝트: mrozekma/Sprint
def send(handler, p_userid, p_body, dryrun = False):
	handler.title('Send message')
	if dryrun:
		handler.wrappers = False
	requirePriv(handler, 'User')

	targetID = int(p_userid)
	target = User.load(targetID)
	if not target:
		ErrorBox.die("No user with ID %d" % targetID)

	message = Message(target.id, handler.session['user'].id, "Direct message", p_body, 'markdown')
	if dryrun:
		print message.render()
	else:
		if p_body == '':
			ErrorBox.die('Empty Body', "No message provided")
		message.save()
		delay(handler, SuccessBox("Message dispatched to %s" % target, close = 3))
		redirect('/messages/sent')
예제 #25
0
파일: groups.py 프로젝트: mrozekma/Sprint
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))
예제 #26
0
파일: admin.py 프로젝트: mrozekma/Sprint
def adminTimePost(handler, p_date, p_time):
	handler.title('Mock time')
	requirePriv(handler, 'Admin')

	try:
		ts = re.match("^(\d{1,2})/(\d{1,2})/(\d{4})$", p_date)
		if not ts:
			raise ValueError("Malformed date: %s" % p_date)
		month, day, year = map(int, ts.groups())

		ts2 = re.match("^(\d{1,2}):(\d{1,2})$", p_time)
		if not ts2:
			raise ValueError("Malformed time: %s" % p_time)
		hour, minute = map(int, ts2.groups())

		effective = datetime(year, month, day, hour, minute, 0)
		delta = effective - datetime.now()
		setNowDelta(delta)
		Event.mockTime(handler, effective, delta)
	except ValueError, e:
		ErrorBox.die(e.message)
예제 #27
0
파일: tasks.py 프로젝트: nolandda/Sprint
def newNotePost(handler, taskid, p_body, dryrun=False):
    handler.title("New Note")
    if dryrun:
        handler.wrappers = False
    requirePriv(handler, "User")

    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)

    note = Note(task.id, handler.session["user"].id, p_body)

    if dryrun:
        print note.render()
    else:
        if p_body == "":
            ErrorBox.die("Empty Body", "No note provided")
        note.save()
        Event.newNote(handler, note)
        redirect("/tasks/%d#note%d" % (task.id, note.id))
예제 #28
0
파일: search.py 프로젝트: mrozekma/Sprint
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')
예제 #29
0
파일: sprints.py 프로젝트: mrozekma/Sprint
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>"
예제 #30
0
파일: sprints.py 프로젝트: mrozekma/Sprint
def sprintRetrospectiveStart(handler, id):
	if not handler.session['user']:
		ErrorBox.die('Forbidden', "You must be logged in to modify sprint info")

	id = int(id)
	sprint = Sprint.load(id)
	if not sprint or sprint.isHidden(handler.session['user']):
		ErrorBox.die('Sprints', "There is no sprint with ID %d" % id)
	elif sprint.owner != handler.session['user']:
		ErrorBox.die('Forbidden', "Only the scrummaster can edit the retrospective")
	elif not (sprint.isReview() or sprint.isOver()):
		ErrorBox.die('Sprint Open', "The retrospective isn't available until the sprint is in review")

	Retrospective.init(sprint)
	redirect("/sprints/%d/retrospective" % sprint.id)
예제 #31
0
def showCode(filename, line, around = None):
	parsedFilename = filename if isabs(filename) else abspath("%s/%s" % (basePath(), filename))
	if not any(parsedFilename.startswith(path) for path in [basePath()] + sys.path):
		print ErrorBox("Illegal filename", "File <b>%s</b> not part of codebase or standard library" % stripTags(filename))
		return
	elif not isfile(parsedFilename):
		print ErrorBox("Illegal filename", "Unknown file <b>%s</b>" % stripTags(filename))
		return

	with open(parsedFilename) as f:
		data = f.read()

	lines = highlightCode(data).split('<br/>')
	if line < 1:
		line = 1
	elif line > len(lines):
		line = len(lines)
	lines = ["<tr class=\"%s\"><td class=\"icon\">&nbsp;</td><td class=\"p_linum\"><a name=\"l%d\" href=\"#l%d\">%s</a></td><td class=\"code_line\">%s</td></tr>" % ('selected_line' if i == line else '', i, i, ("%3d" % i).replace(' ', '&nbsp;'), lines[i-1]) for i in range(1, len(lines)+1)]
	if around:
		lines = lines[line-around-1:line+around]

	lines = "<table class=\"code_default dark\">\n%s\n</table>" % '\n'.join(lines)
	print lines