Skip to content

Commit

Permalink
frontend: migrate API projects namespace to flask-restx
Browse files Browse the repository at this point in the history
  • Loading branch information
nikromen committed Jul 12, 2023
1 parent 46ae367 commit 40f44af
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 100 deletions.
11 changes: 11 additions & 0 deletions frontend/coprs_frontend/coprs/views/apiv3_ns/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import json
from http import HTTPStatus

import flask
import wtforms
import sqlalchemy
Expand Down Expand Up @@ -374,3 +376,12 @@ def rename_fields_helper(input_dict, replace):
for value in values:
output.add(new_key, value)
return output


def responser(result, status: HTTPStatus = HTTPStatus.OK):
"""
Make API response with status
"""
resp = flask.jsonify(result)
resp.status_code = status.value
return resp
2 changes: 0 additions & 2 deletions frontend/coprs_frontend/coprs/views/apiv3_ns/apiv3_builds.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@
from .json2form import get_form_compatible_data




apiv3_builds_ns = Namespace("build", description="Builds")
api.add_namespace(apiv3_builds_ns)

Expand Down
241 changes: 144 additions & 97 deletions frontend/coprs_frontend/coprs/views/apiv3_ns/apiv3_projects.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
from http import HTTPStatus

import flask

from flask_restx import Namespace, Resource

from coprs.views.apiv3_ns import (query_params, get_copr, pagination, Paginator,
GET, POST, PUT, DELETE, set_defaults)
from coprs.views.apiv3_ns.json2form import get_form_compatible_data, get_input_dict
from coprs import db, models, forms, db_session_scope
from coprs.views.misc import api_login_required
from coprs.views.apiv3_ns import apiv3_ns, rename_fields_helper
from coprs.views.apiv3_ns import apiv3_ns, rename_fields_helper, api, responser
from coprs.views.apiv3_ns.schema import (fullname_params, project_model, project_parser,
ownername_field, add_project_parser)
from coprs.logic.actions_logic import ActionsLogic
from coprs.logic.coprs_logic import CoprsLogic, CoprChrootsLogic, MockChrootsLogic
from coprs.logic.complex_logic import ComplexLogic
Expand All @@ -16,6 +23,10 @@
from . import editable_copr


apiv3_projects_ns = Namespace("project", description="Projects")
api.add_namespace(apiv3_projects_ns)


