def view_group(group): ''' Displays information about this group. ''' if flask.request.method == 'POST' and \ not pagure.APP.config.get('ENABLE_USER_MNGT', True): flask.abort(404) group_type = 'user' if pagure.is_admin(): group_type = None group = pagure.lib.search_groups( pagure.SESSION, group_name=group, group_type=group_type) if not group: flask.abort(404, 'Group not found') # Add new user to the group if asked form = pagure.forms.AddUserForm() if pagure.authenticated() and form.validate_on_submit() \ and pagure.APP.config.get('ENABLE_GROUP_MNGT', False): username = form.user.data try: msg = pagure.lib.add_user_to_group( pagure.SESSION, username=username, group=group, user=flask.g.fas_user.username, is_admin=pagure.is_admin(), ) pagure.SESSION.commit() pagure.lib.git.generate_gitolite_acls() flask.flash(msg) except pagure.exceptions.PagureException as err: pagure.SESSION.rollback() flask.flash(err.message, 'error') return flask.redirect( flask.url_for('.view_group', group=group.group_name)) except SQLAlchemyError as err: # pragma: no cover pagure.SESSION.rollback() flask.flash( 'Could not add user `%s` to group `%s`.' % ( username, group.group_name), 'error') pagure.APP.logger.debug( 'Could not add user `%s` to group `%s`.' % ( username, group.group_name)) pagure.APP.logger.exception(err) member = False if pagure.authenticated(): member = pagure.lib.is_group_member( pagure.SESSION, flask.g.fas_user.username, group.group_name) return flask.render_template( 'group_info.html', group=group, form=form, member=member, )
def index(): """ Front page of the application. """ page = flask.request.args.get('page', 1) try: page = int(page) except ValueError: page = 1 limit = APP.config['ITEM_PER_PAGE'] start = limit * (page - 1) repos = pagure.lib.search_projects( SESSION, fork=False, start=start, limit=limit) num_repos = pagure.lib.search_projects( SESSION, fork=False, count=True) total_page = int(ceil(num_repos / float(limit))) if authenticated() and flask.request.path == '/': return index_auth() return flask.render_template( 'index.html', select="projects", repos=repos, repos_length=num_repos, total_page=total_page, page=page, )
def view_users(username=None): """ Present the list of users. """ page = flask.request.args.get('page', 1) try: page = int(page) if page < 1: page = 1 except ValueError: page = 1 users = pagure.lib.search_user(SESSION, pattern=username) private = False # Condition to check non-authorized user should't be able to access private # project of other users if authenticated() and username == flask.g.fas_user.username: private = flask.g.fas_user.username if len(users) == 1: flask.flash('Only one result found, redirecting you to it') return flask.redirect( flask.url_for('view_user', username=users[0].username)) limit = APP.config['ITEM_PER_PAGE'] start = limit * (page - 1) end = limit * page users_length = len(users) users = users[start:end] total_page = int(ceil(users_length / float(limit))) for user in users: repos_length = pagure.lib.search_projects(SESSION, username=user.user, fork=False, count=True, private=private) forks_length = pagure.lib.search_projects(SESSION, username=user.user, fork=True, count=True, private=private) user.repos_length = repos_length user.forks_length = forks_length return flask.render_template( 'user_list.html', users=users, users_length=users_length, total_page=total_page, page=page, select='users', )
def view_issue(repo, issueid, username=None, namespace=None): """ List all issues associated to a repo """ repo = flask.g.repo if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: flask.abort(404, 'Issue not found') if issue.private and not flask.g.repo_admin \ and (not authenticated() or not issue.user.user == flask.g.fas_user.username): flask.abort( 403, 'This issue is private and you are not allowed to view it') status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.UpdateIssueForm( status=status, priorities=repo.priorities, milestones=repo.milestones, close_status=repo.close_status, ) form.status.data = issue.status form.priority.data = str(issue.priority) form.milestone.data = str(issue.milestone) form.private.data = issue.private form.close_status.data = '' if issue.close_status: form.close_status.data = issue.close_status tag_list = pagure.lib.get_tags_of_project(SESSION, repo) knowns_keys = {} for key in issue.other_fields: knowns_keys[key.key.name] = key return flask.render_template( 'issue.html', select='issues', repo=repo, username=username, tag_list=tag_list, issue=issue, issueid=issueid, form=form, knowns_keys=knowns_keys, subscribers=pagure.lib.get_watch_list(SESSION, issue), attachments=issue.attachments, )
def check_api_acls(acls, optional=False): ''' Checks if the user provided an API token with its request and if this token allows the user to access the endpoint desired. ''' flask.g.token = None flask.g.user = None token = None token_str = None if authenticated(): return if 'Authorization' in flask.request.headers: authorization = flask.request.headers['Authorization'] if 'token' in authorization: token_str = authorization.split('token', 1)[1].strip() token_auth = False if token_str: token = pagure.lib.get_api_token(SESSION, token_str) if token and not token.expired: if acls and set(token.acls_list).intersection(set(acls)): token_auth = True flask.g.fas_user = token.user # To get a token, in the `fas` auth user must have signed # the CLA, so just set it to True flask.g.fas_user.cla_done = True flask.g.token = token elif not acls and optional: token_auth = True flask.g.fas_user = token.user # To get a token, in the `fas` auth user must have signed # the CLA, so just set it to True flask.g.fas_user.cla_done = True flask.g.token = token elif optional: return if not token_auth: output = { 'error_code': APIERROR.EINVALIDTOK.name, 'error': APIERROR.EINVALIDTOK.value, } jsonout = flask.jsonify(output) jsonout.status_code = 401 return jsonout
def view_issue(repo, issueid, username=None): """ List all issues associated to a repo """ repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: flask.abort(404, 'Project not found') if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: flask.abort(404, 'Issue not found') if issue.private and not is_repo_admin(repo) \ and (not authenticated() or not issue.user.user == flask.g.fas_user.username): flask.abort( 403, 'This issue is private and you are not allowed to view it') reponame = pagure.get_repo_path(repo) repo_obj = pygit2.Repository(reponame) status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.UpdateIssueForm( status=status, priorities=repo.priorities) form.status.data = issue.status form.priority.data = str(issue.priority) tag_list = pagure.lib.get_tags_of_project(SESSION, repo) return flask.render_template( 'issue.html', select='issues', repo=repo, username=username, repo_obj=repo_obj, tag_list=tag_list, issue=issue, issueid=issueid, form=form, repo_admin=is_repo_admin(repo), )
def view_issue(repo, issueid, username=None, namespace=None): """ List all issues associated to a repo """ repo = flask.g.repo if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: flask.abort(404, 'Issue not found') if issue.private and not flask.g.repo_admin \ and (not authenticated() or not issue.user.user == flask.g.fas_user.username): flask.abort( 403, 'This issue is private and you are not allowed to view it') reponame = pagure.get_repo_path(repo) repo_obj = pygit2.Repository(reponame) status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.UpdateIssueForm( status=status, priorities=repo.priorities, milestones=repo.milestones, ) form.status.data = issue.status form.priority.data = str(issue.priority) form.milestone.data = str(issue.milestone) form.private.data = issue.private tag_list = pagure.lib.get_tags_of_project(SESSION, repo) return flask.render_template( 'issue.html', select='issues', repo=repo, username=username, tag_list=tag_list, issue=issue, issueid=issueid, form=form, )
def view_group(group): ''' Displays information about this group. ''' group_type = 'user' if pagure.is_admin(): group_type = None group = pagure.lib.search_groups(pagure.SESSION, group_name=group, group_type=group_type) if not group: flask.abort(404, 'Group not found') # Add new user to the group if asked form = pagure.forms.AddUserForm() if pagure.authenticated() and form.validate_on_submit(): username = form.user.data try: msg = pagure.lib.add_user_to_group( pagure.SESSION, username=username, group=group, user=flask.g.fas_user.username, is_admin=pagure.is_admin(), ) pagure.SESSION.commit() pagure.generate_gitolite_acls() flask.flash(msg) except pagure.exceptions.PagureException, err: pagure.SESSION.rollback() flask.flash(err.message, 'error') return flask.redirect( flask.url_for('.view_group', group=group.group_name)) except SQLAlchemyError as err: # pragma: no cover pagure.SESSION.rollback() flask.flash( 'Could not add user `%s` to group `%s`.' % (username, group.group_name), 'error') pagure.APP.logger.debug('Could not add user `%s` to group `%s`.' % (username, group.group_name)) pagure.APP.logger.exception(err)
def index(): """ Front page of the application. """ sorting = flask.request.args.get('sorting') or None page = flask.request.args.get('page', 1) try: page = int(page) if page < 1: page = 1 except ValueError: page = 1 limit = APP.config['ITEM_PER_PAGE'] start = limit * (page - 1) repos = pagure.lib.search_projects( SESSION, fork=False, start=start, limit=limit, sort=sorting) num_repos = pagure.lib.search_projects( SESSION, fork=False, count=True) total_page = int(ceil(num_repos / float(limit))) if authenticated() and flask.request.path == '/': return index_auth() return flask.render_template( 'index.html', select="projects", repos=repos, repos_length=num_repos, total_page=total_page, page=page, sorting=sorting, )
def check_api_acls(acls, optional=False): ''' Checks if the user provided an API token with its request and if this token allows the user to access the endpoint desired. ''' flask.g.token = None flask.g.user = None token = None token_str = None if authenticated(): return if 'Authorization' in flask.request.headers: authorization = flask.request.headers['Authorization'] if 'token' in authorization: token_str = authorization.split('token', 1)[1].strip() token_auth = False if token_str: token = pagure.lib.get_api_token(SESSION, token_str) if token and not token.expired: if acls and set(token.acls_list).intersection(set(acls)): token_auth = True flask.g.fas_user = token.user flask.g.token = token elif not acls and optional: token_auth = True flask.g.fas_user = token.user flask.g.token = token elif optional: return if not token_auth: output = { 'error_code': APIERROR.EINVALIDTOK.name, 'error': APIERROR.EINVALIDTOK.value, } jsonout = flask.jsonify(output) jsonout.status_code = 401 return jsonout
def view_issue(repo, issueid, username=None): """ List all issues associated to a repo """ repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: flask.abort(404, 'Project not found') if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: flask.abort(404, 'Issue not found') if issue.private and not is_repo_admin(repo) \ and (not authenticated() or not issue.user.user == flask.g.fas_user.username): flask.abort( 403, 'This issue is private and you are not allowed to view it') status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.UpdateIssueForm(status=status) form.status.data = issue.status return flask.render_template( 'issue.html', select='issues', repo=repo, username=username, issue=issue, issueid=issueid, form=form, repo_admin=is_repo_admin(repo), )
def view_group(group): """ Displays information about this group. """ group_type = "user" if pagure.is_admin(): group_type = None group = pagure.lib.search_groups(pagure.SESSION, group_name=group, group_type=group_type) if not group: flask.abort(404, "Group not found") # Add new user to the group if asked form = pagure.forms.AddUserForm() if pagure.authenticated() and form.validate_on_submit(): username = form.user.data try: msg = pagure.lib.add_user_to_group( pagure.SESSION, username=username, group=group, user=flask.g.fas_user.username, is_admin=pagure.is_admin(), ) pagure.SESSION.commit() pagure.generate_gitolite_acls() flask.flash(msg) except pagure.exceptions.PagureException, err: pagure.SESSION.rollback() flask.flash(err.message, "error") return flask.redirect(flask.url_for(".view_group", group=group.group_name)) except SQLAlchemyError as err: # pragma: no cover pagure.SESSION.rollback() flask.flash("Could not add user `%s` to group `%s`." % (username, group.group_name), "error") pagure.APP.logger.debug("Could not add user `%s` to group `%s`." % (username, group.group_name)) pagure.APP.logger.exception(err)
except pagure.exceptions.PagureException, err: pagure.SESSION.rollback() flask.flash(err.message, 'error') return flask.redirect( flask.url_for('.view_group', group=group.group_name)) except SQLAlchemyError as err: # pragma: no cover pagure.SESSION.rollback() flask.flash( 'Could not add user `%s` to group `%s`.' % (username, group.group_name), 'error') pagure.APP.logger.debug('Could not add user `%s` to group `%s`.' % (username, group.group_name)) pagure.APP.logger.exception(err) member = False if pagure.authenticated(): member = pagure.lib.is_group_member(pagure.SESSION, flask.g.fas_user.username, group.group_name) return flask.render_template( 'group_info.html', group=group, form=form, member=member, ) @pagure.APP.route('/group/<group>/<user>/delete', methods=['POST']) @pagure.cla_required def group_user_delete(user, group):
def index(): """ Front page of the application. """ page = flask.request.args.get('page', 1) try: page = int(page) except ValueError: page = 1 limit = APP.config['ITEM_PER_PAGE'] start = limit * (page - 1) repos = pagure.lib.search_projects(SESSION, fork=False, start=start, limit=limit) num_repos = pagure.lib.search_projects(SESSION, fork=False, count=True) total_page = int(ceil(num_repos / float(limit))) repopage = None forkpage = None user_repos = None user_forks = None total_page_repos = None total_page_forks = None username = None user_repos_length = None user_forks_length = None if authenticated(): username = flask.g.fas_user.username repopage = flask.request.args.get('repopage', 1) try: repopage = int(repopage) except ValueError: repopage = 1 forkpage = flask.request.args.get('forkpage', 1) try: forkpage = int(forkpage) except ValueError: forkpage = 1 repo_start = limit * (repopage - 1) fork_start = limit * (forkpage - 1) user_repos = pagure.lib.search_projects(SESSION, username=username, fork=False, start=repo_start, limit=limit) user_repos_length = pagure.lib.search_projects(SESSION, username=username, fork=False, count=True) user_forks = pagure.lib.search_projects(SESSION, username=username, fork=True, start=fork_start, limit=limit) user_forks_length = pagure.lib.search_projects(SESSION, username=username, fork=True, count=True) total_page_repos = int(ceil(user_repos_length / float(limit))) total_page_forks = int(ceil(user_forks_length / float(limit))) return flask.render_template( 'index.html', repos=repos, total_page=total_page, page=page, username=username, repopage=repopage, forkpage=forkpage, user_repos=user_repos, user_forks=user_forks, total_page_repos=total_page_repos, total_page_forks=total_page_forks, user_repos_length=user_repos_length, user_forks_length=user_forks_length, repos_length=num_repos, )
def view_issues(repo, username=None): """ List all issues associated to a repo """ status = flask.request.args.get('status', None) tags = flask.request.args.getlist('tags') tags = [tag.strip() for tag in tags if tag.strip()] assignee = flask.request.args.get('assignee', None) author = flask.request.args.get('author', None) repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: flask.abort(404, 'Project not found') if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if authenticated(): private = flask.g.fas_user.username # If user is repo admin, show all tickets included the private ones if is_repo_admin(repo): private = None oth_issues = None if status is not None: if status.lower() == 'closed': issues = pagure.lib.search_issues( SESSION, repo, closed=True, tags=tags, assignee=assignee, author=author, private=private, ) oth_issues = pagure.lib.search_issues( SESSION, repo, status='Open', tags=tags, assignee=assignee, author=author, private=private, count=True, ) else: issues = pagure.lib.search_issues( SESSION, repo, status=status, tags=tags, assignee=assignee, author=author, private=private, ) else: issues = pagure.lib.search_issues( SESSION, repo, status='Open', tags=tags, assignee=assignee, author=author, private=private) oth_issues = pagure.lib.search_issues( SESSION, repo, closed=True, tags=tags, assignee=assignee, author=author, private=private, count=True) tag_list = pagure.lib.get_tags_of_project(SESSION, repo) return flask.render_template( 'issues.html', select='issues', repo=repo, username=username, tag_list=tag_list, status=status, issues=issues, oth_issues=oth_issues, tags=tags, assignee=assignee, author=author, repo_admin=is_repo_admin(repo), )
def api_comment_issue(repo, issueid, username=None): """ Comment to an issue ------------------- This endpoint can be used to add a comment to an issue :: /api/0/<repo>/issue/<issue id>/comment /api/0/fork/<username>/<repo>/issue/<issue id>/comment Accepts POST queries only. :arg comment: The comment to add to the specified issue Sample response: :: { "message": "Comment added" } """ repo = pagure.lib.get_project(SESSION, repo, user=username) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) if repo.fullname != flask.g.token.project.fullname: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if issue.private and not is_repo_admin(repo) \ and (not authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError( 403, error_code=APIERROR.EISSUENOTALLOWED) form = pagure.forms.CommentForm(csrf_enabled=False) if form.validate_on_submit(): comment = form.comment.data try: # New comment message = pagure.lib.add_issue_comment( SESSION, issue=issue, comment=comment, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() output['message'] = message except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() APP.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
def update_issue(repo, issueid, username=None): ''' Add a comment to an issue. ''' is_js = flask.request.args.get('js', False) repo = pagure.lib.get_project(SESSION, repo, user=username) if flask.request.method == 'GET': if not is_js: flask.flash('Invalid method: GET', 'error') return flask.redirect(flask.url_for( 'view_issue', username=username, repo=repo.name, issueid=issueid)) if repo is None: flask.abort(404, 'Project not found') if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: flask.abort(404, 'Issue not found') if issue.private and not is_repo_admin(repo) \ and (not authenticated() or not issue.user.user == flask.g.fas_user.username): flask.abort( 403, 'This issue is private and you are not allowed to view it') if flask.request.form.get('edit_comment'): commentid = flask.request.form.get('edit_comment') form = pagure.forms.EditCommentForm() if form.validate_on_submit(): return edit_comment_issue( repo.name, issueid, commentid, username=username) status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.UpdateIssueForm( status=status, priorities=repo.priorities) if form.validate_on_submit(): repo_admin = is_repo_admin(repo) if flask.request.form.get('drop_comment'): commentid = flask.request.form.get('drop_comment') comment = pagure.lib.get_issue_comment( SESSION, issue.uid, commentid) if comment is None or comment.issue.project != repo: flask.abort(404, 'Comment not found') if (flask.g.fas_user.username != comment.user.username or comment.parent.status != 'Open') \ and not is_repo_admin(repo): flask.abort( 403, 'You are not allowed to remove this comment from ' 'this issue') SESSION.delete(comment) try: SESSION.commit() if not is_js: flask.flash('Comment removed') except SQLAlchemyError as err: # pragma: no cover is_js = False SESSION.rollback() LOG.error(err) if not is_js: flask.flash( 'Could not remove the comment: %s' % commentid, 'error') if is_js: return 'ok' else: return flask.redirect(flask.url_for( 'view_issue', username=username, repo=repo.name, issueid=issueid)) comment = form.comment.data depends = [] for depend in form.depends.data.split(','): if depend.strip(): try: depends.append(int(depend.strip())) except ValueError: pass blocks = [] for block in form.blocks.data.split(','): if block.strip(): try: blocks.append(int(block.strip())) except ValueError: pass assignee = form.assignee.data new_status = form.status.data new_priority = None try: new_priority = int(form.priority.data) except: pass tags = [ tag.strip() for tag in form.tag.data.split(',') if tag.strip()] try: # New comment if comment: message = pagure.lib.add_issue_comment( SESSION, issue=issue, comment=comment, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message and not is_js: flask.flash(message) if repo_admin: # Adjust (add/remove) tags messages = pagure.lib.update_tags( SESSION, issue, tags, username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'] ) if not is_js: for message in messages: flask.flash(message) # Assign or update assignee of the ticket message = pagure.lib.add_issue_assignee( SESSION, issue=issue, assignee=assignee or None, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) if message and not is_js: SESSION.commit() flask.flash(message) if repo_admin: # Update status if new_status in status: message = pagure.lib.edit_issue( SESSION, issue=issue, status=new_status, private=issue.private, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: flask.flash(message) # Update priority if str(new_priority) in repo.priorities: message = pagure.lib.edit_issue( SESSION, issue=issue, priority=new_priority, private=issue.private, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: flask.flash(message) # Update ticket this one depends on messages = pagure.lib.update_dependency_issue( SESSION, repo, issue, depends, username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) if not is_js: for message in messages: flask.flash(message) # Update ticket(s) depending on this one messages = pagure.lib.update_blocked_issue( SESSION, repo, issue, blocks, username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) if not is_js: for message in messages: flask.flash(message) except pagure.exceptions.PagureException as err: is_js = False SESSION.rollback() if not is_js: flask.flash(err.message, 'error') except SQLAlchemyError as err: # pragma: no cover is_js = False SESSION.rollback() APP.logger.exception(err) if not is_js: flask.flash(str(err), 'error') if is_js: return 'ok' else: return flask.redirect(flask.url_for( 'view_issue', username=username, repo=repo.name, issueid=issueid))
def update_issue(repo, issueid, username=None, namespace=None): ''' Add a comment to an issue. ''' is_js = flask.request.args.get('js', False) repo = flask.g.repo if flask.request.method == 'GET': if not is_js: flask.flash('Invalid method: GET', 'error') return flask.redirect(flask.url_for( 'view_issue', username=username, repo=repo.name, namespace=repo.namespace, issueid=issueid)) if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: flask.abort(404, 'Issue not found') if issue.private and not flask.g.repo_admin \ and (not authenticated() or not issue.user.user == flask.g.fas_user.username): flask.abort( 403, 'This issue is private and you are not allowed to view it') if flask.request.form.get('edit_comment'): commentid = flask.request.form.get('edit_comment') form = pagure.forms.EditCommentForm() if form.validate_on_submit(): return edit_comment_issue( repo.name, issueid, commentid, username=username) status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.UpdateIssueForm( status=status, priorities=repo.priorities, milestones=repo.milestones, ) if form.validate_on_submit(): repo_admin = flask.g.repo_admin if flask.request.form.get('drop_comment'): commentid = flask.request.form.get('drop_comment') comment = pagure.lib.get_issue_comment( SESSION, issue.uid, commentid) if comment is None or comment.issue.project != repo: flask.abort(404, 'Comment not found') if (flask.g.fas_user.username != comment.user.username or comment.parent.status != 'Open') \ and not flask.g.repo_admin: flask.abort( 403, 'You are not allowed to remove this comment from ' 'this issue') SESSION.delete(comment) try: SESSION.commit() if not is_js: flask.flash('Comment removed') except SQLAlchemyError as err: # pragma: no cover is_js = False SESSION.rollback() LOG.error(err) if not is_js: flask.flash( 'Could not remove the comment: %s' % commentid, 'error') if is_js: return 'ok' else: return flask.redirect(flask.url_for( 'view_issue', username=username, repo=repo.name, namespace=repo.namespace, issueid=issueid)) comment = form.comment.data depends = [] for depend in form.depends.data.split(','): if depend.strip(): try: depends.append(int(depend.strip())) except ValueError: pass blocks = [] for block in form.blocks.data.split(','): if block.strip(): try: blocks.append(int(block.strip())) except ValueError: pass assignee = form.assignee.data new_status = form.status.data new_priority = None try: new_priority = int(form.priority.data) except: pass tags = [ tag.strip() for tag in form.tag.data.split(',') if tag.strip()] new_milestone = None try: new_milestone = form.milestone.data.strip() or None except: pass try: messages = set() # New comment if comment: message = pagure.lib.add_issue_comment( SESSION, issue=issue, comment=comment, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message and not is_js: messages.add(message) if repo_admin: # Adjust (add/remove) tags messages.union(set(pagure.lib.update_tags( SESSION, issue, tags, username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'] ))) # The meta-data can only be changed by admins, which means they # will be missing for non-admin and thus reset if we let them if repo_admin: # Assign or update assignee of the ticket message = pagure.lib.add_issue_assignee( SESSION, issue=issue, assignee=assignee or None, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: messages.add(message) # Update status if new_status in status: message = pagure.lib.edit_issue( SESSION, issue=issue, status=new_status, private=issue.private, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: messages.add(message) # Update priority if str(new_priority) in repo.priorities: message = pagure.lib.edit_issue( SESSION, issue=issue, priority=new_priority, private=issue.private, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: messages.add(message) # Update milestone and privacy setting message = pagure.lib.edit_issue( SESSION, issue=issue, milestone=new_milestone, private=form.private.data, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: messages.add(message) # Update ticket this one depends on messages.union(set(pagure.lib.update_dependency_issue( SESSION, repo, issue, depends, username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ))) # Update ticket(s) depending on this one messages.union(set(pagure.lib.update_blocked_issue( SESSION, repo, issue, blocks, username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ))) if not is_js: for message in messages: flask.flash(message) except pagure.exceptions.PagureException as err: is_js = False SESSION.rollback() if not is_js: flask.flash(err.message, 'error') except SQLAlchemyError as err: # pragma: no cover is_js = False SESSION.rollback() APP.logger.exception(err) if not is_js: flask.flash(str(err), 'error') if is_js: return 'ok' else: return flask.redirect(flask.url_for( 'view_issue', username=username, repo=repo.name, issueid=issueid))
def view_roadmap(repo, username=None, namespace=None): """ List all issues associated to a repo as roadmap """ status = flask.request.args.get('status', 'Open') milestones = flask.request.args.getlist('milestone', None) tags = flask.request.args.getlist('tag', None) repo = flask.g.repo if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if authenticated(): private = flask.g.fas_user.username # If user is repo admin, show all tickets included the private ones if flask.g.repo_admin: private = None all_milestones = sorted(list(repo.milestones.keys())) issues = pagure.lib.search_issues( SESSION, repo, milestones=milestones or all_milestones, tags=tags, private=private, status=status if status.lower()!= 'all' else None, ) # Change from a list of issues to a dict of milestone/issues milestone_issues = defaultdict(list) for issue in issues: saved = False for mlstone in sorted(milestones or all_milestones): if mlstone == issue.milestone: milestone_issues[mlstone].append(issue) saved = True break if saved: continue if status and status.lower() != 'all': for key in milestone_issues.keys(): active = False for issue in milestone_issues[key]: if issue.status == status: active = True break if not active: del milestone_issues[key] tag_list = [ tag.tag for tag in pagure.lib.get_tags_of_project(SESSION, repo) ] if 'unplanned' in all_milestones: index = all_milestones.index('unplanned') cnt = len(all_milestones) all_milestones.insert(cnt, all_milestones.pop(index)) return flask.render_template( 'roadmap.html', select='issues', repo=repo, username=username, tag_list=tag_list, status=status, milestones=all_milestones, requested_stones=milestones, issues=milestone_issues, tags=tags, )
def api_change_status_issue(repo, issueid, username=None): """ Change issue status ------------------- This endpoint can be used to change the status of an issue :: /api/0/<repo>/issue/<issue id>/status /api/0/fork/<username>/<repo>/issue/<issue id>/status Accepts POST queries only. :arg status: The new status of the specified issue Sample response: :: { "message": "Edited successfully issue #1" } """ repo = pagure.lib.get_project(SESSION, repo, user=username) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) if repo != flask.g.token.project: raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if issue.private and not is_repo_admin(repo) \ and (not authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError( 403, error_code=APIERROR.EISSUENOTALLOWED) status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.StatusForm(status=status, csrf_enabled=False) if form.validate_on_submit(): new_status = form.status.data try: # Update status message = pagure.lib.edit_issue( SESSION, issue=issue, status=new_status, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: output['message'] = message else: output['message'] = 'No changes' except pagure.exceptions.PagureException, err: raise pagure.exceptions.APIError( 400, error_code=APIERROR.ENOCODE, error=str(err)) except SQLAlchemyError, err: # pragma: no cover SESSION.rollback() raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
def index(): """ Front page of the application. """ page = flask.request.args.get('page', 1) try: page = int(page) except ValueError: page = 1 limit = APP.config['ITEM_PER_PAGE'] start = limit * (page - 1) repos = pagure.lib.search_projects( SESSION, fork=False, start=start, limit=limit) num_repos = pagure.lib.search_projects( SESSION, fork=False, count=True) total_page = int(ceil(num_repos / float(limit))) repopage = None forkpage = None user_repos = None user_forks = None total_page_repos = None total_page_forks = None username = None user_repos_length = None user_forks_length = None if authenticated(): username = flask.g.fas_user.username repopage = flask.request.args.get('repopage', 1) try: repopage = int(repopage) except ValueError: repopage = 1 forkpage = flask.request.args.get('forkpage', 1) try: forkpage = int(forkpage) except ValueError: forkpage = 1 repo_start = limit * (repopage - 1) fork_start = limit * (forkpage - 1) user_repos = pagure.lib.search_projects( SESSION, username=username, fork=False, start=repo_start, limit=limit) user_repos_length = pagure.lib.search_projects( SESSION, username=username, fork=False, count=True) user_forks = pagure.lib.search_projects( SESSION, username=username, fork=True, start=fork_start, limit=limit) user_forks_length = pagure.lib.search_projects( SESSION, username=username, fork=True, count=True) total_page_repos = int(ceil(user_repos_length / float(limit))) total_page_forks = int(ceil(user_forks_length / float(limit))) return flask.render_template( 'index.html', repos=repos, total_page=total_page, page=page, username=username, repopage=repopage, forkpage=forkpage, user_repos=user_repos, user_forks=user_forks, total_page_repos=total_page_repos, total_page_forks=total_page_forks, user_repos_length=user_repos_length, user_forks_length=user_forks_length, repos_length=num_repos, )
def view_user(username): """ Front page of a specific user. """ user = _get_user(username=username) acl = flask.request.args.get('acl', '').strip().lower() or None repopage = flask.request.args.get('repopage', 1) try: repopage = int(repopage) if repopage < 1: repopage = 1 except ValueError: repopage = 1 forkpage = flask.request.args.get('forkpage', 1) try: forkpage = int(forkpage) if forkpage < 1: forkpage = 1 except ValueError: forkpage = 1 limit = APP.config['ITEM_PER_PAGE'] repo_start = limit * (repopage - 1) fork_start = limit * (forkpage - 1) private = False if authenticated() and username == flask.g.fas_user.username: private = flask.g.fas_user.username repos = pagure.lib.search_projects( SESSION, username=username, fork=False, exclude_groups=APP.config.get('EXCLUDE_GROUP_INDEX'), start=repo_start, limit=limit, private=private) if repos and acl: repos = _filter_acls(repos, acl, user) repos_length = len(repos) forks = pagure.lib.search_projects(SESSION, username=username, fork=True, start=fork_start, limit=limit, private=private) forks_length = len(forks) total_page_repos = int(ceil(repos_length / float(limit))) total_page_forks = int(ceil(forks_length / float(limit))) return flask.render_template( 'user_info.html', username=username, user=user, repos=repos, total_page_repos=total_page_repos, forks=forks, total_page_forks=total_page_forks, repopage=repopage, forkpage=forkpage, repos_length=repos_length, forks_length=forks_length, )
def view_issues(repo, username=None, namespace=None): """ List all issues associated to a repo """ status = flask.request.args.get('status', 'Open') priority = flask.request.args.get('priority', None) tags = flask.request.args.getlist('tags') tags = [tag.strip() for tag in tags if tag.strip()] assignee = flask.request.args.get('assignee', None) author = flask.request.args.get('author', None) search_pattern = flask.request.args.get('search_pattern', None) # Custom fields custom_keys = flask.request.args.getlist('ckeys') custom_values = flask.request.args.getlist('cvalue') custom_search = {} if len(custom_keys) == len(custom_values): for idx, key in enumerate(custom_keys): custom_search[key] = custom_values[idx] repo = flask.g.repo if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') try: priority = int(priority) except: priority = None # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if authenticated(): private = flask.g.fas_user.username # If user is repo admin, show all tickets included the private ones if flask.g.repo_admin: private = None if str(status).lower() in ['all']: status = None oth_issues = None oth_issues_cnt = None total_issues_cnt = pagure.lib.search_issues( SESSION, repo, tags=tags, assignee=assignee, author=author, private=private, priority=priority, count=True) if status is not None: issues = pagure.lib.search_issues( SESSION, repo, closed=True if status.lower() != 'open' else False, status=status.capitalize() if status.lower() != 'closed' else None, tags=tags, assignee=assignee, author=author, private=private, priority=priority, offset=flask.g.offset, limit=flask.g.limit, search_pattern=search_pattern, custom_search=custom_search, ) issues_cnt = pagure.lib.search_issues( SESSION, repo, closed=True if status.lower() != 'open' else False, status=status.capitalize() if status.lower() != 'closed' else None, tags=tags, assignee=assignee, author=author, private=private, priority=priority, search_pattern=search_pattern, custom_search=custom_search, count=True ) oth_issues = pagure.lib.search_issues( SESSION, repo, closed=True if status.lower() != 'open' else False, tags=tags, assignee=assignee, author=author, private=private, priority=priority, count=True, search_pattern=search_pattern, custom_search=custom_search, ) oth_issues_cnt = total_issues_cnt - issues_cnt else: issues = pagure.lib.search_issues( SESSION, repo, tags=tags, assignee=assignee, author=author, private=private, priority=priority, offset=flask.g.offset, limit=flask.g.limit, search_pattern=search_pattern, custom_search=custom_search, ) issues_cnt = total_issues_cnt tag_list = pagure.lib.get_tags_of_project(SESSION, repo) total_page = int(ceil(issues_cnt / float(flask.g.limit)) if issues_cnt > 0 else 1) return flask.render_template( 'issues.html', select='issues', repo=repo, username=username, tag_list=tag_list, status=status, issues=issues, issues_cnt=issues_cnt, total_issues_cnt=total_issues_cnt, oth_issues=oth_issues, oth_issues_cnt=oth_issues_cnt, tags=tags, assignee=assignee, author=author, priority=priority, total_page=total_page, add_report_form=pagure.forms.AddReportForm(), search_pattern=search_pattern, )
def format_loc(loc, commit=None, filename=None, tree_id=None, prequest=None, index=None): """ Template filter putting the provided lines of code into a table """ if loc is None: return output = [ '<div class="highlight">', '<table class="code_table">' ] comments = {} if prequest and not isinstance(prequest, flask.wrappers.Request): for com in prequest.comments: if commit and unicode(com.commit_id) == unicode(commit) \ and unicode(com.filename) == unicode(filename): if com.line in comments: comments[com.line].append(com) else: comments[com.line] = [com] for key in comments: comments[key] = sorted( comments[key], key=lambda obj: obj.date_created) if not index: index = '' cnt = 1 for line in loc.split('\n'): if line == '</pre></div>': break if filename and commit: output.append( '<tr id="c-%(commit)s-%(cnt_lbl)s"><td class="cell1">' '<a id="%(cnt)s" href="#%(cnt)s" data-line-number="%(cnt_lbl)s"></a></td>' '<td class="prc" data-row="%(cnt_lbl)s"' ' data-filename="%(filename)s" data-commit="%(commit)s"' ' data-tree="%(tree_id)s">' '<p>' '<span class="oi prc_img" data-glyph="comment-square" alt="Add comment" title="Add comment"></span>' '</p>' '</td>' % ( { 'cnt': '%s_%s' % (index, cnt), 'cnt_lbl': cnt, 'img': flask.url_for('static', filename='users.png'), 'filename': filename, 'commit': commit, 'tree_id': tree_id, } ) ) else: output.append( '<tr><td class="cell1">' '<a id="%(cnt)s" href="#%(cnt)s" data-line-number="%(cnt_lbl)s"></a></td>' % ( { 'cnt': '%s_%s' % (index, cnt), 'cnt_lbl': cnt, } ) ) cnt += 1 if not line: output.append(line) continue if line.startswith('<div'): line = line.split('<pre style="line-height: 125%">')[1] output.append('<td class="cell2"><pre>%s</pre></td>' % line) output.append('</tr>') tpl_edit = '<a href="%(edit_url)s" ' \ 'class="btn btn-secondary btn-sm" data-comment="%(commentid)s" ' \ 'data-objid="%(requestid)s">' \ '<span class="oi" data-glyph="pencil"></span>' \ '</a>' tpl_edited = '<small class="text-muted" title="%(edit_date)s"> ' \ 'Edited %(human_edit_date)s by %(user)s </small>' tpl_delete = '<button class="btn btn-secondary btn-sm" '\ 'title="Remove comment" '\ 'name="drop_comment" value="%(commentid)s" type="submit" ' \ 'onclick="return confirm(\'Do you really want to remove this comment?\');" '\ '><span class="oi" data-glyph="trash"></span>' \ '</button>' if cnt - 1 in comments: for comment in comments[cnt - 1]: templ_delete = '' templ_edit = '' templ_edited = '' if authenticated() and ( (str(comment.parent.status).lower() in ['true', 'open'] and comment.user.user == flask.g.fas_user.username) or is_repo_admin(comment.parent.project)): templ_delete = tpl_delete % ({'commentid': comment.id}) templ_edit = tpl_edit %({ 'edit_url': flask.url_for( 'pull_request_edit_comment', repo=comment.parent.project.name, requestid=comment.parent.id, commentid=comment.id, username=comment.parent.user.user \ if comment.parent.project.is_fork else None ), 'requestid': comment.parent.id, 'commentid': comment.id, }) if comment.edited_on: templ_edited = tpl_edited %({ 'edit_date':comment.edited_on.strftime( '%b %d %Y %H:%M:%S'), 'human_edit_date': humanize_date(comment.edited_on), 'user': comment.editor.user, }) output.append( '<tr class="inline-pr-comment"><td></td>' '<td colspan="2">' '<div class="card clearfix m-x-1 ">' '<div class="card-block">' '<small><div id="comment-%(commentid)s">' '<img class="avatar circle" src="%(avatar_url)s"/>' '<a href="%(url)s"> %(user)s</a> commented ' '<a class="headerlink" title="Permalink ' 'to this headline" href="#comment-%(commentid)s">' '<span title="%(date)s">%(human_date)s</span>' '</a></div></small>' '<section class="issue_comment">' '<div class="comment_body">' '%(comment)s' '</div>' '</section>' '<div class="issue_actions m-t-2">' '%(templ_edited)s' '<aside class="btn-group issue_action icon pull-xs-right p-b-1">' '%(templ_edit)s' '%(templ_delete)s' '</aside>' '</div></div></div>' '</td></tr>' % ( { 'url': flask.url_for( 'view_user', username=comment.user.user), 'templ_delete': templ_delete, 'templ_edit': templ_edit, 'templ_edited': templ_edited, 'user': comment.user.user, 'avatar_url': avatar_url( comment.user.default_email, 16), 'date': comment.date_created.strftime( '%b %d %Y %H:%M:%S'), 'human_date': humanize_date(comment.date_created), 'comment': markdown_filter(comment.comment), 'commentid': comment.id, 'anchor': u'¶', } ) ) output.append('</table></div>') return '\n'.join(output)
def update_issue(repo, issueid, username=None): ''' Add a comment to an issue. ''' is_js = flask.request.args.get('js', False) repo = pagure.lib.get_project(SESSION, repo, user=username) if flask.request.method == 'GET': if not is_js: flask.flash('Invalid method: GET', 'error') return flask.redirect( flask.url_for('view_issue', username=username, repo=repo.name, issueid=issueid)) if repo is None: flask.abort(404, 'Project not found') if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: flask.abort(404, 'Issue not found') if issue.private and not is_repo_admin(repo) \ and (not authenticated() or not issue.user.user == flask.g.fas_user.username): flask.abort( 403, 'This issue is private and you are not allowed to view it') status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.UpdateIssueForm(status=status) if form.validate_on_submit(): repo_admin = is_repo_admin(repo) if flask.request.form.get('drop_comment'): commentid = flask.request.form.get('drop_comment') comment = pagure.lib.get_issue_comment(SESSION, issue.uid, commentid) if comment is None or comment.issue.project != repo: flask.abort(404, 'Comment not found') if (flask.g.fas_user.username != comment.user.username or comment.parent.status != 'Open') \ and not is_repo_admin(repo): flask.abort( 403, 'You are not allowed to remove this comment from ' 'this issue') SESSION.delete(comment) try: SESSION.commit() if not is_js: flask.flash('Comment removed') except SQLAlchemyError, err: # pragma: no cover is_js = False SESSION.rollback() LOG.error(err) if not is_js: flask.flash('Could not remove the comment: %s' % commentid, 'error') comment = form.comment.data depends = [] for depend in form.depends.data.split(','): if depend.strip(): try: depends.append(int(depend.strip())) except ValueError: pass blocks = [] for block in form.blocks.data.split(','): if block.strip(): try: blocks.append(int(block.strip())) except ValueError: pass assignee = form.assignee.data new_status = form.status.data tags = [tag.strip() for tag in form.tag.data.split(',') if tag.strip()] try: # New comment if comment: message = pagure.lib.add_issue_comment( SESSION, issue=issue, comment=comment, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], redis=REDIS, ) SESSION.commit() if message and not is_js: flask.flash(message) if repo_admin: # Adjust (add/remove) tags messages = pagure.lib.update_tags( SESSION, issue, tags, username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], redis=REDIS) if not is_js: for message in messages: flask.flash(message) # Assign or update assignee of the ticket message = pagure.lib.add_issue_assignee( SESSION, issue=issue, assignee=assignee or None, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], redis=REDIS, ) if message and not is_js: SESSION.commit() flask.flash(message) if repo_admin: # Update status if new_status in status: message = pagure.lib.edit_issue( SESSION, issue=issue, status=new_status, private=issue.private, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], redis=REDIS, ) SESSION.commit() if message: flask.flash(message) # Update ticket this one depends on messages = pagure.lib.update_dependency_issue( SESSION, repo, issue, depends, username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], redis=REDIS, ) if not is_js: for message in messages: flask.flash(message) # Update ticket(s) depending on this one messages = pagure.lib.update_blocked_issue( SESSION, repo, issue, blocks, username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], redis=REDIS, ) if not is_js: for message in messages: flask.flash(message) except pagure.exceptions.PagureException, err: is_js = False SESSION.rollback() if not is_js: flask.flash(err.message, 'error')
def format_loc(loc, commit=None, filename=None, prequest=None, index=None): """ Template filter putting the provided lines of code into a table """ if loc is None: return output = [ '<div class="highlight">', '<table class="code_table">' ] comments = {} if prequest and not isinstance(prequest, flask.wrappers.Request): for com in prequest.comments: if commit and com.commit_id == commit \ and com.filename == filename: if com.line in comments: comments[com.line].append(com) else: comments[com.line] = [com] for key in comments: comments[key] = sorted( comments[key], key=lambda obj: obj.date_created) if not index: index = '' cnt = 1 for line in loc.split('\n'): if filename and commit: output.append( '<tr><td class="cell1">' '<a id="%(cnt)s" href="#%(cnt)s">%(cnt_lbl)s</a></td>' '<td class="prc" data-row="%(cnt_lbl)s"' ' data-filename="%(filename)s" data-commit="%(commit)s">' '<p>' '<img src="%(img)s" alt="Add comment" title="Add comment"/>' '</p>' '</td>' % ( { 'cnt': '%s_%s' % (index, cnt), 'cnt_lbl': cnt, 'img': flask.url_for('static', filename='users.png'), 'filename': filename, 'commit': commit, } ) ) else: output.append( '<tr><td class="cell1">' '<a id="%(cnt)s" href="#%(cnt)s">%(cnt_lbl)s</a></td>' % ( { 'cnt': '%s_%s' % (index, cnt), 'cnt_lbl': cnt, } ) ) cnt += 1 if not line: output.append(line) continue if line == '</pre></div>': continue if line.startswith('<div'): line = line.split('<pre style="line-height: 125%">')[1] output.append('<td class="cell2"><pre>%s</pre></td>' % line) output.append('</tr>') tpl_delete = '<button type="submit" name="drop_comment" ' \ 'value="%(commentid)s"' \ 'onclick="return confirm(\'Do you really want to remove' \ ' this comment?\');"' \ 'title="Remove comment">' \ '<span class="icon icon-remove blue"></span>' \ '</button>' if cnt - 1 in comments: for comment in comments[cnt - 1]: templ_delete = '' if authenticated() and ( (comment.parent.status is True and comment.user.user == flask.g.fas_user.username) or is_repo_admin(comment.parent.project)): templ_delete = tpl_delete % ({'commentid': comment.id}) output.append( '<tr><td></td>' '<td colspan="2"><table style="width:100%%"><tr>' '<td><a href="%(url)s">%(user)s</a></td>' '<td class="right">' '%(date)s%(templ_delete)s' '</td>' '</tr>' '<tr><td colspan="2" class="pr_comment">%(comment)s' '</td></tr>' '</table></td></tr>' % ( { 'url': flask.url_for( 'view_user', username=comment.user.user), 'templ_delete': templ_delete, 'user': comment.user.user, 'date': comment.date_created.strftime( '%b %d %Y %H:%M:%S'), 'comment': markdown_filter(comment.comment), } ) ) output.append('</table></div>') return '\n'.join(output)
def view_issues(repo, username=None, namespace=None): """ List all issues associated to a repo """ status = flask.request.args.get('status', 'Open') priority = flask.request.args.get('priority', None) tags = flask.request.args.getlist('tags') tags = [tag.strip() for tag in tags if tag.strip()] assignee = flask.request.args.get('assignee', None) author = flask.request.args.get('author', None) repo = flask.g.repo if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') try: priority = int(priority) except: priority = None # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if authenticated(): private = flask.g.fas_user.username # If user is repo admin, show all tickets included the private ones if flask.g.repo_admin: private = None if str(status).lower() in ['all']: status = None oth_issues = None if status is not None: issues = pagure.lib.search_issues( SESSION, repo, closed=True if status.lower() == 'closed' else False, status=status.capitalize() if status.lower() != 'closed' else None, tags=tags, assignee=assignee, author=author, private=private, priority=priority, offset=flask.g.offset, limit=flask.g.limit, ) issues_cnt = pagure.lib.search_issues( SESSION, repo, closed=True if status.lower() == 'closed' else False, status=status.capitalize() if status.lower() != 'closed' else None, tags=tags, assignee=assignee, author=author, private=private, priority=priority, count=True ) oth_issues = pagure.lib.search_issues( SESSION, repo, closed=False if status.lower() == 'closed' else True, tags=tags, assignee=assignee, author=author, private=private, priority=priority, count=True, ) else: issues = pagure.lib.search_issues( SESSION, repo, tags=tags, assignee=assignee, author=author, private=private, priority=priority, offset=flask.g.offset, limit=flask.g.limit, ) issues_cnt = pagure.lib.search_issues( SESSION, repo, tags=tags, assignee=assignee, author=author, private=private, priority=priority, count=True) tag_list = pagure.lib.get_tags_of_project(SESSION, repo) reponame = pagure.get_repo_path(repo) repo_obj = pygit2.Repository(reponame) total_page = int(ceil(issues_cnt / float(flask.g.limit))) return flask.render_template( 'issues.html', select='issues', repo=repo, username=username, tag_list=tag_list, status=status, issues=issues, issues_cnt=issues_cnt, oth_issues=oth_issues, tags=tags, assignee=assignee, author=author, priority=priority, total_page=total_page, add_report_form=pagure.forms.AddReportForm(), )
def api_view_issue(repo, issueid, username=None): """ Issue information ----------------- This endpoint can be used to retrieve information about a specific issue/ticket :: /api/0/<repo>/issue/<issue id> /api/0/fork/<username>/<repo>/issue/<issue id> Accepts GET queries only. Sample response: :: { "assignee": null, "blocks": [], "comments": [], "content": "This issue needs attention", "date_created": "1431414800", "depends": [], "id": 1, "private": false, "status": "Open", "tags": [], "title": "test issue", "user": { "fullname": "PY C", "name": "pingou" } } """ repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOISSUE) if authenticated(): if repo != flask.g.token.project: raise pagure.exceptions.APIError( 401, error_code=APIERROR.EINVALIDTOK) if issue.private and not is_repo_admin(repo) \ and (not authenticated() or not issue.user.user == flask.g.fas_user.username): raise pagure.exceptions.APIError( 403, error_code=APIERROR.EISSUENOTALLOWED) jsonout = flask.jsonify(issue.to_json(public=True)) return jsonout
def view_roadmap(repo, username=None, namespace=None): """ List all issues associated to a repo as roadmap """ status = flask.request.args.get('status', 'Open') if status.lower() == 'all': status = None milestone = flask.request.args.getlist('milestone', None) repo = flask.g.repo if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if authenticated(): private = flask.g.fas_user.username # If user is repo admin, show all tickets included the private ones if flask.g.repo_admin: private = None milestones = milestone or list(repo.milestones.keys()) issues = pagure.lib.search_issues( SESSION, repo, milestones=milestones, private=private, ) # Change from a list of issues to a dict of milestone/issues milestone_issues = defaultdict(list) for cnt in range(len(issues)): saved = False for mlstone in sorted(milestones): if mlstone == issues[cnt].milestone: milestone_issues[mlstone].append(issues[cnt]) saved = True break if saved: continue if status: for key in milestone_issues.keys(): active = False for issue in milestone_issues[key]: if issue.status == 'Open': active = True break if not active: del milestone_issues[key] if milestone: for mlstone in milestone: if mlstone not in milestone_issues: milestone_issues[mlstone] = [] tag_list = pagure.lib.get_tags_of_project(SESSION, repo) reponame = pagure.get_repo_path(repo) repo_obj = pygit2.Repository(reponame) milestones_ordered = sorted(list(milestone_issues.keys())) if 'unplanned' in milestones_ordered: index = milestones_ordered.index('unplanned') cnt = len(milestones_ordered) milestones_ordered.insert(cnt, milestones_ordered.pop(index)) return flask.render_template( 'roadmap.html', select='issues', repo=repo, username=username, tag_list=tag_list, status=status, milestones=milestones_ordered, issues=milestone_issues, tags=milestone, )
def api_view_issues(repo, username=None): """ List project's issues --------------------- This endpoint can be used to retrieve the list of all issues of the specified project :: /api/0/<repo>/issues /api/0/fork/<username>/<repo>/issues Accepts GET queries only. :kwarg status: The status of the issues to return, default to 'Open' :kwarg tags: One or more tags to filter the issues returned. If you want to wish to filter for issues not having a specific tag you can mark the tag with an exclamation mark in front of it, for example to get all the issues not tagged as ``easyfix`` you can filter using the tag ``!easyfix`` :kwarg assignee: Filters the issues returned by the user they are assigned to :kwarg author: Filters the issues returned by the user that opened the issue Sample response: :: { "args": { "assignee": null, "author": null, "status": null, "tags": [] }, "issues": [ { "assignee": null, "blocks": [], "comments": [ { "comment": "bing", "date_created": "1427441560", "id": 379, "parent": null, "user": { "fullname": "PY.C", "name": "pingou" } } ], "content": "bar", "date_created": "1427441555", "depends": [], "id": 1, "private": false, "status": "Open", "tags": [], "title": "foo", "user": { "fullname": "PY.C", "name": "pingou" } }, { "assignee": null, "blocks": [], "comments": [], "content": "report", "date_created": "1427442076", "depends": [], "id": 2, "private": false, "status": "Open", "tags": [], "title": "bug", "user": { "fullname": "PY.C", "name": "pingou" } } ] } Second example: { "args": { "assignee": null, "author": null, "status": "Closed", "tags": [ "0.1" ] }, "issues": [ { "assignee": null, "blocks": [], "comments": [], "content": "asd", "date_created": "1427442217", "depends": [], "id": 4, "private": false, "status": "Fixed", "tags": [ "0.1" ], "title": "bug", "user": { "fullname": "PY.C", "name": "pingou" } } ] } """ repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get('issue_tracker', True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.ETRACKERDISABLED) status = flask.request.args.get('status', None) tags = flask.request.args.getlist('tags') tags = [tag.strip() for tag in tags if tag.strip()] assignee = flask.request.args.get('assignee', None) author = flask.request.args.get('author', None) # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if authenticated(): if repo != flask.g.token.project: raise pagure.exceptions.APIError( 401, error_code=APIERROR.EINVALIDTOK) private = flask.g.fas_user.username # If user is repo admin, show all tickets included the private ones if is_repo_admin(repo): private = None if status is not None: if status.lower() == 'closed': issues = pagure.lib.search_issues( SESSION, repo, closed=True, tags=tags, assignee=assignee, author=author, private=private, ) else: issues = pagure.lib.search_issues( SESSION, repo, status=status, tags=tags, assignee=assignee, author=author, private=private, ) else: issues = pagure.lib.search_issues( SESSION, repo, status='Open', tags=tags, assignee=assignee, author=author, private=private) jsonout = flask.jsonify({ 'issues': [issue.to_json(public=True) for issue in issues], 'args': { 'status': status, 'tags': tags, 'assignee': assignee, 'author': author, } }) return jsonout
def update_issue(repo, issueid, username=None, namespace=None): ''' Add a comment to an issue. ''' is_js = flask.request.args.get('js', False) repo = flask.g.repo if flask.request.method == 'GET': if not is_js: flask.flash('Invalid method: GET', 'error') return flask.redirect(flask.url_for( 'view_issue', username=username, repo=repo.name, namespace=repo.namespace, issueid=issueid)) if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') issue = pagure.lib.search_issues(SESSION, repo, issueid=issueid) if issue is None or issue.project != repo: flask.abort(404, 'Issue not found') if issue.private and not flask.g.repo_admin \ and (not authenticated() or not issue.user.user == flask.g.fas_user.username): flask.abort( 403, 'This issue is private and you are not allowed to view it') if flask.request.form.get('edit_comment'): commentid = flask.request.form.get('edit_comment') form = pagure.forms.EditCommentForm() if form.validate_on_submit(): return edit_comment_issue( repo.name, issueid, commentid, username=username) status = pagure.lib.get_issue_statuses(SESSION) form = pagure.forms.UpdateIssueForm( status=status, priorities=repo.priorities, milestones=repo.milestones, close_status=repo.close_status, ) if form.validate_on_submit(): repo_admin = flask.g.repo_admin if flask.request.form.get('drop_comment'): commentid = flask.request.form.get('drop_comment') comment = pagure.lib.get_issue_comment( SESSION, issue.uid, commentid) if comment is None or comment.issue.project != repo: flask.abort(404, 'Comment not found') if (flask.g.fas_user.username != comment.user.username or comment.parent.status != 'Open') \ and not flask.g.repo_admin: flask.abort( 403, 'You are not allowed to remove this comment from ' 'this issue') issue.last_updated = datetime.datetime.utcnow() SESSION.add(issue) SESSION.delete(comment) try: SESSION.commit() if not is_js: flask.flash('Comment removed') except SQLAlchemyError as err: # pragma: no cover is_js = False SESSION.rollback() LOG.error(err) if not is_js: flask.flash( 'Could not remove the comment: %s' % commentid, 'error') if is_js: return 'ok' else: return flask.redirect(flask.url_for( 'view_issue', username=username, repo=repo.name, namespace=repo.namespace, issueid=issueid)) comment = form.comment.data depends = [] for depend in form.depends.data.split(','): if depend.strip(): try: depends.append(int(depend.strip())) except ValueError: pass blocks = [] for block in form.blocks.data.split(','): if block.strip(): try: blocks.append(int(block.strip())) except ValueError: pass assignee = form.assignee.data.strip() or None new_status = form.status.data.strip() or None close_status = form.close_status.data or None if new_status != 'Closed': close_status = None if close_status not in repo.close_status: close_status = None new_priority = None try: new_priority = int(form.priority.data) except: pass tags = [ tag.strip() for tag in form.tag.data.split(',') if tag.strip()] new_milestone = None try: if repo.milestones: new_milestone = form.milestone.data.strip() or None except: pass try: messages = set() # New comment if comment: message = pagure.lib.add_issue_comment( SESSION, issue=issue, comment=comment, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message and not is_js: messages.add(message) # The status field can be updated by both the admin and the # person who opened the ticket. # Update status if repo_admin or flask.g.fas_user.username == issue.user.user: if new_status in status: message = pagure.lib.edit_issue( SESSION, issue=issue, status=new_status, close_status=close_status, private=issue.private, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: messages.add(message) # All the other meta-data can be changed only by admins # while other field will be missing for non-admin and thus # reset if we let them if repo_admin: # Adjust (add/remove) tags messages.union(set(pagure.lib.update_tags( SESSION, issue, tags, username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'] ))) # The meta-data can be changed by admins and issue creator, # where issue creators can only change status of their issue while # other fields will be missing for non-admin and thus reset if we let them if repo_admin: # Assign or update assignee of the ticket message = pagure.lib.add_issue_assignee( SESSION, issue=issue, assignee=assignee or None, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message and message != 'Nothing to change': messages.add(message) # Update priority if str(new_priority) in repo.priorities: message = pagure.lib.edit_issue( SESSION, issue=issue, priority=new_priority, private=issue.private, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: messages.add(message) # Update milestone and privacy setting message = pagure.lib.edit_issue( SESSION, issue=issue, milestone=new_milestone, private=form.private.data, user=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ) SESSION.commit() if message: messages.add(message) # Update the custom keys/fields for key in repo.issue_keys: value = flask.request.form.get(key.name) if value: if key.key_type == 'link': links = value.split(',') for link in links: link = link.replace(' ', '') if not urlpattern.match(link): flask.abort( 400, 'Meta-data "link" field ' '(%s) has invalid url (%s) ' % (key.name, link)) messages.add( pagure.lib.set_custom_key_value( SESSION, issue, key, value) ) # Update ticket this one depends on messages.union(set(pagure.lib.update_dependency_issue( SESSION, repo, issue, depends, username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ))) # Update ticket(s) depending on this one messages.union(set(pagure.lib.update_blocked_issue( SESSION, repo, issue, blocks, username=flask.g.fas_user.username, ticketfolder=APP.config['TICKETS_FOLDER'], ))) if not is_js: for message in messages: flask.flash(message) except pagure.exceptions.PagureException as err: is_js = False SESSION.rollback() flask.flash(err.message, 'error') except SQLAlchemyError as err: # pragma: no cover is_js = False SESSION.rollback() APP.logger.exception(err) flask.flash(str(err), 'error') except filelock.Timeout as err: # pragma: no cover is_js = False SESSION.rollback() APP.logger.exception(err) flask.flash( 'We could not save all the info, please try again', 'error') else: if is_js: return 'notok: %s' % form.errors if is_js: return 'ok' else: return flask.redirect(flask.url_for( 'view_issue', repo=repo.name, username=username, namespace=namespace, issueid=issueid) )
def view_issues(repo, username=None): """ List all issues associated to a repo """ status = flask.request.args.get('status', None) priority = flask.request.args.get('priority', None) tags = flask.request.args.getlist('tags') tags = [tag.strip() for tag in tags if tag.strip()] assignee = flask.request.args.get('assignee', None) author = flask.request.args.get('author', None) repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: flask.abort(404, 'Project not found') if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') try: priority = int(priority) except: priority = None # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if authenticated(): private = flask.g.fas_user.username # If user is repo admin, show all tickets included the private ones if is_repo_admin(repo): private = None oth_issues = None if status is not None: if status.lower() == 'closed': issues = pagure.lib.search_issues( SESSION, repo, closed=True, tags=tags, assignee=assignee, author=author, private=private, priority=priority, ) oth_issues = pagure.lib.search_issues( SESSION, repo, status='Open', tags=tags, assignee=assignee, author=author, private=private, priority=priority, count=True, ) else: issues = pagure.lib.search_issues( SESSION, repo, status=status, tags=tags, assignee=assignee, author=author, private=private, priority=priority, ) else: issues = pagure.lib.search_issues( SESSION, repo, status='Open', tags=tags, assignee=assignee, author=author, private=private, priority=priority) oth_issues = pagure.lib.search_issues( SESSION, repo, closed=True, tags=tags, assignee=assignee, author=author, private=private, priority=priority, count=True) tag_list = pagure.lib.get_tags_of_project(SESSION, repo) reponame = pagure.get_repo_path(repo) repo_obj = pygit2.Repository(reponame) return flask.render_template( 'issues.html', select='issues', repo=repo, username=username, tag_list=tag_list, status=status, issues=issues, oth_issues=oth_issues, tags=tags, assignee=assignee, author=author, priority=priority, repo_admin=is_repo_admin(repo), repo_obj=repo_obj, )
def view_projects(pattern=None, namespace=None): """ Present the list of projects. """ forks = flask.request.args.get('forks') page = flask.request.args.get('page', 1) try: page = int(page) if page < 1: page = 1 except ValueError: page = 1 select = 'projects' # If forks is specified, we want both forks and projects if str(forks).lower() in ['true', '1']: forks = None select = 'projects_forks' else: forks = False private = False if authenticated(): private = flask.g.fas_user.username limit = APP.config['ITEM_PER_PAGE'] start = limit * (page - 1) projects = pagure.lib.search_projects(SESSION, pattern=pattern, namespace=namespace, fork=forks, start=start, limit=limit, private=private) if len(projects) == 1: flask.flash('Only one result found, redirecting you to it') return flask.redirect( flask.url_for('view_repo', repo=projects[0].name, namespace=projects[0].namespace, username=projects[0].user.username if projects[0].is_fork else None)) projects_length = pagure.lib.search_projects(SESSION, pattern=pattern, namespace=namespace, fork=forks, count=True, private=private) total_page = int(ceil(projects_length / float(limit))) return flask.render_template( 'index.html', repos=projects, repos_length=projects_length, total_page=total_page, page=page, select=select, )
flask.flash(err.message, 'error') return flask.redirect( flask.url_for('.view_group', group=group.group_name)) except SQLAlchemyError as err: # pragma: no cover pagure.SESSION.rollback() flask.flash( 'Could not add user `%s` to group `%s`.' % ( username, group.group_name), 'error') pagure.APP.logger.debug( 'Could not add user `%s` to group `%s`.' % ( username, group.group_name)) pagure.APP.logger.exception(err) member = False if pagure.authenticated(): member = pagure.lib.is_group_member( pagure.SESSION, flask.g.fas_user.username, group.group_name) return flask.render_template( 'group_info.html', group=group, form=form, member=member, ) @pagure.APP.route('/group/<group>/<user>/delete', methods=['POST']) @pagure.cla_required def group_user_delete(user, group): """ Delete an user from a certain group
def view_roadmap(repo, username=None): """ List all issues associated to a repo as roadmap """ status = flask.request.args.get('status', 'Open') if status.lower() == 'all': status = None milestone = flask.request.args.getlist('milestone', None) repo = pagure.lib.get_project(SESSION, repo, user=username) if repo is None: flask.abort(404, 'Project not found') if not repo.settings.get('issue_tracker', True): flask.abort(404, 'No issue tracker found for this project') # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if authenticated(): private = flask.g.fas_user.username # If user is repo admin, show all tickets included the private ones if is_repo_admin(repo): private = None milestones = milestone or list(repo.milestones.keys()) tags = ['roadmap'] + milestones issues = pagure.lib.search_issues( SESSION, repo, tags=tags, private=private, ) # Change from a list of issues to a dict of milestone/issues milestone_issues = defaultdict(list) for cnt in range(len(issues)): saved = False for mlstone in sorted(milestones): if mlstone in issues[cnt].tags_text: milestone_issues[mlstone].append(issues[cnt]) saved = True break if saved: continue if not milestone: milestone_issues['unplanned'].append(issues[cnt]) if status: for key in milestone_issues.keys(): active = False for issue in milestone_issues[key]: if issue.status == 'Open': active = True break if not active: del(milestone_issues[key]) if milestone: for mlstone in milestone: if mlstone not in milestone_issues: milestone_issues[mlstone] = [] tag_list = pagure.lib.get_tags_of_project(SESSION, repo) reponame = pagure.get_repo_path(repo) repo_obj = pygit2.Repository(reponame) milestones_ordered = sorted(list(milestone_issues.keys())) if 'unplanned' in milestones_ordered: index = milestones_ordered.index('unplanned') cnt = len(milestones_ordered) milestones_ordered.insert(cnt, milestones_ordered.pop(index)) return flask.render_template( 'roadmap.html', select='issues', repo=repo, username=username, tag_list=tag_list, status=status, milestones=milestones_ordered, issues=milestone_issues, tags=milestone, repo_admin=is_repo_admin(repo), repo_obj=repo_obj, )