[copr] master: [frontend][api] Updated and test projects collection (GET on /projects). Added an option to use fulltext search. (b6106eb)
by vgologuz@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit b6106eb5501486defdb68e80c63c3903979e7c30
Author: Valentin Gologuzov <vgologuz(a)redhat.com>
Date: Wed Sep 2 14:28:02 2015 +0200
[frontend][api] Updated and test projects collection (GET on /projects). Added an option to use fulltext search.
>---------------------------------------------------------------
frontend/coprs_frontend/coprs/logic/coprs_logic.py | 15 +++-
frontend/coprs_frontend/coprs/rest_api/__init__.py | 4 +-
.../coprs/rest_api/resources/project.py | 19 +++--
.../coprs/views/api_ns/api_general.py | 1 +
.../coprs/views/coprs_ns/coprs_general.py | 9 ++-
.../tests/test_api/test_project_r.py | 85 +++++++++++++++++++-
6 files changed, 118 insertions(+), 15 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/logic/coprs_logic.py b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
index d5c8725..ec56f0a 100644
--- a/frontend/coprs_frontend/coprs/logic/coprs_logic.py
+++ b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
@@ -69,13 +69,21 @@ class CoprsLogic(object):
db.session.query(models.Copr)
.join(models.Copr.owner)
.options(db.contains_eager(models.Copr.owner))
- .order_by(models.Copr.id.desc()))
+ )
if not include_deleted:
query = query.filter(models.Copr.deleted.is_(False))
return query
+ @classmethod
+ def set_query_order(cls, query, desc=False):
+ if desc:
+ query = query.order_by(models.Copr.id.desc())
+ else:
+ query = query.order_by(models.Copr.id.asc())
+ return query
+
# user_relation="owned", username=username, with_mock_chroots=False
@classmethod
def get_multiple_owned_by_username(cls, username):
@@ -99,6 +107,11 @@ class CoprsLogic(object):
return query.filter(models.Copr.name == name)
@classmethod
+ def filter_by_owner_name(cls, query, username):
+ # should be already joined with the User table
+ return query.filter(models.User.username == username)
+
+ @classmethod
def join_builds(cls, query):
return (query.outerjoin(models.Copr.builds)
.options(db.contains_eager(models.Copr.builds))
diff --git a/frontend/coprs_frontend/coprs/rest_api/__init__.py b/frontend/coprs_frontend/coprs/rest_api/__init__.py
index 2a4c2f6..2debb8f 100644
--- a/frontend/coprs_frontend/coprs/rest_api/__init__.py
+++ b/frontend/coprs_frontend/coprs/rest_api/__init__.py
@@ -19,8 +19,8 @@ class RootR(Resource):
return {
"_links": {
"self": {"href": url_for(".rootr")},
- "coprs": {"href": url_for(".projectlistr")},
- "copr_chroots": {"href": url_for(".mockchrootlistr")},
+ "projects": {"href": url_for(".projectlistr")},
+ "mock_chroots": {"href": url_for(".mockchrootlistr")},
"builds": {"href": url_for(".buildlistr")},
}
}
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/project.py b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
index d0b989f..cee7bf7 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/project.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
@@ -1,4 +1,5 @@
from logging import getLogger
+from coprs.rest_api.exceptions import MalformedRequest
log = getLogger(__name__)
@@ -52,23 +53,27 @@ class ProjectListR(Resource):
def get(self):
parser = reqparse.RequestParser()
- parser.add_argument('owner', dest='username', type=str)
- parser.add_argument('name', dest='name', type=str)
+ parser.add_argument('owner', type=str)
+ parser.add_argument('name', type=str)
parser.add_argument('limit', type=int)
parser.add_argument('offset', type=int)
+ parser.add_argument('search_query', type=str)
- # parser.add_argument('help', type=bool)
req_args = parser.parse_args()
- if req_args["username"]:
- query = CoprsLogic.get_multiple_owned_by_username(req_args["username"])
+ if req_args["search_query"]:
+ query = CoprsLogic.get_multiple_fulltext(req_args["search_query"])
else:
query = CoprsLogic.get_multiple(flask.g.user)
+ query = CoprsLogic.set_query_order(query)
+
+ if req_args["owner"]:
+ query = CoprsLogic.filter_by_owner_name(query, req_args["owner"])
+
if req_args["name"]:
query = CoprsLogic.filter_by_name(query, req_args["name"])
- # todo: add maximum allowed limit and also use as a default limit
limit = 100
offset = 0
if req_args["limit"]:
@@ -83,7 +88,7 @@ class ProjectListR(Resource):
"_links": {
"self": {"href": url_for(".projectlistr", **req_args)}
},
- "coprs": [render_project(copr) for copr in coprs_list],
+ "projects": [render_project(copr) for copr in coprs_list],
}
return result_dict
diff --git a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py
index 3ded705..eecb919 100644
--- a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py
+++ b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py
@@ -186,6 +186,7 @@ def api_coprs_by_owner(username=None):
if username:
query = CoprsLogic.join_builds(
CoprsLogic.get_multiple_owned_by_username(username))
+ query = CoprsLogic.set_query_order(query)
repos = query.all()
output = {"output": "ok", "repos": []}
diff --git a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
index 6ff6221..a665099 100644
--- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
+++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
@@ -19,6 +19,7 @@ from coprs import exceptions
from coprs import forms
from coprs import helpers
from coprs import models
+from coprs.logic.coprs_logic import CoprsLogic
from coprs.logic.stat_logic import CounterStatLogic
from coprs.rmodels import TimedStatEvents
@@ -35,7 +36,9 @@ from coprs.helpers import parse_package_name, generate_repo_url, CHROOT_RPMS_DL_
@coprs_ns.route("/", defaults={"page": 1})
@coprs_ns.route("/<int:page>/")
def coprs_show(page=1):
- query = coprs_logic.CoprsLogic.get_multiple()
+ query = CoprsLogic.get_multiple()
+ query = CoprsLogic.set_query_order(query, desc=True)
+
paginator = helpers.Paginator(query, query.count(), page)
coprs = paginator.sliced_query
@@ -68,7 +71,8 @@ def coprs_by_owner(username=None, page=1):
return page_not_found(
"User {0} does not exist.".format(username))
- query = coprs_logic.CoprsLogic.get_multiple_owned_by_username(username)
+ query = CoprsLogic.get_multiple_owned_by_username(username)
+ query = CoprsLogic.set_query_order(query, desc=True)
paginator = helpers.Paginator(query, query.count(), page)
@@ -97,6 +101,7 @@ def coprs_by_owner(username=None, page=1):
@coprs_ns.route("/<username>/allowed/<int:page>/")
def coprs_by_allowed(username=None, page=1):
query = coprs_logic.CoprsLogic.get_multiple_allowed_to_username(username)
+ query = CoprsLogic.set_query_order(query, desc=True)
paginator = helpers.Paginator(query, query.count(), page)
coprs = paginator.sliced_query
diff --git a/frontend/coprs_frontend/tests/test_api/test_project_r.py b/frontend/coprs_frontend/tests/test_api/test_project_r.py
index 316a6b7..9a3fa0b 100644
--- a/frontend/coprs_frontend/tests/test_api/test_project_r.py
+++ b/frontend/coprs_frontend/tests/test_api/test_project_r.py
@@ -11,6 +11,7 @@ from coprs.logic.builds_logic import BuildsLogic
from coprs.logic.users_logic import UsersLogic
from coprs.logic.coprs_logic import CoprsLogic
+from coprs.models import Copr
from tests.coprs_test_case import CoprsTestCase, TransactionDecorator
@@ -37,9 +38,87 @@ class TestProjectResource(CoprsTestCase):
obj = json.loads(r.data)
assert obj["_links"]["self"]["href"] == href
- # test_project_list_all
- # test_project_list_by_name
- # test_project_list_by_user
+ def test_project_list_all(self, f_users, f_mock_chroots, f_coprs, f_db):
+ expected_id_set = set(c.id for c in self.basic_coprs_list)
+ href = "/api_2/projects"
+ r = self.tc.get(href)
+ assert r.status_code == 200
+ obj = json.loads(r.data)
+ assert set(p["project"]["id"] for p in obj["projects"]) == \
+ expected_id_set
+
+ def test_project_list_by_user(self, f_users, f_mock_chroots, f_coprs, f_db):
+ expected_id_set = set(
+ c.id for c in self.basic_coprs_list
+ if c.owner == self.u1
+ )
+ href = "/api_2/projects?owner={}".format(self.u1.username)
+ r = self.tc.get(href)
+ assert r.status_code == 200
+ obj = json.loads(r.data)
+ assert set(p["project"]["id"] for p in obj["projects"]) == \
+ expected_id_set
+
+ def test_project_list_by_name(self, f_users, f_mock_chroots, f_coprs, f_db):
+ expected_id_set = set(
+ c.id for c in self.basic_coprs_list
+ if c.name == self.c1.name
+ )
+ href = "/api_2/projects?name={}".format(self.c1.name)
+ r = self.tc.get(href)
+ assert r.status_code == 200
+ obj = json.loads(r.data)
+ assert set(p["project"]["id"] for p in obj["projects"]) == \
+ expected_id_set
+
+ def test_project_list_limit_offset(self, f_users, f_mock_chroots, f_coprs, f_db):
+ # quite hardcoded test
+ s_1 = set(p.id for p in [self.c1, self.c2])
+ s_2 = set(p.id for p in [self.c2, self.c3])
+ s_3 = set(p.id for p in [self.c3])
+
+ href_list = [
+ "/api_2/projects?limit=2",
+ "/api_2/projects?limit=2&offset=1",
+ "/api_2/projects?limit=2&offset=2"
+ ]
+ for href, expected in zip(href_list, [s_1, s_2, s_3]):
+ r = self.tc.get(href)
+ assert r.status_code == 200
+ obj = json.loads(r.data)
+ assert set(p["project"]["id"] for p in obj["projects"]) == \
+ expected
+
+ def test_project_list_search(self, f_users, f_mock_chroots, f_coprs, f_db):
+ self.prefix = u"prefix"
+ self.s_coprs = []
+ c1_username = self.c1.owner.username
+
+ k1 = 3
+ k2 = 5
+ for x in range(k1):
+ self.s_coprs.append(Copr(name=self.prefix + str(x), owner=self.u1))
+
+ for x in range(k2):
+ self.s_coprs.append(Copr(name=self.prefix + str(x), owner=self.u2))
+
+ self.db.session.add_all(self.s_coprs)
+ self.db.session.commit()
+
+ r0 = self.tc.get(u"/api_2/projects?search_query={}".format(self.prefix))
+ assert r0.status_code == 200
+ obj = json.loads(r0.data)
+ assert len(obj["projects"]) == k1 + k2
+ for p in obj["projects"]:
+ assert self.prefix in p["project"]["name"]
+
+ r1 = self.tc.get(u"/api_2/projects?search_query={}&owner={}"
+ .format(self.prefix, c1_username))
+ assert r1.status_code == 200
+ obj = json.loads(r1.data)
+ assert len(obj["projects"]) == k1
+ for p in obj["projects"]:
+ assert self.prefix in p["project"]["name"]
def test_project_create_new(self, f_users, f_mock_chroots, f_users_api):
self.db.session.add_all([self.u1, self.mc1])
8 years, 8 months
[copr] master: Fix missing copr names in yum_repos (RhBug:1258943) URL build.results may or may not end with slash, so when not, the urljoin cuts off it's last part (which is copr name) (c5dcbbc)
by frostyx@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit c5dcbbc5460865042d65b0d337dd9c21b92bd3fd
Author: Jakub Kadlčík <jkadlcik(a)redhat.com>
Date: Wed Sep 2 12:43:32 2015 +0200
Fix missing copr names in yum_repos (RhBug:1258943)
URL build.results may or may not end with slash, so when not,
the urljoin cuts off it's last part (which is copr name)
>---------------------------------------------------------------
.../coprs/views/api_ns/api_general.py | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py
index 6383e0e..3ded705 100644
--- a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py
+++ b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py
@@ -196,7 +196,7 @@ def api_coprs_by_owner(username=None):
for chroot in repo.active_chroots:
release = release_tmpl.format(chroot=chroot)
yum_repos[release] = fix_protocol_for_backend(
- urlparse.urljoin(build.results, release + '/'))
+ os.path.join(build.results, release + '/'))
break
output["repos"].append({"name": repo.name,
@@ -233,7 +233,7 @@ def api_coprs_by_owner_detail(username, coprname):
for chroot in copr.active_chroots:
release = release_tmpl.format(chroot=chroot)
yum_repos[release] = fix_protocol_for_backend(
- urlparse.urljoin(build.results, release + '/'))
+ os.path.join(build.results, release + '/'))
break
output["detail"] = {
"name": copr.name,
8 years, 8 months
[copr] master: [frontend][api] imports cleanup (096b627)
by vgologuz@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit 096b627e27fe92e9f0651b5a83d717dc4fe58739
Author: Valentin Gologuzov <vgologuz(a)redhat.com>
Date: Tue Sep 1 15:04:13 2015 +0200
[frontend][api] imports cleanup
>---------------------------------------------------------------
frontend/coprs_frontend/coprs/rest_api/common.py | 87 ++++++++++++++++++++
.../coprs/rest_api/resources/build.py | 4 +-
.../coprs/rest_api/resources/build_chroot.py | 19 ++---
.../coprs/rest_api/resources/common.py | 27 ------
.../coprs/rest_api/resources/mock_chroot.py | 11 +--
.../coprs/rest_api/resources/project.py | 74 +++--------------
.../coprs/rest_api/resources/project_chroot.py | 16 ++--
7 files changed, 116 insertions(+), 122 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/rest_api/common.py b/frontend/coprs_frontend/coprs/rest_api/common.py
new file mode 100644
index 0000000..da57710
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/rest_api/common.py
@@ -0,0 +1,87 @@
+# coding: utf-8
+import base64
+import datetime
+import functools
+from logging import getLogger
+
+log = getLogger(__name__)
+
+from flask import url_for
+import flask
+
+from ..logic.users_logic import UsersLogic
+from .exceptions import AuthFailed
+from .schemas import CoprChrootSchema, BuildSchema, ProjectSchema
+from .util import mm_serialize_one
+
+
+def render_copr_chroot(chroot):
+ return {
+ "chroot": mm_serialize_one(CoprChrootSchema, chroot),
+ "_links": {
+ "project": {"href": url_for(".projectr", project_id=chroot.copr.id)},
+ "self": {"href": url_for(".projectchrootr",
+ project_id=chroot.copr.id,
+ name=chroot.name)},
+ }
+ }
+
+
+def render_build(build):
+ return {
+ "build": BuildSchema().dump(build)[0],
+ "_links": {
+ "self": {"href": url_for(".buildr", build_id=build.id)},
+ "project": {"href": url_for(".projectr", project_id=build.copr_id)},
+ "chroots": {"href": url_for(".buildchrootlistr", build_id=build.id)}
+ }
+ }
+
+
+def render_project(copr, self_params=None):
+ if self_params is None:
+ self_params = {}
+
+ return {
+ "project": mm_serialize_one(ProjectSchema, copr),
+ "_links": {
+ "self": {"href": url_for(".projectr", project_id=copr.id, **self_params)},
+ "builds": {"href": url_for(".buildlistr", project_id=copr.id)},
+ "chroots": {"href": url_for(".projectchrootlistr", project_id=copr.id)}
+ },
+ }
+
+
+def rest_api_auth_required(f):
+ # todo: move to common.py and test this
+ @functools.wraps(f)
+ def decorated_function(*args, **kwargs):
+ token = None
+ api_login = None
+ try:
+ if "Authorization" in flask.request.headers:
+ base64string = flask.request.headers["Authorization"]
+ base64string = base64string.split()[1].strip()
+ userstring = base64.b64decode(base64string)
+ (api_login, token) = userstring.split(":")
+ except Exception:
+ log.exception("Failed to get auth token from headers")
+ api_login = token = None
+
+ token_auth = False
+ if token and api_login:
+ user = UsersLogic.get_by_api_login(api_login).first()
+ if (user and user.api_token == token and
+ user.api_token_expiration >= datetime.date.today()):
+
+ token_auth = True
+ flask.g.user = user
+ if not token_auth:
+ message = (
+ "Login invalid/expired. "
+ "Please visit https://copr.fedoraproject.org/api "
+ "get or renew your API token.")
+
+ raise AuthFailed(message)
+ return f(*args, **kwargs)
+ return decorated_function
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/build.py b/frontend/coprs_frontend/coprs/rest_api/resources/build.py
index 14f228a..b948a98 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/build.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/build.py
@@ -11,8 +11,8 @@ from coprs.logic.coprs_logic import CoprsLogic
from coprs.logic.builds_logic import BuildsLogic
from coprs.logic.users_logic import UsersLogic
from coprs.rest_api.exceptions import MalformedRequest, CannotProcessRequest, AccessForbidden
-from coprs.rest_api.resources.common import render_build
-from coprs.rest_api.resources.project import rest_api_auth_required
+from ..common import render_build, rest_api_auth_required
+
from coprs.rest_api.schemas import BuildSchema, BuildCreateSchema, BuildCreateFromUrlSchema
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/build_chroot.py b/frontend/coprs_frontend/coprs/rest_api/resources/build_chroot.py
index 751ecaa..7d04edd 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/build_chroot.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/build_chroot.py
@@ -1,22 +1,15 @@
# coding: utf-8
-import json
-import flask
from flask import url_for
-from flask_restful import Resource, reqparse
-
-from marshmallow import Schema, fields, pprint
+from flask_restful import Resource
from ... import models
-from coprs.exceptions import MalformedArgumentException
-from coprs.logic.builds_logic import BuildsLogic, BuildChrootsLogic
-from coprs.rest_api.exceptions import MalformedRequest
-from coprs.rest_api.schemas import MockChrootSchema, CoprChrootSchema, BuildChrootSchema
-
-from coprs.views.misc import api_login_required
-from coprs.logic.coprs_logic import MockChrootsLogic, CoprChrootsLogic, CoprsLogic
+from ...exceptions import MalformedArgumentException
+from ...logic.builds_logic import BuildsLogic, BuildChrootsLogic
-from ..util import get_one_safe, json_loads_safe, mm_deserialize, mm_serialize_one
+from ..exceptions import MalformedRequest
+from ..schemas import BuildChrootSchema
+from ..util import get_one_safe, mm_serialize_one
def render_build_chroot(chroot):
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/common.py b/frontend/coprs_frontend/coprs/rest_api/resources/common.py
deleted file mode 100644
index 85ccfd4..0000000
--- a/frontend/coprs_frontend/coprs/rest_api/resources/common.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# coding: utf-8
-from flask import url_for
-from coprs.rest_api.schemas import CoprChrootSchema, BuildSchema
-from coprs.rest_api.util import mm_serialize_one
-
-
-def render_copr_chroot(chroot):
- return {
- "chroot": mm_serialize_one(CoprChrootSchema, chroot),
- "_links": {
- "project": {"href": url_for(".projectr", project_id=chroot.copr.id)},
- "self": {"href": url_for(".projectchrootr",
- project_id=chroot.copr.id,
- name=chroot.name)},
- }
- }
-
-
-def render_build(build):
- return {
- "build": BuildSchema().dump(build)[0],
- "_links": {
- "self": {"href": url_for(".buildr", build_id=build.id)},
- "project": {"href": url_for(".projectr", project_id=build.copr_id)},
- "chroots": {"href": url_for(".buildchrootlistr", build_id=build.id)}
- }
- }
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/mock_chroot.py b/frontend/coprs_frontend/coprs/rest_api/resources/mock_chroot.py
index 0ee1d0d..2255d4b 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/mock_chroot.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/mock_chroot.py
@@ -1,17 +1,12 @@
# coding: utf-8
-import json
-import flask
from flask import url_for
from flask_restful import Resource, reqparse
-from marshmallow import Schema, fields, pprint
-from coprs.rest_api.schemas import MockChrootSchema
+from ...logic.coprs_logic import MockChrootsLogic
-from coprs.views.misc import api_login_required
-from coprs.logic.coprs_logic import MockChrootsLogic, CoprChrootsLogic, CoprsLogic
-
-from ..util import get_one_safe, json_loads_safe, mm_deserialize
+from ..schemas import MockChrootSchema
+from ..util import get_one_safe
def render_mock_chroot(chroot):
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/project.py b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
index 17dc94b..ede6fc2 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/project.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
@@ -1,76 +1,24 @@
-import base64
-import datetime
-import functools
from logging import getLogger
-from coprs.logic.builds_logic import BuildsLogic
-from coprs.rest_api.resources.common import render_copr_chroot, render_build
log = getLogger(__name__)
import flask
from flask import url_for, make_response
from flask_restful import Resource, reqparse
-from marshmallow import pprint
-
-from coprs import db
-from coprs.exceptions import DuplicateException
-from coprs.logic.complex_logic import ComplexLogic
-from coprs.logic.helpers import slice_query
-from coprs.logic.users_logic import UsersLogic
-from coprs.logic.coprs_logic import CoprsLogic
-from coprs.exceptions import ActionInProgressException, InsufficientRightsException
-from coprs.rest_api.schemas import ProjectSchema, ProjectCreateSchema
-from ..exceptions import ObjectAlreadyExists, AuthFailed, CannotProcessRequest, AccessForbidden
-from ..util import get_one_safe, json_loads_safe, mm_deserialize, render_allowed_method, mm_serialize_one
-
-
-def rest_api_auth_required(f):
- # todo: move to common.py and test this
- @functools.wraps(f)
- def decorated_function(*args, **kwargs):
- token = None
- api_login = None
- try:
- if "Authorization" in flask.request.headers:
- base64string = flask.request.headers["Authorization"]
- base64string = base64string.split()[1].strip()
- userstring = base64.b64decode(base64string)
- (api_login, token) = userstring.split(":")
- except Exception:
- log.exception("Failed to get auth token from headers")
- api_login = token = None
-
- token_auth = False
- if token and api_login:
- user = UsersLogic.get_by_api_login(api_login).first()
- if (user and user.api_token == token and
- user.api_token_expiration >= datetime.date.today()):
-
- token_auth = True
- flask.g.user = user
- if not token_auth:
- message = (
- "Login invalid/expired. "
- "Please visit https://copr.fedoraproject.org/api "
- "get or renew your API token.")
-
- raise AuthFailed(message)
- return f(*args, **kwargs)
- return decorated_function
-
-
-def render_project(copr, self_params=None):
- if self_params is None:
- self_params = {}
- return {
- "project": mm_serialize_one(ProjectSchema, copr),
- "_links": {
- "self": {"href": url_for(".projectr", project_id=copr.id, **self_params)},
- "builds": {"href": url_for(".buildlistr", project_id=copr.id)},
- "chroots": {"href": url_for(".projectchrootlistr", project_id=copr.id)}
- },
- }
+from ... import db
+from ...logic.builds_logic import BuildsLogic
+from ...logic.complex_logic import ComplexLogic
+from ...logic.helpers import slice_query
+from ...logic.coprs_logic import CoprsLogic
+from ...exceptions import ActionInProgressException, InsufficientRightsException
+
+from ...exceptions import DuplicateException
+
+from ..common import rest_api_auth_required, render_copr_chroot, render_build, render_project
+from ..schemas import ProjectSchema, ProjectCreateSchema
+from ..exceptions import ObjectAlreadyExists, CannotProcessRequest, AccessForbidden
+from ..util import get_one_safe, mm_deserialize
class ProjectListR(Resource):
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py b/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py
index 29f6114..d9043a7 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py
@@ -2,8 +2,6 @@
import logging
-from coprs.rest_api.resources.common import render_copr_chroot
-
log = logging.getLogger(__name__)
import flask
@@ -11,16 +9,16 @@ from flask import url_for, make_response
from flask_restful import Resource
from sqlalchemy.exc import IntegrityError
-from coprs.exceptions import InsufficientRightsException, MalformedArgumentException
-from coprs.rest_api.exceptions import AccessForbidden, MalformedRequest, ObjectAlreadyExists, \
- ServerError
-from coprs.rest_api.resources.project import rest_api_auth_required
-from coprs.rest_api.schemas import CoprChrootSchema, CoprChrootCreateSchema
-from coprs.logic.coprs_logic import MockChrootsLogic, CoprChrootsLogic, CoprsLogic
+from ... import db
+from ...logic.coprs_logic import MockChrootsLogic, CoprChrootsLogic, CoprsLogic
+from ...exceptions import InsufficientRightsException, MalformedArgumentException
+from ..exceptions import AccessForbidden, MalformedRequest, \
+ ObjectAlreadyExists, ServerError
+from ..common import rest_api_auth_required, render_copr_chroot
+from ..schemas import CoprChrootSchema, CoprChrootCreateSchema
from ..util import get_one_safe, mm_deserialize
-from ... import db
class ProjectChrootListR(Resource):
8 years, 8 months
[copr] master: [frontend][api] Updated and tested PUT method to /projects/<id> resource (29b9391)
by vgologuz@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit 29b9391ecbcca2b3e0646d995bacb1f2d771d090
Author: Valentin Gologuzov <vgologuz(a)redhat.com>
Date: Tue Sep 1 14:47:22 2015 +0200
[frontend][api] Updated and tested PUT method to /projects/<id> resource
>---------------------------------------------------------------
frontend/coprs_frontend/coprs/logic/coprs_logic.py | 33 +----
.../coprs/rest_api/resources/project.py | 61 ++++------
frontend/coprs_frontend/coprs/rest_api/schemas.py | 58 +++++++--
.../coprs/views/coprs_ns/coprs_general.py | 3 +-
.../tests/test_api/test_project_r.py | 128 ++++++++++++++++++--
5 files changed, 197 insertions(+), 86 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/logic/coprs_logic.py b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
index 0957462..91206cc 100644
--- a/frontend/coprs_frontend/coprs/logic/coprs_logic.py
+++ b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
@@ -4,6 +4,7 @@ from sqlalchemy import and_
from sqlalchemy.event import listen
from sqlalchemy.orm.attributes import NEVER_SET
from sqlalchemy.orm.exc import NoResultFound
+from sqlalchemy.orm.attributes import get_history
from coprs import db
from coprs import exceptions
@@ -164,37 +165,15 @@ class CoprsLogic(object):
db.session.add(copr)
@classmethod
- def update(cls, user, copr, check_for_duplicates=True):
- cls.raise_if_unfinished_blocking_action(
- copr, "Can't change this project name,"
- " another operation is in progress: {action}")
+ def update(cls, user, copr):
+ # we should call get_history before other requests, otherwise
+ # the changes would be forgotten
+ if get_history(copr, "name").has_changes():
+ raise MalformedArgumentException("Change name of the project is forbidden")
users_logic.UsersLogic.raise_if_cant_update_copr(
user, copr, "Only owners and admins may update their projects.")
- existing = cls.exists_for_user(copr.owner, copr.name).first()
- if existing:
- if check_for_duplicates and existing.id != copr.id:
- raise exceptions.DuplicateException(
- "Project: '{0}' already exists".format(copr.name))
-
- else: # we're renaming
- # if we fire a models.Copr.query, it will use the modified copr in session
- # -> workaround this by just getting the name
- old_copr_name = (db.session.query(models.Copr.name)
- .filter(models.Copr.id == copr.id)
- .filter(models.Copr.deleted == False)
- .first()[0])
-
- action = models.Action(action_type=helpers.ActionTypeEnum("rename"),
- object_type="copr",
- object_id=copr.id,
- old_value="{0}/{1}".format(copr.owner.name,
- old_copr_name),
- new_value="{0}/{1}".format(copr.owner.name,
- copr.name),
- created_on=int(time.time()))
- db.session.add(action)
db.session.add(copr)
@classmethod
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/project.py b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
index 6c2cbd7..17dc94b 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/project.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
@@ -19,12 +19,13 @@ from coprs.logic.helpers import slice_query
from coprs.logic.users_logic import UsersLogic
from coprs.logic.coprs_logic import CoprsLogic
from coprs.exceptions import ActionInProgressException, InsufficientRightsException
-from coprs.rest_api.schemas import ProjectSchema
+from coprs.rest_api.schemas import ProjectSchema, ProjectCreateSchema
from ..exceptions import ObjectAlreadyExists, AuthFailed, CannotProcessRequest, AccessForbidden
from ..util import get_one_safe, json_loads_safe, mm_deserialize, render_allowed_method, mm_serialize_one
def rest_api_auth_required(f):
+ # todo: move to common.py and test this
@functools.wraps(f)
def decorated_function(*args, **kwargs):
token = None
@@ -69,10 +70,6 @@ def render_project(copr, self_params=None):
"builds": {"href": url_for(".buildlistr", project_id=copr.id)},
"chroots": {"href": url_for(".projectchrootlistr", project_id=copr.id)}
},
- # "allowed_methods": [
- # render_allowed_method("GET", "Get single copr", require_auth=False),
- # render_allowed_method("DELETE", "Delete current copr", require_auth=True),
- # ]
}
@@ -85,30 +82,19 @@ class ProjectListR(Resource):
"""
owner = flask.g.user
- result = mm_deserialize(ProjectSchema(), flask.request.data)
+ result = mm_deserialize(ProjectCreateSchema(), flask.request.data)
# todo check that chroots are available
req = result.data
-
- extra = {
- k: req[k]
- for k in [
- "description",
- "instructions",
- "contact",
- "homepage",
- "auto_createrepo",
- "build_enable_net",
- "repos"
- ] if k in req
- }
+ name = req.pop("name")
+ selected_chroots = req.pop("chroots")
try:
project = CoprsLogic.add(
user=owner, check_for_duplicates=True,
- name=req["name"],
- selected_chroots=req["chroots"],
- **extra
+ name=name,
+ selected_chroots=selected_chroots,
+ **req
)
db.session.commit()
except DuplicateException as error:
@@ -154,19 +140,6 @@ class ProjectListR(Resource):
"coprs": [render_project(copr) for copr in coprs_list],
}
- # TODO: show only if user provided ?help=true param
- #
- # if req_args.get("help"):
- # result_dict["allowed_methods"] = [
- # render_allowed_method("GET", "Get list of coprs", require_auth=False,
- # params=[
- # "username: filter coprs owned by the user",
- # "limit: show only the given number of coprs",
- # "offset: skip given number of coprs",
- # ]),
- # render_allowed_method("POST", "Creates new copr, send dict with copr fields"),
- # ]
-
return result_dict
@@ -220,5 +193,21 @@ class ProjectR(Resource):
"""
Modifies project by replacement of provided fields
"""
- pass
+ project = get_one_safe(CoprsLogic.get_by_id(int(project_id)))
+
+ project_dict = mm_deserialize(ProjectSchema(), flask.request.data).data
+ # pprint(project_dict)
+
+ for k, v in project_dict.items():
+ setattr(project, k, v)
+
+ try:
+ CoprsLogic.update(flask.g.user, project)
+ db.session.commit()
+ except InsufficientRightsException as err:
+ raise AccessForbidden(str(err))
+
+ resp = make_response("", 201)
+ resp.headers["Location"] = url_for(".projectr", project_id=project.id)
+ return resp
diff --git a/frontend/coprs_frontend/coprs/rest_api/schemas.py b/frontend/coprs_frontend/coprs/rest_api/schemas.py
index 7325e3e..216a880 100644
--- a/frontend/coprs_frontend/coprs/rest_api/schemas.py
+++ b/frontend/coprs_frontend/coprs/rest_api/schemas.py
@@ -2,6 +2,30 @@
from collections import Iterable
from marshmallow import Schema, fields
+from marshmallow import Schema, fields, validates_schema, ValidationError, validate
+
+
+def validate_any(fn_list):
+ """
+ :param fn_list: list of callable functions, each takes one param
+ :return: None if at least one validation function exists without exceptions
+ :raises ValidationError: otherwise
+ """
+ def func(value):
+ # import ipdb; ipdb.set_trace()
+ errors = []
+ for fn in fn_list:
+ try:
+ fn(value)
+ except ValidationError as err:
+ errors.append(str(err))
+ else:
+ return
+ else:
+ errors.insert(0, u"At least one validator should accept given value:")
+ raise ValidationError(errors)
+
+ return func
class SpaceSeparatedList(fields.Field):
@@ -11,8 +35,11 @@ class SpaceSeparatedList(fields.Field):
return value.split()
def _deserialize(self, value):
- if value is None or not isinstance(value, Iterable):
+ if value is None:
return ""
+ elif not isinstance(value, Iterable) or isinstance(value, basestring):
+ raise ValidationError("Value `{}` is not a list of strings"
+ .format(value))
else:
return " ".join(value)
@@ -31,23 +58,28 @@ class MockChrootSchema(Schema):
class ProjectSchema(Schema):
- id = fields.Int()
- name = fields.Str(required=True)
+ id = fields.Int(dump_only=True)
+ name = fields.Str(dump_only=True)
owner = fields.Str(attribute="owner_name", dump_only=True)
- description = fields.Str()
- instructions = fields.Str()
- homepage = fields.Str()
- contact = fields.Str()
+ description = fields.Str(allow_none=True)
+ instructions = fields.Str(allow_none=True)
+ homepage = fields.Url(allow_none=True)
+ contact = fields.Str(validate=validate_any([
+ validate.URL(),
+ validate.Email(),
+ validate.OneOf(["", None]),
+ ]), allow_none=True)
- auto_createrepo = fields.Bool()
- build_enable_net = fields.Bool()
- last_modified = fields.DateTime()
+ disable_createrepo = fields.Bool(allow_none=True)
+ build_enable_net = fields.Bool(allow_none=True)
+ last_modified = fields.DateTime(dump_only=True)
- #additional_repos = fields.List(fields.Str(), attribute="repos_list")
- repos = SpaceSeparatedList()
+ repos = SpaceSeparatedList(allow_none=True)
- # used only for creation
+
+class ProjectCreateSchema(ProjectSchema):
+ name = fields.Str(required=True)
chroots = fields.List(fields.Str(), load_only=True)
diff --git a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
index 5a85b87..6ff6221 100644
--- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
+++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
@@ -347,8 +347,7 @@ def copr_update(username, coprname):
try:
# form validation checks for duplicates
- coprs_logic.CoprsLogic.update(
- flask.g.user, copr, check_for_duplicates=False)
+ coprs_logic.CoprsLogic.update(flask.g.user, copr)
except (exceptions.ActionInProgressException,
exceptions.InsufficientRightsException) as e:
diff --git a/frontend/coprs_frontend/tests/test_api/test_project_r.py b/frontend/coprs_frontend/tests/test_api/test_project_r.py
index 6fae757..c6bb38d 100644
--- a/frontend/coprs_frontend/tests/test_api/test_project_r.py
+++ b/frontend/coprs_frontend/tests/test_api/test_project_r.py
@@ -1,6 +1,7 @@
# coding: utf-8
import base64
+import copy
import json
from marshmallow import pprint
@@ -15,6 +16,19 @@ from tests.coprs_test_case import CoprsTestCase, TransactionDecorator
class TestProjectResource(CoprsTestCase):
+ put_update_dict = {
+ "description": "foo bar",
+ "instructions": "cthulhu fhtagn",
+ "repos": [
+ "http://example.com/repo",
+ "copr://foo/bar"
+ "copr://g/foo/bar"
+ ],
+ "disable_createrepo": True,
+ "build_enable_net": False,
+ "homepage": "http://example.com/foobar",
+ "contact": "foo(a)example.com",
+ }
def test_self(self):
href = "/api_2/projects"
@@ -77,8 +91,9 @@ class TestProjectResource(CoprsTestCase):
project = CoprsLogic.get_by_id(p_id).one()
assert len(obj["project_chroots"]) == len(project.copr_chroots)
- def test_get_one_with_builds(self, f_users, f_mock_chroots,
- f_coprs, f_builds, f_db):
+ def test_get_one_with_builds(
+ self, f_users, f_mock_chroots,
+ f_coprs, f_builds, f_db):
p_id_list = [p.id for p in self.basic_coprs_list]
for p_id in p_id_list:
@@ -93,8 +108,9 @@ class TestProjectResource(CoprsTestCase):
builds = BuildsLogic.get_multiple_by_copr(project).all()
assert len(obj["project_builds"]) == len(builds)
- def test_delete_not_found(self, f_users, f_mock_chroots,
- f_users_api, f_db):
+ def test_delete_not_found(
+ self, f_users, f_mock_chroots,
+ f_users_api, f_db):
href = "/api_2/projects/{}".format("1")
@@ -104,8 +120,9 @@ class TestProjectResource(CoprsTestCase):
)
assert r0.status_code == 404
- def test_delete_ok(self, f_users, f_mock_chroots,
- f_coprs, f_users_api, f_db):
+ def test_delete_ok(
+ self, f_users, f_mock_chroots,
+ f_coprs, f_users_api, f_db):
href = "/api_2/projects/{}".format(self.c1.id)
@@ -116,8 +133,9 @@ class TestProjectResource(CoprsTestCase):
assert r0.status_code == 204
assert self.tc.get(href).status_code == 404
- def test_delete_fail_unfinished_build(self, f_users, f_mock_chroots,
- f_coprs, f_builds, f_users_api, f_db):
+ def test_delete_fail_unfinished_build(
+ self, f_users, f_mock_chroots,
+ f_coprs, f_builds, f_users_api, f_db):
href = "/api_2/projects/{}".format(self.c1.id)
@@ -156,3 +174,97 @@ class TestProjectResource(CoprsTestCase):
)
assert r0.status_code == 403
+ def test_project_put_ok(
+ self, f_users, f_mock_chroots,
+ f_coprs, f_users_api, f_db):
+
+ href = "/api_2/projects/{}".format(self.c1.id)
+ r0 = self.request_rest_api_with_auth(
+ href,
+ method="put",
+ content=self.put_update_dict
+ )
+ assert r0.status_code == 201
+
+ r1 = self.tc.get(r0.headers["Location"])
+ obj = json.loads(r1.data)
+ updated_project = obj["project"]
+ for k, v in self.put_update_dict.items():
+ assert updated_project[k] == v
+
+ def test_project_put_wrong_user(
+ self, f_users, f_mock_chroots,
+ f_coprs, f_users_api, f_db):
+ login = self.u2.api_login
+ token = self.u2.api_token
+
+ href = "/api_2/projects/{}".format(self.c1.id)
+ r0 = self.request_rest_api_with_auth(
+ href,
+ method="put",
+ content=self.put_update_dict,
+ login=login, token=token,
+ )
+ assert r0.status_code == 403
+
+ def test_project_put_not_found(
+ self, f_users, f_mock_chroots, f_users_api, f_db):
+
+ href = "/api_2/projects/1"
+ r0 = self.request_rest_api_with_auth(
+ href,
+ method="put",
+ content=self.put_update_dict,
+ )
+ assert r0.status_code == 404
+
+ def test_project_put_bad_values(
+ self, f_users, f_mock_chroots,
+ f_coprs, f_users_api, f_db):
+ href = "/api_2/projects/{}".format(self.c1.id)
+ cases = []
+
+ def add_case(field, value):
+ t = copy.deepcopy(self.put_update_dict)
+ t[field] = value
+ cases.append(t)
+
+ add_case("repos", "foobar")
+ add_case("repos", 1)
+ add_case("contact", "adsg")
+ add_case("contact", "1")
+ add_case("homepage", "sdg")
+
+ for test_case in cases:
+ r0 = self.request_rest_api_with_auth(
+ href,
+ method="put",
+ content=test_case
+ )
+ assert r0.status_code == 400
+
+ def test_project_put_ok_values(
+ self, f_users, f_mock_chroots,
+ f_coprs, f_users_api, f_db):
+ href = "/api_2/projects/{}".format(self.c1.id)
+ cases = []
+
+ def add_case(field, value):
+ t = copy.deepcopy(self.put_update_dict)
+ t[field] = value
+ cases.append(t)
+
+ add_case("repos", [])
+ add_case("repos", None)
+ add_case("contact", "")
+ add_case("contact", None)
+ add_case("contact", "foo(a)bar.com")
+ add_case("homepage", "http://foo.org/bar/xdeeg?sdfg")
+
+ for test_case in cases:
+ r0 = self.request_rest_api_with_auth(
+ href,
+ method="put",
+ content=test_case
+ )
+ assert r0.status_code == 201
8 years, 8 months
[copr] master: [frontend][api] Updated and tested DELETE method to /projects/<id> resource (0a7e250)
by vgologuz@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit 0a7e250616ca8824adfb0b2cf14a0fe8c3d78e78
Author: Valentin Gologuzov <vgologuz(a)redhat.com>
Date: Tue Sep 1 11:40:14 2015 +0200
[frontend][api] Updated and tested DELETE method to /projects/<id> resource
>---------------------------------------------------------------
.../coprs/rest_api/resources/project.py | 17 ++---
frontend/coprs_frontend/coprs/rest_api/schemas.py | 1 -
.../tests/test_api/test_project_r.py | 64 ++++++++++++++++++++
3 files changed, 71 insertions(+), 11 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/project.py b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
index da72007..6c2cbd7 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/project.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
@@ -20,7 +20,7 @@ from coprs.logic.users_logic import UsersLogic
from coprs.logic.coprs_logic import CoprsLogic
from coprs.exceptions import ActionInProgressException, InsufficientRightsException
from coprs.rest_api.schemas import ProjectSchema
-from ..exceptions import ObjectAlreadyExists, AuthFailed
+from ..exceptions import ObjectAlreadyExists, AuthFailed, CannotProcessRequest, AccessForbidden
from ..util import get_one_safe, json_loads_safe, mm_deserialize, render_allowed_method, mm_serialize_one
@@ -174,18 +174,15 @@ class ProjectR(Resource):
@rest_api_auth_required
def delete(self, project_id):
- copr = get_one_safe(CoprsLogic.get_by_id(int(project_id)))
-
- raise NotImplementedError()
+ project = get_one_safe(CoprsLogic.get_by_id(int(project_id)))
try:
- ComplexLogic.delete_copr(copr)
- except (ActionInProgressException,
- InsufficientRightsException) as err:
- db.session.rollback()
- raise
- else:
+ ComplexLogic.delete_copr(project)
db.session.commit()
+ except ActionInProgressException as err:
+ raise CannotProcessRequest(str(err))
+ except InsufficientRightsException as err:
+ raise AccessForbidden(str(err))
return None, 204
diff --git a/frontend/coprs_frontend/coprs/rest_api/schemas.py b/frontend/coprs_frontend/coprs/rest_api/schemas.py
index 26f4452..7325e3e 100644
--- a/frontend/coprs_frontend/coprs/rest_api/schemas.py
+++ b/frontend/coprs_frontend/coprs/rest_api/schemas.py
@@ -79,7 +79,6 @@ class BuildSchema(Schema):
id = fields.Int(dump_only=True)
state = fields.Str()
- pkgs = fields.Str(dump_only=True)
build_packages = fields.Str(dump_only=True)
pkg_version = fields.Str(dump_only=True)
diff --git a/frontend/coprs_frontend/tests/test_api/test_project_r.py b/frontend/coprs_frontend/tests/test_api/test_project_r.py
index 439820f..6fae757 100644
--- a/frontend/coprs_frontend/tests/test_api/test_project_r.py
+++ b/frontend/coprs_frontend/tests/test_api/test_project_r.py
@@ -92,3 +92,67 @@ class TestProjectResource(CoprsTestCase):
project = CoprsLogic.get_by_id(p_id).one()
builds = BuildsLogic.get_multiple_by_copr(project).all()
assert len(obj["project_builds"]) == len(builds)
+
+ def test_delete_not_found(self, f_users, f_mock_chroots,
+ f_users_api, f_db):
+
+ href = "/api_2/projects/{}".format("1")
+
+ r0 = self.request_rest_api_with_auth(
+ href,
+ method="delete"
+ )
+ assert r0.status_code == 404
+
+ def test_delete_ok(self, f_users, f_mock_chroots,
+ f_coprs, f_users_api, f_db):
+
+ href = "/api_2/projects/{}".format(self.c1.id)
+
+ r0 = self.request_rest_api_with_auth(
+ href,
+ method="delete"
+ )
+ assert r0.status_code == 204
+ assert self.tc.get(href).status_code == 404
+
+ def test_delete_fail_unfinished_build(self, f_users, f_mock_chroots,
+ f_coprs, f_builds, f_users_api, f_db):
+
+ href = "/api_2/projects/{}".format(self.c1.id)
+
+ r0 = self.request_rest_api_with_auth(
+ href,
+ method="delete"
+ )
+ assert r0.status_code == 400
+
+ def test_delete_fail_unfinished_project_action(
+ self, f_users, f_mock_chroots,
+ f_coprs, f_users_api, f_db):
+
+ CoprsLogic.create_delete_action(self.c1)
+ self.db.session.commit()
+ href = "/api_2/projects/{}".format(self.c1.id)
+ r0 = self.request_rest_api_with_auth(
+ href,
+ method="delete"
+ )
+ assert r0.status_code == 400
+
+ def test_delete_wrong_user(
+ self, f_users, f_mock_chroots,
+ f_coprs, f_users_api, f_db):
+
+ login = self.u2.api_login
+ token = self.u2.api_token
+
+ href = "/api_2/projects/{}".format(self.c1.id)
+
+ r0 = self.request_rest_api_with_auth(
+ href,
+ method="delete",
+ login=login, token=token,
+ )
+ assert r0.status_code == 403
+
8 years, 8 months
[copr] master: [frontend][api] Updated and tested GET method to /projects/<id> resource (2dcde99)
by vgologuz@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit 2dcde997662a05bbfb8ed5e14a5e4a9a52d2f044
Author: Valentin Gologuzov <vgologuz(a)redhat.com>
Date: Mon Aug 31 17:51:51 2015 +0200
[frontend][api] Updated and tested GET method to /projects/<id> resource
>---------------------------------------------------------------
.../coprs/rest_api/resources/build.py | 16 +----
.../coprs/rest_api/resources/common.py | 27 +++++++++
.../coprs/rest_api/resources/project.py | 54 +++++++++++++----
.../coprs/rest_api/resources/project_chroot.py | 26 ++------
.../tests/test_api/test_project_r.py | 60 ++++++++++++++++++--
5 files changed, 133 insertions(+), 50 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/build.py b/frontend/coprs_frontend/coprs/rest_api/resources/build.py
index a12a96e..14f228a 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/build.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/build.py
@@ -5,12 +5,13 @@ from flask import url_for, make_response
# from flask_restful_swagger import swagger
-from coprs import db, models
+from coprs import db
from coprs.exceptions import ActionInProgressException, InsufficientRightsException, RequestCannotBeExecuted
from coprs.logic.coprs_logic import CoprsLogic
from coprs.logic.builds_logic import BuildsLogic
from coprs.logic.users_logic import UsersLogic
from coprs.rest_api.exceptions import MalformedRequest, CannotProcessRequest, AccessForbidden
+from coprs.rest_api.resources.common import render_build
from coprs.rest_api.resources.project import rest_api_auth_required
from coprs.rest_api.schemas import BuildSchema, BuildCreateSchema, BuildCreateFromUrlSchema
@@ -20,17 +21,6 @@ from coprs.rest_api.util import get_one_safe, mm_deserialize
from flask_restful import Resource, reqparse
-def render_build(build):
- return {
- "build": BuildSchema().dump(build)[0],
- "_links": {
- "self": {"href": url_for(".buildr", build_id=build.id)},
- "project": {"href": url_for(".projectr", project_id=build.copr_id)},
- "chroots": {"href": url_for(".buildchrootlistr", build_id=build.id)}
- }
- }
-
-
class BuildListR(Resource):
def get(self):
@@ -43,6 +33,8 @@ class BuildListR(Resource):
parser.add_argument('limit', type=int)
parser.add_argument('offset', type=int)
+ # parser.add_argument('package', type=str)
+
req_args = parser.parse_args()
if req_args["project_id"] is not None:
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/common.py b/frontend/coprs_frontend/coprs/rest_api/resources/common.py
new file mode 100644
index 0000000..85ccfd4
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/common.py
@@ -0,0 +1,27 @@
+# coding: utf-8
+from flask import url_for
+from coprs.rest_api.schemas import CoprChrootSchema, BuildSchema
+from coprs.rest_api.util import mm_serialize_one
+
+
+def render_copr_chroot(chroot):
+ return {
+ "chroot": mm_serialize_one(CoprChrootSchema, chroot),
+ "_links": {
+ "project": {"href": url_for(".projectr", project_id=chroot.copr.id)},
+ "self": {"href": url_for(".projectchrootr",
+ project_id=chroot.copr.id,
+ name=chroot.name)},
+ }
+ }
+
+
+def render_build(build):
+ return {
+ "build": BuildSchema().dump(build)[0],
+ "_links": {
+ "self": {"href": url_for(".buildr", build_id=build.id)},
+ "project": {"href": url_for(".projectr", project_id=build.copr_id)},
+ "chroots": {"href": url_for(".buildchrootlistr", build_id=build.id)}
+ }
+ }
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/project.py b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
index 92a2d3c..da72007 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/project.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
@@ -2,10 +2,13 @@ import base64
import datetime
import functools
from logging import getLogger
+from coprs.logic.builds_logic import BuildsLogic
+from coprs.rest_api.resources.common import render_copr_chroot, render_build
+
log = getLogger(__name__)
import flask
-from flask import url_for
+from flask import url_for, make_response
from flask_restful import Resource, reqparse
from marshmallow import pprint
@@ -55,11 +58,14 @@ def rest_api_auth_required(f):
return decorated_function
-def render_project(copr):
+def render_project(copr, self_params=None):
+ if self_params is None:
+ self_params = {}
+
return {
- "copr": mm_serialize_one(ProjectSchema, copr),
+ "project": mm_serialize_one(ProjectSchema, copr),
"_links": {
- "self": {"href": url_for(".projectr", project_id=copr.id)},
+ "self": {"href": url_for(".projectr", project_id=copr.id, **self_params)},
"builds": {"href": url_for(".buildlistr", project_id=copr.id)},
"chroots": {"href": url_for(".projectchrootlistr", project_id=copr.id)}
},
@@ -98,7 +104,7 @@ class ProjectListR(Resource):
}
try:
- copr = CoprsLogic.add(
+ project = CoprsLogic.add(
user=owner, check_for_duplicates=True,
name=req["name"],
selected_chroots=req["chroots"],
@@ -108,7 +114,9 @@ class ProjectListR(Resource):
except DuplicateException as error:
raise ObjectAlreadyExists(data=error)
- return render_project(copr), 201
+ resp = make_response("", 201)
+ resp.headers["Location"] = url_for(".projectr", project_id=project.id)
+ return resp
def get(self):
parser = reqparse.RequestParser()
@@ -182,18 +190,38 @@ class ProjectR(Resource):
return None, 204
def get(self, project_id):
- # parser = reqparse.RequestParser()
- # parser.add_argument('show_builds', type=bool, default=True)
- # parser.add_argument('show_chroots', type=bool, default=True)
- # req_args = parser.parse_args()
+ parser = reqparse.RequestParser()
+ parser.add_argument('show_builds', type=bool, default=False)
+ parser.add_argument('show_chroots', type=bool, default=False)
+ req_args = parser.parse_args()
- copr = get_one_safe(CoprsLogic.get_by_id(int(project_id)))
- return render_project(copr)
+ project = get_one_safe(CoprsLogic.get_by_id(int(project_id)))
+ self_params = {}
+ if req_args["show_builds"]:
+ self_params["show_builds"] = req_args["show_builds"]
+ if req_args["show_chroots"]:
+ self_params["show_chroots"] = req_args["show_chroots"]
+
+ answer = render_project(project, self_params)
+
+ if req_args["show_builds"]:
+ answer["project_builds"] = [
+ render_build(build)
+ for build in BuildsLogic.get_multiple_by_copr(project).all()
+ ]
+
+ if req_args["show_chroots"]:
+ answer["project_chroots"] = [
+ render_copr_chroot(chroot)
+ for chroot in project.copr_chroots
+ ]
+
+ return answer
@rest_api_auth_required
def put(self, project_id):
"""
- Modifies project by replacment of provided fields
+ Modifies project by replacement of provided fields
"""
pass
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py b/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py
index 6e03ee1..29f6114 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py
@@ -1,40 +1,28 @@
# coding: utf-8
-import json
import logging
+
+from coprs.rest_api.resources.common import render_copr_chroot
+
log = logging.getLogger(__name__)
import flask
from flask import url_for, make_response
-from flask_restful import Resource, reqparse
+from flask_restful import Resource
-from marshmallow import Schema, fields, pprint, ValidationError
from sqlalchemy.exc import IntegrityError
from coprs.exceptions import InsufficientRightsException, MalformedArgumentException
-from coprs.rest_api.exceptions import AccessForbidden, ObjectNotFoundError, MalformedRequest, ObjectAlreadyExists, \
+from coprs.rest_api.exceptions import AccessForbidden, MalformedRequest, ObjectAlreadyExists, \
ServerError
from coprs.rest_api.resources.project import rest_api_auth_required
-from coprs.rest_api.schemas import MockChrootSchema, CoprChrootSchema, CoprChrootCreateSchema
+from coprs.rest_api.schemas import CoprChrootSchema, CoprChrootCreateSchema
-from coprs.views.misc import api_login_required
from coprs.logic.coprs_logic import MockChrootsLogic, CoprChrootsLogic, CoprsLogic
-from ..util import get_one_safe, json_loads_safe, mm_deserialize, mm_serialize_one
+from ..util import get_one_safe, mm_deserialize
from ... import db
-def render_copr_chroot(chroot):
- return {
- "chroot": mm_serialize_one(CoprChrootSchema, chroot),
- "_links": {
- "project": {"href": url_for(".projectr", project_id=chroot.copr.id)},
- "self": {"href": url_for(".projectchrootr",
- project_id=chroot.copr.id,
- name=chroot.name)},
- }
- }
-
-
class ProjectChrootListR(Resource):
def get(self, project_id):
diff --git a/frontend/coprs_frontend/tests/test_api/test_project_r.py b/frontend/coprs_frontend/tests/test_api/test_project_r.py
index 902b551..439820f 100644
--- a/frontend/coprs_frontend/tests/test_api/test_project_r.py
+++ b/frontend/coprs_frontend/tests/test_api/test_project_r.py
@@ -2,9 +2,11 @@
import base64
import json
+from marshmallow import pprint
import pytest
import sqlalchemy
+from coprs.logic.builds_logic import BuildsLogic
from coprs.logic.users_logic import UsersLogic
from coprs.logic.coprs_logic import CoprsLogic
@@ -21,9 +23,9 @@ class TestProjectResource(CoprsTestCase):
obj = json.loads(r.data)
assert obj["_links"]["self"]["href"] == href
- @TransactionDecorator("u1")
- def test_create_new(self, f_users, f_mock_chroots, f_db, f_users_api):
+ def test_create_new(self, f_users, f_mock_chroots, f_users_api):
self.db.session.add_all([self.u1, self.mc1])
+ self.db.session.commit()
chroot_name = self.mc1.name
body = {
@@ -34,13 +36,59 @@ class TestProjectResource(CoprsTestCase):
"additional_repos": ["copr://bar/zar", ]
}
- r = self.request_rest_api_with_auth("/api_2/projects", content=body, method="post")
+ r = self.request_rest_api_with_auth(
+ "/api_2/projects",
+ content=body, method="post")
assert r.status_code == 201
- # todo: test header Location
- copr_dict = json.loads(r.data)
- assert copr_dict["copr"]["id"] == 1
+ assert r.headers["Location"].endswith("/api_2/projects/1")
+
r2 = self.tc.get("/api_2/projects/1/chroots")
copr_chroots_dict = json.loads(r2.data)
assert len(copr_chroots_dict["chroots"]) == 1
assert copr_chroots_dict["chroots"][0]["chroot"]["name"] == chroot_name
+ def test_get_one_not_found(self, f_users, f_mock_chroots, f_db):
+ r = self.tc.get("/api_2/projects/1")
+ assert r.status_code == 404
+
+ def test_get_one(self, f_users, f_mock_chroots, f_coprs, f_db):
+
+ p_id_list = [p.id for p in self.basic_coprs_list]
+ for p_id in p_id_list:
+ href = "/api_2/projects/{}".format(p_id)
+ r = self.tc.get(href)
+ assert r.status_code == 200
+ obj = json.loads(r.data)
+
+ assert obj["project"]["id"] == p_id
+ assert obj["_links"]["self"]["href"] == href
+
+ def test_get_one_with_chroots(self, f_users, f_mock_chroots, f_coprs, f_db):
+
+ p_id_list = [p.id for p in self.basic_coprs_list]
+ for p_id in p_id_list:
+ href = "/api_2/projects/{}?show_chroots=True".format(p_id)
+ r = self.tc.get(href)
+ assert r.status_code == 200
+ obj = json.loads(r.data)
+
+ assert obj["project"]["id"] == p_id
+ assert obj["_links"]["self"]["href"] == href
+ project = CoprsLogic.get_by_id(p_id).one()
+ assert len(obj["project_chroots"]) == len(project.copr_chroots)
+
+ def test_get_one_with_builds(self, f_users, f_mock_chroots,
+ f_coprs, f_builds, f_db):
+
+ p_id_list = [p.id for p in self.basic_coprs_list]
+ for p_id in p_id_list:
+ href = "/api_2/projects/{}?show_builds=True".format(p_id)
+ r = self.tc.get(href)
+ assert r.status_code == 200
+ obj = json.loads(r.data)
+
+ assert obj["project"]["id"] == p_id
+ assert obj["_links"]["self"]["href"] == href
+ project = CoprsLogic.get_by_id(p_id).one()
+ builds = BuildsLogic.get_multiple_by_copr(project).all()
+ assert len(obj["project_builds"]) == len(builds)
8 years, 8 months