def search(): """ Search this pagure instance for projects or users. """ stype = flask.request.args.get("type", "projects") term = flask.request.args.get("term") page = flask.request.args.get("page", 1) direct = is_true(flask.request.values.get("direct", False)) try: page = int(page) if page < 1: page = 1 except ValueError: page = 1 if direct: return flask.redirect(flask.url_for("ui_ns.view_repo", repo="") + term) if stype == "projects": return flask.redirect( flask.url_for("ui_ns.view_projects", pattern=term) ) elif stype == "projects_forks": return flask.redirect( flask.url_for("view_projects", pattern=term, forks=True) ) elif stype == "groups": return flask.redirect(flask.url_for("ui_ns.view_group", group=term)) else: return flask.redirect(flask.url_for("ui_ns.view_users", username=term))
def wait_task(taskid): """ Shows a wait page until the task finishes. """ task = pagure.lib.tasks.get_result(taskid) is_js = is_true(flask.request.args.get("js")) prev = flask.request.args.get("prev") if not is_safe_url(prev): prev = flask.url_for("index") count = flask.request.args.get("count", 0) try: count = int(count) if count < 1: count = 0 except ValueError: count = 0 if task.ready(): if is_js: flask.abort(417) return flask.redirect(get_task_redirect_url(task, prev)) else: if is_js: return flask.jsonify({"count": count + 1, "status": task.status}) return flask.render_template( "waiting.html", task=task, count=count, prev=prev )
def api_new_issue(repo, username=None, namespace=None): """ Create a new issue ------------------ Open a new issue on a project. :: POST /api/0/<repo>/new_issue POST /api/0/<namespace>/<repo>/new_issue :: POST /api/0/fork/<username>/<repo>/new_issue POST /api/0/fork/<username>/<namespace>/<repo>/new_issue Input ^^^^^ +-------------------+--------+-------------+---------------------------+ | Key | Type | Optionality | Description | +===================+========+=============+===========================+ | ``title`` | string | Mandatory | The title of the issue | +-------------------+--------+-------------+---------------------------+ | ``issue_content`` | string | Mandatory | | The description of the | | | | | issue | +-------------------+--------+-------------+---------------------------+ | ``private`` | boolean| Optional | | Include this key if | | | | | you want a private issue| | | | | to be created | +-------------------+--------+-------------+---------------------------+ | ``priority`` | string | Optional | | The priority to set to | | | | | this ticket from the | | | | | list of priorities set | | | | | in the project | +-------------------+--------+-------------+---------------------------+ | ``milestone`` | string | Optional | | The milestone to assign | | | | | to this ticket from the | | | | | list of milestones set | | | | | in the project | +-------------------+--------+-------------+---------------------------+ | ``tag`` | string | Optional | | Comma separated list of | | | | | tags to link to this | | | | | ticket from the list of | | | | | tags in the project | +-------------------+--------+-------------+---------------------------+ | ``assignee`` | string | Optional | | The username of the user| | | | | to assign this ticket to| +-------------------+--------+-------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "issue": { "assignee": null, "blocks": [], "close_status": null, "closed_at": null, "closed_by": null, "comments": [], "content": "This issue needs attention", "custom_fields": [], "date_created": "1479458613", "depends": [], "id": 1, "milestone": null, "priority": null, "private": false, "status": "Open", "tags": [], "title": "test issue", "user": { "fullname": "PY C", "name": "pingou" } }, "message": "Issue created" } """ output = {} repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo, project_token=False) user_obj = pagure.lib.query.get_user( flask.g.session, flask.g.fas_user.username ) if not user_obj: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOUSER) form = pagure.forms.IssueFormSimplied( priorities=repo.priorities, milestones=repo.milestones, csrf_enabled=False, ) if form.validate_on_submit(): title = form.title.data content = form.issue_content.data milestone = form.milestone.data or None private = is_true(form.private.data) priority = form.priority.data or None assignee = get_request_data().get("assignee", "").strip() or None tags = [ tag.strip() for tag in get_request_data().get("tag", "").split(",") if tag.strip() ] try: issue = pagure.lib.query.new_issue( flask.g.session, repo=repo, title=title, content=content, private=private, assignee=assignee, milestone=milestone, priority=priority, tags=tags, user=flask.g.fas_user.username, ) flask.g.session.flush() # If there is a file attached, attach it. filestream = flask.request.files.get("filestream") if filestream and "<!!image>" in issue.content: new_filename = pagure.lib.query.add_attachment( repo=repo, issue=issue, attachmentfolder=pagure_config["ATTACHMENTS_FOLDER"], user=user_obj, filename=filestream.filename, filestream=filestream.stream, ) # Replace the <!!image> tag in the comment with the link # to the actual image filelocation = flask.url_for( "ui_ns.view_issue_raw_file", repo=repo.name, username=username, filename="files/%s" % new_filename, ) new_filename = new_filename.split("-", 1)[1] url = "[![%s](%s)](%s)" % ( new_filename, filelocation, filelocation, ) issue.content = issue.content.replace("<!!image>", url) flask.g.session.add(issue) flask.g.session.flush() flask.g.session.commit() output["message"] = "Issue created" output["issue"] = issue.to_json(public=True) except SQLAlchemyError as err: # pragma: no cover flask.g.session.rollback() _log.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError( 400, error_code=APIERROR.EINVALIDREQ, errors=form.errors ) jsonout = flask.jsonify(output) return jsonout
def api_view_issue(repo, issueid, username=None, namespace=None): """ Issue information ----------------- Retrieve information of a specific issue. :: GET /api/0/<repo>/issue/<issue id> GET /api/0/<namespace>/<repo>/issue/<issue id> :: GET /api/0/fork/<username>/<repo>/issue/<issue id> GET /api/0/fork/<username>/<namespace>/<repo>/issue/<issue id> The identifier provided can be either the unique identifier or the regular identifier used in the UI (for example ``24`` in ``/forks/user/test/issue/24``) Sample response ^^^^^^^^^^^^^^^ :: { "assignee": null, "blocks": [], "comments": [], "content": "This issue needs attention", "date_created": "1431414800", "depends": ["4"], "id": 1, "private": false, "status": "Open", "tags": [], "title": "test issue", "user": { "fullname": "PY C", "name": "pingou" } } """ comments = is_true(flask.request.args.get("comments", True)) repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo) issue_id = issue_uid = None try: issue_id = int(issueid) except (ValueError, TypeError): issue_uid = issueid issue = _get_issue(repo, issue_id, issueuid=issue_uid) _check_private_issue_access(issue) jsonout = flask.jsonify(issue.to_json(public=True, with_comments=comments)) return jsonout
def api_view_issues(repo, username=None, namespace=None): """ List project's issues --------------------- List issues of a project. :: GET /api/0/<repo>/issues GET /api/0/<namespace>/<repo>/issues :: GET /api/0/fork/<username>/<repo>/issues GET /api/0/fork/<username>/<namespace>/<repo>/issues Parameters ^^^^^^^^^^ +---------------+---------+--------------+---------------------------+ | Key | Type | Optionality | Description | +===============+=========+==============+===========================+ | ``status`` | string | Optional | | Filters the status of | | | | | issues. Fetches all the | | | | | issues if status is | | | | | ``all``. Default: | | | | | ``Open`` | +---------------+---------+--------------+---------------------------+ | ``tags`` | string | Optional | | A list of tags you | | | | | wish to filter. If | | | | | you want to filter | | | | | for issues not having | | | | | a tag, add an | | | | | exclamation mark in | | | | | front of it | +---------------+---------+--------------+---------------------------+ | ``assignee`` | string | Optional | | Filter the issues | | | | | by assignee | +---------------+---------+--------------+---------------------------+ | ``author`` | string | Optional | | Filter the issues | | | | | by creator | +---------------+---------+--------------+---------------------------+ | ``milestones``| list of | Optional | | Filter the issues | | | strings | | by milestone | +---------------+---------+--------------+---------------------------+ | ``priority`` | string | Optional | | Filter the issues | | | | | by priority | +---------------+---------+--------------+---------------------------+ | ``no_stones`` | boolean | Optional | | If true returns only the| | | | | issues having no | | | | | milestone, if false | | | | | returns only the issues | | | | | having a milestone | +---------------+---------+--------------+---------------------------+ | ``since`` | string | Optional | | Filter the issues | | | | | updated after this date.| | | | | The date can either be | | | | | provided as an unix date| | | | | or in the format Y-M-D | +---------------+---------+--------------+---------------------------+ | ``order`` | string | Optional | | Set the ordering of the | | | | | issues. This can be | | | | | ``asc`` or ``desc``. | | | | | Default: ``desc`` | +---------------+---------+--------------+---------------------------+ | ``page`` | int | Optional | | Specifies which | | | | | page to return | | | | | (defaults to: 1) | +---------------+----------+-------------+---------------------------+ | ``per_page`` | int | Optional | | The number of projects | | | | | to return per page. | | | | | The maximum is 100. | +---------------+----------+-------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "args": { "assignee": null, "author": null, 'milestones': [], 'no_stones': null, 'order': null, 'priority': null, "since": null, "status": "Closed", "tags": [ "0.1" ] }, "total_issues": 1, "issues": [ { "assignee": null, "blocks": ["1"], "close_status": null, "closed_at": null, "closed_by": null, "comments": [], "content": "asd", "custom_fields": [], "date_created": "1427442217", "depends": [], "id": 4, "last_updated": "1533815358", "milestone": null, "priority": null, "private": false, "status": "Fixed", "tags": [ "0.1" ], "title": "bug", "user": { "fullname": "PY.C", "name": "pingou" } } ], 'pagination': { 'first': 'http://localhost/api/0/test/issues?per_page=20&page=1', 'last': 'http://localhost/api/0/test/issues?per_page=20&page=1', 'next': null, 'page': 1, 'pages': 1, 'per_page': 20, 'prev': null }, } """ repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo) assignee = flask.request.args.get("assignee", None) author = flask.request.args.get("author", None) milestone = flask.request.args.getlist("milestones", None) no_stones = flask.request.args.get("no_stones", None) if no_stones is not None: no_stones = is_true(no_stones) priority = flask.request.args.get("priority", None) since = flask.request.args.get("since", None) order = flask.request.args.get("order", None) status = flask.request.args.get("status", None) tags = flask.request.args.getlist("tags") tags = [tag.strip() for tag in tags if tag.strip()] search_id = flask.request.args.get("query_id", None) priority_key = None if priority: found = False if priority in repo.priorities: found = True priority_key = int(priority) else: for key, val in repo.priorities.items(): if val.lower() == priority.lower(): priority_key = key found = True break if not found: raise pagure.exceptions.APIError( 400, error_code=APIERROR.EINVALIDPRIORITY ) # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if api_authenticated(): private = flask.g.fas_user.username # If user is repo committer, show all tickets included the private ones if is_repo_committer(repo): private = None params = { "session": flask.g.session, "repo": repo, "tags": tags, "assignee": assignee, "author": author, "private": private, "milestones": milestone, "priority": priority_key, "order": order, "no_milestones": no_stones, "search_id": search_id, } if status is not None: if status.lower() == "all": params.update({"status": None}) elif status.lower() == "closed": params.update({"closed": True}) else: params.update({"status": status}) else: params.update({"status": "Open"}) updated_after = None if since: # Validate and convert the time if since.isdigit(): # We assume its a timestamp, so convert it to datetime try: updated_after = arrow.get(int(since)).datetime except ValueError: raise pagure.exceptions.APIError( 400, error_code=APIERROR.ETIMESTAMP ) else: # We assume datetime format, so validate it try: updated_after = datetime.datetime.strptime(since, "%Y-%m-%d") except ValueError: raise pagure.exceptions.APIError( 400, error_code=APIERROR.EDATETIME ) params.update({"updated_after": updated_after}) page = get_page() per_page = get_per_page() params["count"] = True issue_cnt = pagure.lib.query.search_issues(**params) pagination_metadata = pagure.lib.query.get_pagination_metadata( flask.request, page, per_page, issue_cnt ) query_start = (page - 1) * per_page query_limit = per_page params["count"] = False params["limit"] = query_limit params["offset"] = query_start issues = pagure.lib.query.search_issues(**params) jsonout = flask.jsonify( { "total_issues": len(issues), "issues": [issue.to_json(public=True) for issue in issues], "args": { "assignee": assignee, "author": author, "milestones": milestone, "no_stones": no_stones, "order": order, "priority": priority, "since": since, "status": status, "tags": tags, }, "pagination": pagination_metadata, } ) return jsonout
def api_subscribe_issue(repo, issueid, username=None, namespace=None): """ Subscribe to an issue --------------------- Allows someone to subscribe to or unsubscribe from the notifications related to an issue. :: POST /api/0/<repo>/issue/<issue id>/subscribe POST /api/0/<namespace>/<repo>/issue/<issue id>/subscribe :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/subscribe POST /api/0/fork/<username>/<namespace>/<repo>/issue/<issue id>/subscribe Input ^^^^^ +--------------+----------+---------------+---------------------------+ | Key | Type | Optionality | Description | +==============+==========+===============+===========================+ | ``status`` | boolean | Mandatory | The intended subscription | | | | | status. ``true`` for | | | | | subscribing, ``false`` | | | | | for unsubscribing. | +--------------+----------+---------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "User subscribed", "avatar_url": "https://image.png", "user": "******" } """ # noqa output = {} repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo) issue = _get_issue(repo, issueid) _check_private_issue_access(issue) form = pagure.forms.SubscribtionForm(csrf_enabled=False) if form.validate_on_submit(): status = is_true(form.status.data) try: # Toggle subscribtion message = pagure.lib.query.set_watch_obj( flask.g.session, user=flask.g.fas_user.username, obj=issue, watch_status=status, ) flask.g.session.commit() output["message"] = message user_obj = pagure.lib.query.get_user( flask.g.session, flask.g.fas_user.username ) output["avatar_url"] = pagure.lib.query.avatar_url_from_email( user_obj.default_email, size=30 ) output["user"] = flask.g.fas_user.username except SQLAlchemyError as err: # pragma: no cover flask.g.session.rollback() _log.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) jsonout = flask.jsonify(output) return jsonout
def api_view_user_activity_date(username, date): """ User activity on a specific date -------------------------------- Use this endpoint to retrieve activity information about a specific user on the specified date. :: GET /api/0/user/<username>/activity/<date> :: GET /api/0/user/ralph/activity/2016-01-02 GET /api/0/user/ralph/activity/2016-01-02?grouped=true Parameters ^^^^^^^^^^ +---------------+----------+--------------+----------------------------+ | Key | Type | Optionality | Description | +===============+==========+==============+============================+ | ``username`` | string | Mandatory | | The username of the user | | | | | whose activity you are | | | | | interested in. | +---------------+----------+--------------+----------------------------+ | ``date`` | string | Mandatory | | The date of interest, | | | | | best provided in ISO | | | | | format: YYYY-MM-DD | +---------------+----------+--------------+----------------------------+ | ``grouped`` | boolean | Optional | | Whether or not to group | | | | | the commits | +---------------+----------+--------------+----------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "activities": [ { "date": "2016-02-24", "date_created": "1456305852", "description": "pingou created PR test#44", "description_mk": "<p>pingou created PR <a href=\"/test/pull-request/44\" title=\"Update test_foo\">test#44</a></p>", "id": 4067, "user": { "fullname": "Pierre-YvesC", "name": "pingou" } }, { "date": "2016-02-24", "date_created": "1456305887", "description": "pingou commented on PR test#44", "description_mk": "<p>pingou commented on PR <a href=\"/test/pull-request/44\" title=\"Update test_foo\">test#44</a></p>", "id": 4112, "user": { "fullname": "Pierre-YvesC", "name": "pingou" } } ] } """ # noqa grouped = is_true(flask.request.args.get("grouped")) tz = flask.request.args.get("tz", "UTC") try: date = arrow.get(date) date = date.strftime("%Y-%m-%d") except arrow.parser.ParserError as err: raise pagure.exceptions.APIError(400, error_code=APIERROR.ENOCODE, error=str(err)) user = _get_user(username=username) activities = pagure.lib.query.get_user_activity_day(flask.g.session, user, date, tz=tz) js_act = [] if grouped: commits = collections.defaultdict(list) acts = [] for activity in activities: if activity.log_type == "committed": commits[activity.project.fullname].append(activity) else: acts.append(activity) for project in commits: if len(commits[project]) == 1: tmp = dict(description_mk=pagure.lib.query.text2markdown( six.text_type(commits[project][0]))) else: tmp = dict(description_mk=pagure.lib.query.text2markdown( "@%s pushed %s commits to %s" % (username, len(commits[project]), project))) js_act.append(tmp) activities = acts for act in activities: activity = act.to_json(public=True) activity["description_mk"] = pagure.lib.query.text2markdown( six.text_type(act)) js_act.append(activity) jsonout = flask.jsonify(dict(activities=js_act, date=date)) return jsonout
def api_view_user_issues(username): """ List user's issues --------------------- List issues opened by or assigned to a specific user across all projects. :: GET /api/0/user/<username>/issues Parameters ^^^^^^^^^^ +---------------+---------+--------------+---------------------------+ | Key | Type | Optionality | Description | +===============+=========+==============+===========================+ | ``page`` | integer | Mandatory | | The page requested. | | | | | Defaults to 1. | +---------------+---------+--------------+---------------------------+ | ``per_page`` | int | Optional | | The number of items | | | | | to return per page. | | | | | The maximum is 100. | +---------------+---------+--------------+---------------------------+ | ``status`` | string | Optional | | Filters the status of | | | | | issues. Fetches all the | | | | | issues if status is | | | | | ``all``. Default: | | | | | ``Open`` | +---------------+---------+--------------+---------------------------+ | ``tags`` | string | Optional | | A list of tags you | | | | | wish to filter. If | | | | | you want to filter | | | | | for issues not having | | | | | a tag, add an | | | | | exclamation mark in | | | | | front of it | +---------------+---------+--------------+---------------------------+ | ``milestones``| list of | Optional | | Filter the issues | | | strings | | by milestone | +---------------+---------+--------------+---------------------------+ | ``no_stones`` | boolean | Optional | | If true returns only the| | | | | issues having no | | | | | milestone, if false | | | | | returns only the issues | | | | | having a milestone | +---------------+---------+--------------+---------------------------+ | ``since`` | string | Optional | | Filter the issues | | | | | updated after this date.| | | | | The date can either be | | | | | provided as an unix date| | | | | or in the format Y-M-D | +---------------+---------+--------------+---------------------------+ | ``order`` | string | Optional | | Set the ordering of the | | | | | issues. This can be | | | | | ``asc`` or ``desc``. | | | | | Default: ``desc`` | +---------------+---------+--------------+---------------------------+ | ``order_key`` | string | Optional | | Set the ordering key. | | | | | This can be ``assignee``| | | | | , ``last_updated`` or | | | | | name of other column. | | | | | Default: | | | | | ``date_created`` | +---------------+---------+--------------+---------------------------+ | ``assignee`` | boolean | Optional | | A boolean of whether to | | | | | return the issues | | | | | assigned to this user | | | | | or not. Defaults to True| +---------------+---------+--------------+---------------------------+ | ``author`` | boolean | Optional | | A boolean of whether to | | | | | return the issues | | | | | created by this user or | | | | | not. Defaults to True | +---------------+---------+--------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "args": { "assignee": true, "author": true, "milestones": [], "no_stones": null, "order": null, "order_key": null, "page": 1, "since": null, "status": null, "tags": [] }, "issues_assigned": [ { "assignee": { "fullname": "Anar Adilova", "name": "anar" }, "blocks": [], "close_status": null, "closed_at": null, "comments": [], "content": "Test Issue", "custom_fields": [], "date_created": "1510124763", "depends": [], "id": 2, "last_updated": "1510124763", "milestone": null, "priority": null, "private": false, "status": "Open", "tags": [], "title": "issue4", "user": { "fullname": "Anar Adilova", "name": "anar" } } ], "issues_created": [ { "assignee": { "fullname": "Anar Adilova", "name": "anar" }, "blocks": [], "close_status": null, "closed_at": null, "comments": [], "content": "Test Issue", "custom_fields": [], "date_created": "1510124763", "depends": [], "id": 2, "last_updated": "1510124763", "milestone": null, "priority": null, "private": false, "status": "Open", "tags": [], "title": "issue4", "user": { "fullname": "Anar Adilova", "name": "anar" } } ], "pagination_issues_assigned": { "first": "http://localhost:5000/api/0/user/anar/issues?per_page=1&page=1", "last": "http://localhost:5000/api/0/user/anar/issues?per_page=1&page=0", "next": null, "page": 1, "pages": 0, "per_page": 1, "prev": null }, "pagination_issues_created": { "first": "http://localhost:5000/api/0/user/anar/issues?per_page=1&page=1", "last": "http://localhost:5000/api/0/user/anar/issues?per_page=1&page=200", "next": "http://localhost:5000/api/0/user/anar/issues?per_page=1&page=2", "page": 1, "pages": 200, "per_page": 1, "prev": null }, "total_issues_assigned": 1, "total_issues_created": 1 } """ # noqa milestone = flask.request.args.getlist("milestones", None) no_stones = flask.request.args.get("no_stones", None) if no_stones is not None: no_stones = is_true(no_stones) since = flask.request.args.get("since", None) order = flask.request.args.get("order", None) order_key = flask.request.args.get("order_key", None) status = flask.request.args.get("status", None) tags = flask.request.args.getlist("tags") tags = [tag.strip() for tag in tags if tag.strip()] page = get_page() per_page = get_per_page() assignee = flask.request.args.get("assignee", "").lower() not in [ "false", "0", "f", ] author = flask.request.args.get("author", "").lower() not in [ "false", "0", "f", ] offset = (page - 1) * per_page limit = per_page params = { "session": flask.g.session, "tags": tags, "milestones": milestone, "order": order, "order_key": order_key, "no_milestones": no_stones, "offset": offset, "limit": limit, } if status is not None: if status.lower() == "all": params.update({"status": None}) elif status.lower() == "closed": params.update({"closed": True}) else: params.update({"status": status}) else: params.update({"status": "Open"}) updated_after = None if since: # Validate and convert the time if since.isdigit(): # We assume its a timestamp, so convert it to datetime try: updated_after = datetime.datetime.fromtimestamp(int(since)) except ValueError: raise pagure.exceptions.APIError( 400, error_code=APIERROR.ETIMESTAMP) else: # We assume datetime format, so validate it try: updated_after = datetime.datetime.strptime(since, "%Y-%m-%d") except ValueError: raise pagure.exceptions.APIError(400, error_code=APIERROR.EDATETIME) params.update({"updated_after": updated_after}) issues_created = [] issues_created_pages = 1 issues_created_cnt = 0 pagination_issues_created = None if author: # Issues authored by this user params_created = params.copy() params_created.update({"author": username}) issues_created = pagure.lib.query.search_issues(**params_created) params_created.update({"offset": None, "limit": None, "count": True}) issues_created_cnt = pagure.lib.query.search_issues(**params_created) pagination_issues_created = pagure.lib.query.get_pagination_metadata( flask.request, page, per_page, issues_created_cnt) issues_assigned = [] issues_assigned_pages = 1 issues_assigned_cnt = 0 pagination_issues_assigned = None if assignee: # Issues assigned to this user params_assigned = params.copy() params_assigned.update({"assignee": username}) issues_assigned = pagure.lib.query.search_issues(**params_assigned) params_assigned.update({"offset": None, "limit": None, "count": True}) issues_assigned_cnt = pagure.lib.query.search_issues(**params_assigned) pagination_issues_assigned = pagure.lib.query.get_pagination_metadata( flask.request, page, per_page, issues_assigned_cnt) jsonout = flask.jsonify({ "pagination_issues_created": pagination_issues_created, "pagination_issues_assigned": pagination_issues_assigned, "total_issues_created_pages": issues_created_pages, "total_issues_assigned_pages": issues_assigned_pages, "total_issues_created": issues_created_cnt, "total_issues_assigned": issues_assigned_cnt, "issues_created": [ issue.to_json(public=True, with_project=True) for issue in issues_created ], "issues_assigned": [ issue.to_json(public=True, with_project=True) for issue in issues_assigned ], "args": { "milestones": milestone, "no_stones": no_stones, "order": order, "order_key": order_key, "since": since, "status": status, "tags": tags, "page": page, "assignee": assignee, "author": author, }, }) return jsonout
def format_loc( loc, commit=None, filename=None, tree_id=None, prequest=None, index=None, isprdiff=False, ): """ Template filter putting the provided lines of code into a table """ if loc is None: return output = ['<div class="highlight">', '<table class="code_table">'] commit_hash = commit if hasattr(commit_hash, "hex"): commit_hash = commit_hash.hex comments = {} if prequest and not isinstance(prequest, flask.wrappers.Request): for com in prequest.comments: if ( commit and com.commit_id == commit_hash 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: if isinstance(filename, str) and six.PY2: filename = filename.decode("UTF-8") if isprdiff and ( line.startswith("@@") or line.startswith("+") or line.startswith("-") ): if line.startswith("@@"): output.append( '<tr class="stretch-table-column bg-light"\ id="c-%(commit)s-%(cnt_lbl)s">' % ({"cnt_lbl": cnt, "commit": commit}) ) elif line.startswith("+"): output.append( '<tr class="stretch-table-column alert-success" \ id="c-%(commit)s-%(cnt_lbl)s">' % ({"cnt_lbl": cnt, "commit": commit}) ) elif line.startswith("-"): output.append( '<tr class="stretch-table-column alert-danger" \ id="c-%(commit)s-%(cnt_lbl)s">' % ({"cnt_lbl": cnt, "commit": commit}) ) else: output.append( '<tr id="c-%(commit)s-%(cnt_lbl)s">' % ({"cnt_lbl": cnt, "commit": commit}) ) output.append( '<td class="cell1">' '<a id="%(cnt)s" href="#%(cnt)s" data-line-number=' '"%(cnt_lbl)s" data-file-number=' '"%(line)s"></a></td>' '<td class="prc border-right" data-row="%(cnt_lbl)s"' ' data-filename="%(filename)s" data-commit="%(commit)s"' ' data-tree="%(tree_id)s">' "<p>" '<span class="fa fa-comment prc_img" style="display: none;"' 'alt="Add comment" title="Add comment"></span>' "</p>" "</td>" % ( { "cnt": "_%s__%s" % (index, cnt), "cnt_lbl": cnt, "line": index, "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("@@"): if prequest and prequest.project_from: rangeline = ( line.partition("@@ ")[2] if line.partition("@@ ")[1] == "@@ " else None ) if rangeline: rangeline = rangeline.split(" @@")[0] linenumber = rangeline.split("+")[1].split(",")[0] line = line + ' <a href="%s#_%s" target="_blank" ' % ( flask.url_for( "ui_ns.view_file", repo=prequest.project_from.name, username=prequest.project_from.user.username if prequest.project_from.is_fork else None, namespace=prequest.project_from.namespace, identifier=prequest.branch_from, filename=filename, ), linenumber, ) line = ( line + 'class="open_changed_file_icon_wrap">' + '<span class="fa fa-file-code-o fa-fw" ' + 'alt="Open changed file" ' + 'title="Open changed file"></span></a>' ) if isprdiff and ( line.startswith("@@") or line.startswith("+") or line.startswith("-") ): if line.startswith("@@"): output.append( '<td class="cell2 stretch-table-column">\ <pre class="text-muted"><code>%s</code></pre></td>' % line ) elif line.startswith("+"): output.append( '<td class="cell2 stretch-table-column">\ <pre class="alert-success"><code>%s</code></pre></td>' % escape(line) ) elif line.startswith("-"): output.append( '<td class="cell2 stretch-table-column">\ <pre class="alert-danger"><code>%s</code></pre></td>' % escape(line) ) else: output.append( '<td class="cell2"><pre><code>%s</code></pre></td>' % (escape(line)) ) output.append("</tr>") tpl_edit = ( '<a href="%(edit_url)s" ' 'class="btn btn-outline-primary border-0" ' 'data-comment="%(commentid)s" ' 'data-objid="%(requestid)s">' '<i class="fa fa-pencil"></i>' "</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-outline-primary border-0" ' 'title="Remove comment" ' 'name="drop_comment" value="%(commentid)s" type="submit" ' "onclick=\"return confirm('Do you really want to remove this" ' comment?\');" ><i class="fa fa-trash"></i>' "</button>" ) if cnt - 1 in comments: for comment in comments[cnt - 1]: templ_delete = "" templ_edit = "" templ_edited = "" if authenticated() and ( ( is_true(comment.parent.status, ["true", "open"]) and comment.user.user == flask.g.fas_user.username ) or is_repo_committer(comment.parent.project) ): templ_delete = tpl_delete % ({"commentid": comment.id}) templ_edit = tpl_edit % ( { "edit_url": flask.url_for( "ui_ns.pull_request_edit_comment", repo=comment.parent.project.name, namespace=comment.parent.project.namespace, 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": format_ts(comment.edited_on), "human_edit_date": humanize_date( comment.edited_on ), "user": comment.editor.user, } ) output.append( '<tr class="inline-pr-comment">' '<td colspan="3" class="p-3 border">' '<div class="card clearfix">' '<div class="card-header bg-light d-flex ' 'align-items-center px-3 py-2">' "<div>" '<div id="comment-%(commentid)s">' '<img class="avatar circle" src="%(avatar_url)s"/>' '<a href="%(url)s" title="%(user_html)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>" "</div>" '<div class="mr-auto">' "%(templ_edit)s" "%(templ_delete)s" "</div>" "</div>" '<div class="card-block">' "<small></small>" '<section class="issue_comment">' '<div class="comment_body">' "%(comment)s" "</div>" "</section>" "</div></div>" "</td></tr>" % ( { "url": flask.url_for( "ui_ns.view_user", username=comment.user.user ), "templ_delete": templ_delete, "templ_edit": templ_edit, "templ_edited": templ_edited, "user": comment.user.user, "user_html": comment.user.html_title, "avatar_url": avatar_url( comment.user.default_email, 16 ), "date": format_ts(comment.date_created), "human_date": humanize_date(comment.date_created), "comment": markdown_filter(comment.comment), "commentid": comment.id, } ) ) output.append("</table></div>") return "\n".join(output)
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 is_true(forks): forks = None select = "projects_forks" else: forks = False private = False if authenticated(): private = flask.g.fas_user.username limit = pagure_config["ITEM_PER_PAGE"] start = limit * (page - 1) projects = pagure.lib.query.search_projects( flask.g.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( "ui_ns.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.query.search_projects( flask.g.session, pattern=pattern, namespace=namespace, fork=forks, count=True, private=private, ) total_page = int(ceil(projects_length / float(limit))) if namespace in pagure_config["ALLOWED_PREFIX"]: namespace = None return flask.render_template( "index.html", namespace=namespace, repos=projects, repos_length=projects_length, total_page=total_page, page=page, select=select, )
def format_loc( loc, commit=None, filename=None, tree_id=None, prequest=None, index=None, isprdiff=False, ): """ Template filter putting the provided lines of code into a table """ if loc is None: return output = ['<div class="highlight">', '<table class="code_table">'] commit_hash = commit if hasattr(commit_hash, "hex"): commit_hash = commit_hash.hex comments = {} if prequest and not isinstance(prequest, flask.wrappers.Request): for com in prequest.comments: if (commit and com.commit_id == commit_hash 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: if isinstance(filename, str) and six.PY2: filename = filename.decode("UTF-8") if isprdiff and (line.startswith("@@") or line.startswith("+") or line.startswith("-")): if line.startswith("@@"): output.append('<tr class="stretch-table-column bg-light"\ id="c-%(commit)s-%(cnt_lbl)s">' % ({ "cnt_lbl": cnt, "commit": commit })) elif line.startswith("+"): output.append( '<tr class="stretch-table-column alert-success" \ id="c-%(commit)s-%(cnt_lbl)s">' % ({ "cnt_lbl": cnt, "commit": commit })) elif line.startswith("-"): output.append( '<tr class="stretch-table-column alert-danger" \ id="c-%(commit)s-%(cnt_lbl)s">' % ({ "cnt_lbl": cnt, "commit": commit })) else: output.append('<tr id="c-%(commit)s-%(cnt_lbl)s">' % ({ "cnt_lbl": cnt, "commit": commit })) output.append( '<td class="cell1">' '<a id="%(cnt)s" href="#%(cnt)s" data-line-number=' '"%(cnt_lbl)s" data-file-number=' '"%(line)s"></a></td>' '<td class="prc border-right" data-row="%(cnt_lbl)s"' ' data-filename="%(filename)s" data-commit="%(commit)s"' ' data-tree="%(tree_id)s">' "<p>" '<span class="fa fa-comment prc_img" style="display: none;"' 'alt="Add comment" title="Add comment"></span>' "</p>" "</td>" % ({ "cnt": "_%s__%s" % (index, cnt), "cnt_lbl": cnt, "line": index, "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("@@"): if prequest and prequest.project_from: rangeline = (line.partition("@@ ")[2] if line.partition("@@ ")[1] == "@@ " else None) if rangeline: rangeline = rangeline.split(" @@")[0] linenumber = rangeline.split("+")[1].split(",")[0] line = line + ' <a href="%s#_%s" target="_blank" ' % ( flask.url_for( "ui_ns.view_file", repo=prequest.project_from.name, username=prequest.project_from.user.username if prequest.project_from.is_fork else None, namespace=prequest.project_from.namespace, identifier=prequest.branch_from, filename=filename, ), linenumber, ) line = (line + 'class="open_changed_file_icon_wrap">' + '<span class="fa fa-file-code-o fa-fw" ' + 'alt="Open changed file" ' + 'title="Open changed file"></span></a>') if isprdiff and (line.startswith("@@") or line.startswith("+") or line.startswith("-")): if line.startswith("@@"): output.append('<td class="cell2 stretch-table-column">\ <pre class="text-muted"><code>%s</code></pre></td>' % line) elif line.startswith("+"): output.append('<td class="cell2 stretch-table-column">\ <pre class="alert-success"><code>%s</code></pre></td>' % escape(line)) elif line.startswith("-"): output.append('<td class="cell2 stretch-table-column">\ <pre class="alert-danger"><code>%s</code></pre></td>' % escape(line)) else: output.append('<td class="cell2"><pre><code>%s</code></pre></td>' % (escape(line))) output.append("</tr>") tpl_edit = ('<a href="%(edit_url)s" ' 'class="btn btn-outline-primary border-0" ' 'data-comment="%(commentid)s" ' 'data-objid="%(requestid)s">' '<i class="fa fa-pencil"></i>' "</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-outline-primary border-0" ' 'title="Remove comment" ' 'name="drop_comment" value="%(commentid)s" type="submit" ' "onclick=\"return confirm('Do you really want to remove this" ' comment?\');" ><i class="fa fa-trash"></i>' "</button>") if cnt - 1 in comments: for comment in comments[cnt - 1]: templ_delete = "" templ_edit = "" templ_edited = "" if authenticated() and ( (is_true(comment.parent.status, ["true", "open"]) and comment.user.user == flask.g.fas_user.username) or is_repo_committer(comment.parent.project)): templ_delete = tpl_delete % ({"commentid": comment.id}) templ_edit = tpl_edit % ({ "edit_url": flask.url_for( "ui_ns.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": format_ts(comment.edited_on), "human_edit_date": humanize_date( comment.edited_on), "user": comment.editor.user, }) output.append( '<tr class="inline-pr-comment">' '<td colspan="3" class="p-3 border">' '<div class="card clearfix">' '<div class="card-header bg-light d-flex ' 'align-items-center px-3 py-2">' "<div>" '<div id="comment-%(commentid)s">' '<img class="avatar circle" src="%(avatar_url)s"/>' '<a href="%(url)s" title="%(user_html)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>" "</div>" '<div class="mr-auto">' "%(templ_edit)s" "%(templ_delete)s" "</div>" "</div>" '<div class="card-block">' "<small></small>" '<section class="issue_comment">' '<div class="comment_body">' "%(comment)s" "</div>" "</section>" "</div></div>" "</td></tr>" % ({ "url": flask.url_for("ui_ns.view_user", username=comment.user.user), "templ_delete": templ_delete, "templ_edit": templ_edit, "templ_edited": templ_edited, "user": comment.user.user, "user_html": comment.user.html_title, "avatar_url": avatar_url(comment.user.default_email, 16), "date": format_ts(comment.date_created), "human_date": humanize_date(comment.date_created), "comment": markdown_filter(comment.comment), "commentid": comment.id, })) output.append("</table></div>") return "\n".join(output)
def api_new_issue(repo, username=None, namespace=None): """ Create a new issue ------------------ Open a new issue on a project. :: POST /api/0/<repo>/new_issue POST /api/0/<namespace>/<repo>/new_issue :: POST /api/0/fork/<username>/<repo>/new_issue POST /api/0/fork/<username>/<namespace>/<repo>/new_issue Input ^^^^^ +-------------------+--------+-------------+---------------------------+ | Key | Type | Optionality | Description | +===================+========+=============+===========================+ | ``title`` | string | Mandatory | The title of the issue | +-------------------+--------+-------------+---------------------------+ | ``issue_content`` | string | Mandatory | | The description of the | | | | | issue | +-------------------+--------+-------------+---------------------------+ | ``private`` | boolean| Optional | | Include this key if | | | | | you want a private issue| | | | | to be created | +-------------------+--------+-------------+---------------------------+ | ``priority`` | string | Optional | | The priority to set to | | | | | this ticket from the | | | | | list of priorities set | | | | | in the project | +-------------------+--------+-------------+---------------------------+ | ``milestone`` | string | Optional | | The milestone to assign | | | | | to this ticket from the | | | | | list of milestones set | | | | | in the project | +-------------------+--------+-------------+---------------------------+ | ``tag`` | string | Optional | | Comma separated list of | | | | | tags to link to this | | | | | ticket from the list of | | | | | tags in the project | +-------------------+--------+-------------+---------------------------+ | ``assignee`` | string | Optional | | The username of the user| | | | | to assign this ticket to| +-------------------+--------+-------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "issue": { "assignee": null, "blocks": [], "close_status": null, "closed_at": null, "closed_by": null, "comments": [], "content": "This issue needs attention", "custom_fields": [], "date_created": "1479458613", "depends": [], "id": 1, "milestone": null, "priority": null, "private": false, "status": "Open", "tags": [], "title": "test issue", "user": { "fullname": "PY C", "name": "pingou" } }, "message": "Issue created" } """ output = {} repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo, project_token=False) user_obj = pagure.lib.query.get_user(flask.g.session, flask.g.fas_user.username) if not user_obj: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOUSER) form = pagure.forms.IssueFormSimplied( priorities=repo.priorities, milestones=repo.milestones, csrf_enabled=False, ) if form.validate_on_submit(): title = form.title.data content = form.issue_content.data milestone = form.milestone.data or None private = is_true(form.private.data) priority = form.priority.data or None assignee = get_request_data().get("assignee", "").strip() or None tags = [ tag.strip() for tag in get_request_data().get("tag", "").split(",") if tag.strip() ] try: issue = pagure.lib.query.new_issue( flask.g.session, repo=repo, title=title, content=content, private=private, assignee=assignee, milestone=milestone, priority=priority, tags=tags, user=flask.g.fas_user.username, ) flask.g.session.flush() # If there is a file attached, attach it. filestream = flask.request.files.get("filestream") if filestream and "<!!image>" in issue.content: new_filename = pagure.lib.query.add_attachment( repo=repo, issue=issue, attachmentfolder=pagure_config["ATTACHMENTS_FOLDER"], user=user_obj, filename=filestream.filename, filestream=filestream.stream, ) # Replace the <!!image> tag in the comment with the link # to the actual image filelocation = flask.url_for( "ui_ns.view_issue_raw_file", repo=repo.name, username=username, filename="files/%s" % new_filename, ) new_filename = new_filename.split("-", 1)[1] url = "[![%s](%s)](%s)" % ( new_filename, filelocation, filelocation, ) issue.content = issue.content.replace("<!!image>", url) flask.g.session.add(issue) flask.g.session.flush() flask.g.session.commit() output["message"] = "Issue created" output["issue"] = issue.to_json(public=True) except SQLAlchemyError as err: # pragma: no cover flask.g.session.rollback() _log.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) else: raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ, errors=form.errors) jsonout = flask.jsonify(output) return jsonout
def api_view_issues(repo, username=None, namespace=None): """ List project's issues --------------------- List issues of a project. :: GET /api/0/<repo>/issues GET /api/0/<namespace>/<repo>/issues :: GET /api/0/fork/<username>/<repo>/issues GET /api/0/fork/<username>/<namespace>/<repo>/issues Parameters ^^^^^^^^^^ +---------------+---------+--------------+---------------------------+ | Key | Type | Optionality | Description | +===============+=========+==============+===========================+ | ``status`` | string | Optional | | Filters the status of | | | | | issues. Fetches all the | | | | | issues if status is | | | | | ``all``. Default: | | | | | ``Open`` | +---------------+---------+--------------+---------------------------+ | ``tags`` | string | Optional | | A list of tags you | | | | | wish to filter. If | | | | | you want to filter | | | | | for issues not having | | | | | a tag, add an | | | | | exclamation mark in | | | | | front of it | +---------------+---------+--------------+---------------------------+ | ``assignee`` | string | Optional | | Filter the issues | | | | | by assignee | +---------------+---------+--------------+---------------------------+ | ``author`` | string | Optional | | Filter the issues | | | | | by creator | +---------------+---------+--------------+---------------------------+ | ``milestones``| list of | Optional | | Filter the issues | | | strings | | by milestone | +---------------+---------+--------------+---------------------------+ | ``priority`` | string | Optional | | Filter the issues | | | | | by priority | +---------------+---------+--------------+---------------------------+ | ``no_stones`` | boolean | Optional | | If true returns only the| | | | | issues having no | | | | | milestone, if false | | | | | returns only the issues | | | | | having a milestone | +---------------+---------+--------------+---------------------------+ | ``since`` | string | Optional | | Filter the issues | | | | | updated after this date.| | | | | The date can either be | | | | | provided as an unix date| | | | | or in the format Y-M-D | +---------------+---------+--------------+---------------------------+ | ``order`` | string | Optional | | Set the ordering of the | | | | | issues. This can be | | | | | ``asc`` or ``desc``. | | | | | Default: ``desc`` | +---------------+---------+--------------+---------------------------+ | ``page`` | int | Optional | | Specifies which | | | | | page to return | | | | | (defaults to: 1) | +---------------+----------+-------------+---------------------------+ | ``per_page`` | int | Optional | | The number of projects | | | | | to return per page. | | | | | The maximum is 100. | +---------------+----------+-------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "args": { "assignee": null, "author": null, 'milestones': [], 'no_stones': null, 'order': null, 'priority': null, "since": null, "status": "Closed", "tags": [ "0.1" ] }, "total_issues": 1, "issues": [ { "assignee": null, "blocks": ["1"], "close_status": null, "closed_at": null, "closed_by": null, "comments": [], "content": "asd", "custom_fields": [], "date_created": "1427442217", "depends": [], "id": 4, "last_updated": "1533815358", "milestone": null, "priority": null, "private": false, "status": "Fixed", "tags": [ "0.1" ], "title": "bug", "user": { "fullname": "PY.C", "name": "pingou" } } ], 'pagination': { 'first': 'http://localhost/api/0/test/issues?per_page=20&page=1', 'last': 'http://localhost/api/0/test/issues?per_page=20&page=1', 'next': null, 'page': 1, 'pages': 1, 'per_page': 20, 'prev': null }, } """ repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo, project_token=False) assignee = flask.request.args.get("assignee", None) author = flask.request.args.get("author", None) milestone = flask.request.args.getlist("milestones", None) no_stones = flask.request.args.get("no_stones", None) if no_stones is not None: no_stones = is_true(no_stones) priority = flask.request.args.get("priority", None) since = flask.request.args.get("since", None) order = flask.request.args.get("order", None) status = flask.request.args.get("status", None) tags = flask.request.args.getlist("tags") tags = [tag.strip() for tag in tags if tag.strip()] search_id = flask.request.args.get("query_id", None) priority_key = None if priority: found = False if priority in repo.priorities: found = True priority_key = int(priority) else: for key, val in repo.priorities.items(): if val.lower() == priority.lower(): priority_key = key found = True break if not found: raise pagure.exceptions.APIError( 400, error_code=APIERROR.EINVALIDPRIORITY) # Hide private tickets private = False # If user is authenticated, show him/her his/her private tickets if api_authenticated(): private = flask.g.fas_user.username # If user is repo committer, show all tickets included the private ones if is_repo_committer(repo): private = None params = { "session": flask.g.session, "repo": repo, "tags": tags, "assignee": assignee, "author": author, "private": private, "milestones": milestone, "priority": priority_key, "order": order, "no_milestones": no_stones, "search_id": search_id, } if status is not None: if status.lower() == "all": params.update({"status": None}) elif status.lower() == "closed": params.update({"closed": True}) else: params.update({"status": status}) else: params.update({"status": "Open"}) updated_after = None if since: # Validate and convert the time if since.isdigit(): # We assume its a timestamp, so convert it to datetime try: updated_after = arrow.get(int(since)).datetime except ValueError: raise pagure.exceptions.APIError( 400, error_code=APIERROR.ETIMESTAMP) else: # We assume datetime format, so validate it try: updated_after = datetime.datetime.strptime(since, "%Y-%m-%d") except ValueError: raise pagure.exceptions.APIError(400, error_code=APIERROR.EDATETIME) params.update({"updated_after": updated_after}) page = get_page() per_page = get_per_page() params["count"] = True issue_cnt = pagure.lib.query.search_issues(**params) pagination_metadata = pagure.lib.query.get_pagination_metadata( flask.request, page, per_page, issue_cnt) query_start = (page - 1) * per_page query_limit = per_page params["count"] = False params["limit"] = query_limit params["offset"] = query_start issues = pagure.lib.query.search_issues(**params) jsonout = flask.jsonify({ "total_issues": len(issues), "issues": [issue.to_json(public=True) for issue in issues], "args": { "assignee": assignee, "author": author, "milestones": milestone, "no_stones": no_stones, "order": order, "priority": priority, "since": since, "status": status, "tags": tags, }, "pagination": pagination_metadata, }) return jsonout
def api_subscribe_issue(repo, issueid, username=None, namespace=None): """ Subscribe to an issue --------------------- Allows someone to subscribe to or unsubscribe from the notifications related to an issue. :: POST /api/0/<repo>/issue/<issue id>/subscribe POST /api/0/<namespace>/<repo>/issue/<issue id>/subscribe :: POST /api/0/fork/<username>/<repo>/issue/<issue id>/subscribe POST /api/0/fork/<username>/<namespace>/<repo>/issue/<issue id>/subscribe Input ^^^^^ +--------------+----------+---------------+---------------------------+ | Key | Type | Optionality | Description | +==============+==========+===============+===========================+ | ``status`` | boolean | Mandatory | The intended subscription | | | | | status. ``true`` for | | | | | subscribing, ``false`` | | | | | for unsubscribing. | +--------------+----------+---------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "User subscribed", "avatar_url": "https://image.png", "user": "******" } """ # noqa output = {} repo = _get_repo(repo, username, namespace) _check_issue_tracker(repo) _check_token(repo) issue = _get_issue(repo, issueid) _check_private_issue_access(issue) form = pagure.forms.SubscribtionForm(csrf_enabled=False) if form.validate_on_submit(): status = is_true(form.status.data) try: # Toggle subscribtion message = pagure.lib.query.set_watch_obj( flask.g.session, user=flask.g.fas_user.username, obj=issue, watch_status=status, ) flask.g.session.commit() output["message"] = message user_obj = pagure.lib.query.get_user(flask.g.session, flask.g.fas_user.username) output["avatar_url"] = pagure.lib.query.avatar_url_from_email( user_obj.default_email, size=30) output["user"] = flask.g.fas_user.username except SQLAlchemyError as err: # pragma: no cover flask.g.session.rollback() _log.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) jsonout = flask.jsonify(output) return jsonout
def api_groups(): """ List groups ----------- Retrieve groups on this Pagure instance. This can then be used as input for autocompletion in some forms/fields. :: GET /api/0/groups Parameters ^^^^^^^^^^ +---------------+----------+---------------+--------------------------+ | Key | Type | Optionality | Description | +===============+==========+===============+==========================+ | ``pattern`` | string | Optional | | Filters the starting | | | | | letters of the group | | | | | names | +---------------+----------+---------------+--------------------------+ | ``page`` | int | Optional | | Specifies which | | | | | page to return | | | | | (defaults to: 1) | +---------------+----------+---------------+--------------------------+ | ``per_page`` | int | Optional | | The number of projects | | | | | to return per page. | | | | | The maximum is 100. | +---------------+----------+---------------+--------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "total_groups": 2, u'pagination': { 'first': 'http://localhost/api/0/groups?per_page=20&extended=1&page=1', 'last': 'http://localhost/api/0/groups?per_page=20&extended=1&page=1', 'next': null, 'page': 1, 'pages': 1, 'per_page': 20, 'prev': None }, "groups": ["group1", "group2"] } """ # noqa pattern = flask.request.args.get("pattern", None) extended = is_true(flask.request.args.get("extended", False)) if pattern is not None and not pattern.endswith("*"): pattern += "*" page = get_page() per_page = get_per_page() group_cnt = pagure.lib.query.search_groups( flask.g.session, pattern=pattern, count=True ) pagination_metadata = pagure.lib.query.get_pagination_metadata( flask.request, page, per_page, group_cnt ) query_start = (page - 1) * per_page query_limit = per_page groups = pagure.lib.query.search_groups( flask.g.session, pattern=pattern, limit=query_limit, offset=query_start ) if extended: groups = [ {"name": grp.group_name, "description": grp.description} for grp in groups ] else: groups = [group.group_name for group in groups] return flask.jsonify( { "total_groups": group_cnt, "groups": groups, "pagination": pagination_metadata, } )
def api_subscribe_pull_request(repo, requestid, username=None, namespace=None): """ Subscribe to an pull-request ---------------------------- Allows someone to subscribe to or unsubscribe from the notifications related to a pull-request. :: POST /api/0/<repo>/pull-request/<request id>/subscribe POST /api/0/<namespace>/<repo>/pull-request/<request id>/subscribe :: POST /api/0/fork/<username>/<repo>/pull-request/<request id>/subscribe POST /api/0/fork/<username>/<namespace>/<repo>/pull-request/<request id>/subscribe Input ^^^^^ +--------------+----------+---------------+---------------------------+ | Key | Type | Optionality | Description | +==============+==========+===============+===========================+ | ``status`` | boolean | Mandatory | The intended subscription | | | | | status. ``true`` for | | | | | subscribing, ``false`` | | | | | for unsubscribing. | +--------------+----------+---------------+---------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "message": "User subscribed", "avatar_url": "https://image.png", "user": "******" } """ # noqa repo = get_authorized_api_project( flask.g.session, repo, user=username, namespace=namespace ) output = {} if repo is None: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOPROJECT) if not repo.settings.get("pull_requests", True): raise pagure.exceptions.APIError( 404, error_code=APIERROR.EPULLREQUESTSDISABLED ) if ( api_authenticated() and flask.g.token and flask.g.token.project and repo != flask.g.token.project ) or not authenticated(): raise pagure.exceptions.APIError(401, error_code=APIERROR.EINVALIDTOK) request = pagure.lib.query.search_pull_requests( flask.g.session, project_id=repo.id, requestid=requestid ) if not request: raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOREQ) form = pagure.forms.SubscribtionForm(csrf_enabled=False) if form.validate_on_submit(): status = is_true(form.status.data) try: # Toggle subscribtion message = pagure.lib.query.set_watch_obj( flask.g.session, user=flask.g.fas_user.username, obj=request, watch_status=status, ) flask.g.session.commit() output["message"] = message user_obj = pagure.lib.query.get_user( flask.g.session, flask.g.fas_user.username ) output["avatar_url"] = pagure.lib.query.avatar_url_from_email( user_obj.default_email, size=30 ) output["user"] = flask.g.fas_user.username except SQLAlchemyError as err: # pragma: no cover flask.g.session.rollback() _log.logger.exception(err) raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR) jsonout = flask.jsonify(output) return jsonout
def api_groups(): """ List groups ----------- Retrieve groups on this Pagure instance. This can then be used as input for autocompletion in some forms/fields. :: GET /api/0/groups Parameters ^^^^^^^^^^ +---------------+----------+---------------+--------------------------+ | Key | Type | Optionality | Description | +===============+==========+===============+==========================+ | ``pattern`` | string | Optional | | Filters the starting | | | | | letters of the group | | | | | names | +---------------+----------+---------------+--------------------------+ | ``page`` | int | Optional | | Specifies which | | | | | page to return | | | | | (defaults to: 1) | +---------------+----------+---------------+--------------------------+ | ``per_page`` | int | Optional | | The number of projects | | | | | to return per page. | | | | | The maximum is 100. | +---------------+----------+---------------+--------------------------+ Sample response ^^^^^^^^^^^^^^^ :: { "total_groups": 2, u'pagination': { 'first': 'http://localhost/api/0/groups?per_page=20&extended=1&page=1', 'last': 'http://localhost/api/0/groups?per_page=20&extended=1&page=1', 'next': null, 'page': 1, 'pages': 1, 'per_page': 20, 'prev': None }, "groups": ["group1", "group2"] } """ # noqa pattern = flask.request.args.get("pattern", None) extended = is_true(flask.request.args.get("extended", False)) if pattern is not None and not pattern.endswith("*"): pattern += "*" page = get_page() per_page = get_per_page() group_cnt = pagure.lib.query.search_groups(flask.g.session, pattern=pattern, count=True) pagination_metadata = pagure.lib.query.get_pagination_metadata( flask.request, page, per_page, group_cnt) query_start = (page - 1) * per_page query_limit = per_page groups = pagure.lib.query.search_groups(flask.g.session, pattern=pattern, limit=query_limit, offset=query_start) if extended: groups = [{ "name": grp.group_name, "description": grp.description } for grp in groups] else: groups = [group.group_name for group in groups] return flask.jsonify({ "total_groups": group_cnt, "groups": groups, "pagination": pagination_metadata, })