Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit 8fda311b272d73e41ecccb537ceee039ecf2ec67
Author: Bohuslav Kabrda <bkabrda(a)redhat.com>
Date: Thu Mar 28 12:09:38 2013 +0100
Start moving authorization from views to logic
- move copr updating logic
- improve the ActionInProgress exception so that we can print it as any other exception, while still making the Action accessible if needed
>---------------------------------------------------------------
coprs_frontend/coprs/exceptions.py | 9 +++++++++
coprs_frontend/coprs/logic/builds_logic.py | 3 ++-
coprs_frontend/coprs/logic/coprs_logic.py | 20 ++++++++++++++++----
.../coprs/views/coprs_ns/coprs_builds.py | 2 +-
.../coprs/views/coprs_ns/coprs_general.py | 10 +++-------
5 files changed, 31 insertions(+), 13 deletions(-)
diff --git a/coprs_frontend/coprs/exceptions.py b/coprs_frontend/coprs/exceptions.py
index da744b2..f1954b2 100644
--- a/coprs_frontend/coprs/exceptions.py
+++ b/coprs_frontend/coprs/exceptions.py
@@ -17,3 +17,12 @@ class ActionInProgressException(BaseException):
def __init__(self, msg, action):
self.msg = msg
self.action = action
+
+ def __unicode__(self):
+ return self.formatted_msg()
+
+ def __str__(self):
+ return self.__unicode__()
+
+ def formatted_msg(self):
+ return self.msg.format(action=self.action)
diff --git a/coprs_frontend/coprs/logic/builds_logic.py b/coprs_frontend/coprs/logic/builds_logic.py
index 247c01a..2a91f95 100644
--- a/coprs_frontend/coprs/logic/builds_logic.py
+++ b/coprs_frontend/coprs/logic/builds_logic.py
@@ -57,7 +57,8 @@ class BuildsLogic(object):
@classmethod
def add(cls, user, pkgs, copr):
- coprs_logic.CoprsLogic.raise_if_unfinished_action(user, copr)
+ coprs_logic.CoprsLogic.raise_if_unfinished_action(user, copr,
+ 'Can\'t build while there is an operation in progress: {action}')
build = models.Build(
pkgs=pkgs,
copr=copr,
diff --git a/coprs_frontend/coprs/logic/coprs_logic.py b/coprs_frontend/coprs/logic/coprs_logic.py
index 1a83147..2b4b2d9 100644
--- a/coprs_frontend/coprs/logic/coprs_logic.py
+++ b/coprs_frontend/coprs/logic/coprs_logic.py
@@ -92,7 +92,9 @@ class CoprsLogic(object):
@classmethod
def update(cls, user, copr, check_for_duplicates = True):
- cls.raise_if_unfinished_action(user, copr)
+ cls.raise_if_unfinished_action(user, copr,
+ 'Can\'t change this Copr name, another operation is in progress: {action}')
+ cls.raise_if_cant_update(user, copr, 'Only owners and admins may update their Coprs.')
existing = cls.exists_for_user(copr.owner, copr.name).first()
if existing:
@@ -115,7 +117,8 @@ class CoprsLogic(object):
def delete(cls, user, copr, check_for_duplicates=True):
# for the time being, we authorize user to do this in view...
# TODO: do we want to dump the information somewhere, so that we can search it in future?
- cls.raise_if_unfinished_action(user, copr)
+ cls.raise_if_unfinished_action(user, copr,
+ 'Can\'t delete this Copr, another operation is in progress: {action}')
action = models.Action(action_type=helpers.ActionTypeEnum('delete'),
object_type='copr',
object_id=copr.id,
@@ -149,13 +152,22 @@ class CoprsLogic(object):
return actions
@classmethod
- def raise_if_unfinished_action(cls, user, copr):
+ def raise_if_unfinished_action(cls, user, copr, message):
"""This method raises ActionInProgressException if given copr has an unfinished
action. Returns None otherwise.
"""
unfinished_actions = cls.unfinished_actions_for(user, copr).all()
if unfinished_actions:
- raise exceptions.ActionInProgressException('Action in progress on this copr.', unfinished_actions[0])
+ raise exceptions.ActionInProgressException(message, unfinished_actions[0])
+
+ @classmethod
+ def raise_if_cant_update(cls, user, copr, message):
+ """This method raises InsufficientRightsException if given user cant update
+ given copr. Returns None otherwise.
+ """
+ # TODO: this is a bit inconsistent - shouldn't the user method be called can_update?
+ if not user.can_edit(copr):
+ raise exceptions.InsufficientRightsException(message)
class CoprPermissionsLogic(object):
diff --git a/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py b/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py
index 1f91e49..a6871cd 100644
--- a/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py
+++ b/coprs_frontend/coprs/views/coprs_ns/coprs_builds.py
@@ -59,7 +59,7 @@ def copr_new_build(username, coprname):
build.memory_reqs = form.memory_reqs.data
build.timeout = form.timeout.data
except exceptions.ActionInProgressException as e:
- flask.flash('Can\'t build in this Copr, there is an operation in progress: {0}'.format(e.action))
+ flask.flash(e)
db.session.rollback()
else:
flask.flash("Build was added")
diff --git a/coprs_frontend/coprs/views/coprs_ns/coprs_general.py b/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
index 6da142e..adba1d0 100644
--- a/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
+++ b/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
@@ -164,10 +164,6 @@ def copr_edit(username, coprname, form=None):
def copr_update(username, coprname):
form = forms.CoprFormFactory.create_form_cls()()
copr = coprs_logic.CoprsLogic.get(flask.g.user, username, coprname).first()
- # only owner can update a copr
- if not flask.g.user.can_edit(copr):
- flask.flash('Only owners and admins may update their Coprs.')
- return flask.redirect(flask.url_for('coprs_ns.copr_detail', username = copr.owner.name, coprname = form.name.data))
if form.validate_on_submit():
# we don't change owner (yet)
@@ -179,8 +175,8 @@ def copr_update(username, coprname):
try:
coprs_logic.CoprsLogic.update(flask.g.user, copr, check_for_duplicates = False) # form validation checks for duplicates
- except exceptions.ActionInProgressException as e:
- flask.flash('Can\'t change this Copr name, there is another operation in progress: {0}'.format(e.action))
+ except (exceptions.ActionInProgressException, exceptions.InsufficientRightsException) as e:
+ flask.flash(e)
db.session.rollback()
else:
flask.flash('Copr was updated successfully.')
@@ -252,7 +248,7 @@ def copr_delete(username, coprname):
coprs_logic.CoprsLogic.delete(flask.g.user, copr)
except exceptions.ActionInProgressException as e:
db.session.rollback()
- flask.flash('Can\'t manipulate this Copr, there is another operation in progress: {0}'.format(e.action))
+ flask.flash(e)
return flask.redirect(flask.url_for('coprs_ns.copr_detail', username=username, coprname=coprname))
else:
db.session.commit()
Repository : http://git.fedorahosted.org/cgit/copr.git
On branch : master
>---------------------------------------------------------------
commit 6361440f2f0265c1369374fb457644f98c5c41dc
Author: Bohuslav Kabrda <bkabrda(a)redhat.com>
Date: Thu Mar 21 11:02:57 2013 +0100
Document models, partially fixes #52
>---------------------------------------------------------------
coprs_frontend/coprs/models.py | 78 ++++++++++++++++++++++++++++++++++------
1 files changed, 67 insertions(+), 11 deletions(-)
diff --git a/coprs_frontend/coprs/models.py b/coprs_frontend/coprs/models.py
index 291df11..22e8efe 100644
--- a/coprs_frontend/coprs/models.py
+++ b/coprs_frontend/coprs/models.py
@@ -55,21 +55,30 @@ class Serializer(object):
return map(lambda x: x.name, self.__table__.columns)
class User(db.Model, Serializer):
+ """Represents user of the copr frontend"""
id = db.Column(db.Integer, primary_key = True)
+ # openid_name for fas, e.g. http://bkabrda.id.fedoraproject.org/
openid_name = db.Column(db.String(100), nullable = False)
+ # just mail :)
mail = db.Column(db.String(150), nullable = False)
+ # is this user proven? proven users can modify builder memory and timeout for single builds
proven = db.Column(db.Boolean, default = False)
+ # is this user admin of the system?
admin = db.Column(db.Boolean, default = False)
+ # stuff for the cli interface
api_login = db.Column(db.String(40), nullable = False, default = 'abc')
api_token = db.Column(db.String(40), nullable = False, default = 'abc')
api_token_expiration = db.Column(db.Date, nullable = False, default = datetime.date(2000, 1, 1))
@property
def name(self):
+ """Returns the short username of the user, e.g. bkabrda"""
return self.openid_name.replace('.id.fedoraproject.org/', '').replace('http://', '')
- def permissions_for_copr(self, copr): # simple caching of permissions for given copr
- # we can't put this into class declaration because the class may be shared by multiple threads
+ def permissions_for_copr(self, copr):
+ """Get permissions of this user for the given copr.
+ Caches the permission during one request, so use this if you access them multiple times
+ """
if not hasattr(self, '_permissions_for_copr'):
self._permissions_for_copr = {}
if not copr.name in self._permissions_for_copr:
@@ -77,6 +86,7 @@ class User(db.Model, Serializer):
return self._permissions_for_copr[copr.name]
def can_build_in(self, copr):
+ """Determine if this user can build in the given copr."""
can_build = False
if copr.owner == self:
can_build = True
@@ -86,6 +96,7 @@ class User(db.Model, Serializer):
return can_build
def can_edit(self, copr):
+ """Determine if this user can edit the given copr."""
can_edit = False
if copr.owner == self:
can_edit = True
@@ -96,6 +107,11 @@ class User(db.Model, Serializer):
@classmethod
def openidize_name(cls, name):
+ """Creates proper openid_name from short name.
+
+ >>> user.openid_name == User.openidize_name(user.name)
+ True
+ """
return 'http://{0}.id.fedoraproject.org/'.format(name)
@property
@@ -105,14 +121,21 @@ class User(db.Model, Serializer):
@property
def coprs_count(self):
+ """Get number of coprs for this user."""
return Copr.query.filter_by(owner=self).count()
class Copr(db.Model, Serializer):
+ """Represents a single copr (private repo with builds, mock chroots, etc.)."""
id = db.Column(db.Integer, primary_key = True)
+ # name of the copr, no fancy chars (checked by forms)
name = db.Column(db.String(100), nullable = False)
+ # string containing urls of additional repos (separated by space)
+ # that this copr will pull dependencies from
repos = db.Column(db.Text)
+ # time of creation as returned by int(time.time())
created_on = db.Column(db.Integer)
+ # description and instructions given by copr owner
description = db.Column(db.Text)
instructions = db.Column(db.Text)
# duplicate information, but speeds up a lot and makes queries simpler
@@ -129,6 +152,7 @@ class Copr(db.Model, Serializer):
@property
def repos_list(self):
+ """Returns repos of this copr as a list of strings"""
return self.repos.split()
@property
@@ -141,12 +165,15 @@ class Copr(db.Model, Serializer):
@property
def active_mock_chroots(self):
+ """Returns list of active mock_chroots of this copr"""
return filter(lambda x: x.is_active, self.mock_chroots)
class CoprPermission(db.Model, Serializer):
- # 0 = nothing, 1 = asked for, 2 = approved
- # not using enum, as that translates to varchar on some DBs
+ """Association class for Copr<->Permission relation"""
+ ## see helpers.PermissionEnum for possible values of the fields below
+ # can this user build in the copr?
copr_builder = db.Column(db.SmallInteger, default = 0)
+ # can this user serve as an admin? (-> edit and approve permissions)
copr_admin = db.Column(db.SmallInteger, default = 0)
# relations
@@ -156,19 +183,31 @@ class CoprPermission(db.Model, Serializer):
copr = db.relationship('Copr', backref = db.backref('copr_permissions'))
class Build(db.Model, Serializer):
+ """Representation of one build in one copr"""
id = db.Column(db.Integer, primary_key = True)
+ # list of space separated urls of packages to build
pkgs = db.Column(db.Text)
+ # was this build canceled by user?
canceled = db.Column(db.Boolean, default = False)
- # These two are present for every build, as they might change in Copr
- # between submitting and starting build => we want to keep them as submitted
+ ## These two are present for every build, as they might change in Copr
+ ## between submitting and starting build => we want to keep them as submitted
+ # list of space separated mock chroot names
chroots = db.Column(db.Text, nullable = False)
+ # list of space separated additional repos
repos = db.Column(db.Text)
+ ## the three below represent time of important events for this build
+ ## as returned by int(time.time())
submitted_on = db.Column(db.Integer, nullable = False)
started_on = db.Column(db.Integer)
ended_on = db.Column(db.Integer)
+ # url of the build results
results = db.Column(db.Text)
+ # status as returned by backend, see build.state for value explanation
+ # (TODO: this would deserve an enum)
status = db.Column(db.Integer)
+ # memory requirements for backend builder
memory_reqs = db.Column(db.Integer, default = constants.DEFAULT_BUILD_MEMORY)
+ # maximum allowed time of build, build will fail if exceeded
timeout = db.Column(db.Integer, default = constants.DEFAULT_BUILD_TIMEOUT)
# relations
@@ -179,6 +218,7 @@ class Build(db.Model, Serializer):
@property
def state(self):
+ """Return text representation of status of this build"""
if self.status == 1:
return 'succeeded'
elif self.status == 0:
@@ -191,17 +231,26 @@ class Build(db.Model, Serializer):
@property
def cancelable(self):
+ """Find out if this build is cancelable.
+
+ ATM, build is cancelable only if it wasn't grabbed by backend.
+ """
return self.state == 'pending'
class MockChroot(db.Model, Serializer):
+ """Representation of mock chroot"""
id = db.Column(db.Integer, primary_key = True)
- os_release = db.Column(db.String(50), nullable = False) # fedora/epel/...
- os_version = db.Column(db.String(50), nullable = False) # 18/rawhide/...
- arch = db.Column(db.String(50), nullable = False) # x86_64/i686/...
+ # fedora/epel/..., mandatory
+ os_release = db.Column(db.String(50), nullable = False)
+ # 18/rawhide/..., optional (mock chroot doesn't need to have this)
+ os_version = db.Column(db.String(50), nullable = False)
+ # x86_64/i686/..., mandatory
+ arch = db.Column(db.String(50), nullable = False)
is_active = db.Column(db.Boolean, default = True)
@property
def chroot_name(self):
+ """Textual representation of name of this chroot"""
if self.os_version:
format_string = '{rel}-{ver}-{arch}'
else:
@@ -211,6 +260,7 @@ class MockChroot(db.Model, Serializer):
arch=self.arch)
class CoprChroot(db.Model, Serializer):
+ """Representation of Copr<->MockChroot relation"""
mock_chroot_id = db.Column(db.Integer, db.ForeignKey('mock_chroot.id'), primary_key = True)
mock_chroot = db.relationship('MockChroot', backref = db.backref('copr_chroots'))
copr_id = db.Column(db.Integer, db.ForeignKey('copr.id'), primary_key = True)
@@ -219,16 +269,22 @@ class CoprChroot(db.Model, Serializer):
cascade='all,delete,delete-orphan'))
class Action(db.Model, Serializer):
+ """Representation of a custom action that needs backends cooperation"""
id = db.Column(db.Integer, primary_key=True)
- # delete, rename,
+ # delete, rename, ...; see helpers.ActionTypeEnum
action_type = db.Column(db.Integer, nullable=False)
- # copr,
+ # copr, ...; downcase name of class of modified object
object_type = db.Column(db.String(20))
+ # id of the modified object
object_id = db.Column(db.Integer)
+ # old and new values of the changed property
old_value = db.Column(db.String(255))
new_value = db.Column(db.String(255))
+ # backend result, see helpers.BackendResultEnum
backend_result = db.Column(db.Integer, default=helpers.BackendResultEnum('waiting'))
+ # optional message from the backend
backend_message = db.Column(db.Text)
+ # time created as returned by int(time.time())
created_on = db.Column(db.Integer)
def __str__(self):