[copr] master: [frontend] update notification texts (b54488a)
by asamalik@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit b54488adf6ce4af3e4e00498567b46e466e49cfa
Author: Adam Samalik <asamalik(a)redhat.com>
Date: Tue Aug 25 17:04:00 2015 +0200
[frontend] update notification texts
>---------------------------------------------------------------
.../coprs/views/coprs_ns/coprs_builds.py | 8 ++++----
.../coprs/views/coprs_ns/coprs_chroots.py | 2 +-
.../coprs/views/coprs_ns/coprs_general.py | 8 ++++----
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py
index 158adba..457753b 100644
--- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py
+++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py
@@ -174,7 +174,7 @@ def copr_new_build_upload(username, coprname):
db.session.rollback()
shutil.rmtree(tmp)
else:
- flask.flash("New build was created")
+ flask.flash("New build has been created.")
db.session.commit()
return flask.redirect(flask.url_for("coprs_ns.copr_builds",
@@ -231,7 +231,7 @@ def copr_new_build(username, coprname):
db.session.rollback()
else:
for pkg in pkgs:
- flask.flash("New build was created: {}".format(pkg))
+ flask.flash("New build has been created: {}".format(pkg))
db.session.commit()
@@ -260,7 +260,7 @@ def copr_cancel_build(username, coprname, build_id, page=1):
flask.flash(str(e), "error")
else:
db.session.commit()
- flask.flash("Build was canceled")
+ flask.flash("Build {} has been canceled successfully.".format(build_id), "success")
return flask.redirect(flask.url_for("coprs_ns.copr_builds",
username=username,
@@ -335,7 +335,7 @@ def copr_delete_build(username, coprname, build_id, page=1):
flask.flash(str(e), "error")
else:
db.session.commit()
- flask.flash("Build was deleted")
+ flask.flash("Build has been deleted successfully.", "success")
return flask.redirect(flask.url_for("coprs_ns.copr_builds",
username=username, coprname=coprname,
diff --git a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_chroots.py b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_chroots.py
index 73d0dab..11c08be 100644
--- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_chroots.py
+++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_chroots.py
@@ -76,7 +76,7 @@ def chroot_update(username, coprname, chrootname):
CoprChrootsLogic.remove_comps(chroot)
flask.flash(
- "Buildroot {0} for project {1} was updated".format(
+ "Buildroot {0} in project {1} has been updated successfully.".format(
chrootname, coprname))
db.session.commit()
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 e1018db..d27a3e2 100644
--- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
+++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
@@ -155,7 +155,7 @@ def copr_new(username):
)
db.session.commit()
- flask.flash("New project was successfully created.", "success")
+ flask.flash("New project has ben created successfully.", "success")
if form.initial_pkgs.data:
pkgs = form.initial_pkgs.data.replace("\n", " ").split(" ")
@@ -354,7 +354,7 @@ def copr_update(username, coprname):
flask.flash(str(e), "error")
db.session.rollback()
else:
- flask.flash("Project was updated successfully.", "success")
+ flask.flash("Project has been updated successfully.", "success")
db.session.commit()
return flask.redirect(flask.url_for("coprs_ns.copr_detail",
@@ -522,7 +522,7 @@ def copr_delete(username, coprname):
coprname=coprname))
else:
db.session.commit()
- flask.flash("Project was deleted successfully.")
+ flask.flash("Project has been deleted successfully.")
return flask.redirect(flask.url_for("coprs_ns.coprs_by_owner",
username=username))
else:
@@ -572,7 +572,7 @@ def copr_legal_flag(username, coprname):
s.sendmail("root@{0}".format(hostname), send_to, msg.as_string())
s.quit()
- flask.flash("Admin was noticed about your report"
+ flask.flash("Admin has been noticed about your report"
" and will investigate the project shortly.")
return flask.redirect(flask.url_for("coprs_ns.copr_detail",
8 years, 8 months
[copr] master: [frontend] footer (5157e9e)
by asamalik@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit 5157e9ee1608542efd64834bbc33c84399f48b46
Author: Adam Samalik <asamalik(a)redhat.com>
Date: Tue Aug 25 16:51:12 2015 +0200
[frontend] footer
>---------------------------------------------------------------
frontend/coprs_frontend/coprs/static/copr.css | 15 ++++
.../coprs_frontend/coprs/templates/layout.html | 83 ++++++++++++++------
2 files changed, 73 insertions(+), 25 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/static/copr.css b/frontend/coprs_frontend/coprs/static/copr.css
index 8c09bcf..138e53f 100644
--- a/frontend/coprs_frontend/coprs/static/copr.css
+++ b/frontend/coprs_frontend/coprs/static/copr.css
@@ -2,6 +2,14 @@
.navbar-sm .navbar-brand{ padding: 0px 12px;font-size: 16px;line-height: 40px; }
.navbar-sm .navbar-nav > li > a { padding-top: 0px; padding-bottom: 0px; line-height: 40px; }
+body {
+ background-color: #eee;
+}
+
+#content {
+ background-color: white;
+}
+
h1.project-name {
margin-bottom: 20px;
}
@@ -10,6 +18,13 @@ h1.project-name {
margin-top: 20px;
}
+footer > div{
+ background-color: #eee;
+ border-top: 1px solid #ccc;
+ margin-top: 20px;
+ padding-top: 20px;
+}
+
#main-bar {
background-image: url('header_background.png');
background-repeat: repeat-x;
diff --git a/frontend/coprs_frontend/coprs/templates/layout.html b/frontend/coprs_frontend/coprs/templates/layout.html
index 55ef109..6dbb767 100644
--- a/frontend/coprs_frontend/coprs/templates/layout.html
+++ b/frontend/coprs_frontend/coprs/templates/layout.html
@@ -90,35 +90,68 @@
#}
<!-- CONTENT -->
- <div class="container">
+ <div id=content>
+ <div class="container">
- <!-- Flash messages -->
- {% with messages = get_flashed_messages(with_categories=true) %}
- {% for type, message in messages %}
- {{ alert(message, type) }}
- {% endfor %}
- {% endwith %}
+ <!-- Flash messages -->
+ {% with messages = get_flashed_messages(with_categories=true) %}
+ {% for type, message in messages %}
+ {{ alert(message, type) }}
+ {% endfor %}
+ {% endwith %}
- <!-- the actual content -->
- {% block body %}{% endblock %}
+ <!-- the actual content -->
+ {% block body %}{% endblock %}
- </div>
- <footer class="footer">
- <hr></hr>
- <div class="container">
- <div class="text-center">
- <ul class="list-inline">
- <li> <a href="{{ url_for('coprs_ns.coprs_show') }}">Home</a> </li>
- <li> <a href="https://fedorahosted.org/copr/">Copr Project</a> </li>
- <li> <a href="https://fedorahosted.org/copr/wiki/UserDocs#FAQ">FAQ</a> </li>
- <li> <a href="{{ url_for('api_ns.api_home') }}">API</a> </li>
- </ul>
- <small>
- Copr frontend build version: __RPM_BUILD_VERSION
- </small>
- </div>
</div>
- </footer>
+ <footer class="footer">
+ <div>
+ <div class="container text-muted">
+ <div class="text-center">
+ <div class="row">
+ <div class="col-sm-4">
+ <dl>
+ <dt> Copr Project </dt>
+ <dd>
+ <ul class="list-unstyled">
+ <li> <a href="https://fedorahosted.org/copr/">Project Homepage</a> </li>
+ <li> <a href="https://fedorahosted.org/copr/wiki/UserDocs#FAQ">FAQ</a> </li>
+ </ul>
+ </dd>
+ </dl>
+ </div>
+ <div class="col-sm-4">
+ <dl>
+ <dt> Site Navigation </dt>
+ <dd>
+ <ul class="list-unstyled">
+ <li> <a href="{{ url_for('coprs_ns.coprs_show') }}">Home</a> </li>
+ <li> <a href="{{url_for('status_ns.importing')}}">Task Queue</a> </li>
+ <li> <a href="{{ url_for('api_ns.api_home') }}">API</a> </li>
+ </ul>
+ </dd>
+ </dl>
+ </div>
+ <div class="col-sm-4">
+ <dl>
+ <dt> Powered by </dt>
+ <dd>
+ <ul class="list-unstyled">
+ <li> <a href="https://www.patternfly.org">PatternFly </a> </li>
+ <li> <a href="https://github.com/release-engineering/dist-git">Dist Git</a> </li>
+ </ul>
+ </dd>
+ </dl>
+ </div>
+ </div>
+ <small class="text-muted">
+ Copr frontend build version: __RPM_BUILD_VERSION
+ </small>
+ </div>
+ </div>
+ </div>
+ </footer>
+ </div>
<!-- FEDORA MENU -->
{% if fedmenu_url is defined %}
8 years, 8 months
[copr] master: [frontend] support alert clases (401d3de)
by asamalik@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit 401d3de46161bc4e4ccc5a1ef2145a8190c624ed
Author: Adam Samalik <asamalik(a)redhat.com>
Date: Tue Aug 25 16:11:21 2015 +0200
[frontend] support alert clases
>---------------------------------------------------------------
frontend/coprs_frontend/coprs/static/copr.css | 4 ++
.../coprs_frontend/coprs/templates/_helpers.html | 35 ++++++++++++++++++++
.../coprs_frontend/coprs/templates/layout.html | 15 +++-----
.../coprs/views/coprs_ns/coprs_builds.py | 8 ++--
.../coprs/views/coprs_ns/coprs_general.py | 16 ++++----
5 files changed, 57 insertions(+), 21 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/static/copr.css b/frontend/coprs_frontend/coprs/static/copr.css
index df7f8ec..8c09bcf 100644
--- a/frontend/coprs_frontend/coprs/static/copr.css
+++ b/frontend/coprs_frontend/coprs/static/copr.css
@@ -6,6 +6,10 @@ h1.project-name {
margin-bottom: 20px;
}
+.alert:first-child {
+ margin-top: 20px;
+}
+
#main-bar {
background-image: url('header_background.png');
background-repeat: repeat-x;
diff --git a/frontend/coprs_frontend/coprs/templates/_helpers.html b/frontend/coprs_frontend/coprs/templates/_helpers.html
index af02681..5eb057c 100644
--- a/frontend/coprs_frontend/coprs/templates/_helpers.html
+++ b/frontend/coprs_frontend/coprs/templates/_helpers.html
@@ -92,3 +92,38 @@
{{ chroot.os_release | capitalize }} {{ chroot.os_version }}
{% endif %}
{% endmacro %}
+
+
+{% macro alert(message, type=None) %}
+ {% if type == "error" %}
+ <div class="alert alert-danger alert-dismissable">
+ <button type="button" class="close" data-dismiss="alert" aria-hidden="true">
+ <span class="pficon pficon-close"></span>
+ </button>
+ <span class="pficon pficon-error-circle-o"></span>
+
+ {% elif type == "warning" %}
+ <div class="alert alert-warning alert-dismissable">
+ <button type="button" class="close" data-dismiss="alert" aria-hidden="true">
+ <span class="pficon pficon-close"></span>
+ </button>
+ <span class="pficon pficon-warning-triangle-o"></span>
+
+ {% elif type == "success" %}
+ <div class="alert alert-success alert-dismissable">
+ <button type="button" class="close" data-dismiss="alert" aria-hidden="true">
+ <span class="pficon pficon-close"></span>
+ </button>
+ <span class="pficon pficon-ok"></span>
+
+ {% else %}
+ <div class="alert alert-info alert-dismissable">
+ <button type="button" class="close" data-dismiss="alert" aria-hidden="true">
+ <span class="pficon pficon-close"></span>
+ </button>
+ <span class="pficon pficon-info"></span>
+ {% endif %}
+
+ {{ message }}
+ </div>
+{% endmacro %}
diff --git a/frontend/coprs_frontend/coprs/templates/layout.html b/frontend/coprs_frontend/coprs/templates/layout.html
index 753668e..55ef109 100644
--- a/frontend/coprs_frontend/coprs/templates/layout.html
+++ b/frontend/coprs_frontend/coprs/templates/layout.html
@@ -1,3 +1,4 @@
+{% from "_helpers.html" import alert %}
<!doctype html>
<html>
<head>
@@ -92,15 +93,11 @@
<div class="container">
<!-- Flash messages -->
- {% for message in get_flashed_messages() %}
- <div class="alert alert-info alert-dismissable">
- <button type="button" class="close" data-dismiss="alert" aria-hidden="true">
- <span class="pficon pficon-close"></span>
- </button>
- <span class="pficon pficon-info"></span>
- {{ message }}
- </div>
- {% endfor %}
+ {% with messages = get_flashed_messages(with_categories=true) %}
+ {% for type, message in messages %}
+ {{ alert(message, type) }}
+ {% endfor %}
+ {% endwith %}
<!-- the actual content -->
{% block body %}{% endblock %}
diff --git a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py
index be7cb8f..158adba 100644
--- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py
+++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py
@@ -170,7 +170,7 @@ def copr_new_build_upload(username, coprname):
build.timeout = form.timeout.data
except (ActionInProgressException, InsufficientRightsException) as e:
- flask.flash(str(e))
+ flask.flash(str(e), "error")
db.session.rollback()
shutil.rmtree(tmp)
else:
@@ -227,7 +227,7 @@ def copr_new_build(username, coprname):
build.timeout = form.timeout.data
except (ActionInProgressException, InsufficientRightsException) as e:
- flask.flash(str(e))
+ flask.flash(str(e), "error")
db.session.rollback()
else:
for pkg in pkgs:
@@ -257,7 +257,7 @@ def copr_cancel_build(username, coprname, build_id, page=1):
try:
builds_logic.BuildsLogic.cancel_build(flask.g.user, build)
except InsufficientRightsException as e:
- flask.flash(str(e))
+ flask.flash(str(e), "error")
else:
db.session.commit()
flask.flash("Build was canceled")
@@ -332,7 +332,7 @@ def copr_delete_build(username, coprname, build_id, page=1):
try:
builds_logic.BuildsLogic.delete_build(flask.g.user, build)
except (InsufficientRightsException, ActionInProgressException) as e:
- flask.flash(str(e))
+ flask.flash(str(e), "error")
else:
db.session.commit()
flask.flash("Build was deleted")
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 8a68e43..e1018db 100644
--- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
+++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
@@ -112,7 +112,7 @@ def coprs_fulltext_search(page=1):
try:
query = coprs_logic.CoprsLogic.get_multiple_fulltext(fulltext)
except ValueError as e:
- flask.flash(str(e))
+ flask.flash(str(e), "error")
return flask.redirect(flask.request.referrer or
flask.url_for("coprs_ns.coprs_show"))
@@ -155,7 +155,7 @@ def copr_new(username):
)
db.session.commit()
- flask.flash("New project was successfully created.")
+ flask.flash("New project was successfully created.", "success")
if form.initial_pkgs.data:
pkgs = form.initial_pkgs.data.replace("\n", " ").split(" ")
@@ -351,10 +351,10 @@ def copr_update(username, coprname):
except (exceptions.ActionInProgressException,
exceptions.InsufficientRightsException) as e:
- flask.flash(str(e))
+ flask.flash(str(e), "error")
db.session.rollback()
else:
- flask.flash("Project was updated successfully.")
+ flask.flash("Project was updated successfully.", "success")
db.session.commit()
return flask.redirect(flask.url_for("coprs_ns.copr_detail",
@@ -378,7 +378,7 @@ def copr_permissions_applier_change(username, coprname):
"Project with name {0} does not exist.".format(coprname))
if copr.owner == flask.g.user:
- flask.flash("Owner cannot request permissions for his own project.")
+ flask.flash("Owner cannot request permissions for his own project.", "error")
elif applier_permissions_form.validate_on_submit():
# we rely on these to be 0 or 1 from form. TODO: abstract from that
if permission is not None:
@@ -474,10 +474,10 @@ def copr_update_permissions(username, coprname):
# don't collide with any actions
except exceptions.InsufficientRightsException as e:
db.session.rollback()
- flask.flash(str(e))
+ flask.flash(str(e), "error")
else:
db.session.commit()
- flask.flash("Project permissions were updated successfully.")
+ flask.flash("Project permissions were updated successfully.", "success")
return flask.redirect(flask.url_for("coprs_ns.copr_detail",
username=copr.owner.name,
@@ -516,7 +516,7 @@ def copr_delete(username, coprname):
exceptions.InsufficientRightsException) as e:
db.session.rollback()
- flask.flash(str(e))
+ flask.flash(str(e), "error")
return flask.redirect(flask.url_for("coprs_ns.copr_detail",
username=username,
coprname=coprname))
8 years, 8 months
[copr] master: [frontend] alerts are dismissable (69ebc29)
by asamalik@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit 69ebc293f4a9b153f8d227d38c24c501585e4c60
Author: Adam Samalik <asamalik(a)redhat.com>
Date: Tue Aug 25 10:47:10 2015 +0200
[frontend] alerts are dismissable
>---------------------------------------------------------------
.../coprs_frontend/coprs/templates/layout.html | 5 ++++-
1 files changed, 4 insertions(+), 1 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/templates/layout.html b/frontend/coprs_frontend/coprs/templates/layout.html
index 79e2f58..753668e 100644
--- a/frontend/coprs_frontend/coprs/templates/layout.html
+++ b/frontend/coprs_frontend/coprs/templates/layout.html
@@ -93,7 +93,10 @@
<!-- Flash messages -->
{% for message in get_flashed_messages() %}
- <div class="alert alert-info">
+ <div class="alert alert-info alert-dismissable">
+ <button type="button" class="close" data-dismiss="alert" aria-hidden="true">
+ <span class="pficon pficon-close"></span>
+ </button>
<span class="pficon pficon-info"></span>
{{ message }}
</div>
8 years, 8 months
[copr] master: frontend][api2] Creating new project through API, added first test (1b17613)
by vgologuz@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit 1b17613e93e1f15d050b497e06f55aa9790bb9b1
Author: Valentin Gologuzov <vgologuz(a)redhat.com>
Date: Mon Aug 24 17:01:26 2015 +0200
frontend][api2] Creating new project through API, added first test
>---------------------------------------------------------------
frontend/coprs_frontend/coprs/logic/coprs_logic.py | 4 +-
.../coprs/rest_api/resources/project.py | 69 ++++++++++++-------
frontend/coprs_frontend/coprs/rest_api/schemas.py | 32 +--------
frontend/coprs_frontend/tests/coprs_test_case.py | 12 ++++
.../tests/test_api/test_project_r.py | 67 +++++++++++++++++++
5 files changed, 127 insertions(+), 57 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/logic/coprs_logic.py b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
index e5b7939..ec93934 100644
--- a/frontend/coprs_frontend/coprs/logic/coprs_logic.py
+++ b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
@@ -138,10 +138,10 @@ class CoprsLogic(object):
return query
@classmethod
- def add(cls, user, name, repos, selected_chroots, description=None,
+ def add(cls, user, name, selected_chroots, repos=None, description=None,
instructions=None, check_for_duplicates=False, **kwargs):
copr = models.Copr(name=name,
- repos=repos,
+ repos=repos or u"",
owner_id=user.id,
description=description or u"",
instructions=instructions or u"",
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/project.py b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
index 720f01f..88544d7 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/project.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
@@ -50,6 +50,21 @@ def rest_api_auth_required(f):
return decorated_function
+def render_project(copr):
+ return {
+ "copr": mm_serialize_one(ProjectSchema, copr),
+ "_links": {
+ "self": {"href": url_for(".projectr", project_id=copr.id)},
+ "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),
+ # ]
+ }
+
+
class ProjectListR(Resource):
@rest_api_auth_required
@@ -60,15 +75,38 @@ class ProjectListR(Resource):
owner = flask.g.user
result = mm_deserialize(ProjectSchema(), flask.request.data)
+ if result.errors:
+ return "Failed to parse request", 400
+
# todo check that chroots are available
- pprint(result.data)
+ req = result.data
+
+ extra = {
+ k: req[k]
+ for k in [
+ "description",
+ "instructions",
+ "contact",
+ "homepage",
+ "auto_createrepo",
+ "build_enable_net",
+ ] if k in req
+ }
+ if "repos_list" in req:
+ extra["repos"] = " ".join(req["repos_list"])
+
try:
- CoprsLogic.add(user=owner, check_for_duplicates=True, **result.data)
+ copr = CoprsLogic.add(
+ user=owner, check_for_duplicates=True,
+ name=req["name"],
+ selected_chroots=req["chroots"],
+ **extra
+ )
db.session.commit()
except DuplicateException as error:
raise ObjectAlreadyExists(data=error)
- return "New copr was created", 201
+ return render_project(copr), 201
def get(self):
parser = reqparse.RequestParser()
@@ -103,16 +141,7 @@ class ProjectListR(Resource):
"_links": {
"self": {"href": url_for(".projectlistr", **req_args)}
},
- "coprs": [
- {
- "copr": mm_serialize_one(ProjectSchema, copr),
- "_links": {
- "self": {"href": url_for(".projectr", project_id=copr.id)}
- },
- }
- for copr in coprs_list
- ],
-
+ "coprs": [render_project(copr) for copr in coprs_list],
}
# TODO: show only if user provided ?help=true param
@@ -157,19 +186,7 @@ class ProjectR(Resource):
# req_args = parser.parse_args()
copr = get_one_safe(CoprsLogic.get_by_id(int(project_id)))
-
- return {
- "copr": ProjectSchema().dump(copr)[0],
- "_links": {
- "self": {"href": url_for(".projectr", project_id=copr.id)},
- "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),
- # ]
- }
+ return render_project(copr)
@rest_api_auth_required
def put(self, project_id):
diff --git a/frontend/coprs_frontend/coprs/rest_api/schemas.py b/frontend/coprs_frontend/coprs/rest_api/schemas.py
index 4cd0912..222e4fe 100644
--- a/frontend/coprs_frontend/coprs/rest_api/schemas.py
+++ b/frontend/coprs_frontend/coprs/rest_api/schemas.py
@@ -16,6 +16,7 @@ class MockChrootSchema(Schema):
class ProjectSchema(Schema):
+ id = fields.Int()
name = fields.Str(required=True)
owner = fields.Str(attribute="owner_name", dump_only=True)
@@ -28,31 +29,10 @@ class ProjectSchema(Schema):
build_enable_net = fields.Bool()
last_modified = fields.DateTime()
- additional_repos = fields.List(fields.Str(), dump_only=True, attribute="repos_list")
+ additional_repos = fields.List(fields.Str(), attribute="repos_list")
# used only for creation
- chroots_to_enable = fields.List(fields.Str(), load_only=True)
- additional_repos_input = fields.Str(name="additional_repos", load_only=True)
-
- _keys_to_make_object = [
- "description",
- "instructions",
- "auto_createrepo"
- ]
-
- def make_object(self, data):
- """
- Create kwargs for CoprsLogic.add
- """
- kwargs = dict(
- name=data["name"].strip(),
- repos=" ".join(data.get("repos", [])),
- selected_chroots=data["chroots"],
- )
- for key in self._keys_to_make_object:
- if key in data:
- kwargs[key] = data[key]
- return kwargs
+ chroots = fields.List(fields.Str(), load_only=True)
class CoprChrootSchema(Schema):
@@ -94,9 +74,3 @@ class BuildSchema(Schema):
source_type = fields.Int()
source_json = fields.Str()
-
- # chroots = fields.List(fields.Nested(BuildChrootSchema))
-
-
-
-
diff --git a/frontend/coprs_frontend/tests/coprs_test_case.py b/frontend/coprs_frontend/tests/coprs_test_case.py
index 240e117..c268177 100644
--- a/frontend/coprs_frontend/tests/coprs_test_case.py
+++ b/frontend/coprs_frontend/tests/coprs_test_case.py
@@ -4,6 +4,7 @@ import os
import time
import glob
from functools import wraps
+import datetime
import pytest
import decorator
@@ -102,6 +103,17 @@ class CoprsTestCase(object):
self.db.session.add_all([self.u1, self.u2, self.u3])
@pytest.fixture
+ def f_users_api(self):
+ """
+ Requires f_users
+ """
+ for u in [self.u1, self.u2, self.u3]:
+ u.api_login = "foo"
+ u.api_token = "bar"
+
+ u.api_token_expiration = datetime.date.today() + datetime.timedelta(days=1000)
+
+ @pytest.fixture
def f_coprs(self):
self.c1 = models.Copr(name=u"foocopr", owner=self.u1)
self.c2 = models.Copr(name=u"foocopr", owner=self.u2)
diff --git a/frontend/coprs_frontend/tests/test_api/test_project_r.py b/frontend/coprs_frontend/tests/test_api/test_project_r.py
new file mode 100644
index 0000000..d57bff8
--- /dev/null
+++ b/frontend/coprs_frontend/tests/test_api/test_project_r.py
@@ -0,0 +1,67 @@
+# coding: utf-8
+
+import base64
+import json
+
+import pytest
+import sqlalchemy
+
+from coprs.logic.users_logic import UsersLogic
+from coprs.logic.coprs_logic import CoprsLogic
+
+from tests.coprs_test_case import CoprsTestCase, TransactionDecorator
+
+
+class TestProjectResource(CoprsTestCase):
+
+ def request_api_with_auth(self, url, content, method="GET"):
+ """
+ :rtype: flask.wrappers.Response
+ """
+
+ userstring = "{}:{}".format(self.u1.api_login, self.u1.api_token)
+ base64string_user = base64.b64encode(userstring)
+ base64string = "Basic " + base64string_user
+
+ kwargs = dict(
+ method=method,
+ content_type="application/json",
+ data=json.dumps(content),
+ headers={
+ "Authorization": base64string
+ }
+ )
+
+ return self.tc.open(url, **kwargs)
+
+ def test_self(self):
+ href = "/api_2/projects"
+ r = self.tc.get(href)
+ assert r.status_code == 200
+ 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):
+ self.db.session.add_all([self.u1, self.mc1])
+
+ chroot_name = self.mc1.name
+ body = {
+ "name": "test_copr",
+ "chroots": [
+ chroot_name,
+ ],
+ "additional_repos": ["copr://bar/zar", ]
+ }
+
+ r = self.request_api_with_auth("/api_2/projects", body, method="post")
+ assert r.status_code == 201
+ copr_dict = json.loads(r.data)
+ assert copr_dict["copr"]["id"] == 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
+
+
+
8 years, 8 months
[copr] master: [frontend][api2] Added {Build, Copr}Chroot[List]R resources (3d0fa27)
by vgologuz@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit 3d0fa27f363e9534b4f6ec4ca1130313858d439c
Author: Valentin Gologuzov <vgologuz(a)redhat.com>
Date: Mon Aug 24 15:45:32 2015 +0200
[frontend][api2] Added {Build,Copr}Chroot[List]R resources
>---------------------------------------------------------------
frontend/coprs_frontend/coprs/models.py | 17 ++----
frontend/coprs_frontend/coprs/rest_api/__init__.py | 10 +++-
.../coprs/rest_api/resources/build.py | 5 +-
.../coprs/rest_api/resources/build_chroot.py | 53 ++++++++++++++++++++
.../coprs/rest_api/resources/project.py | 5 +-
.../coprs/rest_api/resources/project_chroot.py | 40 +++++++++++++--
frontend/coprs_frontend/coprs/rest_api/schemas.py | 26 +++++++--
7 files changed, 129 insertions(+), 27 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/models.py b/frontend/coprs_frontend/coprs/models.py
index cae3155..d23434b 100644
--- a/frontend/coprs_frontend/coprs/models.py
+++ b/frontend/coprs_frontend/coprs/models.py
@@ -171,17 +171,8 @@ class Copr(db.Model, helpers.Serializer):
}
@property
- def yum_repos(self):
- return {}
- # todo: add config option to set backen results base url
- # release_tmpl = "{chroot.os_release}-{chroot.os_version}-{chroot.arch}"
- # result = {}
- # for chroot in repo.active_chroots:
- # release = release_tmpl.format(chroot=chroot)
- # url = fix_protocol_for_backend(urlparse.urljoin(
- # app.config["BACKEND_RESULTS_BASE_URL"], release + '/'))
- # result[release] = url
- # return result
+ def owner_name(self):
+ return self.owner.name
@property
def repos_list(self):
@@ -622,6 +613,10 @@ class CoprChroot(db.Model, helpers.Serializer):
comps_name = db.Column(db.String(127), nullable=True)
@property
+ def buildroot_pkgs_list(self):
+ return self.buildroot_pkgs.split()
+
+ @property
def comps(self):
if self.comps_zlib:
return zlib.decompress(self.comps_zlib).decode("utf-8")
diff --git a/frontend/coprs_frontend/coprs/rest_api/__init__.py b/frontend/coprs_frontend/coprs/rest_api/__init__.py
index cfe3930..7c05d40 100644
--- a/frontend/coprs_frontend/coprs/rest_api/__init__.py
+++ b/frontend/coprs_frontend/coprs/rest_api/__init__.py
@@ -4,9 +4,10 @@ from flask_restful import Resource, Api
from coprs.rest_api.exceptions import ApiError
from coprs.rest_api.resources.build import BuildListR, BuildR
+from coprs.rest_api.resources.build_chroot import BuildChrootListR, BuildChrootR
from coprs.rest_api.resources.mock_chroot import MockChrootListR, MockChrootR
from coprs.rest_api.resources.project import ProjectListR, ProjectR
-
+from coprs.rest_api.resources.project_chroot import ProjectChrootListR, ProjectChrootR
URL_PREFIX = "/api_2"
@@ -52,6 +53,13 @@ api.add_resource(MockChrootR, "/mock_chroots/<name>")
api.add_resource(BuildListR, "/builds")
api.add_resource(BuildR, "/builds/<int:build_id>")
+api.add_resource(ProjectChrootListR, "/projects/<int:project_id>/chroots")
+api.add_resource(ProjectChrootR, "/projects/<int:project_id>/chroots/<name>")
+
+api.add_resource(BuildChrootListR, "/builds/<int:build_id>/chroots")
+api.add_resource(BuildChrootR, "/builds/<int:build_id>/chroots/<name>")
+
+
# app.register_blueprint(rest_api_bp, url_prefix=URL_PREFIX)
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/build.py b/frontend/coprs_frontend/coprs/rest_api/resources/build.py
index 982a790..01f274e 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/build.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/build.py
@@ -81,8 +81,9 @@ class BuildR(Resource):
return {
"build": BuildSchema().dump(build)[0],
"_links": {
- "self": url_for(".buildr", build_id=build_id),
- "project": url_for(".projectr", project_id=build.copr_id),
+ "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/build_chroot.py b/frontend/coprs_frontend/coprs/rest_api/resources/build_chroot.py
index 3117685..9baf263 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/build_chroot.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/build_chroot.py
@@ -1,2 +1,55 @@
# 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.logic.builds_logic import BuildsLogic
+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 ..util import get_one_safe, json_loads_safe, mm_deserialize, mm_serialize_one
+
+
+class BuildChrootListR(Resource):
+ def get(self, build_id):
+ build = get_one_safe(BuildsLogic.get(build_id),
+ "Not found build with id: {}".format(build_id))
+
+ return {
+ "chroots": [
+ {
+ "chroot": mm_serialize_one(BuildChrootSchema, chroot),
+ "_links": {
+ "project": {"href": url_for(".projectr", project_id=build.copr_id)},
+ "self": {"href": url_for(".buildchrootr",
+ build_id=build.id,
+ name=chroot.name)},
+ }
+ } for chroot in build.build_chroots
+ ],
+ "_links": {
+ "self": {"href": url_for(".buildchrootlistr", build_id=build_id)}
+ }
+ }
+
+
+class BuildChrootR(Resource):
+ pass
+ # def get(self, project_id, name):
+ # copr = get_one_safe(CoprsLogic.get_by_id(int(project_id)))
+ # chroot = CoprChrootsLogic.get_by_name_safe(copr, name)
+ #
+ # return {
+ # "chroot": mm_serialize_one(CoprChrootSchema, chroot),
+ # "_links": {
+ # "project": {"href": url_for(".projectr", project_id=copr.id)},
+ # "self": {"href": url_for(".projectchrootr",
+ # project_id=project_id,
+ # name=chroot.name)},
+ # }
+ # }
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/project.py b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
index 61f1665..720f01f 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/project.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
@@ -162,9 +162,8 @@ class ProjectR(Resource):
"copr": ProjectSchema().dump(copr)[0],
"_links": {
"self": {"href": url_for(".projectr", project_id=copr.id)},
- #"chroots": bp_url_for(ChrootListR.endpoint,
- # )
- "builds": {"href": url_for(".buildlistr", project_id=copr.id)}
+ "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),
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 12c6aef..1041c49 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py
@@ -6,17 +6,49 @@ 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 coprs.rest_api.schemas import MockChrootSchema, CoprChrootSchema
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 ..util import get_one_safe, json_loads_safe, mm_deserialize, mm_serialize_one
class ProjectChrootListR(Resource):
- pass
+
+ def get(self, project_id):
+ copr = get_one_safe(CoprsLogic.get_by_id(int(project_id)))
+
+ return {
+ "chroots": [
+ {
+ "chroot": mm_serialize_one(CoprChrootSchema, chroot),
+ "_links": {
+ "project": {"href": url_for(".projectr", project_id=copr.id)},
+ "self": {"href": url_for(".projectchrootr",
+ project_id=project_id,
+ name=chroot.name)},
+ }
+ } for chroot in copr.copr_chroots
+ ],
+ "_links": {
+ "self": {"href": url_for(".projectchrootlistr", project_id=project_id)}
+ }
+ }
class ProjectChrootR(Resource):
- pass
+
+ def get(self, project_id, name):
+ copr = get_one_safe(CoprsLogic.get_by_id(int(project_id)))
+ chroot = CoprChrootsLogic.get_by_name_safe(copr, name)
+
+ return {
+ "chroot": mm_serialize_one(CoprChrootSchema, chroot),
+ "_links": {
+ "project": {"href": url_for(".projectr", project_id=copr.id)},
+ "self": {"href": url_for(".projectchrootr",
+ project_id=project_id,
+ name=chroot.name)},
+ }
+ }
diff --git a/frontend/coprs_frontend/coprs/rest_api/schemas.py b/frontend/coprs_frontend/coprs/rest_api/schemas.py
index 631b489..4cd0912 100644
--- a/frontend/coprs_frontend/coprs/rest_api/schemas.py
+++ b/frontend/coprs_frontend/coprs/rest_api/schemas.py
@@ -17,18 +17,22 @@ class MockChrootSchema(Schema):
class ProjectSchema(Schema):
name = fields.Str(required=True)
+
+ owner = fields.Str(attribute="owner_name", dump_only=True)
description = fields.Str()
instructions = fields.Str()
+ homepage = fields.Str()
+ contact = fields.Str()
auto_createrepo = fields.Bool()
build_enable_net = fields.Bool()
last_modified = fields.DateTime()
- additional_repos = fields.List(fields.Str, dump_only=True, attribute="repos_list")
- # yum_repos = fields.List()
+ additional_repos = fields.List(fields.Str(), dump_only=True, attribute="repos_list")
# used only for creation
- chroots_to_enable = fields.List(fields.Str, load_only=True)
+ chroots_to_enable = fields.List(fields.Str(), load_only=True)
+ additional_repos_input = fields.Str(name="additional_repos", load_only=True)
_keys_to_make_object = [
"description",
@@ -51,12 +55,22 @@ class ProjectSchema(Schema):
return kwargs
+class CoprChrootSchema(Schema):
+
+ buildroot_pkgs = fields.List(fields.Str(), attribute="buildroot_pkgs_list")
+ name = fields.Str(dump_only=True)
+
+ comps = fields.Str(dump_only=True)
+ comps_name = fields.Str()
+ comps_len = fields.Int(dump_only=True)
+
+
class BuildChrootSchema(Schema):
# used only for presentation
state = fields.Str()
- started_on = fields.Int()
- ended_on = fields.Int()
- git_hash = fields.Str()
+ started_on = fields.Int(dump_only=True)
+ ended_on = fields.Int(dump_only=True)
+ git_hash = fields.Str(dump_only=True)
class BuildSchema(Schema):
8 years, 8 months
[copr] master: [frontend][api2] renamed copr->project (84bd523)
by vgologuz@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit 84bd52336d98a0cd4ec0f8cd70f5486c2fb40873
Author: Valentin Gologuzov <vgologuz(a)redhat.com>
Date: Mon Aug 24 12:42:06 2015 +0200
[frontend][api2] renamed copr->project
>---------------------------------------------------------------
frontend/coprs_frontend/coprs/logic/coprs_logic.py | 4 ++
frontend/coprs_frontend/coprs/logic/helpers.py | 13 +++++
frontend/coprs_frontend/coprs/models.py | 2 +-
frontend/coprs_frontend/coprs/rest_api/__init__.py | 49 +++--------------
.../coprs/rest_api/resources/build.py | 17 +++---
.../coprs/rest_api/resources/build_chroot.py | 1 +
.../resources/{chroot.py => mock_chroot.py} | 7 ---
.../rest_api/resources/{copr.py => project.py} | 56 +++++++++----------
.../coprs/rest_api/resources/project_chroot.py | 22 ++++++++
frontend/coprs_frontend/coprs/rest_api/schemas.py | 3 +-
frontend/coprs_frontend/coprs/rest_api/util.py | 6 ++-
11 files changed, 91 insertions(+), 89 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/logic/coprs_logic.py b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
index 7110ea9..e5b7939 100644
--- a/frontend/coprs_frontend/coprs/logic/coprs_logic.py
+++ b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
@@ -92,6 +92,10 @@ class CoprsLogic(object):
.filter(aliased_user.username == username))
@classmethod
+ def filter_by_name(cls, query, name):
+ return query.filter(models.Copr.name == name)
+
+ @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/logic/helpers.py b/frontend/coprs_frontend/coprs/logic/helpers.py
new file mode 100644
index 0000000..d291287
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/logic/helpers.py
@@ -0,0 +1,13 @@
+# coding: utf-8
+
+from sqlalchemy.orm.query import Query
+
+
+def slice_query(query, limit=100, offset=0):
+ """
+ :param Query query:
+ :param int limit:
+ :param int offset:
+ :rtype: Query
+ """
+ return query.limit(limit).offset(offset)
diff --git a/frontend/coprs_frontend/coprs/models.py b/frontend/coprs_frontend/coprs/models.py
index 15e77b7..cae3155 100644
--- a/frontend/coprs_frontend/coprs/models.py
+++ b/frontend/coprs_frontend/coprs/models.py
@@ -570,7 +570,7 @@ class MockChroot(db.Model, helpers.Serializer):
"""
Textual representation of name of this chroot
"""
- return "{0}-{1}-{2}".format(self.os_release, self.os_version, self.arch)
+ return "{}-{}-{}".format(self.os_release, self.os_version, self.arch)
@property
def name_release(self):
diff --git a/frontend/coprs_frontend/coprs/rest_api/__init__.py b/frontend/coprs_frontend/coprs/rest_api/__init__.py
index d94ad8f..cfe3930 100644
--- a/frontend/coprs_frontend/coprs/rest_api/__init__.py
+++ b/frontend/coprs_frontend/coprs/rest_api/__init__.py
@@ -1,28 +1,22 @@
# coding: utf-8
from flask import Response, url_for, Blueprint
-
from flask_restful import Resource, Api
-from flask_restful_swagger import swagger
from coprs.rest_api.exceptions import ApiError
from coprs.rest_api.resources.build import BuildListR, BuildR
-from coprs.rest_api.resources.chroot import MockChrootListR, MockChrootR
-from coprs.rest_api.resources.copr import CoprListR, CoprR
+from coprs.rest_api.resources.mock_chroot import MockChrootListR, MockChrootR
+from coprs.rest_api.resources.project import ProjectListR, ProjectR
-URL_PREFIX = "/api_2.0"
+URL_PREFIX = "/api_2"
class RootR(Resource):
- @swagger.operation(
- notes='List main API endpoints',
- nickname='get',
- )
def get(self):
return {
"_links": {
"self": {"href": url_for(".rootr")},
- "coprs": {"href": url_for(".coprlistr")},
+ "coprs": {"href": url_for(".projectlistr")},
"copr_chroots": {"href": url_for(".mockchrootlistr")},
"builds": {"href": url_for(".buildlistr")},
}
@@ -45,38 +39,12 @@ class MyApi(Api):
# def register_api(app, db):
-rest_api_bp = Blueprint("rest_api_bp",
- __name__,
- # app.import_name
-)
-
-api = MyApi(
-# api = Api(
- # app,
- rest_api_bp,
- # prefix=URL_PREFIX,
- catch_all_404s=True,
-
-)
-
-###################################
-# todo: maybe add later
-# Wrap the Api with swagger.docs. It is a thin wrapper around the Api class that adds some swagger smarts
-# api = swagger.docs(
-# api,
-# # apiVersion='0.1',
-# # basePath=URL_PREFIX,
-# # resourcePath=URL_PREFIX,
-# # api_spec_url='{}/spec'.format(URL_PREFIX)
-# # api_spec_url='/spec',
-# # api_spec_url='{}/spec'.format(URL_PREFIX)
-# api_spec_url='/spec'
-# )
-###################################
+rest_api_bp = Blueprint("rest_api_bp", __name__)
+api = MyApi(rest_api_bp, catch_all_404s=True)
api.add_resource(RootR, "/")
-api.add_resource(CoprListR, "/coprs")
-api.add_resource(CoprR, "/coprs/<int:copr_id>")
+api.add_resource(ProjectListR, "/projects")
+api.add_resource(ProjectR, "/projects/<int:project_id>")
api.add_resource(MockChrootListR, "/mock_chroots")
api.add_resource(MockChrootR, "/mock_chroots/<name>")
@@ -87,7 +55,6 @@ api.add_resource(BuildR, "/builds/<int:build_id>")
# app.register_blueprint(rest_api_bp, url_prefix=URL_PREFIX)
-# TODO: try: https://github.com/sloria/flask-marshmallow
def register_api_error_handler(app):
@app.errorhandler(ApiError)
def handle_api_error(error):
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/build.py b/frontend/coprs_frontend/coprs/rest_api/resources/build.py
index ca5f0c1..982a790 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/build.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/build.py
@@ -23,15 +23,15 @@ class BuildListR(Resource):
parser = reqparse.RequestParser()
parser.add_argument('owner', type=str,)
- parser.add_argument('copr_id', type=int)
+ parser.add_argument('project_id', type=int)
parser.add_argument('limit', type=int)
parser.add_argument('offset', type=int)
req_args = parser.parse_args()
- if req_args["copr_id"] is not None:
- copr = get_one_safe(CoprsLogic.get_by_id(req_args["copr_id"]))
+ if req_args["project_id"] is not None:
+ copr = get_one_safe(CoprsLogic.get_by_id(req_args["project_id"]))
query = BuildsLogic.get_multiple_by_copr(copr)
elif req_args["owner" ] is not None:
user = get_one_safe(UsersLogic.get(req_args["owner"]))
@@ -52,6 +52,9 @@ class BuildListR(Resource):
query = query.offset(req_args["offset"])
builds = query.all()
+
+ self_params = dict(req_args)
+ self_params["limit"] = limit
return {
"builds": [
@@ -63,7 +66,7 @@ class BuildListR(Resource):
} for build in builds
],
"_links": {
- "self": {"href": url_for(".buildlistr", **req_args)},
+ "self": {"href": url_for(".buildlistr", **self_params)},
},
}
@@ -71,9 +74,7 @@ class BuildListR(Resource):
class BuildR(Resource):
def get(self, build_id):
- """
- Get single build by id
- """
+
build = get_one_safe(BuildsLogic.get(build_id),
"Not found build with id: {}".format(build_id))
@@ -81,7 +82,7 @@ class BuildR(Resource):
"build": BuildSchema().dump(build)[0],
"_links": {
"self": url_for(".buildr", build_id=build_id),
- "parent_copr": url_for(".coprr", copr_id=build.id),
+ "project": url_for(".projectr", project_id=build.copr_id),
}
}
diff --git a/builder_image/src/__init__.py b/frontend/coprs_frontend/coprs/rest_api/resources/build_chroot.py
similarity index 94%
copy from builder_image/src/__init__.py
copy to frontend/coprs_frontend/coprs/rest_api/resources/build_chroot.py
index 57d631c..3117685 100644
--- a/builder_image/src/__init__.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/build_chroot.py
@@ -1 +1,2 @@
# coding: utf-8
+
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/chroot.py b/frontend/coprs_frontend/coprs/rest_api/resources/mock_chroot.py
similarity index 94%
rename from frontend/coprs_frontend/coprs/rest_api/resources/chroot.py
rename to frontend/coprs_frontend/coprs/rest_api/resources/mock_chroot.py
index bc0ddcc..0ffa083 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/chroot.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/mock_chroot.py
@@ -44,10 +44,3 @@ class MockChrootR(Resource):
},
}
-
-class CoprChrootListR(Resource):
- pass
-
-
-class CoprChrootR(Resource):
- pass
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/copr.py b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
similarity index 79%
rename from frontend/coprs_frontend/coprs/rest_api/resources/copr.py
rename to frontend/coprs_frontend/coprs/rest_api/resources/project.py
index 05a3493..61f1665 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/copr.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/project.py
@@ -10,14 +10,15 @@ 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 .build import BuildListR
# from .chroot import CoprChrootListR, CoprChrootR
-from coprs.rest_api.schemas import CoprSchema
+from coprs.rest_api.schemas import ProjectSchema
from ..exceptions import ObjectAlreadyExists, AuthFailed
-from ..util import get_one_safe, json_loads_safe, mm_deserialize, render_allowed_method
+from ..util import get_one_safe, json_loads_safe, mm_deserialize, render_allowed_method, mm_serialize_one
def rest_api_auth_required(f):
@@ -49,7 +50,7 @@ def rest_api_auth_required(f):
return decorated_function
-class CoprListR(Resource):
+class ProjectListR(Resource):
@rest_api_auth_required
def post(self):
@@ -58,7 +59,7 @@ class CoprListR(Resource):
"""
owner = flask.g.user
- result = mm_deserialize(CoprSchema(), flask.request.data)
+ result = mm_deserialize(ProjectSchema(), flask.request.data)
# todo check that chroots are available
pprint(result.data)
try:
@@ -70,46 +71,43 @@ class CoprListR(Resource):
return "New copr was created", 201
def get(self):
- """
- Get coprs collection
- :return:
- """
parser = reqparse.RequestParser()
parser.add_argument('owner', dest='username', type=str)
+ parser.add_argument('name', dest='name', type=str)
parser.add_argument('limit', type=int)
parser.add_argument('offset', type=int)
# parser.add_argument('help', type=bool)
req_args = parser.parse_args()
- kwargs = {}
- for key in ["username"]:
- if req_args[key]:
- kwargs[key] = req_args[key]
-
- if "username" in kwargs:
+ if req_args["username"]:
query = CoprsLogic.get_multiple_owned_by_username(req_args["username"])
else:
query = CoprsLogic.get_multiple(flask.g.user)
+ 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"]:
- query = query.limit(req_args["limit"])
-
+ limit = req_args["limit"]
if req_args["offset"]:
- query = query.offset(req_args["offset"])
+ offset = req_args["offset"]
+ query = slice_query(query, limit, offset)
coprs_list = query.all()
result_dict = {
"_links": {
- "self": {"href": url_for(".coprlistr", **req_args)}
+ "self": {"href": url_for(".projectlistr", **req_args)}
},
"coprs": [
{
- "copr": CoprSchema().dump(copr)[0],
+ "copr": mm_serialize_one(ProjectSchema, copr),
"_links": {
- "self": {"href": url_for(".coprr", copr_id=copr.id)}
+ "self": {"href": url_for(".projectr", project_id=copr.id)}
},
}
for copr in coprs_list
@@ -133,11 +131,11 @@ class CoprListR(Resource):
return result_dict
-class CoprR(Resource):
+class ProjectR(Resource):
@rest_api_auth_required
- def delete(self, copr_id):
- copr = get_one_safe(CoprsLogic.get_by_id(int(copr_id)))
+ def delete(self, project_id):
+ copr = get_one_safe(CoprsLogic.get_by_id(int(project_id)))
raise NotImplementedError()
@@ -152,21 +150,21 @@ class CoprR(Resource):
return None, 204
- def get(self, copr_id):
+ 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()
- copr = get_one_safe(CoprsLogic.get_by_id(int(copr_id)))
+ copr = get_one_safe(CoprsLogic.get_by_id(int(project_id)))
return {
- "copr": CoprSchema().dump(copr)[0],
+ "copr": ProjectSchema().dump(copr)[0],
"_links": {
- "self": {"href": url_for(".coprr", copr_id=copr.id)},
+ "self": {"href": url_for(".projectr", project_id=copr.id)},
#"chroots": bp_url_for(ChrootListR.endpoint,
# )
- "builds": {"href": url_for(".buildlistr", copr_id=copr.id)}
+ "builds": {"href": url_for(".buildlistr", project_id=copr.id)}
},
# "allowed_methods": [
# render_allowed_method("GET", "Get single copr", require_auth=False),
@@ -175,7 +173,7 @@ class CoprR(Resource):
}
@rest_api_auth_required
- def put(self, copr_id):
+ def put(self, project_id):
"""
Modifies project by replacment of provided fields
"""
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py b/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py
new file mode 100644
index 0000000..12c6aef
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py
@@ -0,0 +1,22 @@
+# 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 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
+
+
+class ProjectChrootListR(Resource):
+ pass
+
+
+class ProjectChrootR(Resource):
+ pass
diff --git a/frontend/coprs_frontend/coprs/rest_api/schemas.py b/frontend/coprs_frontend/coprs/rest_api/schemas.py
index 0b29b54..631b489 100644
--- a/frontend/coprs_frontend/coprs/rest_api/schemas.py
+++ b/frontend/coprs_frontend/coprs/rest_api/schemas.py
@@ -15,7 +15,7 @@ class MockChrootSchema(Schema):
ordered = True
-class CoprSchema(Schema):
+class ProjectSchema(Schema):
name = fields.Str(required=True)
description = fields.Str()
instructions = fields.Str()
@@ -74,7 +74,6 @@ class BuildSchema(Schema):
ended_on = fields.Int()
results = fields.Str()
- memory_reqs = fields.Int()
timeout = fields.Int()
enable_net = fields.Bool()
diff --git a/frontend/coprs_frontend/coprs/rest_api/util.py b/frontend/coprs_frontend/coprs/rest_api/util.py
index 0fec6c6..d038da6 100644
--- a/frontend/coprs_frontend/coprs/rest_api/util.py
+++ b/frontend/coprs_frontend/coprs/rest_api/util.py
@@ -38,5 +38,9 @@ def mm_deserialize(schema, obj_dict):
result = schema.loads(obj_dict)
if result.errors:
raise MalformedRequest(data=result.errors)
- # import ipdb; ipdb.set_trace()
+
return result
+
+
+def mm_serialize_one(schema, obj):
+ return schema().dump(obj)[0]
8 years, 8 months
[copr] master: [frontend][api] wip ... (f21046d)
by vgologuz@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit f21046d498cdbc511756e9820ea744cb5a7da9ec
Author: Valentin Gologuzov <vgologuz(a)redhat.com>
Date: Fri Aug 21 16:11:01 2015 +0200
[frontend][api] wip ...
>---------------------------------------------------------------
.../coprs/rest_api/resources/copr.py | 12 +++++-------
1 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/copr.py b/frontend/coprs_frontend/coprs/rest_api/resources/copr.py
index 26991c8..05a3493 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/copr.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/copr.py
@@ -78,6 +78,7 @@ class CoprListR(Resource):
parser.add_argument('owner', dest='username', type=str)
parser.add_argument('limit', type=int)
parser.add_argument('offset', type=int)
+
# parser.add_argument('help', type=bool)
req_args = parser.parse_args()
@@ -91,16 +92,13 @@ class CoprListR(Resource):
else:
query = CoprsLogic.get_multiple(flask.g.user)
- # todo: also could be optional
- query = CoprsLogic.join_builds(query)
-
- if "limit" in req_args:
- query = query.offset(req_args["offset"])
-
# todo: add maximum allowed limit and also use as a default limit
if req_args["limit"]:
query = query.limit(req_args["limit"])
+ if req_args["offset"]:
+ query = query.offset(req_args["offset"])
+
coprs_list = query.all()
result_dict = {
@@ -168,7 +166,7 @@ class CoprR(Resource):
"self": {"href": url_for(".coprr", copr_id=copr.id)},
#"chroots": bp_url_for(ChrootListR.endpoint,
# )
- "builds": {"href": url_for(".buildlistr", owner=copr.owner.name, project=copr.name)}
+ "builds": {"href": url_for(".buildlistr", copr_id=copr.id)}
},
# "allowed_methods": [
# render_allowed_method("GET", "Get single copr", require_auth=False),
8 years, 8 months
[copr] master: [frontend][api] removed bp_url_for; use hardcoded endpoints to avoid cyclic imports; use copr_id instead of user/project (1ac84d1)
by vgologuz@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit 1ac84d1bc13763d1dded51cc5d62fcbcb64ac2c7
Author: Valentin Gologuzov <vgologuz(a)redhat.com>
Date: Fri Aug 21 15:57:59 2015 +0200
[frontend][api] removed bp_url_for; use hardcoded endpoints to avoid cyclic imports; use copr_id instead of user/project
>---------------------------------------------------------------
.../coprs_frontend/coprs/logic/builds_logic.py | 8 ++
frontend/coprs_frontend/coprs/logic/coprs_logic.py | 4 +
frontend/coprs_frontend/coprs/rest_api/__init__.py | 19 +++---
.../coprs/rest_api/resources/build.py | 55 +++++++++------
.../coprs/rest_api/resources/chroot.py | 43 ++++++------
.../coprs/rest_api/resources/copr.py | 77 +++++++++++---------
frontend/coprs_frontend/coprs/rest_api/schemas.py | 6 +-
frontend/coprs_frontend/coprs/rest_api/util.py | 12 ---
8 files changed, 121 insertions(+), 103 deletions(-)
diff --git a/frontend/coprs_frontend/coprs/logic/builds_logic.py b/frontend/coprs_frontend/coprs/logic/builds_logic.py
index 45829fc..be28dbd 100644
--- a/frontend/coprs_frontend/coprs/logic/builds_logic.py
+++ b/frontend/coprs_frontend/coprs/logic/builds_logic.py
@@ -106,6 +106,14 @@ class BuildsLogic(object):
return cls.get_multiple().filter(models.Build.copr == copr)
@classmethod
+ def get_multiple_by_owner(cls, user):
+ """ Get collection of builds in copr sorted by build_id descending
+ form the copr owned by `user`
+ """
+ return cls.get_multiple().join(models.Build.copr).filter(
+ models.Copr.owner == user)
+
+ @classmethod
def get_multiple_by_name(cls, username, coprname):
query = cls.get_multiple()
return (query.join(models.Build.copr)
diff --git a/frontend/coprs_frontend/coprs/logic/coprs_logic.py b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
index 4e53e37..7110ea9 100644
--- a/frontend/coprs_frontend/coprs/logic/coprs_logic.py
+++ b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
@@ -32,6 +32,10 @@ class CoprsLogic(object):
return query
@classmethod
+ def get_by_id(cls, copr_id):
+ return cls.get_all().filter(models.Copr.id == copr_id)
+
+ @classmethod
def get(cls, username, coprname, **kwargs):
with_builds = kwargs.get("with_builds", False)
with_mock_chroots = kwargs.get("with_mock_chroots", False)
diff --git a/frontend/coprs_frontend/coprs/rest_api/__init__.py b/frontend/coprs_frontend/coprs/rest_api/__init__.py
index 309ba44..d94ad8f 100644
--- a/frontend/coprs_frontend/coprs/rest_api/__init__.py
+++ b/frontend/coprs_frontend/coprs/rest_api/__init__.py
@@ -6,9 +6,8 @@ from flask_restful_swagger import swagger
from coprs.rest_api.exceptions import ApiError
from coprs.rest_api.resources.build import BuildListR, BuildR
-from coprs.rest_api.resources.chroot import ChrootListR, ChrootR
+from coprs.rest_api.resources.chroot import MockChrootListR, MockChrootR
from coprs.rest_api.resources.copr import CoprListR, CoprR
-from coprs.rest_api.util import bp_url_for
URL_PREFIX = "/api_2.0"
@@ -21,11 +20,11 @@ class RootR(Resource):
)
def get(self):
return {
- "links": {
- "self": bp_url_for(RootR.endpoint),
- "coprs": bp_url_for(CoprListR.endpoint),
- "chroots": bp_url_for(ChrootListR.endpoint),
- "builds": bp_url_for(BuildListR.endpoint),
+ "_links": {
+ "self": {"href": url_for(".rootr")},
+ "coprs": {"href": url_for(".coprlistr")},
+ "copr_chroots": {"href": url_for(".mockchrootlistr")},
+ "builds": {"href": url_for(".buildlistr")},
}
}
@@ -77,10 +76,10 @@ api = MyApi(
api.add_resource(RootR, "/")
api.add_resource(CoprListR, "/coprs")
-api.add_resource(CoprR, "/coprs/<owner>/<project>")
+api.add_resource(CoprR, "/coprs/<int:copr_id>")
-api.add_resource(ChrootListR, "/chroots")
-api.add_resource(ChrootR, "/chroots/<name>")
+api.add_resource(MockChrootListR, "/mock_chroots")
+api.add_resource(MockChrootR, "/mock_chroots/<name>")
api.add_resource(BuildListR, "/builds")
api.add_resource(BuildR, "/builds/<int:build_id>")
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/build.py b/frontend/coprs_frontend/coprs/rest_api/resources/build.py
index 1db3bf4..ca5f0c1 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/build.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/build.py
@@ -1,15 +1,17 @@
# coding: utf-8
import flask
+from flask import url_for
# from flask_restful_swagger import swagger
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.schemas import BuildSchema
-from coprs.rest_api.util import get_one_safe, bp_url_for
+from coprs.rest_api.util import get_one_safe
from flask_restful import Resource, reqparse
@@ -21,21 +23,26 @@ class BuildListR(Resource):
parser = reqparse.RequestParser()
parser.add_argument('owner', type=str,)
- parser.add_argument('project', type=str)
+ parser.add_argument('copr_id', type=int)
parser.add_argument('limit', type=int)
parser.add_argument('offset', type=int)
req_args = parser.parse_args()
- if "owner" and "project" in req_args:
- query = BuildsLogic.get_multiple_by_name(
- req_args["owner"], req_args["project"])
+ if req_args["copr_id"] is not None:
+ copr = get_one_safe(CoprsLogic.get_by_id(req_args["copr_id"]))
+ query = BuildsLogic.get_multiple_by_copr(copr)
+ elif req_args["owner" ] is not None:
+ user = get_one_safe(UsersLogic.get(req_args["owner"]))
+ query = BuildsLogic.get_multiple_by_owner(user)
else:
query = BuildsLogic.get_multiple()
if "limit" in req_args:
limit = req_args["limit"]
+ if limit <= 0 or limit > 100:
+ limit = 100
else:
limit = 100
@@ -50,21 +57,17 @@ class BuildListR(Resource):
"builds": [
{
"build": BuildSchema().dump(build)[0],
- "link": bp_url_for(BuildR.endpoint, build_id=build.id),
+ "_links": {
+ "self": {"href": url_for(".buildr", build_id=build.id)},
+ }
} for build in builds
],
- "links": {
- "self": bp_url_for(BuildListR.endpoint, **req_args),
+ "_links": {
+ "self": {"href": url_for(".buildlistr", **req_args)},
},
}
-# @swagger.model
-# class BuildItem(object):
-# def __init__(self, build_id):
-# pass
-
-
class BuildR(Resource):
def get(self, build_id):
@@ -73,17 +76,27 @@ class BuildR(Resource):
"""
build = get_one_safe(BuildsLogic.get(build_id),
"Not found build with id: {}".format(build_id))
+
return {
"build": BuildSchema().dump(build)[0],
- "links": {
- "self": bp_url_for(BuildR.endpoint, build_id=build_id),
- # TODO: can't do this due to circular imports
- # "parent_copr": url_for(CoprR.endpoint,
- # owner=build.copr.owner.name,
- # project=build.copr.name),
+ "_links": {
+ "self": url_for(".buildr", build_id=build_id),
+ "parent_copr": url_for(".coprr", copr_id=build.id),
}
}
-
+# to get build details and cancel individual build chroots
+# class BuildChrootR(Resource):
+# def get(self, owner, project, name):
+# copr = get_one_safe(CoprsLogic.get(flask.g.user, owner, project),
+# "Copr {}/{} not found".format(owner, project))
+# chroot = get_one_safe(CoprChrootsLogic.get(copr, name))
+#
+# return {
+# "chroot": chroot.to_dict(),
+# "links": {
+# "self": bp_url_for(BuildChrootR.endpoint, owner=owner, project=project, name=name)
+# }
+# }
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/chroot.py b/frontend/coprs_frontend/coprs/rest_api/resources/chroot.py
index 61d6e5a..bc0ddcc 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/chroot.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/chroot.py
@@ -6,49 +6,48 @@ 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 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, bp_url_for
+from ..util import get_one_safe, json_loads_safe, mm_deserialize
-class ChrootListR(Resource):
+class MockChrootListR(Resource):
def get(self):
- chroots = MockChrootsLogic.get_multiple(None).all()
+ # todo: add argument active_only
+ chroots = MockChrootsLogic.get_multiple(active_only=False).all()
return {
- "links": {
- "self": bp_url_for(ChrootListR.endpoint),
+ "_links": {
+ "self": {"href": url_for(".mockchrootlistr")},
},
"chroots": [
{
- "chroot": chroot.to_dict(),
- "link": url_for(ChrootR.endpoint, name=chroot.name),
+ "chroot": MockChrootSchema().dump(chroot)[0],
+ "_links": {
+ "self": {"href": url_for(".mockchrootr", name=chroot.name)},
+ }
} for chroot in chroots
]
}
-class ChrootR(Resource):
+class MockChrootR(Resource):
def get(self, name):
chroot = get_one_safe(MockChrootsLogic.get_from_name(name))
return {
- "chroot": chroot.to_dict(),
- "links": {
- "self": bp_url_for(ChrootR.endpoint, name=chroot.name)
+ "chroot": MockChrootSchema().dump(chroot)[0],
+ "_links": {
+ "self": {"href": url_for(".mockchrootr", name=chroot.name)},
+ "all_chroots": {"href": url_for(".mockchrootlistr")}
},
}
-class BuildChrootR(Resource):
- def get(self, owner, project, name):
- copr = get_one_safe(CoprsLogic.get(flask.g.user, owner, project),
- "Copr {}/{} not found".format(owner, project))
- chroot = get_one_safe(CoprChrootsLogic.get(copr, name))
+class CoprChrootListR(Resource):
+ pass
- return {
- "chroot": chroot.to_dict(),
- "links": {
- "self": bp_url_for(BuildChrootR.endpoint, owner=owner, project=project, name=name)
- }
- }
+
+class CoprChrootR(Resource):
+ pass
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/copr.py b/frontend/coprs_frontend/coprs/rest_api/resources/copr.py
index f1f5e48..26991c8 100644
--- a/frontend/coprs_frontend/coprs/rest_api/resources/copr.py
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/copr.py
@@ -3,6 +3,7 @@ import datetime
import functools
import flask
+from flask import url_for
from flask_restful import Resource, reqparse
from marshmallow import pprint
@@ -13,9 +14,10 @@ from coprs.logic.users_logic import UsersLogic
from coprs.logic.coprs_logic import CoprsLogic
from coprs.exceptions import ActionInProgressException, InsufficientRightsException
from .build import BuildListR
+# from .chroot import CoprChrootListR, CoprChrootR
from coprs.rest_api.schemas import CoprSchema
from ..exceptions import ObjectAlreadyExists, AuthFailed
-from ..util import get_one_safe, json_loads_safe, mm_deserialize, bp_url_for, render_allowed_method
+from ..util import get_one_safe, json_loads_safe, mm_deserialize, render_allowed_method
def rest_api_auth_required(f):
@@ -55,7 +57,6 @@ class CoprListR(Resource):
Creates new copr
"""
owner = flask.g.user
- # new_copr_dict = json_loads_safe(flask.request.data, "")
result = mm_deserialize(CoprSchema(), flask.request.data)
# todo check that chroots are available
@@ -77,6 +78,7 @@ class CoprListR(Resource):
parser.add_argument('owner', dest='username', type=str)
parser.add_argument('limit', type=int)
parser.add_argument('offset', type=int)
+ # parser.add_argument('help', type=bool)
req_args = parser.parse_args()
kwargs = {}
@@ -92,7 +94,7 @@ class CoprListR(Resource):
# todo: also could be optional
query = CoprsLogic.join_builds(query)
- if req_args["offset"]:
+ if "limit" in req_args:
query = query.offset(req_args["offset"])
# todo: add maximum allowed limit and also use as a default limit
@@ -101,40 +103,46 @@ class CoprListR(Resource):
coprs_list = query.all()
- return {
- "links": {
- "self": bp_url_for(CoprListR.endpoint, **req_args)
+ result_dict = {
+ "_links": {
+ "self": {"href": url_for(".coprlistr", **req_args)}
},
"coprs": [
{
"copr": CoprSchema().dump(copr)[0],
- "link": bp_url_for(
- CoprR.endpoint,
- owner=copr.owner.name,
- project=copr.name
- ),
+ "_links": {
+ "self": {"href": url_for(".coprr", copr_id=copr.id)}
+ },
}
for copr in coprs_list
],
- # TODO: show only if user provided ?help=true param
- # "allowed_methods": [
- # render_allowed_method("GET", "Get list of coprs", require_auth=False,
- # params=[
- # "username: filter coprs owned by 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"),
- # ]
+
}
+ # 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
+
class CoprR(Resource):
@rest_api_auth_required
- def delete(self, owner, project):
- copr = get_one_safe(CoprsLogic.get(flask.g.user, owner, project),
- "Copr {}/{} not found".format(owner, project))
+ def delete(self, copr_id):
+ copr = get_one_safe(CoprsLogic.get_by_id(int(copr_id)))
+
+ raise NotImplementedError()
+
try:
ComplexLogic.delete_copr(copr)
except (ActionInProgressException,
@@ -146,24 +154,21 @@ class CoprR(Resource):
return None, 204
- def get(self, owner, project):
+ def get(self, copr_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()
- copr = get_one_safe(CoprsLogic.get(flask.g.user, owner, project),
- "Copr {}/{} not found".format(owner, project))
+ copr = get_one_safe(CoprsLogic.get_by_id(int(copr_id)))
+
return {
"copr": CoprSchema().dump(copr)[0],
- "links": {
- "self": bp_url_for(CoprR.endpoint,
- owner=owner,
- project=project),
- # "chroots":
- "builds": bp_url_for(BuildListR.endpoint,
- owner=copr.owner.name,
- project=copr.name)
+ "_links": {
+ "self": {"href": url_for(".coprr", copr_id=copr.id)},
+ #"chroots": bp_url_for(ChrootListR.endpoint,
+ # )
+ "builds": {"href": url_for(".buildlistr", owner=copr.owner.name, project=copr.name)}
},
# "allowed_methods": [
# render_allowed_method("GET", "Get single copr", require_auth=False),
@@ -172,7 +177,7 @@ class CoprR(Resource):
}
@rest_api_auth_required
- def put(self, owner, project):
+ def put(self, copr_id):
"""
Modifies project by replacment of provided fields
"""
diff --git a/frontend/coprs_frontend/coprs/rest_api/schemas.py b/frontend/coprs_frontend/coprs/rest_api/schemas.py
index 9c243a7..0b29b54 100644
--- a/frontend/coprs_frontend/coprs/rest_api/schemas.py
+++ b/frontend/coprs_frontend/coprs/rest_api/schemas.py
@@ -9,8 +9,10 @@ class AllowedMethodSchema(Schema):
params = fields.List(fields.Str())
-class MockChroot(Schema):
- pass
+class MockChrootSchema(Schema):
+ class Meta:
+ fields = ("name", "os_release", "os_version", "arch", "is_active")
+ ordered = True
class CoprSchema(Schema):
diff --git a/frontend/coprs_frontend/coprs/rest_api/util.py b/frontend/coprs_frontend/coprs/rest_api/util.py
index ac9b0b6..0fec6c6 100644
--- a/frontend/coprs_frontend/coprs/rest_api/util.py
+++ b/frontend/coprs_frontend/coprs/rest_api/util.py
@@ -19,18 +19,6 @@ def render_allowed_method(method, doc, require_auth=True, params=None):
return AllowedMethodSchema().dump(AllowedMethod(method, doc, require_auth, params))[0]
-def bp_url_for(endpoint, *args, **kwargs):
- """
- Prepend endpoing with dot
- :param endpoint:
- :param args:
- :param kwargs:
- :return:
- """
-
- return url_for(".{}".format(endpoint), *args, **kwargs)
-
-
def get_one_safe(query, data_on_error=None):
try:
return query.one()
8 years, 8 months
[copr] master: [frontend] REST API [WIP] (2f07d9f)
by vgologuz@fedoraproject.org
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit 2f07d9f691fa82df68a78a56ff4c0634c6b3324c
Author: Valentin Gologuzov <vgologuz(a)redhat.com>
Date: Tue Dec 16 19:26:49 2014 +0100
[frontend] REST API [WIP]
>---------------------------------------------------------------
frontend/copr-frontend.spec | 4 +
frontend/coprs_frontend/coprs/__init__.py | 5 +
.../coprs_frontend/coprs/logic/complex_logic.py | 27 +++
frontend/coprs_frontend/coprs/logic/coprs_logic.py | 15 +-
frontend/coprs_frontend/coprs/models.py | 43 +++++
frontend/coprs_frontend/coprs/rest_api/__init__.py | 101 +++++++++++
.../coprs_frontend/coprs/rest_api/exceptions.py | 45 +++++
.../coprs/rest_api/resources}/__init__.py | 0
.../coprs/rest_api/resources/build.py | 89 ++++++++++
.../coprs/rest_api/resources/chroot.py | 54 ++++++
.../coprs/rest_api/resources/copr.py | 180 ++++++++++++++++++++
frontend/coprs_frontend/coprs/rest_api/schemas.py | 87 ++++++++++
frontend/coprs_frontend/coprs/rest_api/util.py | 54 ++++++
.../coprs/views/api_ns/api_general.py | 6 +-
.../coprs/views/coprs_ns/coprs_general.py | 7 +-
frontend/coprs_frontend/coprs/views/misc.py | 6 +
.../test_views/test_coprs_ns/test_coprs_general.py | 2 +-
frontend/requirements.txt | 3 +
18 files changed, 714 insertions(+), 14 deletions(-)
diff --git a/frontend/copr-frontend.spec b/frontend/copr-frontend.spec
index e01748b..def204d 100644
--- a/frontend/copr-frontend.spec
+++ b/frontend/copr-frontend.spec
@@ -53,6 +53,8 @@ Requires: python-whoosh >= 2.5.3
Requires: pytz
Requires: python-six
Requires: python-netaddr
+Requires: python-flask-restful
+Requires: python-marshmallow
# for tests:
Requires: pytest
Requires: python-flexmock
@@ -85,6 +87,8 @@ BuildRequires: python-mock
BuildRequires: python-decorator
BuildRequires: python-markdown
BuildRequires: pytz
+BuildRequires: python-flask-restful
+BuildRequires: python-marshmallow
%description
COPR is lightweight build system. It allows you to create new project in WebUI,
diff --git a/frontend/coprs_frontend/coprs/__init__.py b/frontend/coprs_frontend/coprs/__init__.py
index fa02421..dd0eb05 100644
--- a/frontend/coprs_frontend/coprs/__init__.py
+++ b/frontend/coprs_frontend/coprs/__init__.py
@@ -70,3 +70,8 @@ app.register_blueprint(stats_receiver.stats_rcv_ns)
app.register_blueprint(tmp_ns.tmp_ns)
app.add_url_rule("/", "coprs_ns.coprs_show", coprs_general.coprs_show)
+
+from rest_api import rest_api_bp, register_api_error_handler, URL_PREFIX
+register_api_error_handler(app)
+app.register_blueprint(rest_api_bp, url_prefix=URL_PREFIX)
+# register_api(app, db)
diff --git a/frontend/coprs_frontend/coprs/logic/complex_logic.py b/frontend/coprs_frontend/coprs/logic/complex_logic.py
new file mode 100644
index 0000000..3f2bd53
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/logic/complex_logic.py
@@ -0,0 +1,27 @@
+# coding: utf-8
+
+import flask
+from .builds_logic import BuildsLogic
+from .coprs_logic import CoprsLogic
+
+
+class ComplexLogic(object):
+ """
+ Used for manipulation which affects multiply models
+ """
+
+ @classmethod
+ def delete_copr(cls, copr):
+ """
+ Delete copr and all its builds.
+
+ :param copr:
+ :raises ActionInProgressException:
+ :raises InsufficientRightsException:
+ """
+ builds_query = BuildsLogic.get_multiple_by_copr(copr=copr)
+
+ for build in builds_query:
+ BuildsLogic.delete_build(flask.g.user, build)
+
+ CoprsLogic.delete_unsafe(flask.g.user, copr)
diff --git a/frontend/coprs_frontend/coprs/logic/coprs_logic.py b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
index 9ea19dc..4e53e37 100644
--- a/frontend/coprs_frontend/coprs/logic/coprs_logic.py
+++ b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
@@ -130,13 +130,13 @@ class CoprsLogic(object):
return query
@classmethod
- def add(cls, user, name, repos, selected_chroots, description,
- instructions, check_for_duplicates=False, **kwargs):
+ def add(cls, user, name, repos, selected_chroots, description=None,
+ instructions=None, check_for_duplicates=False, **kwargs):
copr = models.Copr(name=name,
repos=repos,
owner_id=user.id,
- description=description,
- instructions=instructions,
+ description=description or u"",
+ instructions=instructions or u"",
created_on=int(time.time()),
**kwargs)
@@ -188,7 +188,10 @@ class CoprsLogic(object):
db.session.add(copr)
@classmethod
- def delete(cls, user, copr, check_for_duplicates=True):
+ def delete_unsafe(cls, user, copr):
+ """
+ Deletes copr without termination of ongoing builds.
+ """
cls.raise_if_cant_delete(user, copr)
# TODO: do we want to dump the information somewhere, so that we can
# search it in future?
@@ -339,7 +342,9 @@ listen(models.Copr.auto_createrepo, 'set', on_auto_createrepo_change,
class CoprChrootsLogic(object):
@classmethod
+
def mock_chroots_from_names(cls, names):
+
db_chroots = models.MockChroot.query.all()
mock_chroots = []
for ch in db_chroots:
diff --git a/frontend/coprs_frontend/coprs/models.py b/frontend/coprs_frontend/coprs/models.py
index ca4d1af..15e77b7 100644
--- a/frontend/coprs_frontend/coprs/models.py
+++ b/frontend/coprs_frontend/coprs/models.py
@@ -1,3 +1,4 @@
+import copy
import datetime
import json
import os
@@ -170,6 +171,19 @@ class Copr(db.Model, helpers.Serializer):
}
@property
+ def yum_repos(self):
+ return {}
+ # todo: add config option to set backen results base url
+ # release_tmpl = "{chroot.os_release}-{chroot.os_version}-{chroot.arch}"
+ # result = {}
+ # for chroot in repo.active_chroots:
+ # release = release_tmpl.format(chroot=chroot)
+ # url = fix_protocol_for_backend(urlparse.urljoin(
+ # app.config["BACKEND_RESULTS_BASE_URL"], release + '/'))
+ # result[release] = url
+ # return result
+
+ @property
def repos_list(self):
"""
Return repos of this copr as a list of strings
@@ -244,6 +258,13 @@ class Copr(db.Model, helpers.Serializer):
def full_name(self):
return "{}/{}".format(self.owner.username, self.name)
+ def to_dict(self, private=False, show_builds=True, show_chroots=True):
+ result = {}
+ for key in ["id", "name", "description", "instructions"]:
+ result[key] = str(copy.copy(getattr(self, key)))
+ result["owner"] = self.owner.name
+ return result
+
class CoprPermission(db.Model, helpers.Serializer):
@@ -355,6 +376,13 @@ class Build(db.Model, helpers.Serializer):
return self.build_chroots[0].git_hash is None
@property
+ def repos_list(self):
+ if self.repos is None:
+ return list()
+ else:
+ return self.repos.split()
+
+ @property
def result_dir_name(self):
return "{:08d}-{}".format(self.id, self.package.name)
@@ -512,6 +540,15 @@ class Build(db.Model, helpers.Serializer):
else:
return src_rpm_name
+ def to_dict(self, options=None):
+ result = super(Build, self).to_dict(options)
+ result["src_pkg"] = result["pkgs"]
+ del result["pkgs"]
+ del result["copr_id"]
+
+ result["state"] = self.state
+ return result
+
class MockChroot(db.Model, helpers.Serializer):
@@ -556,6 +593,12 @@ class MockChroot(db.Model, helpers.Serializer):
"""
return "{0} {1}".format(self.os_release, self.os_version)
+ @property
+ def serializable_attributes(self):
+ attr_list = super(MockChroot, self).serializable_attributes
+ attr_list.extend(["name", "os"])
+ return attr_list
+
class CoprChroot(db.Model, helpers.Serializer):
diff --git a/frontend/coprs_frontend/coprs/rest_api/__init__.py b/frontend/coprs_frontend/coprs/rest_api/__init__.py
new file mode 100644
index 0000000..309ba44
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/rest_api/__init__.py
@@ -0,0 +1,101 @@
+# coding: utf-8
+from flask import Response, url_for, Blueprint
+
+from flask_restful import Resource, Api
+from flask_restful_swagger import swagger
+
+from coprs.rest_api.exceptions import ApiError
+from coprs.rest_api.resources.build import BuildListR, BuildR
+from coprs.rest_api.resources.chroot import ChrootListR, ChrootR
+from coprs.rest_api.resources.copr import CoprListR, CoprR
+from coprs.rest_api.util import bp_url_for
+
+
+URL_PREFIX = "/api_2.0"
+
+
+class RootR(Resource):
+ @swagger.operation(
+ notes='List main API endpoints',
+ nickname='get',
+ )
+ def get(self):
+ return {
+ "links": {
+ "self": bp_url_for(RootR.endpoint),
+ "coprs": bp_url_for(CoprListR.endpoint),
+ "chroots": bp_url_for(ChrootListR.endpoint),
+ "builds": bp_url_for(BuildListR.endpoint),
+ }
+ }
+
+
+class MyApi(Api):
+ # flask-restfull error handling quite buggy right now
+ def error_router(self, original_handler, e):
+ return original_handler(e)
+ # def handle_error(self, e):
+ #
+ # if isinstance(e, sqlalchemy.orm.exc.NoResultFound):
+ # return self.make_response(str(e), 404)
+ #
+ #
+ # super(MyApi, self).handle_error(e)
+
+
+# def register_api(app, db):
+
+
+rest_api_bp = Blueprint("rest_api_bp",
+ __name__,
+ # app.import_name
+)
+
+api = MyApi(
+# api = Api(
+ # app,
+ rest_api_bp,
+ # prefix=URL_PREFIX,
+ catch_all_404s=True,
+
+)
+
+###################################
+# todo: maybe add later
+# Wrap the Api with swagger.docs. It is a thin wrapper around the Api class that adds some swagger smarts
+# api = swagger.docs(
+# api,
+# # apiVersion='0.1',
+# # basePath=URL_PREFIX,
+# # resourcePath=URL_PREFIX,
+# # api_spec_url='{}/spec'.format(URL_PREFIX)
+# # api_spec_url='/spec',
+# # api_spec_url='{}/spec'.format(URL_PREFIX)
+# api_spec_url='/spec'
+# )
+###################################
+
+api.add_resource(RootR, "/")
+api.add_resource(CoprListR, "/coprs")
+api.add_resource(CoprR, "/coprs/<owner>/<project>")
+
+api.add_resource(ChrootListR, "/chroots")
+api.add_resource(ChrootR, "/chroots/<name>")
+
+api.add_resource(BuildListR, "/builds")
+api.add_resource(BuildR, "/builds/<int:build_id>")
+
+# app.register_blueprint(rest_api_bp, url_prefix=URL_PREFIX)
+
+
+# TODO: try: https://github.com/sloria/flask-marshmallow
+def register_api_error_handler(app):
+ @app.errorhandler(ApiError)
+ def handle_api_error(error):
+ response = Response(
+ response="{}\n".format(error.data),
+ status=error.code,
+ mimetype="text/plain",
+ headers=error.headers,
+ )
+ return response
diff --git a/frontend/coprs_frontend/coprs/rest_api/exceptions.py b/frontend/coprs_frontend/coprs/rest_api/exceptions.py
new file mode 100644
index 0000000..0fb14da
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/rest_api/exceptions.py
@@ -0,0 +1,45 @@
+# coding: utf-8
+import six
+
+
+class ApiError(Exception):
+ def __init__(self, code, data, **kwargs):
+ super(ApiError, self).__init__(**kwargs)
+
+ self.code = code
+ self.data = data
+
+ self.headers = kwargs.get("headers", {})
+
+ def __str__(self):
+ return str(self.data)
+
+ if six.PY2:
+ def __unicode__(self):
+ return unicode(self.data)
+
+
+class AuthFailed(ApiError):
+ def __init__(self, data, **kwargs):
+
+ super(AuthFailed, self).__init__(
+ 401, data,
+ # headers={"Authorization": "Basic"},
+ **kwargs)
+
+ self.headers["Authorization"] = "Basic"
+
+
+class ObjectNotFoundError(ApiError):
+ def __init__(self, data, **kwargs):
+ super(ObjectNotFoundError, self).__init__(404, data, **kwargs)
+
+
+class ObjectAlreadyExists(ApiError):
+ def __init__(self, data, **kwargs):
+ super(ObjectAlreadyExists, self).__init__(409, data, **kwargs)
+
+
+class MalformedRequest(ApiError):
+ def __init__(self, data=None, **kwargs):
+ super(MalformedRequest, self).__init__(400, data, **kwargs)
diff --git a/builder_image/src/__init__.py b/frontend/coprs_frontend/coprs/rest_api/resources/__init__.py
similarity index 100%
copy from builder_image/src/__init__.py
copy to frontend/coprs_frontend/coprs/rest_api/resources/__init__.py
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/build.py b/frontend/coprs_frontend/coprs/rest_api/resources/build.py
new file mode 100644
index 0000000..1db3bf4
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/build.py
@@ -0,0 +1,89 @@
+# coding: utf-8
+
+import flask
+
+# from flask_restful_swagger import swagger
+
+from coprs.logic.coprs_logic import CoprsLogic
+from coprs.logic.builds_logic import BuildsLogic
+
+from coprs.rest_api.schemas import BuildSchema
+
+from coprs.rest_api.util import get_one_safe, bp_url_for
+
+from flask_restful import Resource, reqparse
+
+
+class BuildListR(Resource):
+
+ def get(self):
+
+ parser = reqparse.RequestParser()
+
+ parser.add_argument('owner', type=str,)
+ parser.add_argument('project', type=str)
+
+ parser.add_argument('limit', type=int)
+ parser.add_argument('offset', type=int)
+
+ req_args = parser.parse_args()
+
+ if "owner" and "project" in req_args:
+ query = BuildsLogic.get_multiple_by_name(
+ req_args["owner"], req_args["project"])
+ else:
+ query = BuildsLogic.get_multiple()
+
+ if "limit" in req_args:
+ limit = req_args["limit"]
+ else:
+ limit = 100
+
+ query = query.limit(limit)
+
+ if "offset" in req_args:
+ query = query.offset(req_args["offset"])
+
+ builds = query.all()
+ return {
+
+ "builds": [
+ {
+ "build": BuildSchema().dump(build)[0],
+ "link": bp_url_for(BuildR.endpoint, build_id=build.id),
+ } for build in builds
+ ],
+ "links": {
+ "self": bp_url_for(BuildListR.endpoint, **req_args),
+ },
+ }
+
+
+# @swagger.model
+# class BuildItem(object):
+# def __init__(self, build_id):
+# pass
+
+
+class BuildR(Resource):
+
+ def get(self, build_id):
+ """
+ Get single build by id
+ """
+ build = get_one_safe(BuildsLogic.get(build_id),
+ "Not found build with id: {}".format(build_id))
+ return {
+ "build": BuildSchema().dump(build)[0],
+ "links": {
+ "self": bp_url_for(BuildR.endpoint, build_id=build_id),
+ # TODO: can't do this due to circular imports
+ # "parent_copr": url_for(CoprR.endpoint,
+ # owner=build.copr.owner.name,
+ # project=build.copr.name),
+ }
+ }
+
+
+
+
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/chroot.py b/frontend/coprs_frontend/coprs/rest_api/resources/chroot.py
new file mode 100644
index 0000000..61d6e5a
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/chroot.py
@@ -0,0 +1,54 @@
+# 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.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, bp_url_for
+
+
+class ChrootListR(Resource):
+ def get(self):
+ chroots = MockChrootsLogic.get_multiple(None).all()
+ return {
+ "links": {
+ "self": bp_url_for(ChrootListR.endpoint),
+ },
+ "chroots": [
+ {
+ "chroot": chroot.to_dict(),
+ "link": url_for(ChrootR.endpoint, name=chroot.name),
+ } for chroot in chroots
+ ]
+ }
+
+
+class ChrootR(Resource):
+ def get(self, name):
+ chroot = get_one_safe(MockChrootsLogic.get_from_name(name))
+ return {
+ "chroot": chroot.to_dict(),
+ "links": {
+ "self": bp_url_for(ChrootR.endpoint, name=chroot.name)
+ },
+ }
+
+
+class BuildChrootR(Resource):
+ def get(self, owner, project, name):
+ copr = get_one_safe(CoprsLogic.get(flask.g.user, owner, project),
+ "Copr {}/{} not found".format(owner, project))
+ chroot = get_one_safe(CoprChrootsLogic.get(copr, name))
+
+ return {
+ "chroot": chroot.to_dict(),
+ "links": {
+ "self": bp_url_for(BuildChrootR.endpoint, owner=owner, project=project, name=name)
+ }
+ }
diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/copr.py b/frontend/coprs_frontend/coprs/rest_api/resources/copr.py
new file mode 100644
index 0000000..f1f5e48
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/copr.py
@@ -0,0 +1,180 @@
+import base64
+import datetime
+import functools
+
+import flask
+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.users_logic import UsersLogic
+from coprs.logic.coprs_logic import CoprsLogic
+from coprs.exceptions import ActionInProgressException, InsufficientRightsException
+from .build import BuildListR
+from coprs.rest_api.schemas import CoprSchema
+from ..exceptions import ObjectAlreadyExists, AuthFailed
+from ..util import get_one_safe, json_loads_safe, mm_deserialize, bp_url_for, render_allowed_method
+
+
+def rest_api_auth_required(f):
+ @functools.wraps(f)
+ def decorated_function(*args, **kwargs):
+ token = None
+ apt_login = None
+ if "Authorization" in flask.request.headers:
+ base64string = flask.request.headers["Authorization"]
+ base64string = base64string.split()[1].strip()
+ userstring = base64.b64decode(base64string)
+ (apt_login, token) = userstring.split(":")
+ token_auth = False
+ if token and apt_login:
+ user = UsersLogic.get_by_api_login(apt_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
+
+
+class CoprListR(Resource):
+
+ @rest_api_auth_required
+ def post(self):
+ """
+ Creates new copr
+ """
+ owner = flask.g.user
+ # new_copr_dict = json_loads_safe(flask.request.data, "")
+
+ result = mm_deserialize(CoprSchema(), flask.request.data)
+ # todo check that chroots are available
+ pprint(result.data)
+ try:
+ CoprsLogic.add(user=owner, check_for_duplicates=True, **result.data)
+ db.session.commit()
+ except DuplicateException as error:
+ raise ObjectAlreadyExists(data=error)
+
+ return "New copr was created", 201
+
+ def get(self):
+ """
+ Get coprs collection
+ :return:
+ """
+ parser = reqparse.RequestParser()
+ parser.add_argument('owner', dest='username', type=str)
+ parser.add_argument('limit', type=int)
+ parser.add_argument('offset', type=int)
+ req_args = parser.parse_args()
+
+ kwargs = {}
+ for key in ["username"]:
+ if req_args[key]:
+ kwargs[key] = req_args[key]
+
+ if "username" in kwargs:
+ query = CoprsLogic.get_multiple_owned_by_username(req_args["username"])
+ else:
+ query = CoprsLogic.get_multiple(flask.g.user)
+
+ # todo: also could be optional
+ query = CoprsLogic.join_builds(query)
+
+ if req_args["offset"]:
+ query = query.offset(req_args["offset"])
+
+ # todo: add maximum allowed limit and also use as a default limit
+ if req_args["limit"]:
+ query = query.limit(req_args["limit"])
+
+ coprs_list = query.all()
+
+ return {
+ "links": {
+ "self": bp_url_for(CoprListR.endpoint, **req_args)
+ },
+ "coprs": [
+ {
+ "copr": CoprSchema().dump(copr)[0],
+ "link": bp_url_for(
+ CoprR.endpoint,
+ owner=copr.owner.name,
+ project=copr.name
+ ),
+ }
+ for copr in coprs_list
+ ],
+ # TODO: show only if user provided ?help=true param
+ # "allowed_methods": [
+ # render_allowed_method("GET", "Get list of coprs", require_auth=False,
+ # params=[
+ # "username: filter coprs owned by 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"),
+ # ]
+ }
+
+
+class CoprR(Resource):
+
+ @rest_api_auth_required
+ def delete(self, owner, project):
+ copr = get_one_safe(CoprsLogic.get(flask.g.user, owner, project),
+ "Copr {}/{} not found".format(owner, project))
+ try:
+ ComplexLogic.delete_copr(copr)
+ except (ActionInProgressException,
+ InsufficientRightsException) as err:
+ db.session.rollback()
+ raise
+ else:
+ db.session.commit()
+
+ return None, 204
+
+ def get(self, owner, project):
+ # 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()
+
+ copr = get_one_safe(CoprsLogic.get(flask.g.user, owner, project),
+ "Copr {}/{} not found".format(owner, project))
+ return {
+ "copr": CoprSchema().dump(copr)[0],
+ "links": {
+ "self": bp_url_for(CoprR.endpoint,
+ owner=owner,
+ project=project),
+ # "chroots":
+ "builds": bp_url_for(BuildListR.endpoint,
+ owner=copr.owner.name,
+ project=copr.name)
+ },
+ # "allowed_methods": [
+ # render_allowed_method("GET", "Get single copr", require_auth=False),
+ # render_allowed_method("DELETE", "Delete current copr", require_auth=True),
+ # ]
+ }
+
+ @rest_api_auth_required
+ def put(self, owner, project):
+ """
+ Modifies project by replacment of provided fields
+ """
+ pass
+
diff --git a/frontend/coprs_frontend/coprs/rest_api/schemas.py b/frontend/coprs_frontend/coprs/rest_api/schemas.py
new file mode 100644
index 0000000..9c243a7
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/rest_api/schemas.py
@@ -0,0 +1,87 @@
+# coding: utf-8
+from marshmallow import Schema, fields
+
+
+class AllowedMethodSchema(Schema):
+ method = fields.Str()
+ doc = fields.Str()
+ require_auth = fields.Bool()
+ params = fields.List(fields.Str())
+
+
+class MockChroot(Schema):
+ pass
+
+
+class CoprSchema(Schema):
+ name = fields.Str(required=True)
+ description = fields.Str()
+ instructions = fields.Str()
+
+ auto_createrepo = fields.Bool()
+ build_enable_net = fields.Bool()
+ last_modified = fields.DateTime()
+
+ additional_repos = fields.List(fields.Str, dump_only=True, attribute="repos_list")
+ # yum_repos = fields.List()
+
+ # used only for creation
+ chroots_to_enable = fields.List(fields.Str, load_only=True)
+
+ _keys_to_make_object = [
+ "description",
+ "instructions",
+ "auto_createrepo"
+ ]
+
+ def make_object(self, data):
+ """
+ Create kwargs for CoprsLogic.add
+ """
+ kwargs = dict(
+ name=data["name"].strip(),
+ repos=" ".join(data.get("repos", [])),
+ selected_chroots=data["chroots"],
+ )
+ for key in self._keys_to_make_object:
+ if key in data:
+ kwargs[key] = data[key]
+ return kwargs
+
+
+class BuildChrootSchema(Schema):
+ # used only for presentation
+ state = fields.Str()
+ started_on = fields.Int()
+ ended_on = fields.Int()
+ git_hash = fields.Str()
+
+
+class BuildSchema(Schema):
+
+ id = fields.Int()
+ pkgs = fields.Str()
+ build_packages = fields.Str()
+ pkg_version = fields.Str()
+
+ repos_list = fields.List(fields.Str())
+ repos = fields.Str() # legacy
+
+ submitted_on = fields.Int()
+ started_on = fields.Int()
+ ended_on = fields.Int()
+
+ results = fields.Str()
+ memory_reqs = fields.Int()
+ timeout = fields.Int()
+
+ enable_net = fields.Bool()
+
+ source_type = fields.Int()
+ source_json = fields.Str()
+
+ # chroots = fields.List(fields.Nested(BuildChrootSchema))
+
+
+
+
diff --git a/frontend/coprs_frontend/coprs/rest_api/util.py b/frontend/coprs_frontend/coprs/rest_api/util.py
new file mode 100644
index 0000000..ac9b0b6
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/rest_api/util.py
@@ -0,0 +1,54 @@
+# coding: utf-8
+import json
+
+import sqlalchemy.orm.exc
+from .exceptions import ObjectNotFoundError, MalformedRequest
+from schemas import AllowedMethodSchema
+from flask import Response, url_for, Blueprint
+
+
+class AllowedMethod(object):
+ def __init__(self, method, doc, require_auth=True, params=None):
+ self.method = method
+ self.doc = doc
+ self.require_auth = require_auth
+ self.params = params or []
+
+
+def render_allowed_method(method, doc, require_auth=True, params=None):
+ return AllowedMethodSchema().dump(AllowedMethod(method, doc, require_auth, params))[0]
+
+
+def bp_url_for(endpoint, *args, **kwargs):
+ """
+ Prepend endpoing with dot
+ :param endpoint:
+ :param args:
+ :param kwargs:
+ :return:
+ """
+
+ return url_for(".{}".format(endpoint), *args, **kwargs)
+
+
+def get_one_safe(query, data_on_error=None):
+ try:
+ return query.one()
+ except sqlalchemy.orm.exc.NoResultFound:
+ raise ObjectNotFoundError(data_on_error)
+
+
+def json_loads_safe(raw, data_on_error=None):
+ try:
+ return json.loads(raw)
+ except ValueError:
+ raise MalformedRequest(data_on_error or
+ "Failed to deserialize json string")
+
+
+def mm_deserialize(schema, obj_dict):
+ result = schema.loads(obj_dict)
+ if result.errors:
+ raise MalformedRequest(data=result.errors)
+ # import ipdb; ipdb.set_trace()
+ return result
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 2070ae2..b9f220b 100644
--- a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py
+++ b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py
@@ -16,6 +16,7 @@ from coprs import forms
from coprs import helpers
from coprs.helpers import fix_protocol_for_backend
from coprs.logic.api_logic import MonitorWrapper
+from coprs.logic.complex_logic import ComplexLogic
from coprs.views.misc import login_required, api_login_required
@@ -148,11 +149,8 @@ def api_copr_delete(username, coprname):
httpcode = 200
if form.validate_on_submit() and copr:
- builds_query = builds_logic.BuildsLogic.get_multiple_by_copr(copr=copr)
try:
- for build in builds_query:
- builds_logic.BuildsLogic.delete_build(flask.g.user, build)
- CoprsLogic.delete(flask.g.user, copr)
+ ComplexLogic.delete_copr(copr)
except (exceptions.ActionInProgressException,
exceptions.InsufficientRightsException) as err:
output = {"output": "notok", "error": err}
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 6a69513..8a68e43 100644
--- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
+++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
@@ -22,6 +22,8 @@ from coprs import models
from coprs.logic.stat_logic import CounterStatLogic
from coprs.rmodels import TimedStatEvents
+from coprs.logic.complex_logic import ComplexLogic
+
from coprs.views.misc import login_required, page_not_found
from coprs.views.coprs_ns import coprs_ns
@@ -507,12 +509,9 @@ def copr_delete(username, coprname):
copr = coprs_logic.CoprsLogic.get(username, coprname).first()
if form.validate_on_submit() and copr:
- builds_query = builds_logic.BuildsLogic.get_multiple_by_copr(copr=copr)
try:
- for build in builds_query:
- builds_logic.BuildsLogic.delete_build(flask.g.user, build)
- coprs_logic.CoprsLogic.delete(flask.g.user, copr)
+ ComplexLogic.delete_copr(copr)
except (exceptions.ActionInProgressException,
exceptions.InsufficientRightsException) as e:
diff --git a/frontend/coprs_frontend/coprs/views/misc.py b/frontend/coprs_frontend/coprs/views/misc.py
index 38d0ab5..b0a98d7 100644
--- a/frontend/coprs_frontend/coprs/views/misc.py
+++ b/frontend/coprs_frontend/coprs/views/misc.py
@@ -51,6 +51,11 @@ def krb_strip_realm(fullname):
@app.before_request
+def set_empty_user():
+ flask.g.user = None
+
+
+(a)app.before_request
def lookup_current_user():
flask.g.user = username = None
if "openid" in flask.session:
@@ -222,6 +227,7 @@ def api_login_required(f):
return decorated_function
+
def login_required(role=helpers.RoleEnum("user")):
def view_wrapper(f):
@functools.wraps(f)
diff --git a/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_general.py b/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_general.py
index da3f778..bd06cd9 100644
--- a/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_general.py
+++ b/frontend/coprs_frontend/tests/test_views/test_coprs_ns/test_coprs_general.py
@@ -39,7 +39,7 @@ class TestMonitor(CoprsTestCase):
res = self.tc.get("/coprs/{}/{}/monitor/".format(self.u1.name, copr_name))
assert res.status_code == 200
- self.db.session.add(CoprsLogic.delete(self.u1, tmp_copr))
+ self.db.session.add(CoprsLogic.delete_unsafe(self.u1, tmp_copr))
self.db.session.commit()
res = self.tc.get("/coprs/{}/{}/monitor/".format(self.u1.name, copr_name))
diff --git a/frontend/requirements.txt b/frontend/requirements.txt
index 82354b3..8c3653f 100644
--- a/frontend/requirements.txt
+++ b/frontend/requirements.txt
@@ -17,3 +17,6 @@ decorator
python-dateutil
netaddr
alembic
+flask-restful
+# flask-restful-swagger # -- nice to have, but probably later
+marshmallow
8 years, 8 months