コード例 #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
ファイル: 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))
コード例 #3
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)
コード例 #4
0
ファイル: groups.py プロジェクト: mrozekma/Sprint
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)
コード例 #5
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')
コード例 #6
0
ファイル: tasks.py プロジェクト: nolandda/Sprint
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)
コード例 #7
0
ファイル: tasks.py プロジェクト: nolandda/Sprint
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)
コード例 #8
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)
コード例 #9
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('/')
コード例 #10
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)
コード例 #11
0
ファイル: search.py プロジェクト: mrozekma/Sprint
def newSavedSearchPost(handler, p_name, p_query, p_public = False, p_sprintid = None):
	# def die(msg):
		# print msg
		# done()

	handler.title('New Search')
	requirePriv(handler, 'User')
	handler.wrappers = False

	search = SavedSearch(handler.session['user'].id, p_name, p_query, bool(p_public))
	search.save()

	handler.responseCode = 299
	delay(handler, SuccessBox("Saved search <b>%s</b>" % search.safe.name, close = 3, fixed = True))
	if p_sprintid:
		print "/search/saved/%d/run/%s" % (search.id, p_sprintid)
	else:
		print "/search/saved"
コード例 #12
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')
コード例 #13
0
ファイル: users.py プロジェクト: mrozekma/Sprint
def userAvatarSet(handler, username, p_data):
	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 len(p_data) > AVATAR_MAX_SIZE:
		ErrorBox.die("Set avatar", "Avatar too large (%s)" % pluralize(len(p_data), 'byte', 'bytes'))
	type = imgtype(None, p_data)
	if type is None:
		ErrorBox.die("Set avatar", "Image type unrecognized")
	elif type not in AVATAR_TYPES:
		ErrorBox.die("Set avatar", "Unsupported image type %s" % type.upper())

	user.avatar = b64encode(p_data)
	user.save()
	delay(handler, SuccessBox("Avatar uploaded"))
	redirect("/users/%s" % user.username)
コード例 #14
0
ファイル: admin.py プロジェクト: mrozekma/Sprint
def adminProjectsCancelSprintDeletionPost(handler, projectid, id):
	handler.title('Undelete Sprint')
	requirePriv(handler, 'Admin')
	project = Project.load(int(projectid))
	if not project:
		ErrorBox.die('Invalid Project', "No project with ID <b>%d</b>" % int(projectid))
	sprint = Sprint.load(int(id))
	if not sprint:
		ErrorBox.die('Invalid Sprint', "No sprint with ID <b>%d</b>" % int(id))
	if sprint.project != project:
		# We really don't use the project at all, but it's the principle of the thing
		ErrorBox.die('Invalid Sprint', "Project/sprint mismatch")
	if 'deleted' not in sprint.flags:
		ErrorBox.die('Invalid Sprint', "Sprint is not deleted")

	sprint.flags.remove('deleted')
	sprint.save()
	Event.undeleteSprint(handler, sprint)

	delay(handler, SuccessBox("Deletion canceled for %s" % sprint.link(handler.session['user']), close = True))
	redirect("/admin/projects/%d" % project.id)
