def comment(project, issue): """Submits a new comment.""" text = request.form.get('text') error = comment_validation(text) if error is not None: flash(error) else: new_comment = Comment(text=text, user=current_user, issue=issue) db.session.add(new_comment) if current_user == issue.assignee: admin_reviewer_add_notification( project, 'new comment', { 'avatar': current_user.avatar(), 'fullname': current_user.fullname(), 'projectId': project.id, }, issue.id, ) else: issue.assignee.add_notification( 'new comment', { 'avatar': current_user.avatar(), 'fullname': current_user.fullname(), 'projectId': project.id, }, issue.id, ) db.session.commit() return redirect(url_for('issue.issue', id=project.id, issue_id=issue.id))
def close(project, issue): """Marks an issue as closed. Args: project: in: close_issue_permission_required() decorator type: Project description: A Project object whose id is the same as the id in the path. issue: in: close_issue_permission_required() decorator type: Issue description: An Issue object whose id is the same as the id in the path. url: in: formData type: String description: The url to be redirected to. Responses: 302: description: Redirect to the url page. 400: description: Bad request. 403: description: Current user does not have the permission. 404: description: Project or issue does not exist. """ redirect_url = request.form.get('url') issue.status = 'Closed' issue.closed_timestamp = time() if issue.assignee is None: issue.creator.add_notification( 'mark closed', { 'avatar': current_user.avatar(), 'fullname': current_user.fullname(), 'issueTitle': issue.title, }, ) else: issue.assignee.add_notification( 'mark closed', { 'avatar': current_user.avatar(), 'fullname': current_user.fullname(), 'issueTitle': issue.title, }, ) db.session.commit() return redirect(redirect_url)
def create(user_project): """Creates a new issue. Args: user_project: in: permission_required() decorator type: UserProject description: A UserProject object whose user_id belongs to the current user and project_id is the same as the query parameter - id. title: in: formData type: string description: The new issue's title. description: in: formData type: string description: The new issue's description. Responses: 302: description: Redirect to project page. 403: description: Forbidden. 404: description: Project not found. """ title = request.form.get('title') description = request.form.get('description') project = user_project.project error = create_validation(title, description) if error is not None: flash(error) else: new_issue = Issue( title=title, description=description, creator=current_user, project=project, ) db.session.add(new_issue) admin_reviewer_add_notification( project, 'new issue', { 'avatar': current_user.avatar(), 'fullname': current_user.fullname(), 'projectTitle': project.title, 'issueTitle': title, }, ) db.session.commit() os.makedirs( os.path.join(current_app.config['UPLOAD_PATH'], str(new_issue.id))) return redirect(url_for('project.project', id=project.id))
def add_basic_notification(self, name, title): self.add_notification( name, { 'avatar': current_user.avatar(), 'fullname': current_user.fullname(), 'projectTitle': title, }, )
def assign(project, issue): """Assigns an issue. Args: project: in: assign_issue_permission_required() decorator type: Project description: A Project object whose id is the same as the id in the path. issue: in: assign_issue_permission_required() decorator type: Issue description: An Issue object whose id is the same as the id in the path. priority: in: formData type: String description: The priority level of the issue. assignee_id: in: formData type: int description: The assignee's id. Responses: 302: description: Redirect to project page. 400: description: Bad request. 403: description: Current user does not have the permission. 404: description: Project or issue does not exist. """ priority = request.form.get('priority') assignee_id = request.form.get('assignee_id') error = assign_validation(project, issue, priority, assignee_id) if error is not None: flash(error) else: issue.priority = priority issue.status = 'In Progress' issue.assignee_id = assignee_id if issue.assignee != current_user: issue.assignee.add_notification( 'assign issue', { 'avatar': current_user.avatar(), 'fullname': current_user.fullname(), 'projectId': project.id, }, issue.id, ) db.session.commit() return redirect(url_for('project.project', id=project.id))
def change_role(user_project): """Changes a member's role. Changes a member's role. If member's previous role is Reviewer, demote to Developer. Otherwise, promote to Reviewer. Notifies the member. Produces: application/json Args: user_project: in: permission_required() decorator type: UserProject description: A UserProject object whose user_id belongs to the current user and project_id is the same as the query parameter - id. user_id: in: json type: int description: Id of the user to be promoted or demoted. Responses: 200: description: Change successfully. 400: description: Bad request. 404: description: User not found. 422: description: Unprocessable. """ body = request.get_json() user_id = body.get('user_id') if user_id is None: abort(400) project = user_project.project user = User.query.get_or_404(user_id) user_project = change_role_validation(project, user) new_role = user_project.change_role() user.add_notification( 'change role', { 'avatar': current_user.avatar(), 'fullname': current_user.fullname(), 'projectTitle': project.title, 'newRole': new_role.name, }, ) db.session.commit() return {'success': True}
def delete(project, issue): """Deletes an issue. Produces: application/json text/html Args: project: in: delete_issue_permission_required() decorator type: Project description: A Project object whose id is the same as the id in the path. issue: in: delete_issue_permission_required() decorator type: Issue description: An Issue object whose id is the same as the id in the path. Responses: 302: description: Redirect to project page. 400: description: Bad request. 403: description: Forbidden. 404: description: Project or issue does not exist. """ db.session.delete(issue) data = { 'avatar': current_user.avatar(), 'fullname': current_user.fullname(), 'projectTitle': project.title, 'issueTitle': issue.title, } if issue.creator != current_user: issue.creator.add_notification( 'delete issue', data, ) if issue.assignee is not None and issue.assignee != current_user: issue.assignee.add_notification( 'delete issue', data, ) db.session.commit() shutil.rmtree( os.path.join(current_app.config['UPLOAD_PATH'], str(issue.id)), ignore_errors=True, ) return redirect(url_for('project.project', id=project.id))
def restore(project, issue): """Restores a closed or resolved issue back to previous status. Restores a closed or resolved issue back to previous status. Restores resolved issue's status to In Progress. Restores closed issue's status to Open or In Progress based on if there is an existing assignee. Args: project: in: assign_issue_permission_required() decorator type: Project description: A Project object whose id is the same as the id in the path. issue: in: assign_issue_permission_required() decorator type: Issue description: An Issue object whose id is the same as the id in the path. url: in: formData type: String description: The url to be redirected to. Responses: 302: description: Redirect to the url page. 400: description: Bad request. 403: description: Current user does not have the permission. 404: description: Project or issue does not exist. """ redirect_url = request.form.get('url') if issue.status == 'Closed': if issue.assignee is None: status = 'Open' admin_reviewer_add_notification( project, 'mark open', { 'avatar': current_user.avatar(), 'fullname': current_user.fullname(), 'issueTitle': issue.title, }, ) else: status = 'In Progress' if issue.assignee != current_user: issue.assignee.add_notification( 'mark in progress', { 'avatar': current_user.avatar(), 'fullname': current_user.fullname(), 'issueTitle': issue.title, 'preStatus': issue.status, 'projectId': project.id, }, issue.id, ) issue.closed_timestamp = None else: status = 'In Progress' if issue.assignee != current_user: issue.assignee.add_notification( 'mark in progress', { 'avatar': current_user.avatar(), 'fullname': current_user.fullname(), 'issueTitle': issue.title, 'preStatus': issue.status, 'projectId': project.id, }, issue.id, ) issue.resolved_timestamp = None issue.status = status db.session.commit() return redirect(redirect_url)
def edit(project, issue): """Edits an issue. Edits an issue's title and description. If the project's status is In Progress, edits the priority and assignee too. Produces: application/json text/html Args: project: in: edit_issue_permission_required() decorator type: Project description: A Project object whose id is the same as the id in the path. issue: in: edit_issue_permission_required() decorator type: Issue description: An Issue object whose id is the same as the id in the path. title: in: json type: string description: The issue's new title. description: in: json type: string description: The issue's new description. priority: in: json type: String description: The priority level of the issue. assignee_id: in: json type: String description: The assignee's id. Responses: 200: description: Edit successfully. 400: description: Bad request. 403: description: Forbidden. 404: description: Project or issue does not exist. 422: description: Unprocessable. """ body = request.get_json() title = body.get('title') description = body.get('description') if None in (title, description): abort(400) if issue.status == 'In Progress': priority = body.get('priority') assignee_id = body.get('assignee_id') if None in (priority, assignee_id): abort(400) new_assignee = edit_validation(issue, title, description, project, priority, assignee_id) issue.priority = priority if issue.assignee != new_assignee: if issue.assignee != current_user: issue.assignee.add_notification( 'remove assignee', { 'avatar': current_user.avatar(), 'fullname': current_user.fullname(), 'issueTitle': issue.title, }, ) if new_assignee != current_user: new_assignee.add_notification( 'assign issue', { 'avatar': current_user.avatar(), 'fullname': current_user.fullname(), 'projectId': project.id, }, issue.id, ) issue.assignee_id = assignee_id else: edit_validation(issue, title, description) issue.title = title issue.description = description db.session.commit() return {'success': True}
def invite(user_project): """Searches for users to be invited or sends invitation. GET: Args: search: in: query type: string description: The search term. Responses: 200: description: Searched user's information. POST: Args: target: in: json type: string description: The target to be searched with. role: in: json type: string description: Role name to be assigned to the invited user. Responses: 200: description: Invitation successfully sent. 400: description: Bad request. 422: description: Unprocessable. Produces: application/json text/html Args: user_project: in: permission_required() decorator type: UserProject description: A UserProject object whose user_id belongs to the current user and project_id is the same as the query parameter - id. Responses: 403: description: Current user is not a member of the project or does not have the permission. 404: description: Project does not exist. """ project = user_project.project if request.method == 'POST': body = request.get_json() target = body.get('target') role_name = body.get('role') if None in (target, role_name): abort(400) user = invite_validation(project, target, role_name) if user is not None: data = { 'avatar': current_user.avatar(), 'fullname': current_user.fullname(), 'projectTitle': project.title, 'roleName': role_name, } user.add_notification('invitation', data, target_id=project.id) db.session.commit() return {'success': True} search_term = request.args.get('search') if search_term is None: return {'success': True, 'users': []} if search_term[-1] == ' ': search_term = search_term[:-1] ilike_regex = f'{search_term}%' users = ( User.query.filter( User.username.ilike(ilike_regex) | db.func.concat(User.first_name, ' ', User.last_name).ilike(ilike_regex) ) .order_by(db.func.concat(User.first_name, ' ', User.last_name)) .limit(10) .all() ) return { 'success': True, 'users': [ { 'fullname': user.fullname(), 'username': user.username, 'avatar': user.avatar(), 'joined': user in project.users, } for user in users ], }
def create_idea(box_id, idea_id): # if id == 0 create new idea, otherwise update existed idea by id # authenticate user: idea_box = get_idea_box(box_id, current_user) # log out unathorized user: # if idea_box empty then current user belong to different company # if idea box already closed the user modified the url field if not idea_box or not is_open(idea_box.Boxes.close_at): return unathorized("You cannot to edit this Idea.", "error") current_idea = Ideas.query.get(idea_id) colleague = current_user current_user.is_admin = False if idea_id > 0 and current_idea.colleague_id != current_user.id: # this idea belong to different colleague than the current user, check updata_box privileg: if not is_auth_box(current_user): return unathorized("You don't hane privileg to edit this Idea.", "error") else: # current user is an admin with privileg to edit/delete boxes and ideas: current_user.is_admin = True colleague = Colleagues.query.get(current_idea.colleague_id) form = CreateIdeaForm() # change sign-input's labels to the name of current user (name must be hidden for Admins!): form.sign.choices = [ ("incognito", "incognito"), (current_user.user_name, current_user.user_name), (current_user.first_name, current_user.first_name), (current_user.fullname(), current_user.fullname()) ] if not current_user.is_admin else [(current_idea.sign, current_idea.sign)] if form.validate_on_submit(): print("submitted") success = "" error = "" if idea_id == 0: # instantiate new Idea: idea = Ideas(idea=form.idea.data, sign=form.sign.data, box_id=box_id, colleague_id=current_user.id) db.session.add(idea) success = "Thank you for sharing your Idea." error = "Any error occured when post your Idea. Please try again." else: # edit existed idea: error = "Any error occured when edited your Idea. Please try again." if current_idea.idea != form.idea.data: current_idea.idea = form.idea.data success += "Your idea successfully edited.\n" if current_idea.sign != form.sign.data: current_idea.sign = form.sign.data success += f"Your sign changed to {current_idea.sign}.\n" try: db.session.commit() flash(success, "inform") return redirect(url_for("idea_box", id=box_id)) except: db.session.rollback() flash(error, "error") return redirect( url_for("create_idea", box_id=box_id, idea_id=idea_id)) if idea_id > 0: # edit mode: form.submit.label.text = "Edit my Idea" if not current_user.is_admin else f"Edit {colleague.first_name}'s Idea" form.idea.data = current_idea.idea form.sign.data = current_idea.sign else: form.sign.data = current_user.first_name # set first name by default checked return render_template( "create_idea.html", update_box=is_auth_box( current_user), # to add edit icon to authorized admin box=idea_box.Boxes, avatar="incognito-cut.svg" if form.sign.data == "incognito" else get_avatar(colleague), form=form, colleague=colleague, change_logo=is_auth_company( current_user ), # to add click event to change logo for authorized admin logo=get_logo(current_user), nav=get_nav(current_user))