def post(self): """ Create user resource :return: a Created resource """ user_data = request.get_json(silent=True) if not user_data: raise api_errors.InvalidAPIUsage( api_errors.INVALID_JSON_BODY_MSG ) user, errors_dic = create_user(user_data) errors_list = [] if errors_dic: errors_list.append(errors_dic) if errors_list: raise api_errors.InvalidAPIUsage( api_errors.VALIDATION_ERROR_MSG, payload=errors_list ) user.save() create_activation_email(user) return user, 201
def post(self, project_pk, tkt_id): """ Add Attachment to Ticket :param project_pk: Project ID :param tkt_id: Ticket ID :return: Created Attachment """ get_project_request(project_pk) file_item = request.files.get('file') if not file_item: raise api_errors.InvalidAPIUsage(payload=dict( file=api_errors.REQUIRED_MSG)) data = json.loads(request.form.get('data')) if not data: raise api_errors.InvalidAPIUsage(payload=dict( data=api_errors.REQUIRED_MSG)) ticket = get_ticket_request(tkt_id) att = Attachment() att.name = data.get('name') att.size = data.get('size') att.type = data.get('type') att.data = base64.b64encode(file_item.stream.read()) att.save() ticket.files.append(att) ticket.save() # save activity save_notification(project_pk=project_pk, verb='new_attachment', data=att.to_dict()) return att, 200
def post(self, project_pk): """ Move Ticket :param project_pk: Project ID :return: """ get_project_request(project_pk) data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage(api_errors.INVALID_JSON_BODY_MSG) source = data.get('source') destiny = data.get('dest') if not source or not destiny: raise api_errors.InvalidAPIUsage(payload={ 'source': api_errors.REQUIRED_MSG, 'dest': api_errors.REQUIRED_MSG }) remove_from_sprint = False sprint_id = None ticket_id = None if source.get('project_id'): sprint_id = destiny.get('sprint_id') ticket_id = source.get('ticket_id') elif source.get('sprint_id') and destiny.get('sprint_id'): sprint_id = destiny.get('sprint_id') ticket_id = source.get('ticket_id') elif source.get('sprint_id') and destiny.get('project_id'): sprint_id = source.get('sprint_id') ticket_id = source.get('ticket_id') remove_from_sprint = True sprint = get_sprint_request(sprint_id) ticket = get_ticket_request(ticket_id) if remove_from_sprint: SprintTicketOrder.inactivate_spo(sprint, ticket) Ticket.order_items(destiny.get('order')) else: SprintTicketOrder.inactivate_spo(sprint, ticket) tkt_ord_sprint = SprintTicketOrder() tkt_ord_sprint.sprint = sprint tkt_ord_sprint.ticket = ticket tkt_ord_sprint.ticket_repr = ticket.to_dict() tkt_ord_sprint.save() SprintTicketOrder.order_items(destiny.get('order'), sprint) # save activity save_notification(project_pk=project_pk, verb='ticket_movement', data=data) return data, 200
def post(self, project_pk): """ Create a new Project Member :param project_pk: Project ID :return: the created ProjectMember """ data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage( api_errors.INVALID_JSON_BODY_MSG ) project = get_project_request(project_pk) members_added = [] errors_list = [] for member in data: if member.get('value'): user = User.get_by_id(member.get('value')) if not user: errors_list.append( dict(member=api_errors.INVALID_MEMBER_MSG)) else: user = User(email=member.get('text')) user.active = False user.save() if user and project.owner.id != user.id: m = ProjectMember.get_by_project_member(project, user) if not m: m = ProjectMember(project=project) m.member = user m.save() # Send email notification create_new_member_email(m.member, project) members_added.append(m.to_dict()) else: errors_list.append(dict( member=api_errors.INVALID_ALREADY_ADDED_MSG )) if errors_list: raise api_errors.InvalidAPIUsage( api_errors.VALIDATION_ERROR_MSG, payload=errors_list ) if members_added: # save activity save_notification(project_pk=project.id, verb='new_members', data={'members': members_added}) return {}, 200
def post(self): """ Activate a user based on a token :return: token auth """ data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage( api_errors.INVALID_JSON_BODY_MSG ) user = User.get_by_activation_token(data.get('token')) if not user: raise api_errors.MissingResource( api_errors.ACTIVATION_INVALID_TOKEN_MSG ) user.active = True user.save() tokens_dict = current_app.token_handler.generate_tokens_dict( user.pk ) return tokens_dict, 200
def put(self, project_pk, tkt_id, pm_id): """ Assign a member to a ticket, this will update the ticket :param project_pk: Project ID :param tkt_id: Ticket ID :param member_id: Member ID :return: Ticket Object """ get_project_request(project_pk) tkt = get_ticket_request(tkt_id) pm = ProjectMember.get_by_id(pm_id) if not pm: raise api_errors.MissingResource( api_errors.INVALID_PROJECT_MEMBER_MSG) if pm in tkt.assigned_to: raise api_errors.InvalidAPIUsage( api_errors.INVALID_ALREADY_ADDED_MSG) tkt.assigned_to.append(pm) tkt.save() # save activity save_notification(project_pk=project_pk, verb='new_assigment', data=tkt.to_dict()) return tkt, 200
def put(self, project_pk, tkt_id, comment_id): """ Update a comment :param project_pk: Project ID :param tkt_id: Ticket ID :param comment_id: Comment ID :return: Updated Comment """ get_project_request(project_pk) get_ticket_request(tkt_id) comment = Comment.get_by_id(comment_id) if not comment: raise api_errors.MissingResource(api_errors.INVALID_COMMENT_MSG) data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage(api_errors.INVALID_JSON_BODY_MSG) if comment.who.id != current_user.id: raise api_errors.ForbiddenRequest() comment.comment = data.get('comment') comment.save() save_notification(project_pk=project_pk, verb='update_comment', data={ "comment": comment.to_dict(), "ticket": comment.ticket.to_dict() }) return comment, 200
def post(self): """ Endpoint for token requests. Creates a new access token based on provided credentials. :return: JSON with the token, refresh token and expiration time """ data = request.get_json(silent=True) if data: email = data.get('email') password = data.get('password') try: user = User.objects.get(email=email, active=True) except errors.DoesNotExist: raise api_errors.UnauthorizedRequest( api_errors.INVALID_CREDENTIALS_MSG) if not user.verify_password(password): raise api_errors.UnauthorizedRequest( api_errors.INVALID_CREDENTIALS_MSG) tokens_dict = current_app.token_handler.generate_tokens_dict( user.pk) return tokens_dict, 200 raise api_errors.InvalidAPIUsage(api_errors.INVALID_JSON_BODY_MSG)
def post(self, project_pk, column): """ Order Tickets in Columns :param project_pk: Project ID :param column: Column ID :return: Order """ data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage(api_errors.INVALID_JSON_BODY_MSG) project = get_project_request(project_pk) col = Column.get_by_id(column) if not col: raise api_errors.MissingResource(api_errors.INVALID_COLUMN_MSG) if project.project_type == 'S': sprint = get_sprint_request(data.get('sprint')) TicketCT.order_items(data.get('order'), sprint=sprint) else: TicketCT.order_items(data.get('order')) # save activity save_notification(project_pk=project_pk, verb='ticket_column_order', data=data) return data, 200
def put(self, project_pk, tkt_id): """ Update Ticket :param project_pk: Project ID :param tkt_id: Ticket ID :return: updated Ticket Resource """ get_project_request(project_pk) tkt = get_ticket_request(tkt_id) data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage(api_errors.INVALID_JSON_BODY_MSG) tkt.description = data.get('description', tkt.description) tkt.points = data.get('points', tkt.points) tkt.title = data.get('title', tkt.title) tkt.labels = data.get('labels', tkt.labels) tkt.type = data.get('type', tkt.type) tkt.closed = data.get('closed', tkt.closed) if 'related_tickets_data' in data: for tkt_rel_data in data.get('related_tickets_data'): tkt_rel = Ticket.get_by_id(tkt_rel_data.get('value')) if tkt_rel: rt = TicketDependency() rt.ticket = tkt_rel rt.type = tkt_rel_data.get('type', 'R') rt.save() tkt.related_tickets.append(rt) tkt.save() if data.get('sprint'): sprint = Sprint.get_by_id(data.get('sprint')['pk']) if sprint: spo = SprintTicketOrder.get_active_sprint_ticket(sprint, tkt) if not spo: # remove old data if this already exists spo = SprintTicketOrder(sprint=sprint, ticket=tkt) spo.ticket_repr = tkt.to_dict() spo.order = SprintTicketOrder.get_next_order_index( sprint.id) spo.save() else: spo = SprintTicketOrder.get_active_ticket(tkt) if spo: spo.ticket_repr = tkt.to_dict() spo.save() # save activity save_notification(project_pk=project_pk, verb='update_ticket', data=tkt.to_dict()) return tkt, 200
def post(self): """ Social authorization endpoint. """ request_data = request.get_json(silent=True) # Check required data if not request_data: raise errors.InvalidAPIUsage(errors.INVALID_JSON_BODY_MSG) provider_name = request_data.get('provider') provider = oauth_handler.get_provider(provider_name) if not provider: raise errors.InvalidAPIUsage(errors.PROVIDER_INVALID_MSG) access_token = request_data.get('token') if not access_token: raise errors.InvalidAPIUsage(errors.MISSING_PROVIDER_TOKEN_MSG) user_id = request_data.get('user_id') if not user_id: raise errors.InvalidAPIUsage(errors.MISSING_PROVIDER_USER_ID_MSG) # Validate the token error_msg = provider.validate_token(access_token, user_id) if error_msg: raise errors.UnauthorizedRequest(errors.PROVIDER_INVALID_TOKEN_MSG) user_data = provider.get_user_data(access_token) if not user_data: raise errors.BasicAPIException(errors.PROVIDER_INVALID_RESP_MSG) # On new email, register the user user, _ = User.get_or_create(**user_data) user.save() tokens_dict = current_app.token_handler.generate_tokens_dict(user.id) return dict(tokens_dict), 200
def post(self, project_pk): """ Import cards and columns :param project_pk: Project ID :return: List of Tickets and Columns imported """ project = get_project_request(project_pk) try: body = json.loads(request.form.get('data')) except ValueError, e: raise api_errors.InvalidAPIUsage( api_errors.INVALID_JSON_BODY_MSG )
def put(self, project_pk): """ Update a Project Instance :param project_pk: :return: """ project = get_project_request(project_pk) data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage( api_errors.INVALID_JSON_BODY_MSG ) project.active = data.get('active') project.description = data.get('description') project.name = data.get('name') owner = User.get_by_id(data.get('owner_id')) if not owner: raise api_errors.InvalidAPIUsage( api_errors.INVALID_USER_ID_MSG ) project.owner = owner.to_dbref() project.private = data.get('private') project.sprint_duration = data.get('sprint_duration') project.prefix = data.get('prefix') project.project_type = data.get('project_type', 'S') project.save() # save activity save_notification(project_pk=project.pk, verb='update_project', data=project.to_dict()) return project, 200
def post(self, project_pk): """ Update Order """ get_project_request(project_pk) data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage(api_errors.INVALID_JSON_BODY_MSG) Ticket.order_items(data) # save activity save_notification(project_pk=project_pk, verb='backlog_order', data=data) return data, 200
def post(self, project_pk): """ Order sprints in a project :param project_pk: Project ID :return: Order """ data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage(api_errors.INVALID_JSON_BODY_MSG) Sprint.order_items(data) # save activity save_notification(project_pk=project_pk, verb='order_sprints', data=data) return data, 200
def post(self): """ Create Project """ data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage( api_errors.INVALID_JSON_BODY_MSG ) prj = Project(name=data.get('name'), owner=g.user.to_dbref()) prj.active = data.get('active') prj.private = data.get('private') prj.prefix = data.get('prefix', data.get('name')[:3].upper()) prj.description = data.get('description') prj.project_type = data.get('project_type', 'S') # Add initial config prj.sprint_duration = 15 prj.save() # add owner as member pm = ProjectMember(project=prj) pm.member = prj.owner pm.is_owner = True pm.save() # Add 3 columns states col_names = ['ToDo', 'In Progress', 'Done'] for index, c in enumerate(col_names): col = Column() col.title = c col.project = prj if index == len(col_names) - 1: col.done_column = True col.save() # save activity save_notification(project_pk=prj.pk, verb='new_project', data=prj.to_dict()) return prj, 201
def put(self, user_id): """ Update Resource :param user_id: The User ID :return: Return a user instance updated """ data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage(api_errors.INVALID_JSON_BODY_MSG) user = get_user_for_request(user_id) user.first_name = data.get('first_name', user.first_name) user.last_name = data.get('last_name', user.last_name) user.picture = data.get('picture', user.picture) user.email = data.get('email', user.email) user.save() return user, 200
def post(self, project_pk, tkt_id): """ Create a Comment for a ticket :param project_pk: Project ID :param tkt_id: Ticket ID :return: Created Comment """ get_project_request(project_pk) get_ticket_request(tkt_id) data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage(api_errors.INVALID_JSON_BODY_MSG) c = Comment(ticket=tkt_id) c.who = current_user.to_dbref() c.comment = data.get('comment') c.save() if data.get('mentions'): for m in data.get('mentions'): if m is not None: user = User.get_by_id(m) if user: create_notification_email(user, c) # save activity save_notification(project_pk=project_pk, verb='mention', user_to=user, data={ "comment": c.to_dict(), "ticket": c.ticket.to_dict() }) else: # save activity save_notification(project_pk=project_pk, verb='new_comment', data={ "comment": c.to_dict(), "ticket": c.ticket.to_dict() }) return c, 201
def post(self): """ Endpoint for token refreshing. Takes a refresh token from a JSON body and issues a new access token. :return: A new token with a new expiration time. """ data = request.get_json(silent=True) if data: refresh = data.get('refresh_token') expire = current_app.token_handler.expires_in new_token = current_app.token_handler.refresh_token(refresh) if not new_token: raise api_errors.UnauthorizedRequest( api_errors.INVALID_REFRESH_TOKEN_MSG) return dict(token=new_token, expires_in=expire), 200 raise api_errors.InvalidAPIUsage(api_errors.INVALID_JSON_BODY_MSG)
def post(self, project_pk): """ Create Ticket """ data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage(api_errors.INVALID_JSON_BODY_MSG) project = get_project_request(project_pk) tkt = Ticket() last_tkt = Ticket.get_last_ticket(project_pk) tkt.number = last_tkt.number + 1 if last_tkt else 1 tkt.order = Ticket.get_next_order_index(project_pk) tkt.project = project tkt.description = data.get('description') tkt.points = data.get('points', 0) tkt.title = data.get('title') tkt.labels = data.get('labels') tkt.type = data.get('type', 'U') tkt.save() if data.get('sprint'): # Assign to a sprint sprint = Sprint.get_by_id(pk=data.get('sprint')['pk']) if sprint: spo = SprintTicketOrder(sprint=sprint, ticket=tkt) spo.ticket_repr = tkt.to_dict() spo.order = SprintTicketOrder.get_next_order_index(sprint.id) spo.save() # save activity save_notification(project_pk=project_pk, verb='new_ticket', data=tkt.to_dict()) return tkt, 201
def post(self, project_pk): """ Update order of columns :param project_pk: project id :return: same data sent """ prj = get_project_request(project_pk) data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage( api_errors.INVALID_JSON_BODY_MSG ) Column.order_items(data) # save activity save_notification(project_pk=prj.id, verb='order_columns', data=data) return data, 200
def put(self, project_pk, column_pk): """ Update a column instance :param project_pk: Project that belongs :param column_pk: Id of the column :return: The updated resource """ col = Column.get_by_id(column_pk) if not col: raise api_errors.MissingResource( api_errors.INVALID_COLUMN_MSG ) data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage( api_errors.INVALID_JSON_BODY_MSG ) col.title = data.get('title') col.color_max_cards = data.get('color_max_cards', '#FF0000') col.done_column = data.get('done_column', False) col.max_cards = data.get('max_cards', 9999) # check set other columns of the project to done_column in False if col.done_column: Column.clear_done_columns(project_pk) col.save() # save activity save_notification(project_pk=project_pk, verb='update_column', data=col.to_dict()) return col, 200
def post(self, project_pk, sprint_pk): """ Update Order of tickets in sprint :param project_pk: Project ID :param sprint_pk: Sprint ID :return: Order """ get_project_request(project_pk) data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage(api_errors.INVALID_JSON_BODY_MSG) sprint = get_sprint_request(sprint_pk) SprintTicketOrder.order_items(data, sprint) # save activity save_notification(project_pk=project_pk, verb='sprint_ticket_order', data=data) return data, 200
def detect_mime_type(): """ Check all API requests for `application/json` media type. :raise: InvalidAPIUsage error if an incorrect type was used. """ headers_dict = dict(request.headers) if request.method in ['POST', 'PUT']: # For POST/PUT we need to check the content type content_type = headers_dict.get('Content-Type').split(';')[0] if content_type not in APP_ACCEPTED_TYPES: raise errors.InvalidAPIUsage(errors.INVALID_CONTENT_TYPE_MSG) else: # For GET/DELETE we need to check the accept types accept_types = headers_dict.get('Accept') # If there are no accepted types, we assume the client accepts ours if accept_types: accepts_our_mime = ((accept_types in APP_ACCEPTED_TYPES) or ('*/*' in accept_types)) if not accepts_our_mime: raise errors.NotAcceptable(accepted=APP_ACCEPTED_TYPES)
def post(self, project_pk): """ :param project_pk: Project ID :return: Column Resource created """ data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage( api_errors.INVALID_JSON_BODY_MSG ) project = get_project_request(project_pk) col = Column() col.order = Column.objects.count() col.project = project col.title = data.get('title') col.color_max_cards = data.get('color_max_cards', '#FF0000') col.done_column = data.get('done_column', False) col.max_cards = data.get('max_cards', 9999) # Check if already exists one Done column if col.done_column: columns = Column.get_by_project(project) for c in columns: if c.done_column: c.done_column = False c.save() col.save() # save activity save_notification(project_pk=project.pk, verb='new_column', data=col.to_dict()) return col, 200
def put(self, project_pk, sp_id): """ Update a Sprint :param project_pk: :param sp_id: :return: """ get_project_request(project_pk) data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage(api_errors.INVALID_JSON_BODY_MSG) sp = get_sprint_request(sp_id) sp.name = data.get('name', sp.name) if data.get('for_starting'): # sum all the ticket for the initial planning value sto = SprintTicketOrder.get_active_sprint(sp) total_planned_points = 0 if sto: # get the sum of points for s in sto: total_planned_points += s.ticket.points sp.total_points_when_started = total_planned_points sp.start_date = parser.parse(data.get('start_date')) sp.end_date = parser.parse(data.get('end_date')) sp.started = True elif data.get('for_finalized'): # Mark as finalized sp.finalized = True # Get tickets in Done column finished_tickets = [] tickets_to_close_id = [] tt = TicketCT.get_transitions_for_sprint(sp) for t in tt: if t.column.done_column: finished_tickets.append(t.ticket) tickets_to_close_id.append(str(t.ticket.pk)) # Deactivate Tickets SprintTicketOrder.inactivate_list_spo(sp, finished_tickets) # Close Tickets Ticket.close_tickets(tickets_to_close_id) elif data.get('for_editing'): sp.start_date = parser.parse(data.get('start_date')) sp.end_date = parser.parse(data.get('end_date')) sp.save() # save activity save_notification(project_pk=project_pk, verb='update_sprint', data=sp.to_dict()) return sp, 200
class ProjectImport(AuthResource): """ Import Data Project """ def post(self, project_pk): """ Import cards and columns :param project_pk: Project ID :return: List of Tickets and Columns imported """ project = get_project_request(project_pk) try: body = json.loads(request.form.get('data')) except ValueError, e: raise api_errors.InvalidAPIUsage( api_errors.INVALID_JSON_BODY_MSG ) imported_file = request.files.get('file') if not imported_file: raise api_errors.InvalidAPIUsage( api_errors.REQUIRED_MSG ) data = json.loads(imported_file.stream.read().decode('utf-8'), encoding='UTF-8') import_args = body.get('data') tickets = [] last_ticket = Ticket.get_last_ticket(project_pk) starting_number = last_ticket.number if last_ticket else 1 amazon_url = 'https://trello-attachments.s3.amazonaws.com/' if import_args.get('include_cards'): for card in data.get('cards'): t = Ticket() t.title = card.get('name') t.description = card.get('desc') t.labels = [l.get('name') for l in card.get('labels')] t.closed = card.get('closed') for att in card.get('attachments'): location = att.get('url') if amazon_url in location: file_location = urllib2.urlopen(location) file_data = file_location.read() if file_data: att_file = Attachment() att_file.name = att.get('name') att_file.size = att.get('bytes') att_file.type = att.get('mimeType') att_file.data = base64.b64encode(file_data) att_file.save() t.files.append(att_file) t.project = project t.number = starting_number t.order = starting_number - 1 # by default user story t.type = 'U' t.points = 0 tickets.append(t) starting_number += 1 columns = [] if import_args.get('include_cols'): for col in data.get('lists'): if not col.get('closed'): new_col = Column() new_col.title = col.get('name') new_col.project = project columns.append(new_col) if tickets: Ticket.objects.insert(tickets, load_bulk=False) if columns: Column.objects.insert(columns, load_bulk=False) # save activity save_notification(project_pk=project_pk, verb='import', data=data) return [tickets, columns], 200
def post(self, project_pk): """ Move Ticket to Column or Backlog :param project_pk: Project ID :return: Ticket Transition """ project = get_project_request(project_pk) data = request.get_json(silent=True) if not data: raise api_errors.InvalidAPIUsage(api_errors.INVALID_JSON_BODY_MSG) tkt = get_ticket_request(data.get('ticket')) result = {} if data.get('backlog'): latest_tran = TicketCT.get_latest_transition(tkt) if latest_tran: latest_tran.latest_state = False latest_tran.save() SprintTicketOrder.order_items(data.get('order'), data.get('backlog')) # save activity save_notification(project_pk=project_pk, verb='ticket_transition', data=data) result = latest_tran.to_json(), 200 else: # Search already state col = Column.get_by_id(data.get('column')) if not col: raise api_errors.MissingResource(api_errors.INVALID_COLUMN_MSG) user = User.get_by_id(current_user.id) if not user: raise api_errors.MissingResource( api_errors.INVALID_USER_ID_MSG) transition = TicketCT() transition.ticket = tkt transition.column = col transition.order = TicketCT.get_next_order_index(col) if project.project_type == 'S': sprint = get_sprint_request(data.get('sprint')) latest_tran = TicketCT.get_latest_transition(tkt, sprint) transition.sprint = sprint else: latest_tran = TicketCT.get_latest_transition(tkt) if latest_tran: latest_tran.latest_state = False latest_tran.save() transition.latest_state = True transition.who = user transition.save() if project.project_type == 'S': sprint = get_sprint_request(data.get('sprint')) TicketCT.order_items(data.get('order'), sprint=sprint, column=col) else: TicketCT.order_items(data.get('order'), column=col) # save activity save_notification(project_pk=project_pk, verb='ticket_transition', data=transition.to_dict()) result = transition.to_json(), 201 return result