コード例 #15
0
ファイル: admin.py プロジェクト: mrozekma/Sprint
def adminProjectsDeletePost(handler, id, p_newproject = None):
	handler.title('Delete 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))

	sprints = project.getSprints()
	if len(sprints) > 0:
		if p_newproject is None:
			ErrorBox.die('Missing Parameter', "No new project specified")
		p_newproject = to_int(p_newproject, 'newproject', ErrorBox.die)
		target = Project.load(p_newproject)
		if not target:
			ErrorBox.die('Invalid Project', "No target project with ID <b>%d</b>" % p_newproject)
		for sprint in sprints:
			sprint.project = target
			sprint.save()

	project.delete()
	delay(handler, SuccessBox("Project deleted", close = True))
	redirect("/admin/projects")
コード例 #16
0
ファイル: messages.py プロジェクト: mrozekma/Sprint
def deleteMessage(handler, id, p_x = None, p_y = None):
	handler.title('Delete message')
	requirePriv(handler, 'User')

	id = int(id)
	message = Message.load(id)
	if not message:
		ErrorBox.die("Message %d does not exist" % id)
	if message.user == handler.session['user']:
		verb = 'deleted'
		redir = '/messages/inbox'
	elif message.sender == handler.session['user']:
		verb = 'retracted'
		redir = '/messages/sent'
		if message.read:
			ErrorBox.die("Unable to retract read messages")
	else:
		ErrorBox.die("You don't have permission to delete this message")

	message.delete()
	delay(handler, SuccessBox("Message %s" % verb, close = 3))
	redirect(redir)
コード例 #17
0
ファイル: sprints.py プロジェクト: mrozekma/Sprint
def sprintAvailabilityPost(handler, id, p_hours):
	def die(msg):
		print msg
		done()

	handler.wrappers = False
	id = int(id)

	if not handler.session['user']:
		die("You must be logged in to modify sprint info")

	sprint = Sprint.load(id)
	if not sprint or sprint.isHidden(handler.session['user']):
		die("There is no sprint with ID %d" % id)

	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")

	avail = Availability(sprint)
	for k, hours in p_hours.items():
		userid, timestamp = map(int, k.split(',', 1))
		hours = int(hours)

		user = User.load(userid)
		if not user in sprint.members:
			die("Trying to set availability of non-member %s" % user.safe.username)
		time = tsToDate(timestamp)
		if not sprint.start <= timestamp <= sprint.end:
			die("Trying to set availability outside of sprint window")

		avail.set(user, time, hours)

	handler.responseCode = 299
	delay(handler, SuccessBox("Updated availability", close = 3, fixed = True))
	Event.sprintAvailUpdate(handler, sprint)
コード例 #18
0
ファイル: admin.py プロジェクト: mrozekma/Sprint
def adminProjectsDeleteSprintsPost(handler, id, p_newproject = None, p_sprintid = None):
	# p_newproject is part of the form because of adminProjectsMoveSprintsPost; it's unused here
	handler.title('Delete 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))

	if not p_sprintid:
		delay(handler, WarningBox("No sprints to delete", 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.flags.add('deleted')
		sprint.save()
		Event.deleteSprint(handler, sprint)

	delay(handler, SuccessBox("%s queued for deletion. To delete now, run the <a href=\"/admin/cron\">Sprint Cleanup cron job</a>" % pluralize(len(sprints), 'sprint', 'sprints'), close = True))
	redirect("/admin/projects/%d" % project.id)
コード例 #19
0
ファイル: search.py プロジェクト: mrozekma/Sprint
def updateSavedSearch(handler, id, action):
	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 follow your own search")
	elif action == 'follow' and search.following(handler.session['user']):
		ErrorBox.die('Invalid State', "Already following this search")
	elif action == 'unfollow' and not search.following(handler.session['user']):
		ErrorBox.die('Invalid State', "Not following this search")

	if action == 'follow':
		search.follow(handler.session['user'])
		delay(handler, SuccessBox("Search followed"))
	elif action == 'unfollow':
		search.unfollow(handler.session['user'])
		delay(handler, SuccessBox("Search unfollowed"))
	else:
		ErrorBox.die('Invalid Action', "Unknown action %s" % stripTags(action))

	redirect('/search/saved/others')
コード例 #20
0
ファイル: admin.py プロジェクト: mrozekma/Sprint
			del settings['kerberosRealm']
	else:
		settings.kerberosRealm = p_kerberosRealm

	if p_smtpServer == '':
		if settings.smtpServer:
			del settings['smtpServer']
		if settings.smtpFrom:
			del settings['smtpFrom']
	else:
		settings.smtpServer = p_smtpServer
		settings.smtpFrom = p_smtpFrom or "sprint@%s" % p_smtpServer

	settings.autolink = zip(*autolinks)

	delay(handler, SuccessBox("Updated settings", close = True))
	Event.adminSettings(handler, settings)
	redirect('/admin/settings')

@admin('admin/users', 'Users', 'users')
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>"
コード例 #21
0
ファイル: sprints.py プロジェクト: mrozekma/Sprint
def sprintInfoPost(handler, id, p_name, p_start, p_end, p_goals, p_members = None, p_clear = [], p_private = False, p_hidden = False):
	def die(msg):
		print msg
		done()

	handler.wrappers = False

	if not handler.session['user']:
		die("You must be logged in to modify sprint info")

	id = to_int(id, 'id', die)
	p_members = to_int(p_members, 'members', die)
	sprint = Sprint.load(id)
	if not sprint or sprint.isHidden(handler.session['user']):
		die("There is no sprint with ID %d" % id)
	if sprint.owner != handler.session['user']:
		die("You must be the scrummaster to modify sprint information")

	try:
		start = re.match("^(\d{1,2})/(\d{1,2})/(\d{4})$", p_start)
		if not start:
			raise ValueError
		month, day, year = map(int, start.groups())
		start = datetime(year, month, day, 0, 0, 0)
	except ValueError:
		die("Malformed start date: %s" % stripTags(p_start))

	try:
		end = re.match("^(\d{1,2})/(\d{1,2})/(\d{4})$", p_end)
		if not end:
			raise ValueError
		month, day, year = map(int, end.groups())
		end = datetime(year, month, day, 23, 59, 59)
	except ValueError:
		die("Malformed end date: %s" % stripTags(p_end))

	msg = Sprint.validateDates(start, end, tsToDate(sprint.start), tsToDate(sprint.end))
	if msg:
		die(msg)

	goals = map(Goal.load, to_int(p_goals.keys(), 'goals', die))
	if not all(goals):
		die("One or more goals do not exist")

	members = set(map(User.load, p_members)) if p_members else set()
	if not all(members):
		die("One or more members do not exist")
	if sprint.owner not in members:
		die("The scrummaster (%s) must be a sprint member" % sprint.owner)

	tasks = sprint.getTasks()
	changedTasks = set()
	avail = Availability(sprint)
	addMembers = set(members) - set(sprint.members)
	delMembers = set(sprint.members) - set(members)
	for user in delMembers:
		for task in filter(lambda task: user in task.assigned, tasks):
			print "Removing %s from %d<br>" % (user, task.id)
			task.assigned -= {user}
			if len(task.assigned) == 0:
				print "Adding %s to %d<br>" % (sprint.owner, task.id)
				task.assigned = {sprint.owner}
			changedTasks.add(task)
		avail.delete(user)
		sprint.members -= {user}

	# For event dispatching
	changes = OrderedDict([
		('name', None if sprint.name == p_name else p_name),
		('start', None if tsToDate(sprint.start) == start else start),
		('end', None if tsToDate(sprint.end) == end else end),
		('addMembers', addMembers),
		('delMembers', delMembers),

		# Updated later
		('addGoals', []),
		('removeGoals', []),
		('addFlags', []),
		('removeFlags', []),
	])

	sprint.members |= addMembers
	sprint.name = p_name
	p_private = (p_private or p_hidden) # Hidden implies Private
	for flagName, flagValue in (('private', p_private), ('hidden', p_hidden)):
		if flagValue and flagName not in sprint.flags:
			sprint.flags.add(flagName)
			changes['addFlags'].append(flagName)
		elif not flagValue and flagName in sprint.flags:
			sprint.flags.remove(flagName)
			changes['removeFlags'].append(flagName)

	if dateToTs(start) != sprint.start or dateToTs(end) != sprint.end:
		sprint.start = dateToTs(start)
		sprint.end = dateToTs(end)
		avail.trim()

	sprint.save()

	for id in p_goals:
		goal = Goal.load(int(id))
		if goal.name != p_goals[id]:
			if goal.name:
				changes['removeGoals'].append(goal.name)
			if p_goals[id]:
				changes['addGoals'].append(p_goals[id])

		goal.name = p_goals[id]
		goal.save()

	if start:
		for task in sprint.getTasks(includeDeleted = True):
			for rev in task.getRevisions():
				if rev.timestamp < sprint.start:
					rev.timestamp = sprint.start
					rev.save()
				else:
					break
		for task in changedTasks:
			if task.timestamp < sprint.start:
				task.timestamp = sprint.start

	if p_clear:
		ids = [to_int(goalid, 'p_clear', die) for goalid in p_clear]
		for task in tasks:
			if task.goal and task.goal.id in ids:
				task.goal = None
				changedTasks.add(task)

	for task in changedTasks:
		print "Saving new revision for %d<br>" % task.id
		task.saveRevision(handler.session['user'])

	handler.responseCode = 299
	delay(handler, SuccessBox("Updated info", close = 3, fixed = True))
	Event.sprintInfoUpdate(handler, sprint, changes)
コード例 #22
0
ファイル: tasks.py プロジェクト: nolandda/Sprint
def newTaskImportPost(handler, group, source, p_data):
    def die(msg):
        print msg
        done()

    handler.title("Import Tasks")
    requirePriv(handler, "User")
    handler.wrappers = False

    id = int(group)
    group = Group.load(id)
    if not group or group.sprint.isHidden(handler.session["user"]):
        die("No group with ID <b>%d</b>" % id)

    sprint = group.sprint
    if not (sprint.isActive() or sprint.isPlanning()):
        die("Unable to modify inactive sprint")
    elif not sprint.canEdit(handler.session["user"]):
        die("You don't have permission to modify this sprint")

    id = int(source)
    source = Sprint.load(id)
    if not source:
        die("No sprint with ID <b>%d</b>" % id)

    try:
        data = fromJS(p_data)
    except ValueError:
        die("Improperly encoded data")
    if not isinstance(data, list) or not all(
        set(task.keys()) == {"name", "assigned", "status", "groupid", "hours"} for task in data
    ):
        die("Improperly encoded data")
    usernames = {user.username for user in sprint.members}
    if not all(
        set(task["assigned"].split(" ")) <= usernames
        and task["status"] in statuses
        and isinstance(task["groupid"], int)
        and Group.load(task["groupid"]) is not None
        and isinstance(task["hours"], int)
        and task["hours"] >= 0
        for task in data
    ):
        die("Invalid data")

    dataByGroup = {}
    for task in data:
        if task["groupid"] not in dataByGroup:
            dataByGroup[task["groupid"]] = []
        dataByGroup[task["groupid"]].append(task)

    newGroups = {}  # old sprint's group ID -> new sprint's new Group object
    for groupid in dataByGroup:
        oldGroup = Group.load(groupid)
        group = Group.load(sprintid=sprint.id, name=oldGroup.name)
        if not group:  # No group in this sprint with the right name
            if groupid in newGroups:  # Already made a new group
                group = newGroups[groupid]
            else:  # Need a new group
                group = newGroups[groupid] = Group(sprint.id, oldGroup.name)
                group.save()

        for taskData in dataByGroup[groupid]:
            task = Task(
                group.id,
                sprint.id,
                handler.session["user"].id,
                0,
                taskData["name"],
                taskData["status"],
                taskData["hours"],
                {User.load(username=username).id for username in taskData["assigned"].split(" ")},
            )
            task.save()
            Event.newTask(handler, task)

    numGroups, numTasks = len(newGroups), len(data)
    if numGroups > 0 and numGroups > 0:
        delay(
            handler,
            SuccessBox(
                "Added %d %s, %d %s"
                % (numGroups, "group" if numGroups == 1 else "groups", numTasks, "task" if numTasks == 1 else "tasks"),
                close=3,
                fixed=True,
            ),
        )
    elif numGroups > 0:
        delay(
            handler,
            SuccessBox("Added %d %s" % (numGroups, "group" if numGroups == 1 else "groups"), close=3, fixed=True),
        )
    elif numTasks > 0:
        delay(
            handler, SuccessBox("Added %d %s" % (numTasks, "task" if numTasks == 1 else "tasks"), close=3, fixed=True)
        )
    else:
        delay(handler, WarningBox("No changes", close=3, fixed=True))
    handler.responseCode = 299
コード例 #23
0
ファイル: tasks.py プロジェクト: nolandda/Sprint
def newTaskMany(handler, group, p_body, dryrun=False):
    def die(msg):
        print msg
        done()

    handler.wrappers = False
    requirePriv(handler, "User")
    id = int(group)

    defaultGroup = Group.load(id)
    if not defaultGroup or defaultGroup.sprint.isHidden(handler.session["user"]):
        die("No group with ID <b>%d</b>" % id)

    sprint = defaultGroup.sprint
    if not (sprint.isActive() or sprint.isPlanning()):
        die("Unable to modify inactive sprint")
    elif not sprint.canEdit(handler.session["user"]):
        die("You don't have permission to modify this sprint")

    group = defaultGroup
    groups = [group]
    newGroups = []
    tasks = {group: []}
    sep = "|"
    lines = map(lambda x: x.strip(" \r\n"), p_body.split("\n"))
    errors = []
    defaultAssigned = {handler.session["user"] if handler.session["user"] in sprint.members else sprint.owner}

    for line in lines:
        if line == "":
            group = defaultGroup
        elif line[0] == "#":  # Comment
            continue
        elif len(line) == 1:  # Separator
            sep = line[0]
        elif (
            line[0] == "[" and line[-1] == "]" and set(line[1:-1].split(" ")) <= set(u.username for u in sprint.members)
        ):  # Default assigned
            defaultAssigned = {User.load(username=username) for username in line[1:-1].split(" ")}
        elif line[-1] == ":":  # Group
            line = line[:-1]
            group = Group.load(sprintid=sprint.id, name=line)
            if not group:  # Check if new group already defined
                for newGroup in newGroups:
                    if newGroup.name == line:
                        group = newGroup
                        break
            if not group:  # Make new group
                group = Group(sprint.id, line)
                newGroups.append(group)
                group.id = -len(newGroups)
            if not group in groups:  # First time this group has been used in the script
                groups.append(group)
                tasks[group] = []
        else:
            parts = line.split(sep)
            name, assigned, status, hours = None, None, None, None
            if not 2 <= len(parts) <= 4:
                errors.append("Unable to parse (field count mismatch): %s" % stripTags(line))
                continue
            for part in parts:
                part = part.strip()
                if part == "":
                    errors.append("Unable to parse (empty field): %s" % stripTags(line))
                    continue

                    # Hours
                if hours is None:
                    try:
                        hours = int(part)
                        continue
                    except ValueError:
                        pass

                    # Status
                if status is None and part.lower() in statuses:
                    status = part.lower()
                    continue

                    # Assigned
                if assigned is None and set(part.split(" ")) <= set(u.username for u in sprint.members):
                    assigned = set(User.load(username=username) for username in part.split(" "))
                    continue

                    # Name
                if name is None:
                    name = part
                    continue

                errors.append("Unable to parse (no field match on '%s'): %s" % (stripTags(part), stripTags(line)))

            if assigned is None:
                assigned = defaultAssigned
            if status is None:
                status = "not started"
            if name is None or hours is None:
                errors.append("Unable to parse (missing required fields): %s" % stripTags(line))
            if not any(v is None for v in (name, assigned, status, hours)):
                tasks[group].append((name, assigned, status, hours))

    if dryrun:
        handler.log = False
        numTasks = sum(len(taskSet) for taskSet in tasks.values())
        taskHours = sum(
            hours for taskSet in tasks.values() for name, assigned, status, hours in taskSet if status != "deferred"
        )
        ownTaskHours = sum(
            hours
            for taskSet in tasks.values()
            for name, assigned, status, hours in taskSet
            if status != "deferred" and handler.session["user"] in assigned
        )
        avail = Availability(sprint)
        availHours = avail.getAllForward(getNow().date(), handler.session["user"])
        usedHours = sum(task.effectiveHours() for task in sprint.getTasks() if handler.session["user"] in task.assigned)
        availHours -= usedHours
        if errors:
            print ErrorBox("<br>".join(errors))
        if numTasks:
            box = InfoBox
            stats = "Adding %s " % pluralize(numTasks, "task", "tasks")
            if newGroups:
                stats += "and %s " % pluralize(len(newGroups), "group", "groups")
            stats += "for a total of %s" % pluralize(taskHours, "hour", "hours")
            if ownTaskHours != taskHours:
                stats += ", %s yours" % pluralize(ownTaskHours, "hour", "hours")
            if ownTaskHours:
                if availHours == 0:
                    stats += ". You have no future availability for these tasks"
                    box = WarningBox
                elif availHours < 0:
                    stats += ". You are already overcommitted by %s" % pluralize(-availHours, "hour", "hours")
                    box = WarningBox
                else:
                    stats += ", %d%% of your future availability" % (100 * ownTaskHours / availHours)
                    box = WarningBox if ownTaskHours > availHours else InfoBox
            print box(stats)
        elif not errors:
            print InfoBox('Waiting for tasks. Click "Help" above if needed')

        groupedTasks = OrderedDict(
            (
                group,
                [
                    Task(
                        group.id,
                        sprint.id,
                        handler.session["user"].id,
                        0,
                        name,
                        status,
                        hours,
                        {user.id for user in assigned},
                        1,
                        id=0,
                    )
                    for name, assigned, status, hours in tasks[group]
                ],
            )
            for group in groups
        )
        print TaskTable(sprint, False, tasks=groupedTasks, status=True, name=True, assigned=True, hours=True)

    elif errors:
        die("There are unparseable lines in the task script. See the preview for more information")
    else:
        # There's some weirdness in the way groups auto-sequence that breaks when multiple groups are made without saving
        seq = maxOr(group.seq for group in sprint.getGroups()) + 1
        for group in newGroups:
            group.seq = seq
            seq += 1

        for group in groups:
            # Changing a group's ID will change its hash, so this pulls from tasks before saving the group in case it's new
            groupTasks = tasks[group]
            if group in newGroups:
                group.id = 0
            group.save()
            for name, assigned, status, hours in groupTasks:
                task = Task(group.id, group.sprint.id, handler.session["user"].id, 0, name, status, hours)
                task.assigned |= assigned
                task.save()
                Event.newTask(handler, task)

        numGroups = len(newGroups)
        numTasks = sum(map(lambda g: len(g), tasks.values()))
        if numGroups > 0 and numGroups > 0:
            delay(
                handler,
                SuccessBox(
                    "Added %d %s, %d %s"
                    % (
                        numGroups,
                        "group" if numGroups == 1 else "groups",
                        numTasks,
                        "task" if numTasks == 1 else "tasks",
                    ),
                    close=3,
                    fixed=True,
                ),
            )
        elif numGroups > 0:
            delay(
                handler,
                SuccessBox("Added %d %s" % (numGroups, "group" if numGroups == 1 else "groups"), close=3, fixed=True),
            )
        elif numTasks > 0:
            delay(
                handler,
                SuccessBox("Added %d %s" % (numTasks, "task" if numTasks == 1 else "tasks"), close=3, fixed=True),
            )
        else:
            delay(handler, WarningBox("No changes", close=3, fixed=True))
        handler.responseCode = 299
コード例 #24
0
ファイル: admin.py プロジェクト: mrozekma/Sprint
def adminUsersPost(handler, p_action, p_username, p_privileges = [], p_send_welcome = False):
	handler.title('User Management')
	requirePriv(handler, 'Admin')

	for case in switch(p_action):
		if case('resetpw'):
			handler.title('Reset password')
			user = User.load(username = p_username)
			if not user:
				ErrorBox.die('Reset Password', "No user named <b>%s</b>" % stripTags(p_username))

			hadPreviousKey = (user.resetkey != None and user.resetkey != '0')
			user.resetkey = "%x" % random.randint(0x10000000, 0xffffffff)
			user.save()
			Event.genResetKey(handler, user)

			print "Reset key for %s: <a href=\"/resetpw/%s?key=%s\">%s</a><br>" % (user, user.safe.username, user.resetkey, user.resetkey)
			if hadPreviousKey:
				print "<b>Warning</b>: This invalides the previous reset key for this user<br>"
			break
		if case('impersonate'):
			user = User.load(username = p_username)
			if not user:
				ErrorBox.die('Impersonate User', "No user named <b>%s</b>" % stripTags(p_username))

			if not 'impersonator' in handler.session:
				handler.session['impersonator'] = handler.session['user']
				handler.session.remember('impersonator')
			Event.impersonate(handler, user)
			handler.session['user'] = user
			redirect('/')
			break
		if case('sessions'):
			redirect("/admin/sessions?username=%s" % p_username)
			break
		if case('privileges'):
			redirect("/admin/privileges?username=%s" % p_username)
			break
		if case('new'):
			if User.load(username = p_username):
				ErrorBox.die('Add User', "There is already a user named <b>%s</b>" % stripTags(p_username))
			if not re.match("^%s$" % USERNAME_PATTERN, p_username):
				ErrorBox.die('Add User', "Username <b>%s</b> is illegal" % stripTags(p_username))
			if not all(name in privList for name in p_privileges):
				ErrorBox.die('Add User', "Unrecognized privilege name")

			user = User(p_username, '', privileges = set(p_privileges))
			Event.newUser(handler, user)
			for priv in p_privileges:
				Event.grantPrivilege(handler, user, priv, True)

			if p_send_welcome:
				user.resetkey = "%x" % random.randint(0x10000000, 0xffffffff)
				msg = "A Sprint account has been created for you with the username `%s'. " % user.username
				if settings.kerberosRealm:
					msg += "You can use your %s password to login, or set a dedicated Sprint password here: " % settings.kerberosRealm
				else:
					msg += "You can set a password here: "
				msg += "http://%s:%d/resetpw/%s?key=%s" % (gethostname(), PORT, user.safe.username, user.resetkey)
				sendmail(user.getEmail(), "Sprint - New Account", msg)

			user.save()

			delay(handler, SuccessBox("Added user <b>%s</b>" % stripTags(p_username), close = True))
			redirect("/users/%s" % user.username)
			break