def to_dict(copr):
return {
"id": copr.id,
Expand Down Expand Up @@ -77,11 +88,22 @@ def owner2tuple(ownername):
return user, group


@apiv3_ns.route("/project", methods=GET)
@query_params()
def get_project(ownername, projectname):
copr = get_copr(ownername, projectname)
return flask.jsonify(to_dict(copr))
@apiv3_projects_ns.route("")
class Project(Resource):
parser = project_parser()

@apiv3_projects_ns.doc(fullname_params)
@apiv3_projects_ns.marshal_with(project_model)
@apiv3_projects_ns.expect(parser)
@apiv3_projects_ns.response(HTTPStatus.OK.value, "OK, Project data follows...")
def get(self):
"""
Get a project
Get details for a single Copr project.
"""
args = self.parser.parse_args()
copr = get_copr(args.ownername, args.projectname)
return responser(to_dict(copr))


@apiv3_ns.route("/project/list", methods=GET)
Expand Down Expand Up @@ -110,102 +132,127 @@ def search_projects(query, **kwargs):
return flask.jsonify(items=projects, meta=paginator.meta)


@apiv3_ns.route("/project/add/<ownername>", methods=POST)
@api_login_required
def add_project(ownername):
user, group = owner2tuple(ownername)
data = rename_fields(get_form_compatible_data(preserve=["chroots"]))
form_class = forms.CoprFormFactory.create_form_cls(user=user, group=group)
set_defaults(data, form_class)
form = form_class(data, meta={'csrf': False})

if not form.validate_on_submit():
raise InvalidForm(form)
validate_chroots(get_input_dict(), MockChrootsLogic.get_multiple())

bootstrap = None
# backward compatibility
use_bootstrap_container = form.use_bootstrap_container.data
if use_bootstrap_container is not None:
bootstrap = "on" if use_bootstrap_container else "off"
if form.bootstrap.data is not None:
bootstrap = form.bootstrap.data

try:

def _form_field_repos(form_field):
return " ".join(form_field.data.split())

copr = CoprsLogic.add(
name=form.name.data.strip(),
repos=_form_field_repos(form.repos),
user=user,
selected_chroots=form.selected_chroots,
description=form.description.data,
instructions=form.instructions.data,
check_for_duplicates=True,
unlisted_on_hp=form.unlisted_on_hp.data,
build_enable_net=form.enable_net.data,
group=group,
persistent=form.persistent.data,
auto_prune=form.auto_prune.data,
bootstrap=bootstrap,
isolation=form.isolation.data,
homepage=form.homepage.data,
contact=form.contact.data,
disable_createrepo=form.disable_createrepo.data,
delete_after_days=form.delete_after_days.data,
multilib=form.multilib.data,
module_hotfixes=form.module_hotfixes.data,
fedora_review=form.fedora_review.data,
follow_fedora_branching=form.follow_fedora_branching.data,
runtime_dependencies=_form_field_repos(form.runtime_dependencies),
appstream=form.appstream.data,
packit_forge_projects_allowed=_form_field_repos(form.packit_forge_projects_allowed),
)
db.session.commit()
except (DuplicateException,
NonAdminCannotCreatePersistentProject,
NonAdminCannotDisableAutoPrunning) as err:
db.session.rollback()
raise err
return flask.jsonify(to_dict(copr))
@apiv3_projects_ns.route("/add/<ownername>")
class ProjectAdd(Resource):
parser = add_project_parser()

@api_login_required
@apiv3_projects_ns.doc({"ownername": ownername_field.description})
@apiv3_projects_ns.marshal_with(project_model)
@apiv3_projects_ns.expect(parser)
@apiv3_projects_ns.response(HTTPStatus.OK.value, "Copr project created")
def post(self, ownername):
"""
Create new Copr project
"""

user, group = owner2tuple(ownername)
data = rename_fields(get_form_compatible_data(preserve=["chroots"]))
form_class = forms.CoprFormFactory.create_form_cls(user=user, group=group)
set_defaults(data, form_class)
form = form_class(data, meta={'csrf': False})

if not form.validate_on_submit():
# TODO: we should return error codes with message instead exceptions
raise InvalidForm(form)
validate_chroots(get_input_dict(), MockChrootsLogic.get_multiple())

bootstrap = None
# backward compatibility
use_bootstrap_container = form.use_bootstrap_container.data
if use_bootstrap_container is not None:
bootstrap = "on" if use_bootstrap_container else "off"
if form.bootstrap.data is not None:
bootstrap = form.bootstrap.data

try:

@apiv3_ns.route("/project/edit/<ownername>/<projectname>", methods=PUT)
@api_login_required
def edit_project(ownername, projectname):
copr = get_copr(ownername, projectname)
data = rename_fields(get_form_compatible_data(preserve=["chroots"]))
form = forms.CoprForm(data, meta={'csrf': False})

if not form.validate_on_submit():
raise InvalidForm(form)
validate_chroots(get_input_dict(), MockChrootsLogic.get_multiple())

for field in form:
if field.data is None or field.name in ["csrf_token", "chroots"]:
continue
if field.name not in data.keys():
continue
setattr(copr, field.name, field.data)
def _form_field_repos(form_field):
return " ".join(form_field.data.split())

copr = CoprsLogic.add(
name=form.name.data.strip(),
repos=_form_field_repos(form.repos),
user=user,
selected_chroots=form.selected_chroots,
description=form.description.data,
instructions=form.instructions.data,
check_for_duplicates=True,
unlisted_on_hp=form.unlisted_on_hp.data,
build_enable_net=form.enable_net.data,
group=group,
persistent=form.persistent.data,
auto_prune=form.auto_prune.data,
bootstrap=bootstrap,
isolation=form.isolation.data,
homepage=form.homepage.data,
contact=form.contact.data,
disable_createrepo=form.disable_createrepo.data,
delete_after_days=form.delete_after_days.data,
multilib=form.multilib.data,
module_hotfixes=form.module_hotfixes.data,
fedora_review=form.fedora_review.data,
follow_fedora_branching=form.follow_fedora_branching.data,
runtime_dependencies=_form_field_repos(form.runtime_dependencies),
appstream=form.appstream.data,
packit_forge_projects_allowed=_form_field_repos(form.packit_forge_projects_allowed),
)
db.session.commit()
except (DuplicateException,
NonAdminCannotCreatePersistentProject,
NonAdminCannotDisableAutoPrunning) as err:
db.session.rollback()
raise err

if form.chroots.data:
CoprChrootsLogic.update_from_names(
flask.g.user, copr, form.chroots.data)
return responser(to_dict(copr))


@apiv3_projects_ns.route("edit/<ownername>/<projectname>")
class ProjectEdit(Resource):
parser = project_parser()

@api_login_required
@apiv3_projects_ns.doc(fullname_params)
@apiv3_projects_ns.marshal_with(project_model)
@apiv3_projects_ns.expect(parser)
@apiv3_projects_ns.response(HTTPStatus.OK.value, "Copr project successfully edited")
def put(self, ownername, projectname):
"""
Edit Copr project
Edit existing Copr project.
"""
copr = get_copr(ownername, projectname)
data = rename_fields(get_form_compatible_data(preserve=["chroots"]))
form = forms.CoprForm(data, meta={'csrf': False})

if not form.validate_on_submit():
# TODO: we should return error codes with message instead exceptions
raise InvalidForm(form)
validate_chroots(get_input_dict(), MockChrootsLogic.get_multiple())

for field in form:
if field.data is None or field.name in ["csrf_token", "chroots"]:
continue
if field.name not in data.keys():
continue
setattr(copr, field.name, field.data)

if form.chroots.data:
CoprChrootsLogic.update_from_names(
flask.g.user, copr, form.chroots.data)

try:
CoprsLogic.update(flask.g.user, copr)
if copr.group: # load group.id
_ = copr.group.id
db.session.commit()
except (ActionInProgressException,
InsufficientRightsException,
NonAdminCannotDisableAutoPrunning) as ex:
db.session.rollback()
raise ex
try:
CoprsLogic.update(flask.g.user, copr)
if copr.group: # load group.id
_ = copr.group.id
db.session.commit()
except (ActionInProgressException,
InsufficientRightsException,
NonAdminCannotDisableAutoPrunning) as ex:
db.session.rollback()
raise ex

return flask.jsonify(to_dict(copr))
return responser(to_dict(copr))


@apiv3_ns.route("/project/fork/<ownername>/<projectname>", methods=PUT)
Expand Down
Loading

0 comments on commit 40f44af

Please sign in to comment.