Repository :
http://git.fedorahosted.org/cgit/copr.git
On branch : rest_api_2
---------------------------------------------------------------
commit db654492d1014890872c5bf442ad678b281053c3
Author: Valentin Gologuzov <vgologuz(a)redhat.com>
Date: Tue Dec 16 19:26:49 2014 +0100
[frontend] REST API [WIP]
---------------------------------------------------------------
frontend/coprs_frontend/coprs/__init__.py | 5 +
.../coprs_frontend/coprs/logic/complex_logic.py | 27 +++
frontend/coprs_frontend/coprs/logic/coprs_logic.py | 13 +-
frontend/coprs_frontend/coprs/models.py | 24 +++
frontend/coprs_frontend/coprs/rest_api/__init__.py | 120 ++++++++++++
.../coprs_frontend/coprs/rest_api/exceptions.py | 45 +++++
.../coprs/rest_api/resources}/__init__.py | 0
.../coprs/rest_api/resources/build.py | 84 ++++++++
.../coprs/rest_api/resources/chroot.py | 42 ++++
.../coprs/rest_api/resources/copr.py | 199 ++++++++++++++++++++
frontend/coprs_frontend/coprs/rest_api/util.py | 42 ++++
.../coprs/views/api_ns/api_general.py | 6 +-
.../coprs/views/coprs_ns/coprs_general.py | 7 +-
frontend/coprs_frontend/coprs/views/misc.py | 1 +
.../test_views/test_coprs_ns/test_coprs_general.py | 2 +-
frontend/requirements.txt | 1 +
16 files changed, 604 insertions(+), 14 deletions(-)
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 81923fe..09f4f54 100644
--- a/frontend/coprs_frontend/coprs/logic/coprs_logic.py
+++ b/frontend/coprs_frontend/coprs/logic/coprs_logic.py
@@ -129,13 +129,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)
@@ -187,7 +187,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?
diff --git a/frontend/coprs_frontend/coprs/models.py
b/frontend/coprs_frontend/coprs/models.py
index df8a44e..d062bad 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
@@ -256,6 +257,14 @@ class Copr(db.Model, helpers.Serializer):
return True
return False
+ 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):
"""
@@ -501,6 +510,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):
@@ -545,6 +563,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..167b7ee
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/rest_api/__init__.py
@@ -0,0 +1,120 @@
+# 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',
+ # Parameters can be automatically extracted from URLs (e.g. <string:id>)
+ # but you could also override them here, or add other parameters.
+ # parameters=[
+ # {
+ # "name": "todo_id_x",
+ # "description": "The ID of the TODO item",
+ # "required": True,
+ # "allowMultiple": False,
+ # "dataType": 'string',
+ # "paramType": "path"
+ # },
+ # {
+ # "name": "a_bool",
+ # "description": "The ID of the TODO item",
+ # "required": True,
+ # "allowMultiple": False,
+ # "dataType": 'boolean',
+ # "paramType": "path"
+ # }
+ # ]
+ )
+ 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,
+
+)
+
+###################################
+# 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..db4a82e
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/build.py
@@ -0,0 +1,84 @@
+# 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.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 {
+ "links": {
+ "self": bp_url_for(BuildListR.endpoint, **req_args),
+ },
+ "builds": [
+ {
+ "build": build.to_dict(),
+ "link": bp_url_for(BuildR.endpoint, build_id=build.id),
+ } for build in builds
+ ]
+ }
+
+
+(a)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": build.to_dict(),
+ "links": {
+ "self": bp_url_for(BuildR.endpoint, build_id=build_id),
+ # TODO: can't do it 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..9e4652a
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/chroot.py
@@ -0,0 +1,42 @@
+# 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
+
+from ..util import get_one_safe, json_loads_safe, mm_deserialize
+
+
+class ChrootListR(Resource):
+
+ def get(self):
+ chroots = MockChrootsLogic.get_multiple(None).all()
+ return {
+ "links": {
+ "self": 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 {
+ "links": {
+ "self": url_for(ChrootR.endpoint, name=chroot.name)
+ },
+ "chroot": chroot.to_dict()
+ }
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..5783a7f
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/rest_api/resources/copr.py
@@ -0,0 +1,199 @@
+import base64
+import json
+import datetime
+import flask
+from flask import url_for
+from flask_restful import Resource, reqparse
+import functools
+
+from marshmallow import Schema, fields, 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 ..exceptions import ObjectAlreadyExists, AuthFailed
+from ..util import get_one_safe, json_loads_safe, mm_deserialize
+
+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 CoprSchema(Schema):
+ name = fields.Str(required=True)
+ description = fields.Str()
+ instructions = fields.Str()
+
+ auto_createrepo = fields.Bool()
+
+ chroots = fields.List(fields.Str, required=True)
+ repos = fields.List(fields.Str)
+
+ _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 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(), new_copr_dict)
+ # todo check that chroots are available
+ pprint(result.data)
+ try:
+ copr = CoprsLogic.add(user=owner, check_for_duplicates=True, **result.data)
+ db.session.commit()
+ except DuplicateException as error:
+ raise ObjectAlreadyExists(data=error)
+
+ return copr.to_dict(), 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:
+
+ kwargs["username"] = req_args["username"]
+ kwargs["user_relation"] = "owned"
+
+ query = CoprsLogic.get_multiple(
+ flask.g.user,
+ with_builds=True,
+ **kwargs
+ )
+
+ if req_args["offset"]:
+ query = query.offset(req_args["offset"])
+
+ if req_args["limit"]:
+ query = query.limit(req_args["limit"])
+
+ # try:
+ coprs_list = query.all()
+ # except Exception as error:
+ # import ipdb; ipdb.set_trace()
+ # a = 2
+
+ return {
+ "links": {
+ "self": url_for(CoprListR.endpoint, **req_args)
+ },
+ "coprs": [
+ {
+ "copr": copr.to_dict(),
+ "link": url_for(CoprR.endpoint,
+ owner=copr.owner.name,
+ project=copr.name),
+ }
+ for copr in coprs_list
+ ]
+ }
+
+
+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 {
+ "links": {
+ "self": url_for(CoprR.endpoint,
+ owner=owner,
+ project=project),
+ # "chroots":
+ "builds": url_for(BuildListR.endpoint,
+ owner=copr.owner.name,
+ project=copr.name)
+ },
+ "copr": copr.to_dict()
+ }
+
+
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..87fab9a
--- /dev/null
+++ b/frontend/coprs_frontend/coprs/rest_api/util.py
@@ -0,0 +1,42 @@
+# coding: utf-8
+import json
+
+import sqlalchemy.orm.exc
+from .exceptions import ObjectNotFoundError, MalformedRequest
+
+from flask import Response, url_for, Blueprint
+
+
+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.load(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 222c155..b6bc838 100644
--- a/frontend/coprs_frontend/coprs/views/api_ns/api_general.py
+++ b/frontend/coprs_frontend/coprs/views/api_ns/api_general.py
@@ -11,6 +11,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
@@ -140,11 +141,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 8d8e906..91cf8b4 100644
--- a/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
+++ b/frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
@@ -20,6 +20,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
@@ -499,12 +501,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..d3bb3e4 100644
--- a/frontend/coprs_frontend/coprs/views/misc.py
+++ b/frontend/coprs_frontend/coprs/views/misc.py
@@ -222,6 +222,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..9734ddc 100644
--- a/frontend/requirements.txt
+++ b/frontend/requirements.txt
@@ -17,3 +17,4 @@ decorator
python-dateutil
netaddr
alembic
+flask-restful-swagger